infra/aws/public-gateway/README.md

AWS public gateway deployment

The first public doorway is one 1 GB Amazon Linux 2023 Lightsail instance with an attached static IPv4 address. Caddy terminates HTTPS for api.clankie.bot and reverse-proxies the existing gateway container. Cloudflare remains the authoritative DNS provider; the gateway does not create or mutate DNS records.

The Linux bundle with public IPv4 costs USD $7/month and includes the static IP while it stays attached. Cognito Essentials is free for the first 10,000 direct monthly active users, so it adds no identity charge at the invited-beta scale. The instance's 2 TB transfer allowance is far above the first invited-user load; transfer overage, snapshots, Amazon SES email, and later Cognito tiers are separate charges.

Prerequisites

  • AWS CLI access to the target region and an existing Lightsail key pair.
  • Docker with Buildx on the release Mac.
  • The matching private key and the operator's current public IPv4 /32 for the one-time bootstrap.
  • Admin access to the production tailnet and the GitHub repository.

Provision the instance

Provisioning creates AWS resources. It does not deploy the gateway or change Cloudflare DNS.

export CLANKIE_AWS_REGION=us-east-1
export CLANKIE_GATEWAY_KEY_PAIR_NAME=clankie-operator
export CLANKIE_GATEWAY_OPERATOR_CIDR=203.0.113.10/32
infra/aws/public-gateway/deploy.sh provision
infra/aws/public-gateway/deploy.sh bootstrap

Set CLANKIE_GATEWAY_SSH_KEY for the initial public-SSH bootstrap. After the host joins Tailscale, point CLANKIE_GATEWAY_TARGET at its MagicDNS name and the same command upgrades the activator through Tailscale SSH without a key.

The stack uses the micro_3_0 public-IPv4 bundle and the amazon_linux_2023 blueprint. If AWS retires either identifier, pass an active equivalent to CloudFormation's BundleId or BlueprintId parameter rather than changing the gateway contract.

Add a Cloudflare DNS-only A record from api.clankie.bot to the printed GatewayIp. DNS-only keeps the existing streaming and WebSocket semantics direct between the clients and Caddy. Caddy obtains and renews the public TLS certificate after the record resolves and ports 80 and 443 reach the instance.

Establish the private deployment plane

Public SSH is a bootstrap path, not the steady-state release path. Merge the entries in tailnet-policy.fragment.hujson into the tailnet's existing policy; preserve unrelated SSH rules and tests. If the policy still has the default "src": ["*"] wildcard grant, replace it with the fragment's autogroup:member grant. Human member devices retain their existing access, while tagged automation receives only the explicit gateway SSH grant. The policy lets tailnet admins use ec2-user with reauthentication and lets an ephemeral tag:clankie-deployer runner use only clankie-deploy. That account can elevate only through the audited clankie-gateway-activate command.

From the initial public SSH session, enroll the host and enable Tailscale SSH:

sudo tailscale up \
  --hostname=clankie-public-gateway \
  --advertise-tags=tag:clankie-gateway \
  --ssh

Complete the displayed Tailscale login, then verify the private path from the operator Mac. The exact MagicDNS suffix is tailnet-specific.

tailscale ping clankie-public-gateway
ssh ec2-user@clankie-public-gateway

Close the public SSH firewall rule after the private path works:

export CLANKIE_GATEWAY_PUBLIC_SSH=false
infra/aws/public-gateway/deploy.sh provision

Create a Tailscale OpenID Connect trust credential with these settings:

  • issuer: GitHub (https://token.actions.githubusercontent.com)
  • subject: repo:Volpestyle/clankie:environment:production
  • scope: Auth Keys write, restricted to the exact tag:clankie-deployer tag

Tailscale generates the client id and audience. Configure the GitHub production environment with required reviewer protection, allow only main and v* refs, and add those values as secrets alongside this variable:

KindNameValue
secretTS_OAUTH_CLIENT_IDTailscale workload-identity client id
secretTS_AUDIENCEaudience configured on that federated credential
variableCLANKIE_GATEWAY_TAILNET_HOSTthe gateway's full MagicDNS name

The GitHub runner receives a short-lived, tagged tailnet identity. It receives no AWS credential and no SSH private key. See Tailscale's GitHub workload identity federation for the issuer and subject fields used by the OAuth client's trust credential.

Configure accounts and enroll a Mac

Provision the separate Cognito stack, install its public discovery document on the gateway host, and release the account-aware gateway:

export CLANKIE_ACCOUNT_EMAIL_IDENTITY=clankie.bot
export CLANKIE_ACCOUNT_EMAIL_FROM=no-reply@clankie.bot
infra/aws/accounts/deploy.sh provision
infra/aws/public-gateway/deploy.sh configure-account
infra/aws/public-gateway/deploy.sh bootstrap  # activator v2, once
infra/aws/public-gateway/deploy.sh release
infra/aws/accounts/deploy.sh invite tester@example.com

The tester runs /gateway, enters that email and the one-time code Cognito sends, then runs /pair. The Mac keeps its rotating refresh token in Keychain; the gateway derives the host route from the signed account subject and a random installation id. No host id, bearer, AWS access, SSH access, or Tailscale setup is part of the tester journey. Run clankie autostart enable once so Clankie and its relay start after login.

The legacy root-owned host-tokens.json may remain mounted during migration. It is not used for new accounts and can be removed after every existing Mac has signed in through /gateway.

Release

Releases require a clean committed tree. The script builds an immutable amd64 image, copies it over the private Tailscale path, and invokes the root-owned activator. The activator validates the unprivileged upload and host-token permissions, replaces the gateway, checks /health, and starts the pinned Caddy image. Failed gateway or Caddy startup restores the previous working component when one exists.

export CLANKIE_GATEWAY_TARGET=clankie-public-gateway.example-tailnet.ts.net
infra/aws/public-gateway/deploy.sh release
curl --fail https://api.clankie.bot/health

Pushing a v* tag runs the same deployment between the release build and GitHub Release publication. The protected production environment is the approval boundary. The Deploy public gateway workflow can also redeploy the current commit manually. A host activator version mismatch fails closed; run deploy.sh bootstrap through operator access before releasing that change.

Inspect metadata-only logs with:

sudo docker logs --since 30m clankie-gateway
sudo docker logs --since 30m clankie-caddy

Rotate and operate

Account access tokens rotate automatically. The connector refreshes before expiry, reconnects, and republishes fresh pairing routes; paired devices retry their existing host route. Disable a Cognito user to revoke that account. Each Mac installation has its own derived route, while account disable is intentionally account-wide.

Apply OS security updates with sudo dnf upgrade -y and reboot during a small maintenance window. Docker restarts both containers; the Mac reconnects. Caddy certificate state lives in the clankie-caddy-data Docker volume. The stack and release assets recreate the host if the instance is lost. The gateway holds no conversations, grants, or device sessions; when push is configured its one durable table is the delivery registrations in /var/lib/clankie-gateway/push.sqlite, which holds APNs device tokens and is covered in the gateway guide. Losing it costs each phone one re-registration, and restoring a stale copy is worse than losing it.

Deliberate ceiling

This single process is sufficient for App Review and a small invited paid beta. It is not yet a general multi-tenant service: TLS terminates at Caddy, one instance is one failure domain, and gateway routing is in memory. Before unrelated customers share it, add application-layer end-to-end encryption. Obtain SES production access before any unverified tester needs a login code. Add an external live connection broker only when measured load requires a second gateway process. Tailscale remains the private operator and deployment lane throughout.