# dotnews-paas panel

A very small self-hosted deployment dashboard ("basic Vercel") for the dotnews PaaS.
Node 22 + Fastify 5 + EJS + better-sqlite3, no client framework. Runs on
`127.0.0.1:3900` as user `deploy` under PM2, behind Caddy (TLS).

The panel never touches Caddy/Varnish/PM2/releases itself — all server state is
written by the `paas` CLI, which the panel shells out to. The shared SQLite DB
(`sql/schema.sql`, applied idempotently on every start) holds projects and
deployment history; `paas deploy` updates the deployment row itself when the
panel passes `DEPLOY_ID`/`PAAS_SOURCE`/`PAAS_COMMIT` in the environment.

## Features

- Password login (single admin, scrypt hash, signed session cookie, login rate limit)
- Dashboard with per-project latest deployment, PM2 state, domains, and a live
  "deploying" banner (polls `/api/status` every 3 s)
- Project creation (`paas create`), env editor (`shared/.env`), deploy / rollback /
  restart / remove, runtime logs (`pm2 logs --nostream`)
- Push-to-deploy webhooks for GitHub (HMAC), GitLab (token), Gitea (HMAC) and a
  generic URL-secret fallback (Bitbucket, curl)
- Serial in-process deploy queue: clone/fetch → build (`bash -lc`, app env +
  `CI=true`) → `paas deploy`; full log per deployment with live SSE streaming
- Vercel-style project cards: site favicon, primary domain, last commit message,
  short repo/branch, relative "deployed X ago" time
- Homepage snapshot on the project page, refreshed automatically after each
  successful deploy (headless chromium)

## Environment variables

| Variable              | Default                             | Notes |
| --------------------- | ----------------------------------- | ----- |
| `PANEL_PORT`          | `3900`                              | Listens on 127.0.0.1 only. |
| `PAAS_DB`             | `/srv/paas/paas.db`                 | Shared SQLite DB. |
| `PAAS_BIN`            | `/usr/local/bin/paas`               | The paas CLI. |
| `BUILD_DIR`           | `/srv/paas/builds`                  | Git checkouts / build workspace. |
| `LOG_DIR`             | `/srv/paas/logs`                    | Deployment logs (`deploy-<app>-<id>.log`). |
| `SESSION_SECRET`      | — (required)                        | e.g. `openssl rand -hex 32`. |
| `ADMIN_PASSWORD_HASH` | — (required)                        | `scrypt:<salthex>:<keyhex>`, see below. |
| `DEPLOY_KEY_PUB`      | `/home/deploy/.ssh/id_ed25519.pub`  | Shown in the UI for private repos. |
| `PANEL_BASE_URL`      | (empty)                             | e.g. `https://deploy.younex.de`; used to render webhook URLs. |
| `APPS_DIR`            | `/srv/apps`                         | App homes (`<app>/shared/.env`). |
| `PAAS_SCHEMA`         | `../sql/schema.sql` (repo-relative) | Override if the panel runs outside the repo checkout. |
| `SNAPSHOT_DIR`        | `/srv/paas/snapshots`               | Homepage screenshots (`<app>.png`). |
| `CHROMIUM_BIN`        | `chromium`                          | Headless browser for snapshots (see below). |

## Setup

```sh
cd /srv/paas/panel
npm ci || npm install

# generate the admin password hash (prompts with hidden input):
node bin/hash-password.js
# or: node bin/hash-password.js 'my-password'

# fill SESSION_SECRET / ADMIN_PASSWORD_HASH in ecosystem.snippet.cjs, then:
pm2 start ecosystem.snippet.cjs
pm2 save
```

Caddy site (TLS + reverse proxy):

```
deploy.younex.de {
    reverse_proxy 127.0.0.1:3900
}
```

## Webhooks

Each project gets `PANEL_BASE_URL/hooks/<project>/<secret>`.

- **GitHub** — content type `application/json`, webhook *Secret* = the project
  secret (verified via `X-Hub-Signature-256`).
- **GitLab** — put the secret into *Secret token* (`X-Gitlab-Token`).
- **Gitea** — set the webhook secret (`X-Gitea-Signature` HMAC).
- **Generic / Bitbucket** — the secret in the URL is sufficient; keep it private.

Only pushes to the project's configured branch deploy; everything else returns
`200 {ignored:true}`. Branch deletions are ignored.

## Homepage snapshots

The project page shows a thumbnail screenshot of the currently deployed
homepage. Requires a chromium binary on the server:

```sh
apt install chromium     # or set CHROMIUM_BIN to another chromium/chrome binary
```

Snapshots are taken with `chromium --headless=new` against
`https://<primaryDomain>/` (resolved to 127.0.0.1 via `--host-resolver-rules`,
so the request hits the local Caddy directly) and stored as
`SNAPSHOT_DIR/<app>.png`. They refresh automatically after every successful
deploy, and lazily when a project page is opened with a snapshot older than the
latest live deployment. Only one chromium runs at a time; failures are logged
and never break a deploy. If chromium is missing, everything else works — the
project page just shows "snapshot pending".

## Notes

- On start the panel marks any non-terminal deployments as `failed`
  (`step: panel restarted`) — the in-process queue does not survive restarts.
- Env edits take effect on the next deploy; the "Restart app" button runs
  `pm2 restart <app> --update-env` for immediate effect.
- Removing a project runs `paas remove`, deletes its DB rows and its build dir.
- Deployment logs accumulate in `LOG_DIR`; prune old ones with a cron if needed.
