Files

185 lines
8.8 KiB
Markdown

# Website Engine Operations
This document records the verified deployment and access design without containing passwords, tokens, private keys, or credential values.
## Current topology
The current deployment is **unstaged**. The active release is the live site.
| Purpose | Address | Exposure |
| --- | --- | --- |
| Engine control plane | `http://10.138.2.46/` | Private network, Nginx port 80 |
| Rendered live site | `http://10.138.2.46:3000/` | Private network, Nginx port 3000 |
| Control-plane Node service | `http://127.0.0.1:3001/` | Engine container only |
| Gitea web/API | `http://10.138.2.42:3000/` | Private network |
| Gitea SSH | `[email protected]:22` | Private network |
| Public Gitea | `https://git.labyricorn.com/` | Public entry point |
Port 80 proxies to the private Node listener. Port 3000 serves files through the active-release symlink; it does not proxy to Node.
In a future staged deployment, port 3000 may be assigned to the preview candidate instead. That behavior is not configured in the current unstaged deployment.
## Server layout
| Item | Location or name |
| --- | --- |
| Source checkout | `/opt/website-engine-control-plane` |
| Persistent builds | `/var/lib/website-engine/builds` |
| Active-release pointer | `/var/lib/website-engine/builds/current` |
| Service account | `website-engine` |
| systemd service | `labyricorn-control-plane.service` |
| Nginx site | `/etc/nginx/sites-available/website-engine` |
| Versioned service definition | `deploy/labyricorn-control-plane.service` |
| Versioned Nginx definition | `deploy/nginx-website-engine.conf` |
The systemd service is enabled across reboots. Nginx and the control-plane service must both be active for the control plane to work. Nginx alone serves the activated static release on port 3000.
## Private Gitea access
The engine checkout uses this read-only private SSH remote:
```text
[email protected]:Labyricorn/git-website-engine-control-plane.git
```
The Gitea SSH username is `gitea`, not the common default `git`.
The deploy key is named `website-engine-10.138.2.46` in Gitea. Its public fingerprint is:
```text
SHA256:RtuazmPwcRLY6PbgtEaEELoQk8nN7OfsMyaB7Sa++J0
```
The pinned Gitea ED25519 host fingerprint is:
```text
SHA256:XM3wWREeQesVBhLltXXnGCmpCr6M1bWH+545f792tGc
```
Credential files on the engine server:
| File | Owner/mode | Purpose |
| --- | --- | --- |
| `/var/lib/website-engine/.ssh/gitea_ed25519` | `website-engine`, `0600` | Private deploy key; never display or copy into Git |
| `/var/lib/website-engine/.ssh/gitea_ed25519.pub` | `website-engine`, `0644` | Public deploy key |
| `/var/lib/website-engine/.ssh/known_hosts` | `website-engine`, `0600` | Pinned Gitea host key |
The repository-local `core.sshCommand` selects this identity, requires the pinned host key, disables interactive prompting, and refuses unrecognized host keys. The deploy key has read access only.
Older protected HTTP credentials remain outside the repository under `/root/.config/gitea/`. They are not needed for deployment fetches now that private SSH works. Never print, commit, or copy their values into commands, logs, issues, or documentation.
## Verified access matrix
| Path | Result |
| --- | --- |
| Engine to `http://10.138.2.42:3000/` | HTTP 200 |
| Engine to the private Gitea API | HTTP 200 |
| Anonymous private HTTP Git read | HTTP 401, expected for the private repository |
| SSH as `[email protected]` | Rejected; incorrect Gitea SSH user |
| SSH as `[email protected]` | Authenticated with the deploy key |
| Private SSH `git ls-remote` | Successful |
| Engine checkout versus `origin/main` | Synchronized when last verified |
| GPG key for Git authentication | Not applicable; GPG signs commits but does not authenticate Git transport |
## Release and deployment flow
1. Verify changes locally with `npm test`, `npm run lint`, and `npm run build`.
2. Commit and push `main` to Gitea.
3. On the engine server, update only with a fast-forward pull:
```bash
git -C /opt/website-engine-control-plane pull --ff-only
```
4. Install locked dependencies and rebuild when application code or dependencies changed:
```bash
cd /opt/website-engine-control-plane
npm ci --no-audit --no-fund
npm test
npm run lint
npm run build
```
5. Install changed versioned service or Nginx configuration, validate it, and restart only the affected service.
6. Trigger and validate an engine build before activation.
7. Activate the release. Activation updates the `current` symlink atomically, and Nginx begins serving it on port 3000 without copying files into the Nginx configuration directory.
Do not deploy an unpushed working tree or bypass `--ff-only`. The server checkout should remain reproducible from Gitea.
## Verification checks
Run these checks on the engine server after deployment:
```bash
systemctl is-active labyricorn-control-plane.service nginx
ss -lntp | grep -E '(:80|:3000|:3001)'
curl -fsS http://127.0.0.1:3001/api/health
curl -fsSI http://127.0.0.1:3000/
readlink -f /var/lib/website-engine/builds/current
git -C /opt/website-engine-control-plane rev-list --left-right --count HEAD...origin/main
```
Expected listeners:
- Nginx on `0.0.0.0:80` and `0.0.0.0:3000`, with IPv6 equivalents;
- Node on `127.0.0.1:3001` only.
An unknown rendered-site route should return HTTP 404 and the generated `404.html` page.
## Security rules
- Add only public keys to Gitea. Never transfer or paste the private deploy key.
- Keep the deploy key read-only unless a separately reviewed workflow genuinely requires pushes from the engine.
- Prefer the private SSH remote for engine-to-Gitea Git traffic.
- Treat plain private HTTP as unencrypted. Do not send tokens through it when SSH can perform the operation.
- Do not disable strict host-key checking. If the Gitea host fingerprint changes, stop and verify the new fingerprint from the Gitea console before updating `known_hosts`.
- Do not place secrets in Git remotes, environment files committed to Git, service definitions, command histories, or documentation.
- Preserve the dedicated `website-engine` service account and its restrictive file permissions.
## Key rotation
1. Generate a new key under a new filename on the engine server.
2. Add only the new public key to Gitea as a read-only deploy key.
3. Verify SSH authentication and `git ls-remote` with the new key.
4. Update the repository-local `core.sshCommand` to the new key.
5. Verify a normal fetch through the private remote.
6. Revoke the old deploy key in Gitea.
7. Remove the old private key only after the new path has been verified end to end.
Do not overwrite the active private key in place during rotation; retaining the old key until verification provides a safe rollback path.
## Repository theme build contract
Production presentation is owned by `.theme/` in the repository selected by `packages/site-definition/site.yml`. There is no built-in production renderer or preview fallback. Configuration is Git-owned and read-only in control-plane v1; `PUT /api/site-config` returns `405 E_CONFIG_READ_ONLY`.
Each build resolves `LABYRICORN_SITE_DEFINITION_REF` (default `HEAD`) once to a full commit and materializes an archive of that commit. Set these variables when the site definition is not the application checkout:
```text
LABYRICORN_SITE_DEFINITION_REPOSITORY=/srv/site-definition
LABYRICORN_SITE_DEFINITION_REF=refs/heads/main
LABYRICORN_SITE_DEFINITION_PATH=packages/site-definition
LABYRICORN_BUILD_ROOT=/var/lib/website-engine/builds
```
The artifact ID is derived from the canonical input descriptor. Operational run IDs and execution time do not enter artifact bytes. The release layout is:
```text
<build-root>/
├── releases/sha256-<input-digest>/
├── staging -> releases/sha256-<input-digest>/
└── current -> releases/sha256-<input-digest>/
```
Staging and promotion verify every entry in `checksums.json`. A modified artifact is rejected. Promotion updates `current`; it does not render or copy a second release. On startup, valid `staging` and `current` pointers are rediscovered and verified.
### Theme failure behavior
- Missing or invalid `.theme/theme.yml`: the build fails before a release directory is created.
- Unknown manifest keys, unsafe paths, missing templates/assets, or strict-Liquid errors: the build fails closed.
- No selected staging/live release: `/api/live-site/html` returns `503 E_RELEASE_UNAVAILABLE`.
- Unknown route: the selected artifact's `404.html` is returned with HTTP 404.
- Rollback activates a previously verified immutable release; it never recreates or mutates release bytes.
Repository themes may contain Liquid templates and manifest-declared browser assets. They may not execute build programs, declare custom filters/tags, use dynamic includes, emit inline scripts/event handlers/styles, or reference undeclared local dependencies. HTTPS fonts require a pinned SHA-256 checksum and are cached only after verification.