Commit graph

8 commits

Author SHA1 Message Date
Claude
0b3cbf8734
feat: private repo support via encrypted git token
- 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
2026-03-26 08:24:55 +00:00
Claude
48b9ccf152
feat: M4 Hardening — encryption, resource limits, monitoring, backups
## 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
2026-03-24 15:06:42 +00:00
Claude
f4aa6972e1
feat: shared Postgres with per-app schemas
One Postgres 16 instance runs in the infra stack (docker-compose).
Each app can be given its own isolated schema with a dedicated,
scoped Postgres user via the new Database card on the app detail page.

What was added:

infra/
  docker-compose.yml  — postgres:16-alpine service + hiy-pg-data
                        volume; POSTGRES_URL injected into server
  .env.example        — POSTGRES_PASSWORD entry

server/
  Cargo.toml          — sqlx postgres feature
  src/db.rs           — databases table (SQLite) migration
  src/models.rs       — Database model
  src/main.rs         — PgPool (lazy) added to AppState;
                        /api/apps/:id/database routes registered
  src/routes/mod.rs   — databases module
  src/routes/databases.rs — GET / POST / DELETE handlers:
      provision  — creates schema + scoped PG user, sets search_path,
                   injects DATABASE_URL env var
      deprovision — DROP OWNED BY + DROP ROLE + DROP SCHEMA CASCADE,
                   removes SQLite record
  src/routes/ui.rs    — app_detail queries databases table, renders
                        db_card based on provisioning state
  templates/app_detail.html — {{db_card}} placeholder +
                              provisionDb / deprovisionDb JS

Apps connect via:
  postgres://hiy-<app>:<pw>@postgres:5432/hiy
search_path is set on the role so no URL option is needed.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 13:16:39 +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
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
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
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