Skip to content

Commit 2fced76

Browse files
authored
Merge pull request #122 from escendit/feat/epm-t141-gh121-deploy-coldframe-at-home-and-build-my
feat(docs): deploy Coldframe at home and build my own apps
2 parents 05c2837 + bcab954 commit 2fced76

21 files changed

Lines changed: 1449 additions & 39 deletions

File tree

‎.github/workflows/docs.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ jobs:
6464
- name: Check links and paths
6565
run: docs/check-links.sh
6666

67+
# The realm writer of the deploy guide (Story 10.3); needs jq, which the runner has.
68+
- name: Test the realm writer
69+
run: deploy/realm/test.sh
70+
71+
# The Compose reference example parses with the placeholder settings, and each required
72+
# variable stops `config` when it is missing (deploy/compose/test.sh). Nothing is started.
73+
- name: Validate the Compose example
74+
run: |
75+
docker compose -f deploy/compose/compose.yaml --env-file deploy/compose/.env.example config --quiet
76+
deploy/compose/test.sh
77+
6778
commands:
6879
name: Commands
6980
runs-on: ubuntu-24.04

‎README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and deployment all live in this repository.
1111

1212
- [Developer quickstart](docs/quickstart.md): run the local stack and every test suite.
1313
- [Build guide](docs/build/README.md): build, flash and add your own Node and Hub.
14+
- [Deploy guide](docs/deploy/README.md): run Coldframe on your home server, then
15+
[build your own apps](docs/build/apps.md).
1416
- [Hardware design](hardware/README.md): schematics, BOM, wiring, enclosure and probe sealing.
1517
- [Hub radio coexistence spike](docs/spikes/hub-radio-coexistence.md): what the Hub's radio can do at once.
1618

@@ -22,7 +24,7 @@ and deployment all live in this repository.
2224
| [`packages/`](packages) | Contracts, shared libraries and generated clients the runtimes reference |
2325
| [`tests/`](tests) | All tests, split by language |
2426
| [`aspire/`](aspire) | The local development stack, which is also the integration-test host |
25-
| [`deploy/`](deploy) | Helm charts, Fleet bundles and a Compose example |
27+
| [`deploy/`](deploy) | Helm charts, Fleet bundles and a Compose reference example ([`deploy/compose`](deploy/compose/README.md)); deploy at home with [`docs/deploy`](docs/deploy/README.md) |
2628
| [`hardware/`](hardware) | The reference Node and Hub design: schematics, BOM, wiring, enclosure |
2729
| [`docs/`](docs) | Guides and references |
2830

‎_bmad-output/implementation-artifacts/spec-10-3-deploy-coldframe-at-home-and-build-my-own-apps.md‎

Lines changed: 202 additions & 0 deletions
Large diffs are not rendered by default.

‎deploy/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Everything needed to run Coldframe outside a developer machine.
1111
| Epic 2, Story 2.5a | The Fleet bundles: a `fleet.yaml` in every chart folder, the operator bundles and the reference GitRepo in [`fleet/`](fleet), and the `rke2-traefik` bundle in [`rke2/`](rke2/README.md); the GitOps install runbook [`docs/operations/install.md`](../docs/operations/install.md). |
1212
| Epic 2, Story 2.5b | The Fleet smoke [`fleet/smoke.sh`](fleet/smoke.sh): Fleet installs the bundles on a disposable cluster in dependency order, then an upgrade and three restarts lose no committed data |
1313
| Epic 10, Story 10.1 | One release per tag `vX.Y.Z`: images, Hub and Node firmware, chart packages and `SHA256SUMS` on a GitHub Release, the wire gate and the dry run: see [`release/`](release) and the runbook [`docs/operations/release.md`](../docs/operations/release.md). [`charts/set-version.sh`](charts/set-version.sh) sets the charts to the version before tagging |
14-
| Epic 10 | The Compose file, as a reference example only |
14+
| Epic 10, Story 10.3 | The adopter deploy guide [`docs/deploy/`](../docs/deploy/README.md), the realm writer [`realm/prepare-realm.sh`](realm/prepare-realm.sh), and the Compose file in [`compose/`](compose/README.md), as a reference example only (not the supported deployment) |
1515

1616
No secret value is ever committed here. Charts reference Secrets by fixed names and keys, listed in
1717
[`SECRETS.md`](SECRETS.md), and never create one (AD-15).

