Two bugs:
1. verify() built the login URL with `//login` (double slash) — now `/login`
2. safe_path() rejected absolute https:// next-URLs, so after login the
user was silently dropped at `/` instead of their original app URL.
Replaced safe_path with safe_redirect(next, domain) which allows relative
paths OR absolute URLs whose host is the configured domain (or a subdomain).
safe_path is kept as a thin wrapper (domain="") for the admin-UI middleware
where next is always a relative path.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
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
- 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
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
- 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
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
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
Two bugs causing 'can't see why deploy failed':
- showLog() called window.location.reload() on the SSE 'done' event,
wiping the log panel before the user could read it.
- For already-finished deploys, SSE would immediately fire 'done' and
reload, showing logs for < 1 second.
Fix:
- showLog() now fetches the deploy via REST first. If done, it renders
the stored log directly (no SSE). If still running, it streams via
SSE and closes without reloading when done.
- Added onerror fallback: re-fetches the log via REST if SSE drops.
- Status badge (green/red) updates inline instead of triggering reload.
- Page now auto-opens the latest deploy log on load so the failure
reason is visible immediately without any clicking.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH