second first commit

This commit is contained in:
Zakaria
2026-09-22 08:57:00 -04:00
parent e88d7e2ff3
commit 0aef7fb2d8
4766 changed files with 1236986 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
# Floci Console Contract v1
**Status:** stable. Implemented by Floci since the release this page shipped in.
**Audience:** authors of a web console that wants to run against Floci.
Floci can run any web console as a sidecar container, not only the one it ships. This page is the
contract between the two. A console that implements it runs with no configuration at all: the
operator sets one image name and Floci does the rest.
If you are running a console rather than writing one, see [Web Console](index.md).
## The short version
Three things make a console a Floci console:
1. It listens for HTTP on the port given in the `PORT` environment variable, default `4500`.
2. It serves `GET /api/health`.
3. It talks to Floci at the URL in `AWS_ENDPOINT_URL`.
Everything else on this page is detail.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in
[RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
## What Floci gives the console
Floci starts the console container with the same AWS baseline it gives every container it launches,
so a console built on any AWS SDK is configured entirely by that SDK's ordinary endpoint and
credential discovery.
| Variable | Example | Notes |
|---|---|---|
| `AWS_ENDPOINT_URL` | `http://floci:4566` | The canonical endpoint. Every SDK reads it. |
| `FLOCI_ENDPOINT` | `http://floci:4566` | The same value, for a console that predates the contract. |
| `FLOCI_HOSTNAME` | `floci` | Host part of the endpoint. |
| `AWS_REGION` | `us-east-1` | Floci's default region. |
| `AWS_DEFAULT_REGION` | `us-east-1` | The same value. |
| `AWS_ACCESS_KEY_ID` | `test` | Placeholder. Floci's own credentials are never forwarded. |
| `AWS_SECRET_ACCESS_KEY` | `test` | Placeholder. |
| `AWS_SESSION_TOKEN` | `test` | Placeholder. |
| `PORT` | `4500` | The port the console MUST listen on. |
| `FLOCI_CLOUD` | `aws` | Which emulator this is: `aws`, `gcp`, `az` or `oci`. |
| `FLOCI_TLS_SKIP_VERIFY` | `1` | Present only when the operator opted out of TLS verification. |
| `NODE_TLS_REJECT_UNAUTHORIZED` | `0` | The same instruction in the form a Node or Bun client honours. |
Operator-supplied `extra-env` entries are applied last and may override any of these, except
`PORT` and `AWS_ENDPOINT_URL`. Those two are structural: Floci has already used them to publish
the container's port, to aim the readiness probe, and to decide whether an existing sidecar is
still addressing this Floci. Setting them through `extra-env` would change only the console's
copy, leaving a sidecar that listens where nothing is published or that is recreated on every
check. Floci ignores such an entry and logs which key to use instead:
`floci.services.ui.internal-port` and `floci.services.ui.endpoint` set the environment and the
structural path together.
## What the console gives Floci
### The health endpoint
The console MUST serve `GET /api/health`. Floci polls it while the browser waits on the
interstitial page, and again whenever the console's status is checked.
```json
{
"status": "ok",
"endpoint": "http://floci:4566",
"error": null,
"console": { "name": "my-console", "version": "1.2.3" }
}
```
| Field | Required | Meaning |
|---|---|---|
| `status` | yes | `ok`, `unavailable`, or anything else for "still coming up" |
| `endpoint` | no | The Floci endpoint the console is actually using. Shown to the user when it cannot be reached. |
| `error` | no | Why the console cannot reach Floci. Shown to the user verbatim. |
| `console` | no | Informational; carried in logs and diagnostics. |
How Floci reads it:
| Response | Floci's conclusion |
|---|---|
| `200` and `status` is `ok` | ready: the browser is redirected to the console |
| `200` and `status` is `unavailable` | not ready, and `endpoint` plus `error` are shown to the user |
| `200` and any other `status` | not ready, keep polling (a cold boot looks like this) |
| anything other than `200`, or no response | not ready, keep polling |
`status` MUST report whether the console can actually reach Floci, not merely whether its own
process is alive. Reporting `ok` while Floci is unreachable turns a diagnosable failure into a
console that loads and then shows nothing. A console that genuinely cannot tell SHOULD say so with
[an empty `health-ready-field` label](#self-description-labels), which makes any `200` count as
ready.
The health endpoint MUST NOT require authentication, and SHOULD respond within a second.
### Reaching Floci
- The console MUST take its endpoint from `AWS_ENDPOINT_URL` and MUST NOT hardcode
`localhost:4566`. The address is resolved at start time and is usually a container IP on a shared
Docker network, not localhost.
- The console MUST sign requests with SigV4 using the injected credentials. Floci accepts the
placeholder values; it does not accept unsigned requests on the AWS APIs.
- The console MUST tolerate an endpoint whose host is a bare IP address and whose scheme is plain
`http` even when Floci is running with TLS enabled. Floci's self-signed certificate carries no IP
SAN for its own container address, and its listener does HTTP/HTTPS protocol detection on one
port, so `http://<container-ip>:4566` is the reachable form. See
[TLS / HTTPS](../configuration/tls.md).
- When `FLOCI_TLS_SKIP_VERIFY` is set, the console SHOULD skip certificate verification on its
connection to Floci, and only on that connection.
- The console SHOULD re-resolve rather than cache a dead endpoint. Floci's container IP changes
when Floci restarts.
### The container
- The console MUST serve its application at `/`. Floci redirects the browser to
`http://<host>:<published-port>/` and has no way to add a path prefix.
- The console MUST bind `0.0.0.0`, not `127.0.0.1`, or the published port reaches nothing.
- The console MUST NOT require the Docker socket, a mounted volume, or any host path. Floci gives
it none of them.
- The console SHOULD run as a non-root user.
- The console SHOULD start in well under a minute. Floci polls indefinitely, but the user is
watching an interstitial page the whole time.
- The console MAY be stateless and MUST tolerate being stopped and recreated at any time: Floci
recreates it whenever the endpoint it was built with no longer addresses the running Floci.
## Self-description labels
A console whose shape differs from the contract's defaults can say so in its own image labels,
rather than making every operator configure it by hand. Floci reads them from the image at start
time.
Labels are read only when the image declares the contract version Floci implements:
```dockerfile
LABEL io.floci.console.contract="1"
LABEL io.floci.console.name="StackPort"
LABEL io.floci.console.port="8080"
LABEL io.floci.console.health-path="/api/health"
LABEL io.floci.console.health-ready-field="status"
LABEL io.floci.console.health-ready-value="ok"
LABEL io.floci.console.health-unavailable-value="unavailable"
LABEL io.floci.console.endpoint-env="CONSOLE_API_URL"
LABEL io.floci.console.clouds="aws"
LABEL io.floci.console.url="https://example.com/my-console"
```
| Label | Default | Meaning |
|---|---|---|
| `contract` | _(none)_ | Must be `1`. Without it every other label is ignored. |
| `name` | _(none)_ | Display name used in Floci's logs and error messages. |
| `port` | `4500` | Port the console listens on. A value that is not a usable port number is ignored with a warning. |
| `health-path` | `/api/health` | Path Floci probes. A leading slash is added if missing. |
| `health-ready-field` | `status` | Field in the health response that reports readiness. Declare it **empty** to say the endpoint is a plain liveness check, which makes any `200` count as ready. |
| `health-ready-value` | `ok` | Value of that field meaning the console reached Floci. |
| `health-unavailable-value` | `unavailable` | Value meaning the console is up but cannot reach Floci. |
| `endpoint-env` | _(none)_ | An **additional** variable to repeat the endpoint in, for a console that reads neither `AWS_ENDPOINT_URL` nor `FLOCI_ENDPOINT`. |
| `clouds` | `aws` | Comma-separated emulators the console supports. Informational today. |
Only the labels you set are applied; the rest keep their resolved values.
## How Floci resolves a console's shape
Four layers, first match wins, resolved separately for each setting:
1. explicit `floci.services.ui.*` configuration set by the operator
2. `io.floci.console.*` labels on the image
3. a built-in profile for a console Floci recognises by image name
4. the contract v1 defaults
Layer 3 exists for one console today: `floci/floci-ui` answers `/api/clouds/aws/status` with a
`runtime` field rather than `/api/health` with a `status` field, because it predates the contract.
The entry retires once floci-ui serves `/api/health`.
The practical consequence is that an operator running a contract-conformant console sets nothing
beyond the image name, and an operator running a console that neither conforms nor labels itself
can still make it work from configuration alone.
## Conformance checklist
- [ ] Listens on `0.0.0.0:$PORT`, defaulting to `4500` when `PORT` is unset
- [ ] Serves `GET /api/health` unauthenticated, returning JSON with a `status` field
- [ ] `status` is `ok` only when Floci is actually reachable
- [ ] Reports `unavailable` with `endpoint` and `error` when Floci is not reachable
- [ ] Reads `AWS_ENDPOINT_URL`; nothing is hardcoded to `localhost:4566`
- [ ] Signs with SigV4 using the injected credentials
- [ ] Works when the endpoint is `http://<ip>:4566` and when it is `https://<name>:4566`
- [ ] Honours `FLOCI_TLS_SKIP_VERIFY`
- [ ] Serves the application at `/`
- [ ] Needs no Docker socket, no volume, no host path
- [ ] Declares `io.floci.console.contract="1"` and any label whose value differs from the default
## Reference health handlers
=== "Node"
```js
import express from "express";
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
const endpoint = process.env.AWS_ENDPOINT_URL;
const sts = new STSClient({ endpoint, region: process.env.AWS_REGION });
const app = express();
app.get("/api/health", async (_req, res) => {
try {
await sts.send(new GetCallerIdentityCommand({}));
res.json({ status: "ok", endpoint });
} catch (err) {
res.json({ status: "unavailable", endpoint, error: String(err) });
}
});
app.listen(Number(process.env.PORT ?? 4500), "0.0.0.0");
```
=== "Go"
```go
endpoint := os.Getenv("AWS_ENDPOINT_URL")
http.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
body := map[string]any{"status": "ok", "endpoint": endpoint}
if _, err := sts.GetCallerIdentity(r.Context(), &sts.GetCallerIdentityInput{}); err != nil {
body = map[string]any{"status": "unavailable", "endpoint": endpoint, "error": err.Error()}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(body)
})
port := os.Getenv("PORT")
if port == "" {
port = "4500"
}
http.ListenAndServe("0.0.0.0:"+port, nil)
```
## Known consoles
| Console | Image | Notes |
|---|---|---|
| Floci UI | `floci/floci-ui` | Floci's own console, the default. Served by a built-in profile until it adopts `/api/health`. |
| [Floci Dash](https://github.com/ofsazib/floci-dash) | `ghcr.io/ofsazib/floci-dash` | AWS Console-style dashboard. Honours `PORT`; reads `FLOCI_URL` and answers `/api/healthz`, so it needs those two named. [Setup](index.md#example-floci-dash). |
| [StackPort](https://github.com/DaviReisVieira/stackport) | `davireis/stackport` | Third-party; listens on 8080 and answers `/api/health`. |
Built a console against this contract? Open a pull request adding it to this table, or an issue at
[floci-io/floci](https://github.com/floci-io/floci/issues) if something in the contract got in your
way.
+171
View File
@@ -0,0 +1,171 @@
# Web Console
Floci ships a browser-based console for inspecting the resources in your local emulator. It runs as
a sidecar container that Floci starts for you on demand.
**Open it at:** `http://localhost:4566/_floci/ui`
The first request starts the console container, so you land on a short "starting the console" page
that redirects as soon as it is ready. Later requests redirect straight away.
## How it works
Floci starts nothing at boot. On the first `/_floci/ui` request it:
1. pulls the console image if it is not present locally,
2. starts it with `-p 4500:4500` on Floci's Docker network,
3. hands it Floci's own reachable address plus the standard AWS environment,
4. polls the console's health endpoint until it reports it can reach Floci,
5. redirects your browser to `http://<host>:4500/`.
A console container that survives a Floci restart is adopted rather than recreated, unless the
address it was built with no longer points at the running Floci, in which case it is replaced so
the console reconnects without you doing anything.
Console container logs are streamed into CloudWatch Logs under the log group `/floci/ui`, so
`awslocal logs tail /floci/ui` shows them.
## Ports
Port `4500` is bound on the host by Docker, not by Floci, so it needs **no** `ports:` entry in your
`docker-compose.yml`. See [Ports Reference](../configuration/ports.md).
## Configuration
All keys live under `floci.services.ui.*`, so `FLOCI_SERVICES_UI_*` as environment variables.
| Variable | Default | Description |
|---|---|---|
| `FLOCI_SERVICES_UI_ENABLED` | `true` | Enable the console sidecar |
| `FLOCI_SERVICES_UI_IMAGE` | `floci/floci-ui:latest` | Console image to run |
| `FLOCI_SERVICES_UI_CONTAINER_NAME` | `floci-ui` | Name of the sidecar container |
| `FLOCI_SERVICES_UI_PORT` | `4500` | Host port the console is published on |
| `FLOCI_SERVICES_UI_KEEP_RUNNING_ON_SHUTDOWN` | `false` | Leave the sidecar running when Floci stops |
| `FLOCI_SERVICES_UI_DOCKER_NETWORK` | _(none)_ | Docker network for the sidecar (overrides `FLOCI_SERVICES_DOCKER_NETWORK`) |
| `FLOCI_SERVICES_UI_ENDPOINT` | _(derived)_ | Floci endpoint handed to the console, instead of deriving it from the Docker host and TLS settings |
| `FLOCI_SERVICES_UI_EXTRA_ENV` | _(none)_ | Extra `KEY=VALUE` entries for the console, comma-separated (escape a literal comma as `\,`). Applied last, so an entry may override an injected default |
| `FLOCI_SERVICES_UI_INSECURE_SKIP_TLS_VERIFY` | `false` | Have the console skip TLS verification on its connection to Floci |
The next group exists only for a console that neither follows the
[console contract](console-contract.md) nor describes itself in its image labels. Leave them unset
otherwise: setting one overrides both of those discovery paths.
| Variable | Default | Description |
|---|---|---|
| `FLOCI_SERVICES_UI_INTERNAL_PORT` | _(discovered, else `4500`)_ | Port the console listens on inside its container |
| `FLOCI_SERVICES_UI_ENDPOINT_ENV` | _(none)_ | An extra variable to repeat the Floci endpoint in, for a console reading neither `AWS_ENDPOINT_URL` nor `FLOCI_ENDPOINT` |
| `FLOCI_SERVICES_UI_STATUS_PATH` | _(discovered, else `/api/health`)_ | Path the readiness probe requests |
| `FLOCI_SERVICES_UI_STATUS_READY_FIELD` | _(discovered, else `status`)_ | JSON field reporting readiness. Set to `none` for a console whose health endpoint is a plain liveness check, which makes any `200` count as ready |
| `FLOCI_SERVICES_UI_STATUS_READY_VALUE` | _(discovered, else `ok`)_ | Value of that field meaning the console reached Floci |
| `FLOCI_SERVICES_UI_STATUS_UNAVAILABLE_VALUE` | _(discovered, else `unavailable`)_ | Value meaning the console is up but cannot reach Floci |
## Running a different console
Any console implementing the [Floci console contract](console-contract.md) works with nothing but
an image name:
```yaml
environment:
FLOCI_SERVICES_UI_IMAGE: acme/my-console:1.0
```
A console that listens elsewhere, or answers a different health route, needs that much said. For
[StackPort](https://github.com/DaviReisVieira/stackport), which listens on 8080 and answers
`/api/health`:
```yaml
environment:
FLOCI_SERVICES_UI_IMAGE: davireis/stackport:latest
FLOCI_SERVICES_UI_CONTAINER_NAME: floci-stackport
FLOCI_SERVICES_UI_PORT: "8080"
FLOCI_SERVICES_UI_INTERNAL_PORT: "8080"
```
### Example: Floci Dash
[Floci Dash](https://github.com/ofsazib/floci-dash) is an AWS Console-style dashboard for Floci. It
deviates from the contract in exactly two places, so two settings are all it takes:
```yaml
services:
floci:
image: floci/floci:latest
ports:
- "4566:4566"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
FLOCI_HOSTNAME: floci
FLOCI_BASE_URL: http://floci:4566
FLOCI_SERVICES_UI_IMAGE: ghcr.io/ofsazib/floci-dash:latest
FLOCI_SERVICES_UI_CONTAINER_NAME: floci-dash
# Floci Dash reads its endpoint from FLOCI_URL, not AWS_ENDPOINT_URL
FLOCI_SERVICES_UI_ENDPOINT_ENV: FLOCI_URL
# and answers /api/healthz rather than /api/health
FLOCI_SERVICES_UI_STATUS_PATH: /api/healthz
```
Then open `http://localhost:4566/_floci/ui` and Floci starts the dashboard on port 4500.
Nothing else is needed, and it is worth seeing why:
- **The port is not configured.** Floci Dash defaults to 3000 but honours `PORT`, which Floci sets
to the resolved internal port, so it listens on 4500 and Floci publishes it there. A console that
ignores `PORT` needs `FLOCI_SERVICES_UI_INTERNAL_PORT` instead, as StackPort does above.
- **The health fields are not configured.** Floci Dash answers `{"status": "ok"}`, which already
matches the contract's `status` field and `ok` value. Only its path differs.
- **The endpoint is not configured, only its name.** Floci resolves its own reachable address at
start time; `FLOCI_SERVICES_UI_ENDPOINT_ENV` just repeats that value under a second name.
Two caveats:
- Its EC2 terminal feature wants the Docker socket, which a Floci-managed sidecar is not given. The
rest of the dashboard works; that one feature does not.
- `/api/healthz` is a liveness check: it answers `{"status": "ok"}` whether or not the dashboard can
reach Floci. So Floci reports it ready as soon as it is up. If the dashboard loads but every page
is empty, check that `FLOCI_SERVICES_UI_ENDPOINT_ENV: FLOCI_URL` is actually set. Without it the
dashboard falls back to its own `localhost:4566`, which inside its container is nothing, and the
readiness probe cannot tell.
!!! tip "Two labels would remove both settings"
A console can carry its own quirks in its image instead of asking every operator to configure
them. Adding `io.floci.console.contract="1"`, `io.floci.console.health-path="/api/healthz"` and
`io.floci.console.endpoint-env="FLOCI_URL"` to the image would reduce the above to
`FLOCI_SERVICES_UI_IMAGE` alone. See [Console Contract v1](console-contract.md#self-description-labels).
The endpoint itself is never configured by hand. Floci resolves its own reachable address at start
time and injects it as `AWS_ENDPOINT_URL`, which is also what tells a console left over from a
previous run that it is pointing at a dead address.
## Troubleshooting
!!! note "The page says the image is unavailable"
Floci could not pull the console image. Pull it yourself to see the real error:
`docker pull floci/floci-ui:latest`.
!!! note "The page says Floci could not reach the container runtime"
Floci needs the Docker socket. Check that `/var/run/docker.sock` is mounted into the Floci
container. On SELinux hosts the bind-mount may need relabeling (`:z`) or
`--security-opt label=disable`. See [Docker Configuration](../configuration/docker.md).
!!! note "The console loads but says it cannot reach Floci"
The message includes the address the console tried. If Floci is running with TLS, the derived
endpoint may be `https://<container-ip>:4566`, which fails certificate verification because
Floci's self-signed certificate carries no IP SAN for its own container address. Either point
the console at the plain-HTTP form, which reaches the same TLS-enabled listener:
```yaml
FLOCI_SERVICES_UI_ENDPOINT: http://floci:4566
```
or set `FLOCI_SERVICES_UI_INSECURE_SKIP_TLS_VERIFY: "true"`. See [TLS / HTTPS](../configuration/tls.md).
!!! note "The console keeps saying it is starting"
Check the container's own logs: `awslocal logs tail /floci/ui --follow`, or
`docker logs floci-ui`. A console whose health endpoint is at a different path than Floci is
probing never reports ready; `FLOCI_SERVICES_UI_STATUS_PATH` fixes that.