diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..e9001a0 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Deploy + +# Deploys the web server to Fly.io. +# Triggers: pushing a version tag (v*), or manual dispatch. +# Requires the FLY_API_TOKEN repository secret (create with +# `flyctl tokens create deploy -x 999999h`). + +on: + push: + tags: ["v*"] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install flyctl + run: | + curl -L https://fly.io/install.sh | sh + echo "$HOME/.fly/bin" >> "$GITHUB_PATH" + + - name: Deploy + run: flyctl deploy --remote-only + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/Makefile b/Makefile index 954df12..8887fe4 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ BUILD_DIR=bin GO=go GOFLAGS=-v -.PHONY: build build-server build-all test lint clean cover +.PHONY: build build-server build-all test lint clean cover deploy deploy-status build: $(GO) build $(GOFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/exploredns @@ -27,3 +27,12 @@ lint: clean: rm -rf $(BUILD_DIR) rm -f coverage.out coverage.html + +# Deploy the web server to Fly.io (requires flyctl and a configured app; +# see "Deploying to Fly.io" in README.md). +deploy: + flyctl deploy --remote-only + +deploy-status: + flyctl status + flyctl checks list diff --git a/README.md b/README.md index 3e923c4..fe49990 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,54 @@ data: {} Completed jobs are kept in memory for one hour before being purged. +### Server configuration + +The server is safe to expose publicly by default and reads these environment +variables at startup: + +| Variable | Default | Meaning | +|---|---|---| +| `EXPLOREDNS_JOB_TIMEOUT` | `5m` | Hard deadline per traversal (Go duration). Timed-out jobs report `error` with any partial results. | +| `EXPLOREDNS_MAX_JOBS` | `8` | Maximum concurrent traversals; further `POST /api/traverse` requests get `429`. | +| `EXPLOREDNS_CORS_ORIGIN` | *(unset)* | Off by default (the SPA is same-origin). Set an origin — or `*` for development — to enable cross-origin API access. | + +--- + +## Deploying to Fly.io + +The repo ships a [fly.toml](fly.toml) that builds `Dockerfile.web` and runs +the web server with scale-to-zero machines in `syd` (edit `app` / +`primary_region` to taste). + +### First-time setup + +```sh +flyctl auth login +flyctl apps create exploredns # match the app name in fly.toml +make deploy # flyctl deploy --remote-only +``` + +`make deploy-status` shows machine and health-check state. The app serves +the SPA at `https://.fly.dev/` with `/api/health` as the health check. + +### Continuous deployment + +`.gitea/workflows/deploy.yml` deploys on any `v*` tag push (or manual +dispatch). It needs a `FLY_API_TOKEN` repository secret: + +```sh +flyctl tokens create deploy -x 999999h +``` + +### Notes + +- Traversal traffic is outbound UDP/TCP port 53, which Fly machines allow; + upstream root discovery uses Fly's internal resolver via `/etc/resolv.conf` + and falls back to the built-in IANA root hints. +- The job timeout, job cap, and same-origin CORS defaults above are what make + unauthenticated public exposure reasonable; tighten `EXPLOREDNS_MAX_JOBS` + if the app attracts traffic. + --- diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..b2e0770 --- /dev/null +++ b/fly.toml @@ -0,0 +1,34 @@ +# Fly.io configuration for the ExploreDNS web server. +# Deploy with `make deploy` (or `flyctl deploy --remote-only`). +# First-time setup: see "Deploying to Fly.io" in README.md. + +app = "exploredns" +primary_region = "syd" + +[build] + dockerfile = "Dockerfile.web" + +[env] + # Public-exposure guards (web/api reads these at startup). + EXPLOREDNS_JOB_TIMEOUT = "5m" + EXPLOREDNS_MAX_JOBS = "8" + +[http_service] + internal_port = 8080 + force_https = true + # Scale to zero when idle; SSE streams count as active connections, + # so machines are not stopped mid-traversal. + auto_stop_machines = "stop" + auto_start_machines = true + min_machines_running = 0 + + [[http_service.checks]] + interval = "30s" + timeout = "5s" + grace_period = "10s" + method = "GET" + path = "/api/health" + +[[vm]] + size = "shared-cpu-1x" + memory = "256mb"