Commit Graph

18 Commits

Author SHA1 Message Date
null 0e4718334b chore(release): v0.9.8 2026-09-10 19:21:51 -05:00
null 8e04201d68 chore(release): v0.9.7 2026-09-10 06:01:25 -05:00
null 11992d8d95 chore(release): v0.9.6 2026-09-10 05:06:40 -05:00
null 2f1e24892a fix(seo): the production sitemap had no dates, and the build context had secrets
Three things, all in the path between this repository and the running image.
Closes #225, #224 and #223.

1. THE PRODUCTION SITEMAP CARRIED NO LASTMOD AT ALL. Dates come from git
   history, and the image build cannot see git: .dockerignore excludes .git and
   node:alpine has no git binary. prerender.js read the failure into an empty
   catch commented "git unavailable or file untracked", so all 18 URLs came out
   undated while the build printed a success line. Local builds looked perfect,
   which is why nobody caught it.

   release.sh now computes the map where git exists, passes it as the
   SITEMAP_LASTMOD build arg, and then asks the built image whether its sitemap
   has dates, refusing to publish one that does not. prerender prints the count
   on every run, so "18 URLs, 0 dated" can never again read as success. The
   route-to-source map moved into scripts/lib/routes.js, where a service page
   now also counts its own content file, so editing one page's copy moves that
   page's date and no other.

   Proven: an image built with the arg carries 18 lastmod entries; a build with
   git deliberately unreadable and no arg reports "18 URLs, 0 carrying a
   lastmod" and warns.

2. THE DOCKER BUILD CONTEXT CARRIED CLIENT MATERIAL AND LIVE SECRETS. .drop/,
   zoho.md (the reCAPTCHA secret and the Zoho tokens), Levi.md and two 30 MB
   zips were all sent to the daemon on every build, along with four agent
   workspaces. The final image copies only built output, so none of it ever
   shipped, but one careless COPY would have changed that. Proven by listing the
   context from inside a throwaway image: before, all of it; after, none of it.

3. UNTRACKED FILES PASSED SILENTLY. docker build packs the working tree, so an
   untracked module the code imports produces an image that works and a tag that
   cannot rebuild it. release.sh now refuses while untracked files are present,
   and pre-commit's note counts them too.

#223 also claimed post-commit hides a refused push. It does not: it printed
"push was refused. The commit is safe locally and the branch is now ahead."
during this batch. The issue was corrected on the tracker rather than acted on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:58:37 -05:00
null b35f171b8c chore(release): v0.9.5 2026-08-18 04:06:22 -05:00
null 0decc03642 chore(release): v0.9.4 2026-08-18 03:34:19 -05:00
Neo 77d5131473 chore(docker): label the image with its version so status.sh can report it
scripts/status.sh --deployed-version reads org.opencontainers.image.version and
the image carried no such label, so on 2026-08-18 it correctly answered
"unknown" and the digest was the only way to tell one deploy from another. Step
3 of the incident runbook depends on that command.

ARG APP_VERSION defaults to 0.9.3 and can be overridden at build time.

Verified by building: docker build succeeds and
`docker image inspect --format '{{json .Config.Labels}}'` reports
org.opencontainers.image.version = 0.9.3, plus title and source.

Takes effect on the next image build. OPERATIONS.md says so rather than
implying the running container has it — it does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:34:27 -05:00
Ripley 615043db44 fix(infra): queuenorth.com is the production origin, and it is this deployment
_null confirmed queuenorth.com as the permanent public origin, which settles
#212 and exposed a wrong claim written earlier the same day.

docs/OPERATIONS.md said queuenorth.com "is not this deployment ... do not
diagnose against it". That came from a DNS lookup and an assumption. It is this
deployment: both hostnames serve the identical bundle and this server's own
/api/health shape, 24.41.108.95 is this network's own public IP, and both reach
qn-website-dev on nebula — queuenorth.com through nginx-proxy-manager on
thor/exodus, qn.isnull.dev through Cloudflare. Two front doors, one container,
no non-production environment.

That is the worst direction for a runbook to be wrong in, so the correction
quotes the wrong sentence rather than replacing it silently. The QA Round 0
table likewise gained the production observations as extra rows instead of
having its originals rewritten.

Dockerfile: the CORS_ORIGIN fallback was '*'. The server sets credentials:true
and browsers reject '*' with credentials outright, so that fallback would have
broken every form rather than over-permitting. Now the real origin.

