diff --git a/CLAUDE.md b/CLAUDE.md index 50bbee0..9d56edd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -345,7 +345,6 @@ Observed and standardized across servers: | fv-ml1 | 10.251.50.54 | Fountain Valley (`10.251.0.0/16`) | GPU / AI inference (bare metal, **4× RTX PRO 6000 Blackwell Max-Q, 96 GB each = 391 GB VRAM**) | `servers/fv-ml1/README.md` | | irv-ml1 | 100.64.0.6 (headscale mesh); 10.6.110.50 (Irvine LAN, routed via mesh) | Irvine — via headscale mesh; wg0 lifeline (10.100.79.3) dormant post-cutover | GPU / AI inference (bare metal, RTX 3090 + RTX A6000, native stacks) | `servers/irv-ml1/README.md` | | ana-docker | 10.250.50.70 | Anaheim | General-purpose Docker host (non-GPU VM on pfi-pve) | `servers/ana-docker/README.md` | -| pfi-ana-webhost | 10.250.50.52 | Anaheim | VM on pfi-pve (VMID 110) — web workload | `servers/pfi-ana-webhost/README.md` | | ana-filebot | 10.250.50.53 | Anaheim | LXC on pfi-pve (CT 112) — file-task automation | `servers/ana-filebot/README.md` | | pfi-pteradactyl | 10.250.50.55 | Anaheim | VM on pfi-pve (VMID 107) — Pterodactyl game panel | `servers/pfi-pteradactyl/README.md` | | pfi-tacticalrmm | 10.250.50.57 | Anaheim | VM on pfi-pve (VMID 111) — TacticalRMM | `servers/pfi-tacticalrmm/README.md` | diff --git a/dns/internal.yaml b/dns/internal.yaml index e87ece7..530d3f1 100644 --- a/dns/internal.yaml +++ b/dns/internal.yaml @@ -61,7 +61,6 @@ hosts: - {name: ana-wg, site: ana, v4: 10.250.50.252, note: WireGuard host} - {name: corviduo-dev, site: ana, v4: 10.250.50.152, note: Worldtree-team dev VM (PFI-hosted)} - {name: pbs-ana, site: ana, v4: 10.250.50.90, note: Proxmox Backup Server — fleet primary} - - {name: pfi-ana-webhost, site: ana, v4: 10.250.50.52, note: web workload} - {name: pfi-postgres, site: ana, v4: 10.250.50.80, note: shared Postgres} - {name: pfi-pteradactyl, site: ana, v4: 10.250.50.55, note: game panel} - {name: pfi-tacticalrmm, site: ana, v4: 10.250.50.57, note: TacticalRMM} diff --git a/docs/pfi/proxmox-vms.md b/docs/pfi/proxmox-vms.md index c29659e..b985653 100644 --- a/docs/pfi/proxmox-vms.md +++ b/docs/pfi/proxmox-vms.md @@ -34,7 +34,7 @@ every NIC. Linux = virtio, Windows = e1000. CTs take DHCP (reserved). | 105 | PFI-Postgres | 10.250.50.80 | 16 (4×4) | 8 G | 80G ospool | Debian 11 | yes / order 3, up 60s | shared Postgres (vaultwarden/gitea/paperless); PGDATA local since 2026-04-23. `servers/pfi-postgres/` | | 106 | corviduo-dev | 10.250.50.152 | 8 (2×4, x86-64-v2-AES) | 8 G | 80G ospool | Debian 13 | yes / order 10 | Worldtree-team dev VM, PFI-hosted. `servers/corviduo-dev/` | | 107 | PFI-Pteradactyl | 10.250.50.55 | 8 (2×4, host) | 8 G | 256G ospool | Debian 11 | yes / — | Pterodactyl game panel. `servers/pfi-pteradactyl/` | -| 110 | PFI-ANA-Webhost | 10.250.50.52 | 16 (4×4) | 4 G (balloon 1 G) | 250G ospool | Debian 11 | yes / order 30, up/down 120s | web workload. `servers/pfi-ana-webhost/` | +| ~~110~~ | ~~PFI-ANA-Webhost~~ **destroyed 2026-10-04** (last image `pbs-ana:backup/vm/110/2026-10-04T10:28:18Z`) | ~~10.250.50.52~~ | 16 (4×4) | 4 G (balloon 1 G) | 250G ospool | Debian 11 | yes / order 30, up/down 120s | web workload. `servers/pfi-ana-webhost/` | | 111 | pfi-tacticalrmm | 10.250.50.57 | 16 (4×4) | 8 G | 256G ospool | Linux | yes / order 20 | TacticalRMM. `servers/pfi-tacticalrmm/` | All VMs have the QEMU guest agent enabled. diff --git a/servers/pfi-ana-webhost/README.md b/docs/retired/pfi-ana-webhost/README.md similarity index 73% rename from servers/pfi-ana-webhost/README.md rename to docs/retired/pfi-ana-webhost/README.md index 22db8ca..b119bc9 100644 --- a/servers/pfi-ana-webhost/README.md +++ b/docs/retired/pfi-ana-webhost/README.md @@ -1,4 +1,6 @@ -# pfi-ana-webhost — RETIRED 2026-10-04 +# pfi-ana-webhost — DESTROYED 2026-10-04 + +> **Destroyed 2026-10-04 ~1726 (Prime: "retire the webhost — we may want one back in the future, and if so we'll build it fresh").** `qm destroy 110 --purge` on pfi-pve. Last image kept in PBS: `pbs-ana:backup/vm/110/2026-10-04T10:28:18Z` (+ the two before it). Cleaned up with it: FortiGate policy 18, address `ana-webhost-ip`, VIPs `web-to-webhost` + `ssh-to-webhost`, service `ssh-mapped-2223`, MAC object `pfi-ana-webhost`, DHCP reservation (server 5 entry 11); public A `pfi-ana-webhost.phasefinal.com`; internal `pfi-ana-webhost.ana.internal`. **38.120.12.45 is now unmapped.** FortiGate backups on nh3-dev: `/var/tmp/ana-gw-config-20261004T235515Z-pre-webhost-retire.conf`, `/var/tmp/ana-gw-config-20261005T002204Z-pre-webhost-objects-delete.conf`. > **Retired 2026-10-04 (Prime: "retire dragonfirepro").** VM 110 shut down, `onboot 0`, out of the nightly vzdump > (last backup `pbs-ana:backup/vm/110/2026-10-04T10:28:18Z`; the VM is kept, not destroyed). FortiGate policy 18 diff --git a/servers/pfi-ana-webhost/system-details.txt b/docs/retired/pfi-ana-webhost/system-details.txt similarity index 100% rename from servers/pfi-ana-webhost/system-details.txt rename to docs/retired/pfi-ana-webhost/system-details.txt diff --git a/docs/runbooks/disaster-recovery.md b/docs/runbooks/disaster-recovery.md index 2eea2c6..1888b05 100644 --- a/docs/runbooks/disaster-recovery.md +++ b/docs/runbooks/disaster-recovery.md @@ -84,7 +84,7 @@ pfi-pve during morning backup window. Backup suspected as trigger ### pfi-pve (Proxmox VE, 10.250.250.31) **Blast radius — everything hosted on it:** -- ana-docker (VM), ana-nas (CT 109), pfi-postgres (VM 105), pfi-ana-webhost, ana-filebot, pfi-pteradactyl, pfi-tacticalrmm, ana-wg, PBS-ANA (VM), + others +- ana-docker (VM), ana-nas (CT 109), pfi-postgres (VM 105), ana-filebot, pfi-pteradactyl, pfi-tacticalrmm, ana-wg, PBS-ANA (VM), + others Essentially **all Anaheim primary services** go offline. Because ana-nas lives here too, Tier-0 cascade applies simultaneously. @@ -197,11 +197,11 @@ Essentially **all Anaheim primary services** go offline. Because ana-nas lives h **Recovery:** `pct start 112`. -### pfi-ana-webhost (VM on pfi-pve) — web workload +### ~~pfi-ana-webhost~~ — destroyed 2026-10-04 -**Blast radius:** hosted website(s) offline. - -**Recovery:** VM restart; web server auto-start. +Retired and destroyed (Prime). Nothing in the fleet depends on it. Its sites live elsewhere: phasefinal.com on Cloudflare +Pages (`stacks/phasefinal-web/`), dragonfireacoustics.com on Cloudflare Pages in the client's account. Last image in PBS: +`pbs-ana:backup/vm/110/2026-10-04T10:28:18Z`. --- diff --git a/persistent-memory.md b/persistent-memory.md index da81903..8c09345 100644 --- a/persistent-memory.md +++ b/persistent-memory.md @@ -119,7 +119,8 @@ _As of 2026-10-04 ~0546 PT. The newest subsection is first; older subsections ca ### 2026-10-03/04: live now (as of 2026-10-04 ~0546 PT) -- **pfi-ana-webhost RETIRED 2026-10-04 1657 (Prime: "retire dragonfirepro")**: VM 110 stopped, `onboot 0`, out of vzdump (backups kept); FortiGate policy 18 (WAN → HTTP/HTTPS/SSH to it) disabled, VIPs left inert; config backup on nh3-dev `/var/tmp/ana-gw-config-20261004T235515Z-pre-webhost-retire.conf`. ⭐ **dragonfireacoustics.com is NOT ours to serve:** Cloudflare Pages in the client's account since 2026-09-24 (source only on nh3-dev `~/development/adhoc/dragonfireacoustics-site`, no git remote; creds `nh3-dev/.config/cloudflare/dragonfireacoustics-com`); domain on Cloudflare Registrar to 2035-10-30, transfer-locked. **Supersedes the two 2026-09-17 dragonfire entries below** (expiry risk resolved; webhost no longer involved). → `servers/pfi-ana-webhost/README.md` +- **pfi-ana-webhost DESTROYED 2026-10-04 ~1726 (Prime: retire, build fresh if ever needed)**: `qm destroy 110 --purge`; last image `pbs-ana:backup/vm/110/2026-10-04T10:28:18Z`. All FortiGate objects for it deleted (policy 18, VIPs, address, service, DHCP reservation; **38.120.12.45 now unmapped**), public + internal DNS records removed, inventory moved to `docs/retired/pfi-ana-webhost/`. ⭐ dragonfireacoustics.com = Cloudflare Pages in the CLIENT's account (source only on nh3-dev `~/development/adhoc/dragonfireacoustics-site`, no remote). **Supersedes the two 2026-09-17 dragonfire entries.** +- **phasefinal.com on Cloudflare Pages since 2026-10-04 ~1720** (project `phasefinal`, our account `vh@phasefinal`): www + apex CNAME → `phasefinal.pages.dev`; nginx container on ana-docker retired. Privacy page hosting line rewritten (Prime-approved wording). `_headers` + `404.html` added. ⚠ **Apex no longer 301s to www** (both serve) and the migration used **jackdaw's** Pages token: **Prime to mint `infra-ops-pages-token`** (Account Pages Edit + zone phasefinal.com DNS Edit + Single Redirect Edit), then add the apex→www Redirect Rule. Deploy: `stacks/phasefinal-web/deploy-pages.sh`. - **VM 101 PFI-ANA-DC (pfi-pve) and VM 104 nh3-laser (nh3-pve) RETIRED from running (Prime 2026-10-04 1634: "we aren't running the DC or the remote laser")**: 101 shut down cleanly, both `onboot 0`, both excluded from their nightly vzdump (last backups kept). That also ends the PVE 9 Secure Boot/VirtIO backup warnings. ⚠ If either is ever started again: enroll the Microsoft 2023 certs first (`qm enroll-efi-keys`, BitLocker check). - **infra-ops login on pbs-ana + pbs-nh3** (Prime 2026-10-04): uid 850, fleet key, NOPASSWD + `log_output`, bootstrapped via guest agent; `servers/pbs-*/ssh-target` now infra-ops. - **PBS 3 → 4: DONE 2026-10-04 1144** (Prime via Miranda): pbs-nh3 then pbs-ana, both PBS 4.2.7 / Debian 13.7. VM backups first (pbs-ana had none anywhere), disk snapshots `pre-pbs4`, read-only datastores, detached dist-upgrades. Proven: v3→v4 sync, PVE 9→PBS 4 write (ct/113), v4→v4 sync. ⚠ My first prep killed apt mid-dpkg on both (qemu-guest-agent restarted itself under the in-band apt); repaired in ~5 min, PBS never stopped serving. **Open:** sfsrv-ana (PVE 8) 04:00 run on 10-05 is the one untested pair; drop `pre-pbs4` snapshots after it; vzdumps until ~10-11. → `docs/runbooks/pbs-pbs4-upgrade-plan.md` diff --git a/scripts/bootstrap-infra-ops-fleet.sh b/scripts/bootstrap-infra-ops-fleet.sh index f4bc7ab..3a60981 100755 --- a/scripts/bootstrap-infra-ops-fleet.sh +++ b/scripts/bootstrap-infra-ops-fleet.sh @@ -26,7 +26,7 @@ PLAYBOOK=playbooks/bootstrap-infra-ops-user.yaml # Tier 1 — compute / docker hosts (where the agent recurs) TIER1=(irv-ml1 ana-ml2 ana-docker nh3-docker nh3-dev) # Tier 2 — PFI app VMs / LXCs -TIER2=(pfi-ana-webhost ana-filebot pfi-pteradactyl pfi-tacticalrmm ana-nas) +TIER2=(ana-filebot pfi-pteradactyl pfi-tacticalrmm ana-nas) # Tier 3 — sensitive infra (DB, backup, network, hypervisors): opt-in via PFI_TIER=all TIER3=(pfi-postgres pbs-ana pbs-nh3 ana-wg pfi-pve nh3-pve) diff --git a/scripts/fleet-ownership-audit.sh b/scripts/fleet-ownership-audit.sh index b6a1a9f..b77af2f 100755 --- a/scripts/fleet-ownership-audit.sh +++ b/scripts/fleet-ownership-audit.sh @@ -50,7 +50,7 @@ declare -A TARGET=( [nh3-docker]=10.100.50.40 [nh3-extdev]=10.100.50.42 [nh3-nas]=syncuser@10.100.50.50 [nh3-pve]=10.100.250.60 [pbs-ana]=lkraven@10.250.50.90 [pbs-nh3]=lkraven@10.100.50.90 - [pfi-ana-webhost]=10.250.50.52 [pfi-gx10]=10.100.50.60 + [pfi-gx10]=10.100.50.60 [pfi-postgres]=lkraven@10.250.50.80 [pfi-pteradactyl]=10.250.50.55 [pfi-pve]=10.250.250.31 [pfi-tacticalrmm]=10.250.50.57 [vm-esh-nas]=lkraven@10.0.50.154 diff --git a/servers/pfi-ana-webhost/ssh-target b/servers/pfi-ana-webhost/ssh-target deleted file mode 100644 index f2b5be4..0000000 --- a/servers/pfi-ana-webhost/ssh-target +++ /dev/null @@ -1 +0,0 @@ -lkraven@10.250.50.52 diff --git a/servers/pfi-pve/README.md b/servers/pfi-pve/README.md index 74f9c1a..7b7df85 100644 --- a/servers/pfi-pve/README.md +++ b/servers/pfi-pve/README.md @@ -37,7 +37,7 @@ hypervisor): - **VM 105** — `pfi-postgres` (PFI-Postgres; PGDATA on local VM disk as of 2026-04-23 migration off ana-nas NFS) — `servers/pfi-postgres/` - **VM 106** — `corviduo-dev` (Worldtree-team dev VM, PFI-hosted) — `servers/corviduo-dev/` - **VM 107** — `pfi-pteradactyl` (game panel) — `servers/pfi-pteradactyl/` -- **VM 110** — `pfi-ana-webhost` — **RETIRED 2026-10-04: stopped, `onboot 0`, out of vzdump** — `servers/pfi-ana-webhost/` +- ~~**VM 110** — `pfi-ana-webhost`~~ — **destroyed 2026-10-04** (Prime); last image `pbs-ana:backup/vm/110/2026-10-04T10:28:18Z`; history in `docs/retired/pfi-ana-webhost/` - **VM 111** — `pfi-tacticalrmm` — `servers/pfi-tacticalrmm/` - **CT 109** — `ana-nas` (Debian LXC serving NFS/SMB; bind-mounts from ospool; data-serving SPOF — see `memory/incident_ana_nas_spof.md`) — `servers/ana-nas/` diff --git a/stacks/phasefinal-web/README.md b/stacks/phasefinal-web/README.md index 15cad63..cde145d 100644 --- a/stacks/phasefinal-web/README.md +++ b/stacks/phasefinal-web/README.md @@ -1,7 +1,11 @@ # phasefinal-web -The Phase Final, Inc. corporate site — nine static pages served by nginx on -**ana-docker**, fronted by Traefik at `www.phasefinal.com`. +The Phase Final, Inc. corporate site — nine static pages on **Cloudflare Pages** +(project `phasefinal`, account `vh@phasefinal`, `phasefinal.pages.dev`), at +`www.phasefinal.com` and `phasefinal.com`. Moved off ana-docker 2026-10-04 (Prime); +the nginx container there was retired (`/opt/docker/{compose,conf}/phasefinal-web.retired-20261004`). +`conf/site/` is the deployed tree, including `_headers` (the response headers nginx used +to set) and `404.html` (without it Pages serves the home page for every unknown path). Built from the design brief at `docs/design/` (boothed 2026-08-29); the markup and CSS came from a Claude Design session and are checked in verbatim under @@ -21,35 +25,31 @@ requests rule. Both must be undone on import: Without these the page silently falls back to system fonts and looks wrong rather than broken, which is the failure mode you won't notice. -## Routing — and the healthcheck trap that looks like a routing bug - -Routers come from the compose labels: `phasefinal-web` serves -`www.phasefinal.com`; `phasefinal-apex` 301s `phasefinal.com` to it. - -⚠ **Traefik silently skips containers Docker reports as unhealthy.** There is no -error, no log line, and no router — it looks exactly like a broken provider. The -first deploy of this stack hit that: the healthcheck used -`http://localhost/`, which resolves to `::1` in `nginx:alpine`, and nginx listens -on IPv4 only, so the check got "Connection refused" forever and the container -never left `unhealthy`. **Use `127.0.0.1`, never `localhost`, in a healthcheck -for an IPv4-only listener** — and if a correctly-labelled container never appears -in `/api/http/routers`, check `docker inspect --format '{{.State.Health.Status}}'` -*before* suspecting Traefik. - ## Deploy ```bash -scripts/deploy-stack.sh ana-docker phasefinal-web -ssh infra-ops@10.250.50.70 'cd /opt/docker/compose/phasefinal-web && docker compose up -d' +stacks/phasefinal-web/deploy-pages.sh # conf/site → Pages production (branch main) ``` -## DNS +Needs a vaulted Cloudflare token with Account → Cloudflare Pages: Edit (see the script). +⚠ The 2026-10-04 migration used jackdaw's token as a one-off; a dedicated +`nh3-dev/.config/cloudflare/infra-ops-pages-token` is pending Prime. Check the result on +the per-deploy preview URL wrangler prints before trusting the custom domains. -`www.phasefinal.com` → A `38.120.12.44` (the Anaheim public IP, forwarded to -ana-docker's Traefik). The apex `phasefinal.com` has **no** record — the site is -www-only by operator decision. Cloudflare-proxied is the intended end state, for -edge caching and the automatic email-address obfuscation the contact block -relies on. +## DNS and redirects + +`www.phasefinal.com` and `phasefinal.com` → CNAME `phasefinal.pages.dev`, Cloudflare-proxied +(both are Pages custom domains). Cloudflare-proxied is also what provides the email-address +obfuscation the contact block relies on. ⚠ **The apex no longer 301s to www**: Traefik used +to do that, Pages `_redirects` cannot redirect between hostnames, and no vaulted token may +create a zone Redirect Rule. Both hostnames serve the site until Prime mints a token with +Single Redirect: Edit; then add the rule `phasefinal.com/* → https://www.phasefinal.com/${1}` (301). + +## Lesson from the ana-docker era (2026-08-29) + +Traefik silently skips containers Docker reports as unhealthy: no error, no router. The first +deploy's healthcheck used `http://localhost/`, which is `::1` in `nginx:alpine` while nginx +listened on IPv4 only. Use `127.0.0.1` in healthchecks for IPv4-only listeners. ## Content constraints diff --git a/stacks/phasefinal-web/cloudflare/README.md b/stacks/phasefinal-web/cloudflare/README.md index 64cb8a0..67cde5d 100644 --- a/stacks/phasefinal-web/cloudflare/README.md +++ b/stacks/phasefinal-web/cloudflare/README.md @@ -7,11 +7,13 @@ reviewable and reproducible rather than living only in someone's dashboard. | name | type | value | proxied | |---|---|---|---| -| `www.phasefinal.com` | A | `38.120.12.44` | yes | -| `phasefinal.com` | A | `38.120.12.44` | yes | +| `www.phasefinal.com` | CNAME | `phasefinal.pages.dev` | yes | +| `phasefinal.com` | CNAME | `phasefinal.pages.dev` | yes | -`38.120.12.44` is the Anaheim public IP, forwarded to ana-docker's Traefik. -The apex 301s to `www` via a Traefik middleware, not a Cloudflare rule. +Since 2026-10-04 both are Cloudflare Pages custom domains (project `phasefinal`). They used to be +A `38.120.12.44` → ana-docker's Traefik, which also 301'd the apex to www; that redirect is gone +until a Redirect Rule can be created (see `../README.md`). The cache rule below still applies and +is harmless with Pages as the origin (it respects origin headers). ## Cache ruleset diff --git a/stacks/phasefinal-web/compose.yaml b/stacks/phasefinal-web/compose.yaml deleted file mode 100644 index ad6c6f2..0000000 --- a/stacks/phasefinal-web/compose.yaml +++ /dev/null @@ -1,40 +0,0 @@ -services: - web: - image: nginx:alpine - container_name: phasefinal-web - restart: unless-stopped - volumes: - - /opt/docker/conf/phasefinal-web/site:/usr/share/nginx/html:ro - - /opt/docker/conf/phasefinal-web/nginx.conf:/etc/nginx/conf.d/default.conf:ro - networks: - - tnet - healthcheck: - # 127.0.0.1, NOT localhost: in this image `localhost` resolves to ::1 and - # nginx listens on IPv4 only, so `localhost` gives "Connection refused" - # forever. Traefik SKIPS unhealthy containers, so a wrong healthcheck here - # silently means "no route" rather than "unhealthy service". - test: ["CMD", "wget", "-qO-", "http://127.0.0.1:80/"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - labels: - - traefik.enable=true - - traefik.http.routers.phasefinal-web.rule=Host(`www.phasefinal.com`) - - traefik.http.routers.phasefinal-web.tls=true - - traefik.http.routers.phasefinal-web.tls.certresolver=anaprod - - traefik.http.services.phasefinal-web.loadbalancer.server.port=80 - # apex -> www, 301 - - traefik.http.routers.phasefinal-apex.rule=Host(`phasefinal.com`) - - traefik.http.routers.phasefinal-apex.tls=true - - traefik.http.routers.phasefinal-apex.tls.certresolver=anaprod - - traefik.http.routers.phasefinal-apex.service=phasefinal-web - - traefik.http.routers.phasefinal-apex.middlewares=phasefinal-to-www - - traefik.http.middlewares.phasefinal-to-www.redirectregex.regex=^https?://phasefinal\.com/(.*) - - traefik.http.middlewares.phasefinal-to-www.redirectregex.replacement=https://www.phasefinal.com/$${1} - - traefik.http.middlewares.phasefinal-to-www.redirectregex.permanent=true - -networks: - tnet: - name: traefik-net - external: true diff --git a/stacks/phasefinal-web/conf/nginx.conf b/stacks/phasefinal-web/conf/nginx.conf deleted file mode 100644 index b49bb5e..0000000 --- a/stacks/phasefinal-web/conf/nginx.conf +++ /dev/null @@ -1,18 +0,0 @@ -server { - listen 80; - server_name _; - root /usr/share/nginx/html; - index index.html; - - # Static site; long cache on immutable assets, short on the document so - # a dictated edit goes live on the next deploy without a purge. - location = / { add_header Cache-Control "public, max-age=300"; try_files /index.html =404; } - location ~* \.(woff2|svg|png|ico)$ { add_header Cache-Control "public, max-age=31536000, immutable"; } - location ~* \.css$ { add_header Cache-Control "public, max-age=3600"; } - - add_header X-Content-Type-Options nosniff always; - add_header X-Frame-Options DENY always; - add_header Referrer-Policy no-referrer always; - - location / { try_files $uri $uri/ =404; } -} diff --git a/stacks/phasefinal-web/conf/site/404.html b/stacks/phasefinal-web/conf/site/404.html new file mode 100644 index 0000000..aba912c --- /dev/null +++ b/stacks/phasefinal-web/conf/site/404.html @@ -0,0 +1,55 @@ + + +
+ + + +There is nothing at this address. Return to the home page.
+Nothing from your browser. Fonts and images are served from this site itself. No advertising or analytics scripts are loaded, so no cross-site profiling occurs on our side.
Inquiries sent to inquiry@phasefinal.com are retained as ordinary business email for correspondence and engagement records. We do not add you to marketing lists; there are no marketing lists.
This site is served from infrastructure the firm operates, fronted by Cloudflare for TLS and caching. Server logs exist for abuse control and are not joined to anything else.
This site is static files hosted on Cloudflare Pages, which serves it over TLS. The firm keeps no server logs for it; Cloudflare processes requests under its own privacy policy.
Write to inquiry@phasefinal.com.