mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Add Fly.io deployment for the App Store review test server
Adds a flyImage Nix output (backend + bundled web UI with an env-driven startup config wrapper), a fly.toml with a persistent volume, and a deploy README. Scanners are disabled and the API key comes from a Fly secret so nothing sensitive is baked into the image. Also adds flyctl + skopeo to the dev shell for daemon-less build/push. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ab8cdcc275
commit
386e459c12
@@ -0,0 +1,89 @@
|
|||||||
|
# Deploying the OOTT test server to Fly.io
|
||||||
|
|
||||||
|
A throwaway, pay-as-you-go backend (REST API + bundled web UI) on
|
||||||
|
[Fly.io](https://fly.io), for Apple to exercise the iOS app during App Store
|
||||||
|
review. Tear it down when review is done so you stop paying.
|
||||||
|
|
||||||
|
The image is the reproducible Nix build (same backend + bundled Flutter web as
|
||||||
|
the production image). Config is rendered at startup from the environment, so:
|
||||||
|
|
||||||
|
- the **API key** comes from a Fly **secret** (never baked into the image or git),
|
||||||
|
- the **SQLite database** lives on a Fly **volume** (survives restarts/redeploys),
|
||||||
|
- the **network scanners are disabled** — a cloud host has no LAN to scan; this
|
||||||
|
is purely an API/UI server for the reviewer.
|
||||||
|
|
||||||
|
Everything below runs inside the Nix dev shell, which now ships `flyctl` and
|
||||||
|
`skopeo` (no Docker daemon required):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix develop # or: direnv / your usual dev shell entry
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. One-time setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Log in (opens a browser).
|
||||||
|
fly auth login
|
||||||
|
|
||||||
|
# Create the app. Use this name everywhere below (and in fly.toml's `app`).
|
||||||
|
fly apps create oott-test
|
||||||
|
|
||||||
|
# Persistent storage for the SQLite DB. Match the region in fly.toml.
|
||||||
|
fly volumes create oott_data --region ams --size 1 --app oott-test
|
||||||
|
|
||||||
|
# The API key the iOS app will authenticate with (keep it; you'll hand the same
|
||||||
|
# value to the reviewer / configure it in the build under test).
|
||||||
|
fly secrets set OOTT_API_KEY="<pick-a-strong-key>" --app oott-test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Build the image and push it to the Fly registry
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Build the Fly image (backend + bundled web UI + startup config wrapper).
|
||||||
|
nix build .#flyImage -o result-fly
|
||||||
|
|
||||||
|
# Push the image straight from the Nix store archive to Fly's registry.
|
||||||
|
# No Docker daemon involved; skopeo authenticates with a short-lived Fly token.
|
||||||
|
skopeo copy --dest-creds "x:$(fly auth token)" \
|
||||||
|
docker-archive:result-fly \
|
||||||
|
docker://registry.fly.io/oott-test:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Deploy
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fly deploy --app oott-test \
|
||||||
|
--config deploy/fly/fly.toml \
|
||||||
|
--image registry.fly.io/oott-test:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Your server is now at `https://oott-test.fly.dev`. Quick check:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -H "Authorization: Bearer <your-OOTT_API_KEY>" \
|
||||||
|
https://oott-test.fly.dev/api/test
|
||||||
|
# -> "OOTT_API_OK"
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `https://oott-test.fly.dev/web` for the UI, `https://oott-test.fly.dev/api/docs`
|
||||||
|
for the API explorer.
|
||||||
|
|
||||||
|
## 4. Tear down (stop paying)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fly apps destroy oott-test # removes the app, machine, and volume
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Redeploying after a change:** repeat steps 2 and 3.
|
||||||
|
- **Cost:** one `shared-cpu-1x`/512MB machine + a 1GB volume is on the order of a
|
||||||
|
few US dollars a month, billed by usage — well under a dollar for a few days.
|
||||||
|
To trim it further between reviewer sessions, flip `fly.toml` to scale-to-zero
|
||||||
|
(`auto_stop_machines = "stop"`, `min_machines_running = 0`) and redeploy.
|
||||||
|
- **Empty UI:** the reviewer sees a working but device-less app, since there is
|
||||||
|
no LAN to discover. If the review benefits from a populated screen, seed a few
|
||||||
|
sample devices via the API (`PUT /api/devices`) after deploying.
|
||||||
|
- **Config knobs** are env vars in `fly.toml` (`OOTT_LOG_LEVEL`,
|
||||||
|
`OOTT_NOTIFICATIONS_METHOD`, `OOTT_PORT`, `OOTT_DATA_DIR`); the API key is the
|
||||||
|
`OOTT_API_KEY` secret.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Fly.io config for the OOTT App Store review test server.
|
||||||
|
#
|
||||||
|
# This deploys the backend (REST API + bundled Flutter web UI) as a single
|
||||||
|
# always-on machine with a persistent volume for the SQLite database. It is a
|
||||||
|
# throwaway, pay-as-you-go server: tear it down once review is done
|
||||||
|
# (`fly apps destroy oott-test`).
|
||||||
|
#
|
||||||
|
# See deploy/fly/README.md for the full deploy procedure.
|
||||||
|
|
||||||
|
app = "oott-test" # change to your chosen Fly app name
|
||||||
|
primary_region = "ams" # change to a region near you / the reviewers
|
||||||
|
|
||||||
|
# The image is built with Nix and pushed to the Fly registry (see README),
|
||||||
|
# so there is no [build] section: deploy with `fly deploy --image ...`.
|
||||||
|
|
||||||
|
[env]
|
||||||
|
OOTT_LOG_LEVEL = "info"
|
||||||
|
OOTT_NOTIFICATIONS_METHOD = "none" # no push/pushover on the test server
|
||||||
|
OOTT_PORT = "8080"
|
||||||
|
OOTT_DATA_DIR = "/data"
|
||||||
|
|
||||||
|
# Persistent SQLite storage. Create the volume once with:
|
||||||
|
# fly volumes create oott_data --region ams --size 1
|
||||||
|
[[mounts]]
|
||||||
|
source = "oott_data"
|
||||||
|
destination = "/data"
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 8080
|
||||||
|
force_https = true # Apple ATS requires HTTPS for the app's API calls
|
||||||
|
# Keep the machine always on during review so a reviewer never hits a cold
|
||||||
|
# start. To save money once review is over, set auto_stop_machines = "stop"
|
||||||
|
# and min_machines_running = 0 to scale to zero.
|
||||||
|
auto_stop_machines = "off"
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 1
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
size = "shared-cpu-1x"
|
||||||
|
memory = "512mb"
|
||||||
@@ -75,6 +75,8 @@
|
|||||||
clippy # Rust linter
|
clippy # Rust linter
|
||||||
pythonEnv
|
pythonEnv
|
||||||
gh # GitHub CLI tool (for release process)
|
gh # GitHub CLI tool (for release process)
|
||||||
|
flyctl # Fly.io CLI (deploy/fly/: test server for App Store review)
|
||||||
|
skopeo # push the Nix-built image to the Fly registry without a Docker daemon
|
||||||
];
|
];
|
||||||
|
|
||||||
# fish > all
|
# fish > all
|
||||||
@@ -150,13 +152,60 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Package definition
|
# Package definition
|
||||||
packages = forEachSystem (system: {
|
packages = forEachSystem (system: let
|
||||||
oott = pkgsBySystem.${system}.oott;
|
pkgs = pkgsBySystem.${system};
|
||||||
default = pkgsBySystem.${system}.oott;
|
|
||||||
|
# Startup wrapper for the Fly.io image. The backend only reads its config
|
||||||
|
# from a TOML file, but on Fly we want the (secret) API key to come from a
|
||||||
|
# Fly secret and the rest of the deployment knobs from plain env vars, with
|
||||||
|
# nothing sensitive baked into the image. So we render /data/oott.toml from
|
||||||
|
# the environment on every boot and then exec the backend against it. The
|
||||||
|
# scanners are forced off: a cloud host has no LAN to scan, this is purely
|
||||||
|
# an API/UI server for App Store review.
|
||||||
|
flyEntrypoint = pkgs.writeShellApplication {
|
||||||
|
name = "oott-fly-entrypoint";
|
||||||
|
runtimeInputs = [pkgs.coreutils pkgs.oott];
|
||||||
|
text = ''
|
||||||
|
: "''${OOTT_API_KEY:?OOTT_API_KEY must be set (fly secrets set OOTT_API_KEY=...)}"
|
||||||
|
data_dir="''${OOTT_DATA_DIR:-/data}"
|
||||||
|
mkdir -p "$data_dir"
|
||||||
|
cat > "$data_dir/oott.toml" <<EOF
|
||||||
|
[database]
|
||||||
|
path = "$data_dir/oott.db"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
level = "''${OOTT_LOG_LEVEL:-info}"
|
||||||
|
|
||||||
|
[notifications]
|
||||||
|
method = "''${OOTT_NOTIFICATIONS_METHOD:-none}"
|
||||||
|
|
||||||
|
[web_server]
|
||||||
|
ip_address = "0.0.0.0"
|
||||||
|
port = ''${OOTT_PORT:-8080}
|
||||||
|
api_key = "$OOTT_API_KEY"
|
||||||
|
|
||||||
|
[arp_scanner]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[mdns_scanner]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[ssdp_scanner]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[dhcp_scanner]
|
||||||
|
enabled = false
|
||||||
|
EOF
|
||||||
|
exec oott --config "$data_dir/oott.toml"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
oott = pkgs.oott;
|
||||||
|
default = pkgs.oott;
|
||||||
|
|
||||||
# Docker image generation
|
# Docker image generation
|
||||||
# use via 'nix build .#dockerImage'
|
# use via 'nix build .#dockerImage'
|
||||||
dockerImage = with pkgsBySystem.${system};
|
dockerImage = with pkgs;
|
||||||
dockerTools.buildLayeredImage {
|
dockerTools.buildLayeredImage {
|
||||||
name = "oott";
|
name = "oott";
|
||||||
tag = "latest";
|
tag = "latest";
|
||||||
@@ -171,6 +220,23 @@
|
|||||||
Env = ["SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt"];
|
Env = ["SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Fly.io deployment image (see deploy/fly/). Same backend + bundled
|
||||||
|
# front-end as dockerImage, but its config is generated at startup from the
|
||||||
|
# environment (flyEntrypoint) so the DB can live on a Fly volume and the
|
||||||
|
# API key can come from a Fly secret. Build with 'nix build .#flyImage'.
|
||||||
|
flyImage = pkgs.dockerTools.buildLayeredImage {
|
||||||
|
name = "oott-fly";
|
||||||
|
tag = "latest";
|
||||||
|
contents = [flyEntrypoint pkgs.oott pkgs.cacert];
|
||||||
|
# /tmp for the process, /data as the mountpoint for the Fly volume.
|
||||||
|
extraCommands = "mkdir -p tmp data";
|
||||||
|
config = {
|
||||||
|
Cmd = ["${flyEntrypoint}/bin/oott-fly-entrypoint"];
|
||||||
|
Env = ["SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"];
|
||||||
|
ExposedPorts = {"8080/tcp" = {};};
|
||||||
|
};
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
# Modules definition
|
# Modules definition
|
||||||
|
|||||||
Reference in New Issue
Block a user