box/README.md
2026-05-14 23:36:16 +02:00

6.1 KiB

Project Box

Project Box is a Go + vanilla web app for managing user projects.

Features:

  • Workspace model mapping each app-user workspace to a host Unix account
  • Create projects per authenticated user
  • Auto-create Forgejo repository or onboard an existing repository URL
  • Auto-generate Caddyfile route fragment and reload Caddy (optional)
  • Manage project env vars
  • Auto-generate user-scoped systemd service per project
  • PostgreSQL persistence with startup migration
  • Authentik forward-auth header support + dev mode demo user
  • HTTP server on TCP and optional Unix socket
  • Request/access logs

Run

  1. Copy config:
    cp .env.example .env
    
  2. Start PostgreSQL and create database box.
  3. Start app:
    go run .
    
  4. Open http://localhost:8080.

Environment Variables

See .env.example.

Important:

  • DEV_MODE=true uses a demo user and bypasses forward-auth headers.
  • DEV_MODE=false expects auth headers from your reverse proxy:
    • X-authentik-username
    • X-authentik-email
  • DB_PROVISION_ENABLED=true provisions a dedicated PostgreSQL role/database per app.
  • DB_PROVISION_ADMIN_URL is the admin DSN used for provisioning (defaults to DATABASE_URL).
  • FORGEJO_BASE_URL + FORGEJO_TOKEN enable repository creation.
  • FORGEJO_GIT_USERNAME is used with FORGEJO_TOKEN for non-interactive HTTPS git clone/fetch (default oauth2).
  • FORGEJO_ORG switches creation from personal repos to org repos.
  • UNIX_SOCKET_PATH enables unix socket listener in addition to LISTEN_ADDR.
  • CADDY_ROUTE_ENABLED=true enables route provisioning.
  • CADDY_AUTH_ENABLED=true enables authentik-style forward auth on provisioned app routes.
  • CADDY_AUTH_UPSTREAM sets auth upstream dial (default localhost:9000).
  • CADDY_AUTH_URI sets auth check URI (default /outpost.goauthentik.io/auth/caddy).
  • CADDY_FRAGMENT_PATH points to generated Caddyfile fragment path (default /etc/caddy/fragments/project-manager.caddy).
  • CADDY_RELOAD_COMMAND command run after fragment write (default sudo -n systemctl reload caddy).
  • CADDY_AUTH_IMPORT_NAME snippet import name used per app host block (default auth).
  • CADDY_PERSIST_CONFIG_PATH is only useful if you still use Caddy Admin API externally.
  • CADDY_ADMIN_URL points to the Caddy Admin API (default http://localhost:2019).
  • CADDY_SERVER_ID is the HTTP server object id under apps.http.servers (default srv0).
  • CADDY_DOMAIN_SUFFIX controls generated host as <slug>.<suffix> (if empty: <user>-<slug>.local).
  • WEBHOOK_BASE_URL sets absolute webhook URLs returned by API/UI (recommended behind reverse proxy).

Caddyfile Fragment Behavior

On provisioning/reprovision, when CADDY_ROUTE_ENABLED=true, the app regenerates a managed fragment containing all project routes and then runs CADDY_RELOAD_COMMAND.

Each route block is:

  • <slug>.<domain> {
  • import auth (name controlled by CADDY_AUTH_IMPORT_NAME, optional when auth enabled)
  • log
  • reverse_proxy unix//<unix-user-home>/projects/<app>/app.sock
  • }

In your static /etc/caddy/Caddyfile, include:

import /etc/caddy/fragments/project-manager.caddy

Workspaces and Unix Users

  • Create one or more workspaces per authenticated user.
  • Each workspace stores a unix_user (must exist on the host).
  • Project creation requires selecting a workspace.
  • Service/env files are written under that Unix user's home:
    • ~/.config/systemd/user/<service>.service
    • ~/.config/project-manager/<app-user>/<project>/service.env
  • The app attempts to run user-systemd commands as that Unix user via:
    • sudo -n -u <unix_user> systemctl --user ...

Existing Repos

  • In project creation, set repo_url (or fill "Existing Repo URL" in UI) to onboard an existing repo.
  • If repo_url is provided, Forgejo repo creation is skipped.
  • If repo_url is empty, the app tries to create a Forgejo repo when Forgejo env vars are configured.

Auto Deploy via Webhook

  • Each project gets a unique deploy webhook URL, exposed as webhook_url in project API responses and shown in UI.
  • For repos hosted on the configured Forgejo instance, the app now auto-provisions the repository webhook via Forgejo API.
  • If auto-provisioning cannot apply (for example external non-Forgejo repo), configure a webhook manually to call webhook_url.
  • Deploy runs only when payload ref equals refs/heads/main.
  • Deploy behavior:
    1. Clone repo to ~/projects/<app>/repo if missing.
    2. Otherwise fetch origin main and reset --hard origin/main.
    3. Build binary to ~/projects/<app>/bin/app.
    4. Sync static assets from ~/projects/<app>/repo/static to ~/projects/<app>/data/static.
    5. Restart the project user service via systemctl --user restart.

Notes:

  • WEBHOOK_BASE_URL should be set to an externally reachable base URL so Forgejo can call webhook endpoints.

Runtime Status and Logs

  • Per-project runtime endpoints:
    • GET /api/projects/:id/status
    • GET /api/projects/:id/logs?lines=200
    • POST /api/projects/:id/reprovision (retry provisioning for failed/pending projects)
  • Status is read via systemctl --user show <service>.
  • Logs are read via journalctl --user -u <service>.
  • Both commands are executed as the workspace Unix user via sudo -n -u <unix_user> ....
  • The app also auto-runs loginctl enable-linger <unix_user> (via sudo) to keep user systemd available without active login.

Systemd

For each project in workspace <ws> owned by unix user <unix_user>, the app writes:

  • env file: <unix-user-home>/projects/<app>/.env
  • binary path used by systemd: <unix-user-home>/projects/<app>/bin/app
  • unit file: <home-of-unix-user>/.config/systemd/user/projectmgr-<app-user>-<project>.service

Then it attempts as that unix user:

  • systemctl --user daemon-reload
  • systemctl --user enable --now <service>

The generated .env is synced from the project env-var configuration and always includes:

  • LISTEN_NETWORK=unix
  • LISTEN_ADDRESS=<unix-user-home>/projects/<app>/app.sock
  • DATABASE_URL=postgres://<app_db_user>:<generated_password>@.../<app_db_name>

If user systemd is not available in the runtime environment, project creation still succeeds and unit files are still generated.