Commit graph

104 commits

Author SHA1 Message Date
Claude
06ababa7c6
Fix Podman socket for rootless setup on Raspberry Pi
start.sh now activates the Podman user socket via systemctl --user if it
isn't running yet, then exports DOCKER_HOST and PODMAN_SOCK so that
podman compose (which delegates to the docker-compose plugin) can connect.

docker-compose.yml mounts ${PODMAN_SOCK} into the socat proxy container
at a fixed internal path (/podman.sock), so it works for both rootful
(/run/podman/podman.sock) and rootless (/run/user/<UID>/podman/podman.sock)
without hardcoding the UID.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-21 18:08:10 +00:00
Claude
dd107aacdb
Fix start.sh: docker compose → podman compose
Missed in the previous Podman migration commit.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-21 18:03:41 +00:00
Claude
4319b99102
Replace Docker with Podman throughout
- builder/build.sh: all docker commands → podman (build, run, stop, rm,
  network create, images, rmi, inspect)
- server/src/routes/apps.rs: docker stop/restart → podman
- server/src/routes/ui.rs: docker inspect → podman
- infra/Dockerfile.server: install podman instead of docker.io
- infra/docker-compose.yml: rename docker-proxy → podman-proxy, mount
  /run/podman/podman.sock (rootful Podman socket), update DOCKER_HOST
- infra/Makefile: docker compose → podman compose

Podman is daemonless and rootless by default; OCI images are identical so
no build-pipeline changes are needed beyond renaming the CLI.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 14:58:52 +00:00
Claude
0569252edf
Add roadmap: Podman + git push deploy + self-hosted git
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 14:46:14 +00:00
Claude
b20980458e
Update Cargo.lock for bcrypt dependency
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 14:23:31 +00:00
Claude
2cdbf270f6
Add multi-user security service with per-app authorization
Control plane:
- Users and app grants stored in SQLite (users + user_apps tables)
- bcrypt password hashing
- Sessions: HashMap<token, user_id> (in-memory, cleared on restart)
- Bootstrap: first admin auto-created from HIY_ADMIN_USER/HIY_ADMIN_PASS if DB is empty
- /admin/users page: create/delete users, toggle admin, grant/revoke app access
- /api/users + /api/users/:id/apps/:app_id REST endpoints (admin-only)

Deployed apps:
- Every app route now uses Caddy forward_auth pointing at /auth/verify
- /auth/verify checks session cookie + user_apps grant (admins have access to all apps)
- Unauthenticated -> 302 to /login?next=<original URL>
- Authorised but not granted -> /denied page
- Session cookie set with Domain=.DOMAIN_SUFFIX for cross-subdomain auth

Other:
- /denied page for "logged in but not granted" case
- Login page skips re-auth if already logged in
- Cookie uses SameSite=Lax (required for cross-subdomain redirect flows)

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 14:22:57 +00:00
Claude
4454744cba
Add session-based auth to dashboard and API
- New HIY_ADMIN_USER / HIY_ADMIN_PASS env vars control access
- Login page at /login with redirect-after-login support
- Cookie-based sessions (HttpOnly, SameSite=Strict); cleared on restart
- Auth middleware applied to all routes except /webhook/:app_id (HMAC) and /login
- Auth is skipped when credentials are not configured (dev mode, warns at startup)
- Logout link in both dashboard nav bars
- Caddy admin port 2019 no longer published to the host in docker-compose

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 13:45:16 +00:00
Claude
6ff8c9a267
Fix Caddy route registration: discover server name dynamically
Caddy's Caddyfile adapter names servers 'srv0' (not 'hiy'), so
PATCHing /config/apps/http/servers/hiy/routes was a no-op. Now we
query /config/apps/http/servers/ to find the actual server name
before updating routes.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 13:34:21 +00:00
Claude
c7adf84c5f
Caddyfile: wire ACME_EMAIL env var for Let's Encrypt registration 2026-03-20 13:14:01 +00:00
Claude
b9171d2504
Fix env_file path: .env is in project root, not infra/ 2026-03-20 13:06:29 +00:00
Claude
d7d8df759a
Add fallback default in Caddyfile for DOMAIN_SUFFIX
Without a fallback, an unset DOMAIN_SUFFIX expands to an empty string,
making Caddy parse the site block as a second global options block and
fail to start. Using {:localhost} defaults to localhost.
2026-03-20 13:02:14 +00:00
Claude
44c1bf03b4
Load .env directly via env_file so DOMAIN_SUFFIX reaches containers
Using compose-level ${DOMAIN_SUFFIX} substitution only works when docker
compose is run from the same directory as the .env file. env_file loads
the file relative to the compose file, so it works regardless of CWD.
2026-03-20 12:55:12 +00:00
Claude
a9490da8a8
Fix Caddy startup: remove empty ACME_EMAIL that caused parse error
Caddy's email directive requires a non-empty argument. Since ACME_EMAIL
wasn't set, Caddy failed to parse the config. Email is optional for
Let's Encrypt — remove the directive entirely and document it as a
manual opt-in comment.
2026-03-20 12:49:39 +00:00
Claude
dc59293c5e
Replace Cloudflare DNS challenge with standard Let's Encrypt HTTP-01
Caddy's built-in ACME support handles TLS automatically — no CF_API_TOKEN,
no Cloudflare account, no DNS plugin needed. Requires ports 80+443 forwarded
to the Pi and ACME_EMAIL set in infra/.env.
2026-03-20 11:41:40 +00:00
Claude
3794f4cf36
Fix Dockerfile heredoc parse error in RUN if block
Use printf instead of heredoc for cargo config — heredoc inside a
conditional RUN block confuses Docker's parser (fi becomes an unknown
instruction). The config is always written; unused linker entries are
harmless on native builds.
2026-03-20 10:42:46 +00:00
Claude
3096d251c6
Fix Dockerfile: skip cross-compilers when building natively
gcc-aarch64-linux-gnu is an x86→arm64 cross-compiler; it doesn't exist
on arm64 hosts (like the Pi). Only install cross-toolchains and set cargo
linker config when BUILDPLATFORM != TARGETPLATFORM.
2026-03-20 10:40:12 +00:00
Claude
2060606adc
Consolidate to single .env at repo root
Add ACME_EMAIL to root .env.example.
start.sh now reads root .env and passes it to docker compose.
Removed infra/.env.example.
2026-03-20 10:21:35 +00:00
Claude
d5a5875899
Add TLS setup to start.sh; drop Cloudflare requirement
start.sh now generates proxy/caddy.json at launch time with Let's Encrypt
automatic HTTPS (HTTP-01 or TLS-ALPN-01 challenge — no Cloudflare needed).

Reads DOMAIN_SUFFIX and ACME_EMAIL from infra/.env before starting.
Added infra/.env.example to document required vars.
2026-03-20 10:18:01 +00:00
Claude
b060ec68af
Add start.sh and Makefile build-only targets
start.sh builds via 'make build' (platform auto-detected) then starts
services detached with 'docker compose up -d'.

Makefile gains build/build-<platform> targets that build images without
starting, mirroring the existing up/<platform> targets.
2026-03-20 10:06:24 +00:00
Claude
00da63ec80
Auto-detect platform by default; use DOCKER_DEFAULT_PLATFORM for cross-compile targets
Remove hardcoded platform from compose file so plain 'make up' (or
'docker compose up --build') always builds natively for the host.
Explicit targets (up-arm64, up-armv7, etc.) set DOCKER_DEFAULT_PLATFORM.
2026-03-20 10:03:36 +00:00
Claude
0fecb9a4fe
Add up-win alias (Windows Docker Desktop uses linux/amd64 via WSL2) 2026-03-20 10:02:21 +00:00
Claude
5484b29af6
Add up-x64 alias for up-amd64 in Makefile 2026-03-20 10:01:26 +00:00
Claude
588e74a626
Multi-platform Docker build: amd64, arm64, armv7, armv6
Dockerfile now uses BuildKit TARGETARCH/TARGETVARIANT to pick the Rust
cross-compilation target automatically. The build stage always runs on
the host platform for speed.

Makefile provides named targets:
  make up-amd64   # Mac Intel / Linux desktop
  make up-arm64   # Mac M1/M2/M3, Pi 4/5 (64-bit OS)
  make up-armv7   # Pi 2/3/4 (32-bit OS)
  make up-armv6   # Pi Zero / Pi 1
