**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`.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Developer Homepage
|
||||
|
||||
A static portfolio site packaged for deployment with Podman quadlets.
|
||||
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
|
||||
|
||||
@@ -12,34 +13,96 @@ 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:
|
||||
|
||||
```bash
|
||||
podman run --rm -p 0.0.0.0:8080:8080 -v ./src:/usr/share/nginx/html:z homepage:latest
|
||||
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:
|
||||
|
||||
```bash
|
||||
./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
|
||||
|
||||
1. Copy the container definition to systemd:
|
||||
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):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
cp homepage.container ~/.config/containers/systemd/
|
||||
cp <deploy-config>/homepage.container ~/.config/containers/systemd/
|
||||
```
|
||||
|
||||
2. Reload systemd and start the service:
|
||||
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:
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user start homepage.container
|
||||
systemctl --user start homepage.service
|
||||
systemctl --user status homepage.service
|
||||
```
|
||||
|
||||
3. Enable auto-start on boot:
|
||||
|
||||
```bash
|
||||
systemctl --user enable homepage.container
|
||||
```
|
||||
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 are in the `src/` directory. Edit `index.html`, `style.css`, and `script.js` directly. Rebuild the image after making changes.
|
||||
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.
|
||||
|
||||
```bash
|
||||
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`.
|
||||
|
||||
Reference in New Issue
Block a user