docs: add apps/ authoring guide + realign READMEs with 26.4-alpha
Closes #9. New apps/README.md walks through the four-file contract (manifest.json, docker-compose.yaml, .env.example, icon.svg) with the rules enforced by furtka/manifest.py and the SVG sanitiser, using apps/fileshare as the reference. Root README: release list now covers 26.1/26.3/26.4 (26.2 stalled on the jq apt hang). Local HTTPS Phase 1 and the post-build smoke VM on pollux both flip to [x]; the old proksi.local HTTPS TODO becomes a Phase 2 entry (dedicated local CA + HTTPS on the live-installer wizard). iso/README: mDNS is wired — live ISO advertises proksi.local, installed box defaults to furtka.local (the form's default hostname, not proksi). HTTPS section notes Caddy tls internal on :443 shipped in 26.4 while the wizard itself is still HTTP. Overlay table picks up etc/hostname, etc/issue, furtka-update-issue, and furtka-issue.service. website/README: auto-deploy via .forgejo/workflows/deploy-site.yml is the default path now; website/deploy.sh stays as the SSH-hop fallback for off-CI pushes, and deploy-ci.sh is called out in the structure map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
850d656169
commit
9ae14f4108
4 changed files with 155 additions and 20 deletions
|
|
@ -106,7 +106,7 @@ None of these nail the "your dad can set this up" experience. The installer wiza
|
|||
- [x] Release process + CI — CalVer tags, conventional commits, Forgejo Actions (ruff, pytest, JSON, link checks), `26.0-alpha` tagged
|
||||
- [x] Forgejo runner live on Proxmox VM (`forge-runner-01`, Ubuntu 24.04) — docker-outside-of-docker with host-mode jobs for ISO builds, setup captured in [docs/runner-setup.md](docs/runner-setup.md) + [ops/forgejo-runner/](ops/forgejo-runner/)
|
||||
- [x] **ISO-build in CI** — `.forgejo/workflows/build-iso.yml` runs `iso/build.sh` on every push to `main` and publishes the resulting `.iso` as the `furtka-iso` artifact (14 d retention). Push → green run → download → test.
|
||||
- [x] **Forgejo Releases + tag-driven release pipeline** — `.forgejo/workflows/release.yml` fires on `[0-9]*` tags, `scripts/build-release-tarball.sh` packages `furtka/` + `apps/` + `assets/` + a root VERSION, `scripts/publish-release.sh` uploads tarball + sha256 + release.json to the Forgejo releases page. `26.1-alpha` and `26.3-alpha` live at [releases](https://forgejo.sourcegate.online/daniel/furtka/releases). Needs one repo secret (`FORGEJO_RELEASE_TOKEN`).
|
||||
- [x] **Forgejo Releases + tag-driven release pipeline** — `.forgejo/workflows/release.yml` fires on `[0-9]*` tags, `scripts/build-release-tarball.sh` packages `furtka/` + `apps/` + `assets/` + a root VERSION, `scripts/publish-release.sh` uploads tarball + sha256 + release.json to the Forgejo releases page. Releases `26.1-alpha`, `26.3-alpha`, and `26.4-alpha` live at [releases](https://forgejo.sourcegate.online/daniel/furtka/releases) (26.2 stalled on a `jq` apt hang, fixed in 26.3). Needs one repo secret (`FORGEJO_RELEASE_TOKEN`).
|
||||
- [x] **Walking-skeleton live ISO — end to end** — `iso/build.sh` produces a hybrid BIOS/UEFI Arch-based ISO. It boots in a Proxmox VM, DHCPs onto the LAN, shows a console welcome with `http://proksi.local:5000` (+ IP fallback), serves the Flask webinstaller, runs `archinstall --silent`, reboots the VM via a Reboot-now button, and the installed system logs in and runs `docker ps` without sudo. Build infra in [`iso/`](iso/).
|
||||
- [x] **Drop loop/rom devices from drive list** — `webinstaller/drives.py` filters by `lsblk` `TYPE=disk`, so the live squashfs and CD-ROM no longer appear as install targets. Boot-USB filtering on bare metal is still TODO; see [iso/README.md](iso/README.md).
|
||||
- [x] **Rebrand GRUB menu** — `iso/build.sh` rewrites "Arch Linux install medium" → "Furtka Live Installer" across GRUB, syslinux, and systemd-boot configs; default entry marked `(Recommended)`.
|
||||
|
|
@ -117,8 +117,10 @@ None of these nail the "your dad can set this up" experience. The installer wiza
|
|||
- [x] **On-box web UI uplevel** — shared `/style.css` served by Caddy, persistent top nav, landing page with an "Your apps" tile grid + live status, `/apps` with real per-app icons (inlined SVG from each manifest), new `/settings` page (hostname, IP, version, kernel, RAM, Docker, uptime + Furtka-updates card). `prefers-color-scheme` light/dark.
|
||||
- [x] **Versioned on-box layout + Phase 1 per-app updates** — `/opt/furtka/versions/<ver>/` + `current` symlink; `/var/lib/furtka/` for runtime state. `POST /api/apps/<name>/update` runs `docker compose pull` + compares digests + conditional `up -d`.
|
||||
- [x] **Phase 2 Furtka self-update** — `/settings` → Check → Update now. Downloads signed tarball (SHA256), stages, atomic symlink flip, reloads Caddy, daemon-reload, restarts services, health-checks the new api with auto-rollback on failure. CLI: `furtka update [--check]` + `furtka rollback`. Validated end-to-end on VM 2026-04-16 (`26.0-alpha` → `26.3-alpha` → rollback → reboot).
|
||||
- [x] **Local HTTPS Phase 1** — Caddy `tls internal` on `:443` alongside plain `:80`. Per-box root CA generated on first start, `rootCA.crt` downloadable from `/settings`, per-OS install guide at `/https-install/`. Opt-in "force HTTPS" toggle only exposes itself once the current browser already trusts the cert, so enabling it can't lock the user out. Shipped in 26.4-alpha.
|
||||
- [x] **Post-build smoke VM on Proxmox** — `.forgejo/workflows/build-iso.yml` hands the freshly built ISO to `scripts/smoke-vm.sh`, which boots it in a throwaway VM on `pollux` (192.168.178.165) and curls the webinstaller on `:5000`. VMID range 9000–9099, last 5 kept. Green end-to-end since 26.4-alpha.
|
||||
- [ ] Installer wizard screens S3–S7 — per-device purpose, network, domain, SSL, diagnostic. S5/S6 blocked on managed-gateway DNS infra not yet built.
|
||||
- [ ] `https://proksi.local` with a local CA (today: plain HTTP at `http://proksi.local:5000`)
|
||||
- [ ] Local HTTPS Phase 2 — dedicated local CA (not Caddy's `tls internal`), streamlined one-click install across Win/Mac/Linux/Android, and HTTPS on the live-installer wizard (`https://proksi.local:5000`).
|
||||
- [ ] Caddy + Authentik wired into first-boot bootstrap
|
||||
- [ ] Managed gateway infrastructure — `ns1/ns2.furtka.org` + DNS-01 wildcard automation
|
||||
- [ ] First containerized service (Nextcloud?) with auto-SSO + auto-subdomain
|
||||
|
|
|
|||
113
apps/README.md
Normal file
113
apps/README.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Building a Furtka app from a Docker image
|
||||
|
||||
A Furtka app is a folder with four files. The reconciler walks `/var/lib/furtka/apps/*` at boot, validates each manifest, ensures the declared volumes exist, and runs `docker compose up -d` per app. Filesystem is the only source of truth — no database.
|
||||
|
||||
Use `apps/fileshare/` as the reference implementation.
|
||||
|
||||
## Folder layout
|
||||
|
||||
```
|
||||
apps/<name>/
|
||||
manifest.json # required — app metadata and user-facing settings
|
||||
docker-compose.yaml # required — filename is .yaml, not .yml
|
||||
.env.example # required — keys consumed by docker-compose, with safe defaults
|
||||
icon.svg # required — referenced by manifest.icon
|
||||
```
|
||||
|
||||
The folder name must equal `manifest.name`. The scanner rejects mismatches.
|
||||
|
||||
## `manifest.json`
|
||||
|
||||
All top-level fields except `description_long` and `settings` are required.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myapp",
|
||||
"display_name": "My App",
|
||||
"version": "0.1.0",
|
||||
"description": "One-line summary shown in the app list.",
|
||||
"description_long": "Longer German prose shown on the app page. Optional.",
|
||||
"volumes": ["data"],
|
||||
"ports": [8080],
|
||||
"icon": "icon.svg",
|
||||
"settings": [
|
||||
{
|
||||
"name": "ADMIN_PASSWORD",
|
||||
"label": "Passwort",
|
||||
"description": "Wird beim ersten Start gesetzt.",
|
||||
"type": "password",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules enforced by `furtka/manifest.py`:
|
||||
|
||||
- `volumes` — short names, strings. Namespaced to `furtka_<app>_<short>` at runtime.
|
||||
- `ports` — integers. Informational only; compose owns the actual port binding.
|
||||
- `settings[].name` — must match `^[A-Z_][A-Z0-9_]*$`. This name becomes both the env-var key and the form-field ID.
|
||||
- `settings[].type` — one of `text`, `password`, `number`.
|
||||
- `settings[].required` — if true, the install refuses when the value is empty.
|
||||
- `settings[].default` — optional string. Used to pre-fill the form and the bootstrapped `.env`.
|
||||
|
||||
## `docker-compose.yaml`
|
||||
|
||||
- File extension is `.yaml`. The compose runner hardcodes this — `.yml` will not be found.
|
||||
- Reference manifest volumes as `furtka_<app>_<short>` with `external: true`. The reconciler creates the volume *before* `compose up`, so compose must not try to manage its lifecycle.
|
||||
- Values from `.env` are substituted by compose in the usual `${VAR}` form.
|
||||
- If the upstream image ships a HEALTHCHECK that misbehaves on Furtka's setup, disable it — a permanently-unhealthy container scares users reading `docker ps`.
|
||||
- Pin images to a digest or stable tag when you can. `:latest` is acceptable for an MVP but noisy.
|
||||
|
||||
Minimal example:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/example/myapp:1.2.3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- furtka_myapp_data:/var/lib/myapp
|
||||
|
||||
volumes:
|
||||
furtka_myapp_data:
|
||||
external: true
|
||||
```
|
||||
|
||||
## `.env.example`
|
||||
|
||||
One `KEY=VALUE` per line. Every key declared in `manifest.settings` should have a line here so the compose file resolves cleanly on first install even before the user opens the form.
|
||||
|
||||
Do not use `changeme` (or any value listed in `furtka.installer.PLACEHOLDER_SECRETS`) as the default for a required secret. The install step scans the final `.env` and refuses to finish if a placeholder survives — this is the guardrail that stops us shipping an app with a known password.
|
||||
|
||||
For non-secret values (usernames, paths), sensible defaults are fine and go straight into `.env` on first install.
|
||||
|
||||
## `icon.svg`
|
||||
|
||||
- 64×64 viewBox, no width/height attributes so the UI can scale it.
|
||||
- Use `fill="currentColor"` (and `stroke="currentColor"`) so the icon picks up the current theme instead of baking in a color.
|
||||
- Keep it single-path-ish. These render small in the app grid.
|
||||
- The icon is inlined into the `/apps` page by the defensive SVG sanitiser, which strips `<script>`, `on*` attributes, and `javascript:` refs and enforces a 16 KB cap. Anything fancier than static paths and shapes will be rejected.
|
||||
|
||||
## Install and test
|
||||
|
||||
From the repo root on a dev box with Furtka installed:
|
||||
|
||||
```
|
||||
sudo furtka app install ./apps/myapp
|
||||
```
|
||||
|
||||
`furtka app install` runs a reconcile as its last step, so the container is up once the command returns. Open the Web UI (`http://furtka.local/`), fill in the settings form, and confirm the app starts. `docker ps` should show one container per compose service; `docker volume ls` should show `furtka_myapp_*`.
|
||||
|
||||
To bundle the app into the ISO, drop the folder into `apps/` before `iso/build.sh` runs — the build tarballs the whole `apps/` tree into the image.
|
||||
|
||||
## Out of scope (for now)
|
||||
|
||||
- Sharing volumes between apps. v1 keeps them isolated.
|
||||
- Auth on the Web UI. The UI itself has a banner about this.
|
||||
- Automatic updates. User-triggered per-app update is `POST /api/apps/<name>/update`.
|
||||
- A network catalog. `furtka app install <name>` only resolves bundled apps in `/opt/furtka/apps/`.
|
||||
|
|
@ -21,15 +21,18 @@ The script re-execs itself inside a privileged `archlinux:latest` container. Tha
|
|||
The build starts from Arch's stock `releng` profile (the same one used to build the official Arch ISO), then overlays our customizations from `overlay/`:
|
||||
|
||||
| Overlay file | Effect |
|
||||
|-------------------------------------------|----------------------------------------------------------------------------------|
|
||||
|----------------------------------------------|----------------------------------------------------------------------------------|
|
||||
| `overlay/packages.extra` | Appended to the package list. Adds `python`, `python-flask`, `avahi`, `nss-mdns` |
|
||||
| `overlay/profiledef.sh` | Appended to `profiledef.sh`. Renames the ISO to `furtka-*` with a dated version |
|
||||
| `overlay/airootfs/opt/furtka/` | Directory where `webinstaller/` is copied at build time |
|
||||
| `overlay/airootfs/etc/systemd/system/` | Contains `furtka-webinstaller.service` + a symlink into `multi-user.target.wants/` so it auto-starts on boot |
|
||||
| `overlay/airootfs/etc/hostname` | Live-ISO hostname (`proksi`) so mDNS advertises the installer as `proksi.local` |
|
||||
| `overlay/airootfs/etc/issue` | Welcome banner on the TTY pointing users at `http://proksi.local:5000` |
|
||||
| `overlay/airootfs/usr/local/bin/furtka-update-issue` | Rewrites `/etc/issue` at runtime so the banner also shows the DHCP-assigned IP as a fallback URL |
|
||||
| `overlay/airootfs/etc/systemd/system/` | `furtka-webinstaller.service` (Flask on :5000) + `furtka-issue.service` (runs the banner-updater on network-online), each symlinked into `multi-user.target.wants/` to auto-start on boot |
|
||||
|
||||
The systemd service runs `flask --app app run --host 0.0.0.0 --port 5000` under `/opt/furtka`. The `0.0.0.0` binding is important — the Flask default is localhost-only, which wouldn't be reachable from another machine on the LAN.
|
||||
|
||||
mDNS (`proksi.local`) via avahi is installed but not yet wired. First milestone is just "boot → browser → wizard at raw IP". Naming comes next.
|
||||
mDNS is wired: `avahi-daemon` + `nss-mdns` come from `packages.extra`, the live ISO's hostname is `proksi`, and as soon as `systemd-networkd-wait-online` fires the installer is reachable at `http://proksi.local:5000`. The raw IP still shows on the console for fallback — some Windows clients need the Bonjour service for `.local` to resolve at all.
|
||||
|
||||
## Test flow
|
||||
|
||||
|
|
@ -51,7 +54,7 @@ mDNS (`proksi.local`) via avahi is installed but not yet wired. First milestone
|
|||
Once `archinstall` finishes and you click **Reboot now**, the VM comes up into the installed system. No more port `:5000` — the wizard ISO is gone. Instead:
|
||||
|
||||
- **Console**: agetty shows `Furtka is ready. Open http://<hostname>.local …` with the IP fallback underneath.
|
||||
- **Browser** at `http://<hostname>.local` (default `http://proksi.local`): Caddy-served landing page with three live status tiles (uptime, Docker version, free disk) refreshed every 30 s by `furtka-status.timer`.
|
||||
- **Browser** at `http://<hostname>.local` (default `http://furtka.local` — the form's default hostname is `furtka`; only the live-installer ISO uses `proksi`): Caddy-served landing page with three live status tiles (uptime, Docker version, free disk) refreshed every 30 s by `furtka-status.timer`. Since 26.4-alpha, `https://<hostname>.local` is also served via Caddy's `tls internal` — trust `rootCA.crt` from `/settings` to clear browser warnings.
|
||||
- **SSH**: `ssh <user>@<hostname>.local` works; `docker ps` works without `sudo` because the user is in the `docker` group.
|
||||
|
||||
This is a demo shell — no Authentik, no app store yet. The landing page lives at `/srv/furtka/www/`, served by Caddy on `:80` per `/etc/caddy/Caddyfile`. All of this is written into the target by `webinstaller/app.py`'s `_post_install_commands` via archinstall's `custom_commands`.
|
||||
|
|
@ -59,5 +62,5 @@ This is a demo shell — no Authentik, no app store yet. The landing page lives
|
|||
## Known rough edges
|
||||
|
||||
- **Disk space**: the first time you build on a fresh host, the squashfs/xorriso steps need ~15 GB free. If the host's LVM-root is smaller, `xorriso` silently dies at the very end with "Image size exceeds free space on media".
|
||||
- **No HTTPS yet**. The Furtka plan is "local CA + green padlock on `https://proksi.local`" — that's a later milestone. For now, plain HTTP.
|
||||
- **Live-installer wizard is still HTTP-only**. `http://proksi.local:5000` during install has no TLS; the installed box gets Caddy + `tls internal` on `:443` once it reboots (26.4-alpha), but bringing the same story to the wizard itself is a later milestone.
|
||||
- **Boot USB could appear as an install target on bare metal**. On a VM the ISO is a CD-ROM (filtered) and SATA is the only disk, so the picker only shows the install target. On bare metal with a USB stick, the USB is `TYPE=disk` and shows up alongside the real install drive; a user could in theory pick the USB they just booted from. Mitigating this needs detecting the boot media (via `findmnt /run/archiso/bootmnt` or similar) and filtering it out in `webinstaller/drives.py`.
|
||||
|
|
|
|||
|
|
@ -19,21 +19,37 @@ Hosted on `forge-runner-01` (Proxmox VM, Ubuntu 24.04). Hugo runs on the VM;
|
|||
nginx serves the built output from `/var/www/furtka.org`. TLS is terminated by
|
||||
an upstream openresty reverse proxy — the VM itself only speaks plain HTTP.
|
||||
|
||||
First time only, on the VM:
|
||||
### Auto-deploy on push-to-main (default)
|
||||
|
||||
```sh
|
||||
ssh forge-runner
|
||||
sudo /srv/furtka-site/ops/nginx/setup-vm.sh # or copy the script over first
|
||||
```
|
||||
`.forgejo/workflows/deploy-site.yml` fires on every push to `main` that touches
|
||||
`website/**`. The self-hosted runner *is* forge-runner-01, so the whole deploy
|
||||
collapses to a local rsync into `/srv/furtka-site/` + `hugo --minify` into
|
||||
`/var/www/furtka.org/`. No SSH hop, no secrets. Runs in under a minute.
|
||||
|
||||
From then on, deploy from your dev machine:
|
||||
The in-CI script is `website/deploy-ci.sh`. Don't invoke it from your dev box —
|
||||
it assumes it's already on the target host.
|
||||
|
||||
### Manual deploy (fallback)
|
||||
|
||||
For out-of-band pushes (feature branch, CI outage), deploy from your dev
|
||||
machine:
|
||||
|
||||
```sh
|
||||
./website/deploy.sh
|
||||
```
|
||||
|
||||
The script rsyncs `website/` to `/srv/furtka-site/` on the VM and runs
|
||||
`hugo --minify` into `/var/www/furtka.org`.
|
||||
This rsyncs `website/` to `/srv/furtka-site/` on the VM over SSH and runs
|
||||
`hugo --minify` into `/var/www/furtka.org`. Same end state as the CI path,
|
||||
just with an SSH hop.
|
||||
|
||||
### First-time VM setup
|
||||
|
||||
Only needed once, when provisioning a fresh forge-runner VM:
|
||||
|
||||
```sh
|
||||
ssh forge-runner
|
||||
sudo /srv/furtka-site/ops/nginx/setup-vm.sh # or copy the script over first
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
|
|
@ -48,7 +64,8 @@ layouts/ Custom inline theme — no external theme or framework
|
|||
index.html Home-only layout with editorial hero
|
||||
assets/css/main.css Stylesheet (fingerprinted + minified on build)
|
||||
static/favicon.svg Gate mark in crimson
|
||||
deploy.sh Rsync + remote Hugo build
|
||||
deploy.sh Manual rsync + remote Hugo build (over SSH, for off-CI pushes)
|
||||
deploy-ci.sh Local rsync + Hugo build — runs on forge-runner-01 from CI
|
||||
```
|
||||
|
||||
## Design
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue