Commit graph

22 commits

Author SHA1 Message Date
Claude
872efc74ce
fix: non-admin users with app grants can now push via HTTP
The user_apps check was silently failing because sqlx::query_scalar
without an explicit type annotation would hit a runtime decoding error,
which .unwrap_or(None) swallowed — always returning None → 403.

All three DB calls in check_push_access now use match + tracing::error!
so failures are visible in logs instead of looking like a missing grant.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 10:41:26 +00:00
Claude
5bc1948f1a
feat: make repo URL optional when creating an app
Useful for git-push-only deploys where no external repo URL is needed.
- CreateApp.repo_url: String → Option<String>
- DB schema default: repo_url TEXT NOT NULL DEFAULT ''
- UI validation no longer requires the field
- Label marked (optional) in the form

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 10:37:17 +00:00
Claude
bb26a81fe6
fix: add tracing + explicit types to check_push_access
Makes the exact failure reason (app not found vs. no access grant)
visible in `docker compose logs server`.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 09:57:18 +00:00
Claude
4504c22af8
fix: explicit SQLx type + debug tracing for HTTP git auth
Fixes potential silent failure where sqlx::query_scalar couldn't infer
the return type at runtime. Also adds step-by-step tracing so the exact
failure point (no header / bad base64 / key not found / db error) is
visible in `docker compose logs server`.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 08:33:10 +00:00
Claude
0c995f9a0a
feat: HTTP git push with API key auth
Replaces SSH as the primary git push path — no key generation needed.

  # Admin UI: Users → Generate key (shown once)
  git remote add hiy http://hiy:API_KEY@myserver/git/myapp
  git push hiy main

What was added:

- api_keys DB table (id, user_id, label, key_hash/SHA-256, created_at)
  Keys are stored as SHA-256 hashes; the plaintext is shown once on
  creation and never stored.

- routes/api_keys.rs
  GET/POST /api/users/:id/api-keys  — list / generate
  DELETE   /api/api-keys/:key_id    — revoke

- HTTP Smart Protocol endpoints (public, auth via Basic + API key)
    GET  /git/:app/info/refs        — ref advertisement
    POST /git/:app/git-receive-pack — receive pack, runs post-receive hook

  Authentication: HTTP Basic where the password is the API key.
  git prompts once and caches via the OS credential store.
  post-receive hook fires as normal and queues the build.

- Admin UI: API keys section per user with generate/revoke and a
  one-time reveal box showing the ready-to-use git remote command.

SSH path (git-shell + authorized_keys) is still functional for users
who prefer it; both paths feed the same build queue.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-23 12:59:02 +00:00
Claude
cb0795617f
feat: git push deploy (roadmap step 2)
Full self-contained git push flow — no GitHub required:

  git remote add hiy ssh://hiy@myserver/myapp
  git push hiy main

What was added:

- Bare git repo per app (HIY_DATA_DIR/repos/<app-id>.git)
  Initialised automatically on app create; removed on app delete.
  post-receive hook is written into each repo and calls the internal
  API to queue a build using the same pipeline as webhook deploys.

- SSH key management
  New ssh_keys DB table. Admin UI (/admin/users) now shows SSH keys
  per user with add/remove. New API routes:
    GET/POST /api/users/:id/ssh-keys
    DELETE   /api/ssh-keys/:key_id
  On every change, HIY rewrites HIY_SSH_AUTHORIZED_KEYS with
  command= restricted entries pointing at hiy-git-shell.

- scripts/git-shell
  SSH command= override installed at HIY_GIT_SHELL (default
  /usr/local/bin/hiy-git-shell). Validates the push via
  GET /internal/git/auth, then exec's git-receive-pack on the
  correct bare repo.

- Internal API routes (authenticated by shared internal_token)
    GET  /internal/git/auth          -- git-shell permission check
    POST /internal/git/:app_id/push  -- post-receive build trigger

- Builder: git-push deploys use file:// path to the local bare repo
  instead of the app's remote repo_url.

- internal_token persists across restarts in HIY_DATA_DIR/internal-token.

New env vars:
  HIY_SSH_AUTHORIZED_KEYS  path to the authorized_keys file to manage
  HIY_GIT_SHELL            path to the git-shell script on the host

Both webhook and git-push deploys feed the same build queue.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-23 08:54:55 +00:00
Claude
1671aaf8e8
fix: break infinite redirect for non-admin users on admin UI
Root cause: auth_middleware redirected all non-admins (including logged-in
ones) to /login, and login_page redirected logged-in users back — a loop.

Fix:
- auth_middleware now distinguishes unauthenticated (→ /login?next=) from
  logged-in-but-not-admin (→ /denied), breaking the loop entirely
- /denied page's "sign in with a different account" link now goes to /logout
  first, so clicking it clears the session before the login form appears

The login_page auto-redirect for logged-in users is restored, which is
required for the Caddy forward_auth flow (deployed apps redirecting through
/login?next=<app-url>).

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-23 08:24:41 +00:00
Claude
812c81104a
fix: prevent infinite redirect loop for non-admin users on admin UI
When a non-admin user with a valid session cookie visited an admin-protected
route, auth_middleware redirected them to /login?next=<admin-path>, and
login_page immediately redirected them back because they were "logged in",
causing an infinite redirect loop.

Fix: only skip the login page when the logged-in user is also an admin.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-23 07:50:32 +00:00
Claude
4aea0357b6
fix: app login redirect broken after forward_auth challenge
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
2026-03-22 18:34:07 +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
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
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
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
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
Claude
d322cc3ce1
Fix: log viewer wipes itself due to auto-reload on deploy done
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
2026-03-19 08:57:01 +00:00
Claude
8f5bb158cb
M1: Rust control plane, builder, dashboard, and infra
- Cargo workspace with hiy-server (axum 0.7 + sqlx SQLite + tokio)
- SQLite schema: apps, deploys, env_vars (inline migrations, no daemon)
- Background build worker: sequential queue, streams stdout/stderr to DB
- REST API: CRUD for apps, deploys, env vars; GitHub webhook with HMAC-SHA256
- SSE endpoint for live build log streaming
- Monospace HTMX-free dashboard: app list + per-app detail, log viewer, env editor
- builder/build.sh: clone/pull → detect strategy (Dockerfile/buildpack/static)
  → docker build → swap container → update Caddy via admin API → prune images
- infra/docker-compose.yml + Dockerfile.server for local dev (no Pi needed)
- proxy/Caddyfile: auto-HTTPS off for local, comment removed for production
- .env.example

Compiles clean (zero warnings). Run locally:
  cp .env.example .env && cargo run --bin hiy-server

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 08:25:59 +00:00