claude/heroku-clone-mvp-plan-NREhc #1

Merged
sander merged 42 commits from claude/heroku-clone-mvp-plan-NREhc into main 2026-03-29 07:24:40 +00:00
Owner

latest changes from GitHub

latest changes from GitHub
sander added 42 commits 2026-03-29 07:24:22 +00:00
Merge pull request #1 from shautvast/claude/heroku-clone-mvp-plan-NREhc
## Env var encryption at rest (AES-256-GCM)
- server/src/crypto.rs: new module — encrypt/decrypt with AES-256-GCM
  Key = SHA-256(HIY_SECRET_KEY); non-prefixed values pass through
  transparently for zero-downtime migration
- Cargo.toml: aes-gcm = "0.10"
- routes/envvars.rs: encrypt on SET; list returns masked values (••••)
- routes/databases.rs: pg_password and DATABASE_URL stored encrypted
- routes/ui.rs: decrypt pg_password when rendering DB card
- builder.rs: decrypt env vars when writing the .env file for containers
- .env.example: add HIY_SECRET_KEY entry

## Per-app resource limits
- apps table: memory_limit (default 512m) + cpu_limit (default 0.5)
  added via idempotent ALTER TABLE in db.rs migration
- models.rs: App, CreateApp, UpdateApp gain memory_limit + cpu_limit
- routes/apps.rs: persist limits on create, update via PUT
- builder.rs: pass MEMORY_LIMIT + CPU_LIMIT to build script
- builder/build.sh: use $MEMORY_LIMIT / $CPU_LIMIT in podman run
  (replaces hardcoded --cpus="0.5"; --memory now also set)

## Monitoring (opt-in compose profile)
- infra/docker-compose.yml: gatus + netdata under `monitoring` profile
  Enable: podman compose --profile monitoring up -d
  Gatus on :8080, Netdata on :19999
- infra/gatus.yml: Gatus config checking HIY /api/status every minute

## Backup cron job
- infra/backup.sh: dumps SQLite, copies env files + git repos into a
  dated .tar.gz; optional rclone upload; 30-day local retention
  Suggested cron: 0 3 * * * /path/to/infra/backup.sh

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
podman-compose requires all networks referenced in service configs to be
explicitly declared in the top-level networks block. Docker Compose
creates the default network implicitly, but podman-compose errors with
'missing networks: default'.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Podman without unqualified-search registries configured in
/etc/containers/registries.conf refuses to resolve short image names.
Prefix every image with docker.io/library/ (official images) or
docker.io/<org>/ (third-party) so pulls succeed unconditionally.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
rust:slim-bookworm doesn't include gcc, and aes-gcm's build deps (via
cc-rs) need a C compiler. With --target x86_64-unknown-linux-gnu set
explicitly, cc-rs looks for the cross-compiler 'x86_64-linux-gnu-gcc'
instead of native 'gcc'.

Fix: install gcc in the build stage and add a [target.x86_64-*] linker
entry pointing to 'gcc' so cc-rs finds it on native x86_64 builds.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
podman-compose does not populate BUILDPLATFORM/TARGETARCH build args, so
the platform-detection logic always fell back to x86_64 — even on arm64.
This caused cc-rs to look for 'x86_64-linux-gnu-gcc' instead of 'gcc'.

Replace the entire cross-compile scaffolding with a plain native build:
  cargo build --release (no --target)

Cargo targets the host platform automatically. If cross-compilation is
ever needed it can be reintroduced with a properly-tested setup.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
include_str!("../../templates/...") is resolved at compile time, so the
template files must be present in the Docker build context. The previous
Dockerfile only copied server/src, not server/templates.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
DOMAIN_SUFFIX=local (or any non-localhost LAN name) caused a TLS handshake
failure because Caddy attempted an ACME challenge that can never succeed for
private domains.

- Caddyfile: tls {$ACME_EMAIL:internal} — falls back to Caddy's built-in CA
  when ACME_EMAIL is absent, uses Let's Encrypt when it is set.
- start.sh: ACME_EMAIL is now optional; missing it prints a warning instead
  of aborting, so local/LAN setups work without an email address.

To trust the self-signed cert in a browser run: caddy trust

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
- db.rs: add nullable git_token column (idempotent ALTER TABLE ADD COLUMN)
- models.rs: git_token on App (#[serde(skip_serializing)]), CreateApp, UpdateApp
- routes/apps.rs: encrypt token on create/update; empty string clears it
- builder.rs: decrypt token, pass as GIT_TOKEN env var to build script
- build.sh: GIT_TERMINAL_PROMPT=0 (fail fast, not hang); when GIT_TOKEN is
  set, inject it into the HTTPS clone URL as x-token-auth; strip credentials
  from .git/config after clone/fetch so the token is never persisted to disk

Token usage: PATCH /api/apps/:id with {"git_token": "ghp_..."}
Clear token:  PATCH /api/apps/:id with {"git_token": ""}

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Adds a 'Git Authentication' card to the app detail page with:
- Status badge (Token configured / No token)
- Password input to set/update the token
- Clear button (only shown when a token is stored)

Token is saved/cleared via PATCH /api/apps/:id — no new endpoints needed.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
x-token-auth is Bitbucket/Gitea-specific; GitHub doesn't recognise it and
returns a misleading 403 'Write access not granted'. x-access-token is the
username GitHub documents for PAT auth and is also accepted by GitLab/Gitea.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Apps default to private (require login). Marking an app public bypasses
the forward_auth check so anyone can access it without logging in.

Changes:
- db.rs: is_public INTEGER NOT NULL DEFAULT 0 column (idempotent)
- models.rs: is_public: i64 on App; is_public: Option<bool> on UpdateApp
- Cargo.toml: add reqwest for Caddy admin API calls from Rust
- routes/apps.rs: PATCH is_public → save flag + immediately push updated
  Caddy route (no redeploy needed); caddy_route() builds correct JSON for
  both public (plain reverse_proxy) and private (forward_auth) cases
- builder.rs: pass IS_PUBLIC env var to build.sh
- build.sh: use IS_PUBLIC to select route type on deploy
- ui.rs + app_detail.html: private/public badge + toggle button in subtitle

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
- docker-compose.yml: Forgejo service on hiy-net, configured via env vars
- postgres-init/01-forgejo.sql: creates forgejo user + database on first Postgres init
- .env.example: document FORGEJO_DB_PASSWORD and FORGEJO_DOMAIN

Routing: add FORGEJO_DOMAIN as an app in HIY pointing to forgejo:3000,
or add a Caddyfile block manually.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Forgejo is a docker-compose service, not a HIY-deployed container. HIY's
dynamic routing uses the hiy-<id>:<port> naming convention which doesn't
match. A static block pointing to forgejo:3000 is the correct approach.

FORGEJO_DOMAIN falls back to forgejo.localhost so Caddy starts cleanly
on installs that don't use Forgejo.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
--resume caused Caddyfile changes (e.g. new Forgejo block) to be silently
ignored on restart because Caddy preferred its saved in-memory config.

Instead, Caddy now always starts clean from the Caddyfile, and the HIY
server re-registers every app's Caddy route from the DB on startup
(restore_caddy_routes). This gives us the best of both worlds:
- Caddyfile changes (static services, TLS config) are always picked up
- App routes are restored automatically without needing a redeploy

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Replaced hardcoded 'CHANGE_ME' in the SQL init file with a shell script
that reads FORGEJO_DB_PASSWORD from the environment. Also pass the variable
into the postgres service in docker-compose.yml so it is available at init time.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Sets FORGEJO__security__INSTALL_LOCK=true so Forgejo skips the first-run
wizard and uses the env var configuration directly.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Adds a pg_isready healthcheck to the postgres service and upgrades the
Forgejo depends_on to condition: service_healthy, preventing the
"connection refused" crash on startup.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Forgejo restart: unless-stopped handles the retry loop until Postgres is ready.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
backup.sh now covers all data:
- SQLite via podman exec into server container (fallback to host path)
- Postgres via pg_dumpall inside postgres container
- Forgejo data volume via podman volume export
- Caddy TLS certificates via podman volume export
- .env file (plaintext secrets — store archive securely)

restore.sh reverses each step: imports volumes, restores Postgres,
restores SQLite, optionally restores .env (--force to overwrite).

Both scripts find containers dynamically via compose service labels
so they work regardless of the container name podman-compose assigns.

.env.example documents HIY_BACKUP_DIR, HIY_BACKUP_REMOTE,
HIY_BACKUP_RETAIN_DAYS.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Adds restore_app_containers() which runs at startup alongside
restore_caddy_routes(). For each app with a successful deploy it
inspects the container state via `podman inspect` and runs
`podman start` if the container is exited (e.g. after a host reboot).
Missing containers are logged as warnings requiring a manual redeploy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Adds the act-runner service alongside Forgejo. It connects to the
Podman socket proxy so CI jobs can build and run containers on the Pi.

Also enables FORGEJO__actions__ENABLED on the Forgejo service.

FORGEJO_RUNNER_TOKEN must be set in .env — obtain it from:
  Forgejo → Site Administration → Actions → Runners → Create new runner

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
auto-update.sh fetches origin every 5 minutes. If new commits are
found it pulls and selectively restarts only what changed:
- server/ or Cargo.*  → rebuild + restart server container
- docker-compose.yml  → full stack up -d
- proxy/Caddyfile     → caddy reload
- anything else       → no restart needed

start.sh now installs hiy-update.service + hiy-update.timer alongside
the existing hiy.service boot unit.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Covers everything that was done manually on the Pi:
- apt packages: podman, aardvark-dns, sqlite3, git, uidmap, python3-pip
- podman-compose via pip (to ~/.local/bin)
- rclone (optional, prompted)
- .env creation from template with prompted values and generated passwords
- git upstream tracking for auto-update
- hands off to start.sh at the end

Safe to re-run — all steps are idempotent.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
code.forgejo.org is the source repo, not the container registry.
The OCI registry is data.forgejo.org and the image is 'runner', not 'act_runner'.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
The data.forgejo.org/forgejo/runner image doesn't auto-register from
env vars — it needs create-runner-file called explicitly before the
daemon starts. The entrypoint handles registration on first run (no
/data/.runner file) then execs the daemon on all subsequent starts.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
The UI registration token is not a hex string — create-runner-file --secret
expects a hex secret. Use the register subcommand with --token instead,
which accepts the token from the Forgejo UI directly.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
The runner is on hiy-net and can reach Forgejo directly at http://forgejo:3000
rather than going out through the public IP and Caddy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Automatically loads HIY_BACKUP_DIR, HIY_BACKUP_REMOTE, HIY_BACKUP_RETAIN_DAYS
and other vars from .env so the cron job works without extra shell setup.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
sander merged commit 506912ff09 into main 2026-03-29 07:24:40 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: sander/Hostityourself#1
No description provided.