auto-deploy.sh only ever worked because someone chmod +x'd it directly
on the server after cloning, outside git - a fix that lived nowhere
git could see. A `git checkout --` to that file (recovering from an
unrelated direct edit) silently restored the tracked 644 mode,
breaking the deploy timer with "Permission denied" until caught via
journalctl. bootstrap-env.sh had the identical latent bug, just never
triggered since it's only ever run manually.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
nginx resolves the api/web service names to a container IP once, at
its own worker-process startup. docker compose up -d only recreates
containers whose image/config changed, so nginx keeps proxying to the
old, now-dead IP after a deploy rebuilds those containers - every
request 502s, which shows up in the browser as a misleading CORS
error since the bare 502 carries no Access-Control-Allow-Origin
header. Confirmed live: this caused a real multi-hour production
outage after the last two deploys.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Logs a distinct db_viewer_accessed event (not just the earlier
session_created "requested" event) when an admin's browser actually
completes the hand-off into Adminer. Adds a password-confirmed
account-deletion box to Settings, relying on the existing ON DELETE
CASCADE foreign keys to clean up everything the account owns. Adds an
admin-only "require password change" flag that get_current_user
enforces server-side (403 on everything except /auth/me,
/auth/change-password, /auth/logout) - meant for handing a demo
account to someone with a known sample password.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Local dev gets an unauthenticated Adminer instance bound to loopback
only. In production, any account with is_admin=true can open it -
the app mints a short-lived token from a live admin session, which
Nginx's new db.ciagent.org block exchanges for a session cookie that
re-checks admin status on every request, instead of a shared static
password that wouldn't scale to multiple admins or revoke live.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
[email protected] is a fixed, non-deliverable placeholder (no mail
relay serves that domain, and Mailpit was removed a while back) - prefilling
it was just noise the local dev would always need to clear. Real accounts
still get their own email prefilled as before.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Was always blank by default. Now prefilled from useCurrentUser() once it
loads (defaultValues can't do this since the query resolves after mount),
gated on the field not being dirty yet so it never clobbers something the
user already typed - still fully editable either way.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Previously hidden entirely when NEXT_PUBLIC_GIT_REPO_URL was unset, which
meant it never appeared on localhost or on anyone else's clone that hadn't
explicitly configured it. It should always point somewhere - the canonical
repo (git.ciagent.org) is the sensible default everywhere, overridable only
by a deployment that runs its own separate git server.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Was only ever settable via .env - now follows the same pattern as
Cloudflare Turnstile: SystemSecretKey.RESEND_API_KEY + a META entry
covers storage/encryption/frontend rendering automatically (the
Settings UI's Server secrets box is fully data-driven off this list).
Wired the register/login/resend-verification/request-password-reset
route handlers to use get_effective_settings so an admin-set key
actually reaches the emails those flows send, not just .env's value.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
scripts/auto-deploy.sh + a systemd timer (2min interval) that fetches
origin/master and, if ahead, pulls/rebuilds/migrates/restarts - same
sequence as the manual update steps in DEPLOYMENT.md, just scheduled.
Polling instead of a Gitea webhook deliberately: no extra exposed
service, no Docker socket mounted into a container, no shared secret
to manage - it's the same trust boundary as a manual SSH deploy.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
COPY --chown fixes the files being copied in, but WORKDIR had already
created /app as root beforehand - the directory entry itself stayed
root:root (no write bit for appuser), so celery beat's schedule-file write
still failed with Permission denied even after the earlier --chown fix.
Confirmed live on the actual deployment (local testing hadn't caught this).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Sets up everything needed to deploy behind Cloudflare with a self-hosted
git server: multi-stage prod Dockerfiles (non-root), docker-compose.prod.yml
(Postgres/Redis with no host ports, Nginx reverse proxy, Gitea with
public-read/admin-write access control), scripts/bootstrap-env.sh to
auto-generate required secrets on first clone, and DEPLOYMENT.md covering
the full runbook. Provider API keys (Anthropic/Brave/NinjaPear/USPTO/
Turnstile) are deliberately kept out of .env in favor of the existing
DB-backed Settings UI, so the public repo stays safe to expose.
Also fixes two bugs only surfaced by live-testing the prod stack: Celery
beat couldn't write its schedule file as a non-root user, and Gitea's
embedded SSH server conflicted with the base image's own sshd on port 22.
Co-Authored-By: Claude Sonnet 5 <[email protected]>