Files
homepage/README.md
T
ducoterra dcd066d953
Build and Push Container / build-and-push (push) Successful in 26s
phase: 01_fix_history_xss
**Phase 01 — final verification pass (all criteria green)**

- Verified M-1 fix in `src/terminal.js`: ArrowUp / ArrowDown(`>0`) / ArrowDown-else now route through `updateDisplay()` (`textContent`); no `innerHTML` sink touches `cmdLine`/`commandHistory`.
- `grep -n innerHTML src/terminal.js` → 12 hits, all allowed (static prompt literals, `clear`/prank wipes, `outLine.innerHTML = output` gated on the static canned `neofetch` string). **C1 ✓**
- XSS regression test at `tests/e2e/02-terminal.spec.js:156` present + passing; re-proved it is a real guard: re-injected the old `innerHTML` recall into `dist/terminal.*.js` → test failed (`#xss-marker` received 1); rebuild → byte-identical to `src/`. **C2 ✓**
- `./build.sh` → `dist/` OK; `npm test` → **45 passed (0 failed)**; `bash .agents/validate.sh` → **exit 0** (suite + repo-hygiene gates). **C3 ✓**
- Branch coverage of modified code: all 3 recall arms asserted (ArrowUp ×2 specs, ArrowDown `>0` in the existing history spec, ArrowDown-else → empty prompt in the new spec) → >90% gate met; no JS coverage/lint toolchain exists in the locked stack, so lint = `node --check src/*.js tests/e2e/*.js playwright.config.js` + `bash -n build.sh .agents/validate.sh` — clean.
- C4 ✓ terminal/vim/achievements/menu specs all green; only the three recall branches changed.
- Docs (step 6, full README/AGENTS review): README — run section now states the image already contains the built `dist/` (mount `dist/`, never `src/`); new “Serving & configuration” section for `nginx.conf`, incl. **empirically measured** (built image on 127.0.0.1:8124) that `/`, `/index.html` and hashed assets currently return *only* cache headers — the `add_header` non-merge gap = finding M-4/Phase 03; TLS/HSTS-at-edge rule; Quadlet section fixed (unit is not in this repo; Quadlet generates `homepage.service`, so `… start homepage.service`, and `systemctl enable` is a no-op for transient Quadlet units → `[Install]` + `daemon-reload` + linger); Development gained the Node 20+ floor, `npx playwright install chromium`, and `.agents/validate.sh`. AGENTS.md — added the validate.sh gate, flagged `./scripts/check-headers.sh` as not yet in the repo (Phase 03), and scoped the M-1 rule with the allowed-`innerHTML` list.
- Notable: `.agents/PLAN.md` still says “44 tests” (3 places) and its §7 roadmap omits phases 04/05 — left untouched per the no-PLAN-edit rule; flag for the next PLAN review. No live-infra touched (temp podman image built/removed for the header measurement only).
- Next pending phase: `02_simulate_fetch_commands`.
2026-10-22 22:45:40 -04:00

4.1 KiB

Developer Homepage

A static portfolio site (vanilla HTML/CSS/JS + a fake terminal easter egg) packaged as an nginx container and deployed with Podman Quadlet.

Build & Run with Podman

Build the image

podman build -t homepage:latest .

Run the container

The image runs build.sh itself and copies the hashed dist/ output into the nginx html root, so the built site is already inside the image:

podman run --rm -p 0.0.0.0:8080:8080 homepage:latest

To iterate on src/ without rebuilding the image, build locally and bind-mount the built output over the html root — mount dist/, never src/, because index.html requests content-hashed filenames:

./build.sh
podman run --rm -p 0.0.0.0:8080:8080 -v ./dist:/usr/share/nginx/html:z homepage:latest

Visit http://localhost:8080 in your browser.

Serving & configuration

nginx.conf is copied into the image as the site config. It listens on 8080 over plain HTTP, serves the hashed build, declares the hardening headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy), serves index.html with no-cache, and caches the hashed assets as immutable for a year.

Known gap (audit finding M-4, Phase 03): nginx does not merge add_header across configuration levels, so the location blocks that set their own headers drop the server-level security headers — measured against the built image, /, /index.html and a hashed asset currently get only the cache headers. Every add_header block must repeat the hardening headers until that phase lands.

TLS and HSTS are the job of the reverse proxy in front of the container (reeseapps.com terminates at the Caddy edge). Do not add Strict-Transport-Security or a listen 443 block to nginx.conf.

Deploy with Quadlet

The homepage.container Quadlet unit is not tracked in this repository — it lives with the deployment host's config. Install it under ~/.config/containers/systemd/ (copy it from wherever the host's unit files are kept):

mkdir -p ~/.config/containers/systemd
cp <deploy-config>/homepage.container ~/.config/containers/systemd/

It must publish host port 8080 and use the image CI publishes: gitea.reeseapps.com/services/homepage (main on every push to main, plus one tag per release).

Quadlet is a systemd generator: it turns homepage.container into a homepage.service unit when systemd regenerates units, so manage it under the .service name:

systemctl --user daemon-reload
systemctl --user start homepage.service
systemctl --user status homepage.service

Auto-start on boot comes from the unit's [Install] section (e.g. WantedBy=default.target), which the generator applies while generating — the Quadlet services are transient, so systemctl enable has no effect on them; re-run systemctl --user daemon-reload after editing the unit. For a user-mode unit that must come up without a login session, enable lingering once: loginctl enable-linger $USER.

Development

Source files live in src/: index.html, style.css, script.js and the flat terminal modules (terminal.js, terminal-commands.js, terminal-vim.js, terminal-achievements.js). Keep every .js file flat in src/ — build.sh only hashes root-level files.

npm install        # installs the pinned @playwright/test dev dependency
./build.sh         # src/ -> dist/ (content-hashed asset names)
npm test           # Playwright E2E suite against dist/ on 127.0.0.1:8123

Always build before testing: the suite exercises the built artifact in dist/, never src/. npm test starts (or reuses) its own static server, so no manual server is needed. Rebuild the image (podman build) after changing src/.

Requires Node.js 20+ (the @playwright/test engine floor) and the Chromium revision cached for the pinned Playwright version — run npx playwright install chromium if it is missing, and see AGENTS.md before bumping the pin.

The validation gate the phased pipeline runs per task is .agents/validate.sh (full E2E suite plus repo-hygiene checks): bash .agents/validate.sh.