2026-03-20 09:55:53 +00:00
Claude
3c0adff880
Collapse deploy logs by default on app details page 2026-03-20 09:51:30 +00:00
Claude
8dab4231ea
Add info logging to webhook handler
Makes it easy to see if GitHub is hitting the endpoint, whether the
signature check passes, and whether a deploy is triggered.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 09:36:25 +00:00
Claude
ee78f3ff0a
Explicitly pass DOMAIN_SUFFIX and CADDY_API_URL to build script
Environment inheritance from the server process was not reliably
propagating these vars into the spawned bash subprocess.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-20 09:28:59 +00:00
Shautvast
f92545ed4e armv7 target for my old pi 2026-03-19 15:55:43 +01:00
Claude
95ac2adcb0
docs: fix docker-compose for docker.io users — install separately, note hyphen syntax
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 14:27:52 +00:00
Claude
1532bc170b
docs: clarify Docker install — add docker.io fallback, warn against bare apt install docker-ce
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 14:10:27 +00:00
Claude
c7c4e7a2ec
docs: add Raspberry Pi end-to-end setup guide
Covers: OS flash, SSH hardening, ufw/fail2ban, Docker install,
Cloudflare DNS + wildcard TLS, platform startup via docker compose,
first app deploy, webhook setup, daily backups, Netdata/Gatus monitoring,
platform upgrades, and a troubleshooting table.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 13:29:41 +00:00
Claude
ec0f421137
feat(control-plane): add Stop and Restart app controls
- POST /api/apps/:id/stop    → docker stop hiy-{id}
- POST /api/apps/:id/restart → docker restart hiy-{id}

Dashboard (apps table): Stop / Restart buttons alongside Deploy and Delete.
App detail page: container status badge + Stop / Restart buttons in the nav bar.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 12:55:17 +00:00
Claude
217bafc464
feat(control-plane): system overview card, container runtime status, fix auto-refresh
Dashboard now shows:
- System card at top: CPU 1-min load average, RAM used/total, disk used/total
  (reads /proc/loadavg, /proc/meminfo, df -k /)
- Two status columns in the apps table:
  - "Container" — actual Docker runtime state (running/exited/restarting/not deployed)
    via `docker inspect` on each app's hiy-{id} container
  - "Last Deploy" — build pipeline status (queued/building/success/failed)
