Files
homepage/README.md
T
ducoterra bcfaa1cac6
Build and Push Container / build-and-push (push) Successful in 26s
phase: 02_simulate_fetch_commands
All green. Final report:

**Phase 02 — final verification pass** (both tasks already in `.agents/phases/complete/02_simulate_fetch_commands/`)
- Verified: no `fetch(` remains in `src/` (grep exit 1) or in built `dist/*.js`
- Verified: `simulatedCurlOutput()` / `simulatedWgetOutput()` in `src/terminal-commands.js` (lines 63/73), wired into `src/terminal.js` curl/wget branches
- Verified: regression test `curl/wget are simulated — no real network request leaves the origin` in `tests/e2e/02-terminal.spec.js` — asserts simulated bodies, `web_navigator` toast + localStorage persistence, and zero non-`baseURL` requests
- Tests: `./build.sh` → OK; `npm test` → **46 passed, 0 failed** (≥46 required); `bash .agents/validate.sh` → **exit 0** (E2E + repo-hygiene gates)
- No linter configured beyond validate.sh (vanilla JS, LOCKED no-toolchain)
- Completion criteria: ①no `fetch(` ✅ ②simulators present ✅ ③regression tests present & passing ✅ ④build+test 46 green ✅ ⑤no behavior change (page/terminal/vim/achievements/menu suites all green) ✅
- Documentation: full README/AGENTS.md review — both already updated in working tree and consistent with the project (simulated curl/wget, validate.sh gate, Podman build flow vs Dockerfile, Node ≥20 matches `@playwright/test` engines, M-4 header gap correctly documented as pending Phase 03) — **verified consistent, no changes needed**
- Note: `PLAN.md` still says "44 tests" (now 46) but PLAN is locked — flagged for the harness/user, not edited
- Next pending phase: `03_nginx_security_headers`
2026-10-22 23:14:49 -04:00

119 lines
4.7 KiB
Markdown

# 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
```bash
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 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
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 <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:
```bash
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.
```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
```
The fake terminal performs **no real network I/O**. `curl` and `wget` render
deterministic simulated output built by `simulatedCurlOutput()` /
`simulatedWgetOutput()` in `src/terminal-commands.js`, which echo the requested
URL and say they are simulated (audit finding M-2, Phase 02) — add new command
output the same canned way. The E2E suite guards this: one test records every
request outside the test origin while running `curl` and `wget`, so a real
network call reintroduced into `src/` fails the suite. That test derives the
allowed origin from `baseURL` in `playwright.config.js`, so changing the port
there needs no test edit.
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`.