# Connect Fly.io

An account of Fly.io is connected once, on the instance, by a person who then gives missions access to its resources; the agent never receives the credential. Apps on Fly.io: machines, images, volumes and snapshots, secrets, IP addresses and certificates — one resource per app.

One account is one Fly organization token; one resource is one app of that organization. The owner labels each app staging or prod, and writes are held per label. Machines only run images pinned by digest under the allowed prefixes of the account, secret values are never read back, and running a command inside a machine is its own permission.

Permissions, presets and tools of this service: [Fly.io plugin](/plugins/fly).

## How to connect

On the instance: Accounts → Connect an account, then pick the service. A company app, when the service needs one, is registered once by an administrator under Settings → Administration → Company apps, or set in the configuration (`providers.<id>.*`, see [configuration keys](/docs/configuration/keys)). Every instance registers its own app: this gateway never uses an app shared with another one.

### The usual way: with a organization token pasted by hand

1. Install flyctl and sign in as a member of the organization the agents will work in.
2. Create an organization token: fly tokens create org --org \<slug\> --name vaubaan-gateway --expiry 2160h.
3. Attenuate it so it cannot touch billing, membership or the organization itself: write \[{"type":"NoAdminFeatures","body":{}}\] to caveats.json, then run fly tokens attenuate -f caveats.json -t "\<token\>".
4. Paste the attenuated token here, with the organization slug. Keep the organization that hosts this gateway or its control plane separate: never connect it here.
5. Fly tokens carry no permission choice beyond their caveats: Vaubaan decides who may do what.

Where: [https://fly.io/dashboard](https://fly.io/dashboard)

Recommended expiry: 90 days.

What the connect page asks:

- Organization token (secret): From \`fly tokens create org\`, attenuated with NoAdminFeatures. Never \`fly auth token\`.
- Organization: The slug of the Fly organization the token was created for.
- Images machines may run: Repository prefixes, comma-separated; empty means ghcr.io/vaubaan/gateway. Images must also be pinned by digest. Refresh the account's resources after changing it.

The service's documentation: [https://fly.io/docs/security/tokens/](https://fly.io/docs/security/tokens/)

## Disconnecting

Revoke the token in the Fly dashboard (organization, Tokens) or with fly tokens revoke \<id\>.

## Registration notes from the plugin

From the plugin's [README](https://github.com/vaubaan/gateway/blob/main/plugins/fly/README.md), written for the people who register the app or create the token.

### Credential and resources

Kind `api-key`, three fields:

| Field            | Secret | Purpose                                                                                                                                                     |
| ---------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_token`      | yes    | A token from `fly tokens create org`, attenuated with `NoAdminFeatures` (`fly tokens attenuate`). Sent as `Authorization: FlyV1 …`. Never `fly auth token`. |
| `org_slug`       | no     | The one organization the connection works in. Checked at paste against `GET /v1/tokens/current`: a token reaching another organization is refused.          |
| `image_prefixes` | no     | Repository prefixes machines may run, comma-separated; empty means `ghcr.io/vaubaan/gateway`.                                                               |

Fly offers OAuth to extension partners only (scope `read`), so a token is
the only way. `discoverResources` lists the organization's apps
(`GET /v1/apps?org_slug=…`, cursor pages of 500, **at most 2000 apps**) and
returns **one resource per app**: `ref` = the app name, with `app_id`,
`org_slug`, `network` and `image_prefixes` in its metadata.