- Auto-refresh now calls /api/status every 5 s and updates both columns
  (fixes the previous broken refresh that used app.status which didn't exist)

New API endpoint: GET /api/status → {app_id: {deploy, container}} for all apps

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 12:20:09 +00:00
Claude
b83de1e743
Fix usability issues: redirect on missing app and back-to-dashboard after deploy
- app_detail now redirects to / instead of 404 when app is not found
  (handles case where app was removed while user was on the detail page)
- Add a "← Dashboard" button in the log panel that appears once a
  deployment finishes (both success and failed), giving the user a clear
  path back to the main screen

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 12:10:12 +00:00
Claude
944feb39ec
fix: pass PORT env var to app container
Apps follow the Heroku convention of binding to $PORT at runtime.
Without --env PORT=$PORT, containers use their default port which
doesn't match what Caddy is configured to dial, causing 502s.
2026-03-19 11:34:33 +00:00
Shautvast
fd7d417471 latest rust slim 2026-03-19 12:33:08 +01:00
Claude
d7eb5ef6fe
fix: use PATCH not PUT to update Caddy routes
Caddy admin API: PUT creates a key (409 if exists), PATCH replaces it.
Since routes always exists after startup from caddy.json, PATCH is correct.
2026-03-19 11:28:00 +00:00
Claude
2df3c579e4
fix: switch Docker access to TCP via socat proxy; add Caddy error logging
- Add docker-proxy (alpine/socat) sidecar that exposes the Docker Unix
  socket as TCP on port 2375, so server needs no privileged socket mount
- Set DOCKER_HOST=tcp://docker-proxy:2375 in server environment
- App containers are still spawned on the host daemon and join hiy-net,
  so Caddy can still reach them
- Log actual Caddy PUT response body and HTTP status on failure
  instead of a silent warning
2026-03-19 11:24:50 +00:00
Claude
2e98ce957e
fix: make Caddy route upsert robust against missing/invalid routes
- Add --fail to the GET so a 404 (no 'hiy' server yet, stale volume)
  falls back to [] instead of passing error JSON to Python
- Python now guards against non-list responses with try/except
- Always re-append the dashboard catch-all route so it survives
  even when routes are rebuilt from scratch
2026-03-19 11:17:06 +00:00
Claude
a8b22d8e2d
fix: switch to Caddy JSON config so dynamic routes work correctly
The Caddyfile created a server with an auto-generated name, not 'hiy',
so build.sh's PUT to /config/apps/http/servers/hiy/routes was creating
a parallel server that never received traffic.

- Replace Caddyfile with caddy.json that names the server 'hiy' with
  the dashboard as a catch-all fallback route
- Insert app routes at index 0 so host-matched routes are evaluated
  before the catch-all dashboard fallback
- Update docker-compose to mount caddy.json and pass --config flag
2026-03-19 11:02:57 +00:00
Claude
11db59d612
fix: guard against non-dict route entries when filtering Caddy routes
r.get() crashed when the Caddy API returned a routes array containing
string elements. Added isinstance(r, dict) check and also made the
match[0] traversal safer by using any() over the match list.
2026-03-19 10:55:40 +00:00
Claude
bddc1a8027
fix: use musl static linking to eliminate glibc version dependency
Build hiy-server targeting aarch64-unknown-linux-musl so the binary
has no glibc dependency at all, making the runtime image irrelevant
to glibc version mismatches. Uses rustls (already in Cargo.toml) so
no OpenSSL vendoring needed. SQLite is bundled by sqlx.
2026-03-19 10:48:46 +00:00
Claude
f6e6d1f8a3
fix: upgrade builder to rust:1.94-slim-bookworm 2026-03-19 10:46:37 +00:00
Claude
4e8aa1614e
fix: pin builder to rust:1.77-slim-bookworm to match runtime glibc
rust:1.77-slim has drifted to a newer Debian base with glibc 2.39,
but debian:bookworm-slim only has glibc 2.36, causing a GLIBC_2.39
not found error at runtime. Pinning to the explicit bookworm variant
keeps both stages on the same glibc version.
2026-03-19 10:45:51 +00:00
Claude
3d35e220e4
build.sh: give actionable instructions when Caddy is unreachable
The old message just said 'expose manually if needed' with no guidance.
Now it prints the exact docker commands to publish the port directly
and how to find the container IP for a custom reverse proxy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 10:27:17 +00:00
Claude
54ddc0d856
build.sh: fix empty array expansion under set -u
${arr[@]} with set -u throws 'unbound variable' when the array is empty.
Use ${arr[@]+"${arr[@]}"} which only expands if the array is set.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 09:58:20 +00:00
Claude
efca2b5941
build.sh: skip --env-file when env file does not exist
docker run fails hard if the path passed to --env-file is missing.
Make the flag conditional so apps without an env file (or where the
file hasn't been created yet) still start successfully.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 09:47:21 +00:00
Shautvast
98385ea743 proper var use 2026-03-19 10:45:52 +01:00
Claude
1dc70ff046
Add .gitattributes: force LF line endings for shell scripts
Without this, git on macOS with core.autocrlf=true checks out .sh
files with CRLF. Bash then reads 'CONTAINER_NAME\r' as the variable
name (never set), causing 'unbound variable' with set -u.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 09:40:26 +00:00
Claude
e1a01173ed
Fix: capture all script output + always log exit code
Two silent failure modes:

1. lines() drops any output chunk not terminated with \n — a script
   that crashes mid-line (or any final output without a newline) was
   silently swallowed. Switched to raw 4KB chunk reads which stream
   incrementally and capture everything.

2. A non-zero exit with no output (e.g. bash exit 127 'command not
   found') left the log completely empty. Now always appends
   '[hiy] exit code: N' after the process finishes so there is always
   at least one diagnostic line regardless of script output.

Exit code lookup:
  exit code: 0   -> success
  exit code: 1   -> script hit 'set -e' on a failing command
  exit code: 127 -> bash could not find the script or a command in it
  exit code: 126 -> script found but not executable (chmod +x missing)
  exit code: signal -> process killed by OS signal

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 09:37:21 +00:00
Claude
c3f300e8ad
Fix: surface build errors in deploy log instead of swallowing them
When run_build() returned an Err (e.g. spawn failure because the
build script path doesn't resolve) the error was only written to
tracing, leaving the deploy log empty and the user with no clue.

- build_worker now appends the Rust error message to the deploy log
  before setting status=failed, so it appears in the UI.
- run_build logs CWD, resolved script path, exists=true/false, build
  dir, and env file path before attempting spawn, so there is always
  at least one diagnostic line in the log even if spawn itself fails.
- spawn() error is wrapped with the attempted path for clarity.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 09:06:37 +00:00