healthcheck.sh and preflight.sh now watch production by default, with the
second front door reachable through their env overrides — the two ingresses
terminate TLS in different places and can rot independently.

Also fills a gap adoption left explicitly undone: the deploy path is a Portainer
stack, id 58 on nebula, found from the container's own compose labels.
OPERATIONS.md documents it, including that the stack file is a separate copy
from this repository's docker-compose.yml and the two have already drifted.

That drift is all that remains of #212 — one trailing slash on line 21 of the
stack file. Left in place: nothing is broken today, and fixing it recreates the
container and takes both front doors down together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:08:12 -05:00
null d074e597b2 feat(zoho): WebToLead forwarding mode, veteran-owned certified badge, Docker/env/CI updates (batch 0.9.1) 2026-06-14 16:08:29 -05:00
null 05b27d216a feat: Dockerfile/docker-compose updates, server improvements, contact form with recaptcha, API integration (batch 0.9.0) 2026-06-14 15:37:26 -05:00
null 4e57efdc53 fix: DB schema UNIQUE constraint, Docker healthcheck, DB permissions (#120 #121 #123) (batch 9.0) 2026-05-17 21:34:39 -05:00
null debde23ab7 fix(zoho): fix OAuth token endpoint, improve lead field mapping, add upsert
- Fix critical bug: token refresh now uses ZOHO_ACCOUNTS_DOMAIN
  (accounts.zoho.com) instead of API domain (www.zohoapis.com).
  The OAuth token endpoint lives on a different domain.
- Remove unnecessary redirect_uri from refresh token request
- Add ZOHO_ACCOUNTS_DOMAIN env var (separate from API domain)
- Split contact name into First_Name/Last_Name for Zoho schema
- Replace Service_Interest (non-standard field) with Description
  + Lead_Source: Website (standard picklist value)
- Switch from Insert to Upsert API with duplicate_check_fields:
  [Email] so duplicate submissions update instead of error
- Add trigger: ['workflow'] for explicit workflow control
- Add token refresh retry (1 retry on transient failure)
- Add ZOHO_CASES_ENABLED env var for future Cases forwarding
- Update .env.example with full Zoho config documentation
- Update FUTURE.md with detailed Phase 7 Zoho integration plan
- Remove obsolete ZOHO_REDIRECT_URI from Dockerfile
2026-05-17 18:37:10 -05:00
null 762c9d899d fix(docker): slim runner stage — native deps in separate build stage (567MB → 248MB) 2026-05-17 16:34:36 -05:00
null ca67974c5f fix(docker): add python3/make/g++ for better-sqlite3 native build (batch 0.6.8) 2026-05-17 15:48:43 -05:00
null 7d476f36e8 fix(security): audit fixes #4 #6 #10 + hero rewrite (batch 0.5.2)
- #4: Replace su-exec with USER nodejs in Dockerfile (P0)
- #6: Add UNIQUE constraint on leads.email with migration (P1)
- #10: Consistent NULL handling for optional fields (P1)
- Hero section rewrite: B2B value proposition, prominent 8x8 badge
- Clean up .bak file left by agent
2026-05-17 14:44:34 -05:00
null 7257633d94 feat: rate limiting, helmet security headers, CORS, trust proxy, Docker env vars (v0.4.7) 2026-05-13 18:37:32 -05:00
null ba0d039cdc fix: reduce Docker image from 331MB to 215MB — remove duplicate node_modules layer
v0.2.2: Removed COPY --from=builder node_modules from runner stage.
The full dev+prod modules (116MB) were being copied as a permanent
Docker layer, then npm ci --omit=dev installed a separate prod-only
set on top. Now only the prod install runs, cutting 116MB.
2026-05-12 02:04:52 -05:00
null 1f3e3864f9 feat: Docker batch 0.2.1 — production-ready containerization
- Multi-stage Dockerfile with non-root nodejs user
- Healthcheck using Node 20 built-in fetch (no wget)
- docker-entrypoint.sh: root permission fix, then exec to nodejs
- server/db.js: deferred SQLite init for Docker volume permissions
- docker-compose.yml with named volumes for persistence
- .dockerignore and .env.example added
- README updated with Docker usage section

Security reviewed by Private Hudson. All blockers resolved.
2026-05-12 01:57:55 -05:00