‎deploy/charts/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ In the namespace of the releases (the defaults assume one namespace):
7878
database Secrets (each `username` equal to its role name) and `coldframe-backup-s3`.
7979
2. An S3 bucket off the node for the backups, and its endpoint.
8080
3. For Keycloak, a ConfigMap holding the `coldframe` realm file, with the redirect URIs of your
81-
host names (the realm in `aspire/keycloak/realms` is for localhost and is not packaged).
81+
host names (the realm in `aspire/keycloak/realms` is for localhost and is not packaged): write
82+
it with [`../realm/prepare-realm.sh`](../realm/prepare-realm.sh) `--web-origin https://<domain>`
83+
([`docs/deploy/`](../../docs/deploy/README.md#4-keycloak-realm-and-phase-two)).
8284
4. A domain with its DNS zone at Cloudflare, the Secret `coldframe-dns01` holding an API token for
8385
that zone, and split DNS for the three hosts
8486
([`docs/operations/split-dns.md`](../../docs/operations/split-dns.md)).

‎deploy/compose/.env.example‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# The settings of the Compose reference example (deploy/compose/README.md): REFERENCE EXAMPLE ONLY,
2+
# not the supported deployment. Copy this file to .env (git ignores it) and replace every value.
3+
# The values here are placeholders that only make `docker compose config` pass.
4+
#
5+
# Compose interpolates `$` in .env values: use the suggested openssl commands, and single-quote any
6+
# value that contains `$`. Never commit a filled .env.
7+
8+
# The release to run, without the leading v (a tag vX.Y.Z publishes the images as X.Y.Z). There
9+
# is no default and no `latest`.
10+
COLDFRAME_VERSION=X.Y.Z
11+
12+
# The LAN name or IP address of the Docker host. The browser and the containers both use it, so
13+
# the Keycloak issuer is the same string for both (web http://<host>:3000, Keycloak <host>:8180).
14+
COLDFRAME_HOST=192.168.1.10
15+
16+
# PostgreSQL: the superuser, and the passwords of the roles coldframe, temporal and keycloak.
17+
# The coldframe password must not contain ';' (it goes into a connection string).
18+
POSTGRES_PASSWORD=replace-me-postgres
19+
COLDFRAME_DB_PASSWORD=replace-me-coldframe-db
20+
TEMPORAL_DB_PASSWORD=replace-me-temporal-db
21+
KEYCLOAK_DB_PASSWORD=replace-me-keycloak-db
22+
23+
# The temporary admin of Keycloak's master realm, created on the first start.
24+
KEYCLOAK_ADMIN_USERNAME=admin
25+
KEYCLOAK_ADMIN_PASSWORD=replace-me-keycloak-admin
26+
27+
# The secrets of the coldframe-web and coldframe-server clients, substituted into the realm at
28+
# the first import (openssl rand -hex 32).
29+
WEB_CLIENT_SECRET=replace-me-web-client-secret
30+
SERVER_CLIENT_SECRET=replace-me-server-client-secret
31+
32+
# The SMTP server for registration and invitation emails. SMTP_USERNAME and SMTP_PASSWORD may stay
33+
# empty with SMTP_AUTH=false.
34+
SMTP_HOST=smtp.example.org
35+
SMTP_PORT=587
36+
SMTP_FROM=coldframe@example.org
37+
SMTP_USERNAME=
38+
SMTP_PASSWORD=
39+
SMTP_AUTH=true
40+
SMTP_STARTTLS=true
41+
SMTP_SSL=false
42+
43+
# Device enrolment: the X25519 private key as PKCS#8 PEM, on one line with \n between the lines
44+
# and in double quotes (deploy/compose/README.md shows the command), and the key-encryption key
45+
# of the stored Device keys, at least 32 characters (openssl rand -base64 32). Record the KEK
46+
# outside this machine: without it no enrolled Device works.
47+
ENROLMENT_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\nreplace-me\n-----END PRIVATE KEY-----\n"
48+
DEVICE_KEK=replace-me-with-at-least-32-characters

‎deploy/compose/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
coldframe-realm.json

‎deploy/compose/README.md‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Compose reference example
2+
3+
> **Reference example only, not the supported deployment (NFR7).** The supported path is
4+
> single-node RKE2 with Fleet: [`docs/deploy/`](../../docs/deploy/README.md). Nothing here is
5+
> tested beyond `docker compose config`, and no upgrade, backup or restore path exists for it.
6+
7+
[`compose.yaml`](compose.yaml) runs the released images the way the Helm charts in
8+
[`deploy/charts`](../charts/README.md) do, on one Docker host: PostgreSQL 18.6 with the databases
9+
`coldframe`, `temporal`, `temporal_visibility` and `keycloak` ([`postgres-init.sh`](postgres-init.sh)),
10+
NATS 2.15.0 with JetStream, the Temporal schema and namespace one-shots and Temporal Server 1.31.2,
11+
Phase Two Keycloak, the migration one-shot, the Server and the web app. It is useful to read how the
12+
pieces fit together, or to try the web app on a LAN before you set up the real thing.
13+
14+
What it does not have:
15+
16+
- **No TLS.** Everything is plain HTTP on the LAN: the Server trusts an `http://` issuer
17+
(`Identity__RequireHttpsMetadata=false`), the web app talks to Keycloak over HTTP
18+
(`KEYCLOAK_ALLOW_INSECURE_HTTP=true`) and its session cookie is not `Secure`. Never expose these
19+
ports to the internet.
20+
- **No backups.** The data lives in the Docker volumes `postgres` and `nats`; `docker compose down -v`
21+
deletes it.
22+
- **The web app only.** Release builds of the mobile apps need HTTPS with a public certificate, and
23+
no push credentials are wired in ([`docs/build/apps.md`](../../docs/build/apps.md)).
24+
25+
## Ports
26+
27+
| Port on the Docker host | Service |
28+
| --- | --- |
29+
| 3000 | the web app: `http://<COLDFRAME_HOST>:3000` |
30+
| 8080 | the Server |
31+
| 8180 | Keycloak: `http://<COLDFRAME_HOST>:8180`, issuer `http://<COLDFRAME_HOST>:8180/realms/coldframe` |
32+
33+
`COLDFRAME_HOST` is one LAN name or IP address of the Docker host. The browser and the containers
34+
both use it, so the issuer in a token is the same string the Server and the web app expect; the
35+
containers must be able to reach the host's published ports under that name.
36+
37+
## Start it
38+
39+
From the repository root, at the release tag you run (`git checkout vX.Y.Z`), with Docker Compose
40+
v2.23 or later and `jq`:
41+
42+
1. Write the realm for your origin. The origin must be exactly `http://<COLDFRAME_HOST>:3000`,
43+
with the same `COLDFRAME_HOST` as in `.env` (step 2); a mismatch only shows as a `redirect_uri`
44+
error at the first sign-in, after the one-time import. `--allow-http` exists for this example
45+
only:
46+
47+
```sh
48+
deploy/realm/prepare-realm.sh --allow-http --web-origin http://<COLDFRAME_HOST>:3000 \
49+
--out deploy/compose/coldframe-realm.json
50+
```
51+
52+
2. Copy the settings and replace every value (git ignores `.env`; never commit it):
53+
54+
```sh
55+
cp deploy/compose/.env.example deploy/compose/.env
56+
$EDITOR deploy/compose/.env
57+
```
58+
59+
`COLDFRAME_VERSION` is the release without the `v` (there is no default and no `latest`).
60+
Generate the secrets with `openssl rand -hex 32` and the Device key-encryption key with
61+
`openssl rand -base64 32`; record that key apart from this machine, since no enrolled Device works
62+
without it. The enrolment key goes on one line with `\n` between its lines:
63+
64+
```sh
65+
printf 'ENROLMENT_PRIVATE_KEY_PEM="%s"\n' "$(openssl genpkey -algorithm X25519 | awk '{printf "%s\\n", $0}')"
66+
```
67+
68+
3. Start it:
69+
70+
```sh
71+
docker compose -f deploy/compose/compose.yaml --env-file deploy/compose/.env up -d
72+
docker compose -f deploy/compose/compose.yaml --env-file deploy/compose/.env ps -a
73+
```
74+
75+
`temporal-schema`, `temporal-namespace` and `migrations` run once and exit 0; the rest keep
76+
running. On a host with SELinux (Fedora, RHEL) the containers cannot read the bind-mounted
77+
`postgres-init.sh` and `coldframe-realm.json` until you relabel them
78+
(`chcon -t container_file_t deploy/compose/postgres-init.sh deploy/compose/coldframe-realm.json`).
79+
80+
4. Open `http://<COLDFRAME_HOST>:3000`, Register, verify the email (the SMTP values of `.env`) and
81+
create a Site, as in [section 6 of the deploy guide](../../docs/deploy/README.md#6-first-sign-in).
82+
83+
The realm is imported on the first start only; to change it later use the Keycloak admin console
84+
(`http://<COLDFRAME_HOST>:8180/admin`, the bootstrap admin of `.env`), or start over with
85+
`docker compose ... down -v`, which deletes every volume.

0 commit comments

Comments
 (0)