# Target verbs, by kind:# local (terminal solver, incl. system packages) — install / uninstall# system (the web stack, sudo + systemd) — deploy / remove / redeploy [/ upgrade]# Each system target's verb matches the action its script in scripts/setup/ takes..PHONY:install-allinstall-minimalinstall-systeminstall-chromeinstall-primesieve-numpy \install-hooksuninstall-hooksinstall-completionsuninstall-completions \install-credentialsinstall-claudeuninstall-claudeinstall-nodejsuninstall-nodejs \deploy-system-venvremove-system-venvredeploy-system-venv \deploy-frontendremove-frontendupgrade-frontendredeploy-frontend \deploy-egressremove-egressupgrade-egress \deploy-ddnsremove-ddns \deploy-firewallremove-firewall \deploy-smtpremove-smtpupgrade-smtp \deploy-authremove-authupgrade-authredeploy-auth \deploy-msgremove-msgupgrade-msgredeploy-msg \deploy-siteremove-siteupgrade-siteredeploy-site \deploy-userremove-userupgrade-userredeploy-user \deploy-webremove-webupgrade-webredeploy-weblogout-webstatus-web \testrunauditverify-solutionsuninstallversionreleasecheck-versionVENV:=.venvPYTHON:=$(VENV)/bin/pythonPIP:=$(VENV)/bin/pip# ── The terminal solver: install / uninstall (local) ───────────────────────────────## Full developer install: system deps → venv → all dependency groups → git hooks → completionsinstall-all:install-systeminstall-chrome$(VENV)$(PIP)install--upgradepip$(PIP)uninstall-y-qsolver2>/dev/null||true# the single-package dist before the split$(PIP)install--upgrade-e"packages/solver[ai,dev,show,solutions]"-epackages/site-epackages/webshellrm-rfbuildpackages/*/buildpackages/*/*.egg-info$(MAKE)install-primesieve-numpy$(MAKE)install-hooks$(MAKE)install-completions$(MAKE)install-credentials$(MAKE)install-claude@printf"✓ install-all complete: full developer environment ready\n"## Minimal install: base + solutions + show (no dev tools)install-minimal:install-systeminstall-chrome$(VENV)$(PIP)install--upgradepip$(PIP)uninstall-y-qsolver2>/dev/null||true# the single-package dist before the split$(PIP)install--upgrade-e"packages/solver[show,solutions]"-epackages/site-epackages/webshellrm-rfbuildpackages/*/buildpackages/*/*.egg-info$(MAKE)install-primesieve-numpy@printf"✓ install-minimal complete: base environment ready\n"## Install system packages (apt) via setup scriptinstall-system:./scripts/setup/dev_env.shinstallpythonprimesievec@printf"✓ install-system complete: apt packages installed\n"## Install Chrome browserinstall-chrome:./scripts/setup/chrome.shinstall@printf"✓ install-chrome complete: Chrome installed\n"## Build & install the optional primesieve.numpy extension from source (NumPy 2.x)install-primesieve-numpy:$(VENV)./scripts/setup/primesieve_numpy_extension.shinstall@printf"✓ install-primesieve-numpy complete: primesieve.numpy extension ready\n"## Install bash tab-completions for all project scriptsinstall-completions:./scripts/bash_completions.shinstall@printf"✓ install-completions complete: bash completions installed\n"## Remove bash tab-completionsuninstall-completions:./scripts/bash_completions.shuninstall@printf"✓ uninstall-completions complete: bash completions removed\n"## Install git hooks into .git/hooks/ and reset hooksPath to defaultinstall-hooks:gitconfig--unsetcore.hooksPath||true./scripts/setup/githooks.shinstall--force@printf"✓ install-hooks complete: git hooks installed\n"## Remove git hooks from .git/hooks/uninstall-hooks:./scripts/setup/githooks.shuninstall@printf"✓ uninstall-hooks complete: git hooks removed\n"## Configure gh as the git credential helper (logging in first if needed) and set the git identityinstall-credentials:./scripts/git/configure-identity.sh@printf"✓ install-credentials complete: gh configured as git credential helper, git identity set\n"## Install the Claude Code CLI (driven by the AI commands claude-solve / claude-blog)install-claude:./scripts/setup/claude_code.shinstall@printf"✓ install-claude complete: Claude Code CLI installed\n"## Remove the Claude Code CLIuninstall-claude:./scripts/setup/claude_code.shuninstall@printf"✓ uninstall-claude complete: Claude Code CLI removed\n"## Install a standalone Node.js under ~/.local (dev-only, no sudo; drives the SRP interop test)install-nodejs:./scripts/setup/nodejs.shinstall@printf"✓ install-nodejs complete\n"## Remove the standalone Node.js installed by install-nodejsuninstall-nodejs:./scripts/setup/nodejs.shuninstall@printf"✓ uninstall-nodejs complete\n"## Create venv if it doesn't exist$(VENV):python3.14-mvenv$(VENV)@printf"✓ venv created at $(VENV)\n"## Remove the venv (system deps must be removed separately)uninstall:uninstall-hooksuninstall-completionsrm-rf$(VENV)buildpackages/*/buildpackages/*/*.egg-info@printf"✓ uninstall complete: venv removed\n"@printf"To remove system dependencies run:\n\t./scripts/setup/dev_env.sh uninstall python primesieve c\n"@printf"To remove the Claude Code CLI run:\n\tmake uninstall-claude\n"# ── Day to day: tests, the shell, versions, the audit ──────────────────────────────## Run the unit test suite (tests/)test:$(VENV)$(PYTHON)-munittestdiscover-stests-t.## Run the interactive solver shellrun:$(VENV)$(VENV)/bin/solver## Show the running build's version (packages/solver/solver/version.py, plus how far HEAD is past its tag)version:$(VENV)@$(VENV)/bin/solverversion## Cut a release: bump packages/solver/solver/version.py from Conventional Commits, commit,## tag, and push the commit + tag to origin. ARGS=--dry-run previews; ARGS=--no-push## stops before publishing.release:@./scripts/version/release.sh$(ARGS)## Guard: the version redeploy-web would ship (packages/solver/solver/version.py) must have its## vX.Y.Z tag on origin, so a redeploy never runs a release collaborator clones## cannot reach (a locally-tagged-but-unpushed release runs the venv ahead of every## clone). Queries origin — needs network. A prerequisite of redeploy-web.check-version:@ver=$$(sed-nE"s/^__version__ = '([^']*)'.*/\1/p"packages/solver/solver/version.py);\ if [ -z "$$ver" ]; then \ printf "✗ check-version: could not read the version from packages/solver/solver/version.py\n" >&2; exit 1; \ fi; \ tag="v$$ver"; \ if git ls-remote --tags origin "$$tag" 2>/dev/null | grep -q "refs/tags/$$tag"; then \ printf "✓ check-version: %s is published on origin\n" "$$tag"; \ else \ printf "✗ check-version: %s is NOT on origin — publish the release before deploying.\n" "$$tag" >&2; \ printf " run 'make release' (bump + push) or 'git push origin HEAD %s'\n" "$$tag" >&2; \ exit 1; \ fi## Audit what git stores across the WHOLE tracked tree (~25s): every file under## solutions/private is encrypted at rest, and no file anywhere is a compiled binary.## The git hooks only audit what you actually stage or push, so this is the periodic## sweep that also covers blobs committed before the hooks were in place.## Counts only; offenders are always listed. Per-file detail: scripts/git/audit.shaudit:$(VENV)@PYTHON=$(PYTHON)./scripts/git/audit.sh--summary## Re-check every solved problem against its test cases (eval in a loop), with a live## progress line; the full output goes to .verify-solutions.log. LIST='{solved}[0:10]'## checks part of the list, LIST='14 76 478' just those problems. Takes a while: it runs## every solution on its main case.verify-solutions:$(VENV)@PATH="$(VENV)/bin:$$PATH"./scripts/ops/verify-solutions.sh$(if$(LIST),'$(LIST)')# ── The web stack: deploy / remove / redeploy (sudo + systemd) ─────────────────────## Deploy the root-owned /opt/euler system venv the euler-* app services run from (sudo)deploy-system-venv:./scripts/setup/venv.shdeploy@printf"✓ deploy-system-venv complete: /opt/euler venv deployed\n"## Remove the /opt/euler system venv (leaves service identities, state, and units intact) (sudo)remove-system-venv:./scripts/setup/venv.shremove@printf"✓ remove-system-venv complete: /opt/euler venv removed\n"## Rebuild the /opt/euler system venv from scratch: remove, then deploy (sudo)redeploy-system-venv:./scripts/setup/venv.shredeploy@printf"✓ redeploy-system-venv complete: /opt/euler venv rebuilt\n"## Deploy the web edge: Caddy + acme.sh + euler-caddy.service (see docs/web-server-guide.md)deploy-frontend:./scripts/setup/frontend.shdeploy@printf"✓ deploy-frontend complete: web edge deployed (sudo systemctl for lifecycle)\n"## Remove the web edge (prompts before deleting /etc/euler, acme.sh, and the service users)remove-frontend:./scripts/setup/frontend.shremove@printf"✓ remove-frontend complete: web edge removed\n"## Upgrade the web edge packages (Caddy + acme.sh; regenerate the Caddyfile + unit)upgrade-frontend:./scripts/setup/frontend.shupgrade@printf"✓ upgrade-frontend complete: web edge upgraded\n"## Refresh the static web-content + Caddyfile and reload the edgeredeploy-frontend:./scripts/setup/frontend.shredeploy@printf"✓ redeploy-frontend complete: web-content + Caddyfile reloaded\n"## Deploy the egress proxy: Squid domain-allowlist + euler-proxy.service (see docs/web-server-guide.md)deploy-egress:./scripts/setup/egress.shdeploy@printf"✓ deploy-egress complete: egress proxy deployed (sudo systemctl for lifecycle)\n"## Remove the egress proxy (prompts before deleting /etc/euler-proxy and the euler-proxy user)remove-egress:./scripts/setup/egress.shremove@printf"✓ remove-egress complete: egress proxy removed\n"## Upgrade the egress proxy package (Squid; regenerate the config + unit)upgrade-egress:./scripts/setup/egress.shupgrade@printf"✓ upgrade-egress complete: egress proxy upgraded\n"## Deploy the dynamic-DNS updater timer (public access only — keeps the A record current)deploy-ddns:./scripts/setup/ddns.shdeploy@printf"✓ deploy-ddns complete: DDNS timer deployed\n"## Remove the dynamic-DNS updater timer (name.com records left untouched)remove-ddns:./scripts/setup/ddns.shremove@printf"✓ remove-ddns complete: DDNS timer removed\n"## Deploy the kernel egress firewall: per-uid nftables ruleset + euler-firewall.service (DD-8)deploy-firewall:./scripts/setup/firewall.shdeploy@printf"✓ deploy-firewall complete: euler egress ruleset loaded (boot-enabled)\n"## Remove the kernel egress firewall (flushes the euler table; nothing else touched)remove-firewall:./scripts/setup/firewall.shremove@printf"✓ remove-firewall complete: euler egress ruleset removed\n"## Deploy the loopback SMTP relay: euler-smtp user + service, scoped Gmail creds (DD-8)deploy-smtp:./scripts/setup/smtp.shdeploy@printf"✓ deploy-smtp complete: loopback mail relay deployed (sudo systemctl for lifecycle)\n"## Remove the loopback SMTP relay (prompts before deleting smtp.env and the euler-smtp user)remove-smtp:./scripts/setup/smtp.shremove@printf"✓ remove-smtp complete: loopback mail relay removed\n"## Upgrade the loopback SMTP relay (alias of deploy: relay + config + unit, restart)upgrade-smtp:./scripts/setup/smtp.shupgrade@printf"✓ upgrade-smtp complete: loopback mail relay upgraded\n"## Deploy the auth-service runtime: euler-auth/euler-adm + /opt/euler venv + auth.env (DD-5/DD-6)deploy-auth:./scripts/setup/auth.shdeploy@printf"✓ deploy-auth complete: auth runtime deployed\n"## Remove the auth service (prompts before deleting the venv, state, and identities)remove-auth:./scripts/setup/auth.shremove@printf"✓ remove-auth complete: auth service removed\n"## Upgrade the auth service (alias of deploy: pip re-install the repo into /opt/euler,## refresh config + unit)upgrade-auth:./scripts/setup/auth.shupgrade@printf"✓ upgrade-auth complete: auth service upgraded\n"## Redeploy the shared /opt/euler venv (auth.sh owns it) + restart the auth serviceredeploy-auth:./scripts/setup/auth.shredeploy@printf"✓ redeploy-auth complete: venv + authorizations refreshed, auth restarted\n"## Deploy the message spool: the euler-msg user + /etc/euler/msg.env + euler-msg.service## (user<->staff threads; see docs/web-server-guide.md § Messaging). Needs the shared## /opt/euler venv, which auth.sh owns — run deploy-auth firstdeploy-msg:./scripts/setup/msg.shdeploy@printf"✓ deploy-msg complete: message spool deployed\n"## Remove the message spool (prompts before deleting the threads and the identity)remove-msg:./scripts/setup/msg.shremove@printf"✓ remove-msg complete: message spool removed\n"## Upgrade the message spool (alias of deploy: refresh config + unit, restart)upgrade-msg:./scripts/setup/msg.shupgrade@printf"✓ upgrade-msg complete: message spool upgraded\n"## Restart the message spool against the current /opt/euler venv (redeploy-auth## rebuilds that venv; this only bounces the service onto it)redeploy-msg:./scripts/setup/msg.shredeploy@printf"✓ redeploy-msg complete: message spool restarted\n"## Deploy the public site: the euler-site user + /etc/euler/site.env + euler-site.service,## serving the packaged content (euler-content, installed into the shared venv by## deploy-auth) to signed-out visitors (docs/package-guide.md § 4)deploy-site:./scripts/setup/site.shdeploy@printf"✓ deploy-site complete: public site deployed\n"## Remove the public site (signed-out visitors get the login page again)remove-site:./scripts/setup/site.shremove@printf"✓ remove-site complete: public site removed\n"## Upgrade the public site (alias of deploy: refresh config + unit, restart)upgrade-site:./scripts/setup/site.shupgrade@printf"✓ upgrade-site complete: public site upgraded\n"## Restart the public site against the current /opt/euler venv and content package## (redeploy-auth rebuilds both); deploys it when it is not installed yetredeploy-site:./scripts/setup/site.shredeploy@printf"✓ redeploy-site complete: public site restarted\n"## Deploy the per-user provisioning layer (MT-7): the euler-user group,## /etc/euler/user.env, and the euler-user@.service/.socket template. Per-collaborator## uids/homes/clones are created later by `users add <email>`## (euler_webshell.users → user.sh provision), which clones ~/euler straight## from the public GitHub repo (ciphertext at rest, MT-13).deploy-user:./scripts/setup/user.shdeploy@printf"✓ deploy-user complete: per-user provisioning layer deployed\n"## Remove the per-user layer (refuses while any provisioned collaborator remains)remove-user:./scripts/setup/user.shremove@printf"✓ remove-user complete: per-user provisioning layer removed\n"## Re-assert the shared per-user layer (alias of deploy; re-lays the unit template)upgrade-user:./scripts/setup/user.shupgrade@printf"✓ upgrade-user complete: per-user provisioning layer upgraded\n"## Fast path: refresh /etc/euler/user.env and bounce the running per-user instances## (their sockets re-activate them against the freshly rebuilt venv; drops live shells)redeploy-user:./scripts/setup/user.shredeploy@printf"✓ redeploy-user complete: per-user config refreshed, instances bounced\n"## Deploy the full web stack, in dependency order: edge (Caddy+ACME+web-content),## egress (Squid), DDNS, kernel egress firewall, SMTP relay, auth service (and the## shared /opt/euler venv), message spool, public site, and the per-user provisioning## layer. Each kit stays independently operable; the later kits reload the firewall as## their service users appear. (sudo required)deploy-web:deploy-frontenddeploy-egressdeploy-ddnsdeploy-firewalldeploy-smtpdeploy-authdeploy-msgdeploy-sitedeploy-user@printf"✓ deploy-web complete: full web stack deployed\n"## Remove the full web stack (reverse order; the kits prompt before deleting state)remove-web:logout-webremove-userremove-siteremove-msgremove-authremove-smtpremove-firewallremove-ddnsremove-egressremove-frontend@printf"✓ remove-web complete: full web stack removed\n"## Upgrade the full web stack in place (regenerate configs, redeploy, restart;## ddns and firewall have no upgrade action — their deploy is idempotent and doubles## as one, and the firewall reload is the final consistency pass over the euler uids)upgrade-web:logout-webupgrade-frontendupgrade-egressdeploy-ddnsupgrade-smtpupgrade-authupgrade-msgupgrade-siteupgrade-user./scripts/setup/firewall.shreload@printf"✓ upgrade-web complete: full web stack upgraded\n"# What each stopping target's notice says; LOGOUT_REASON on the command line overrides it.LOGOUT_REASON_redeploy-web:=theserverisredeployingLOGOUT_REASON_upgrade-web:=theserverisupgradingLOGOUT_REASON_remove-web:=thewebstackisbeingremovedLOGOUT_REASON?=$(LOGOUT_REASON_$(firstword$(MAKECMDGOALS)))## Warn every running instance, then sign everyone out: a 5s countdown, held by running work## for up to 5 minutes. redeploy-web, upgrade-web and remove-web run it first. Set the notice## with LOGOUT_REASON; LOGOUT_ARGS=--now skips the wait.logout-web:./scripts/setup/user.shlogout-all$(if$(LOGOUT_REASON),"$(LOGOUT_REASON)")$(LOGOUT_ARGS)## Fast redeploy of code, templates, and static assets WITHOUT touching identities,## ACLs, units, certs, or the firewall: refresh the edge's static content + Caddyfile →## rebuild the shared venv (auth) → restart the message spool and the public site on it →## bounce the per-user instances so their sockets re-activate them against it. The edge goes first: the restarted services name each## asset by a hash of its bytes (?v=, cached immutable), so those bytes must already be## there. logout-web goes before all of it, so users are warned and signed out first: it## blocks until their running work ends or the limit passes. Skip the wait with## LOGOUT_ARGS=--now. The## everyday "I changed Python/templates/CSS/JS, push it" turnaround. Gated on## check-version so the deployed build's tag is on origin (run serially — the default —## so the guard runs before any deploy step).redeploy-web:check-versionlogout-webredeploy-frontendredeploy-authredeploy-msgredeploy-siteredeploy-user@printf"✓ redeploy-web complete: code, templates, and static assets redeployed\n"## Report the whole web stack in one read-only sweep, in deploy order: the edge, the## egress proxy, DDNS, the kernel firewall, the mail relay, the auth service, the## message spool, and the per-user tier. The last section is the collaborator roster — one entry per unix name## (the slug: the uid, home, socket and unit instance are all called this) with the## e-mail as an alias, and per user whether their instance is running and whether their## web shell is live and currently connected to a terminal.#### Read-only, but needs sudo: the state it reads is root-owned (nftables, /etc/euler),## the clones sit under 0700 homes, and the shell report is a query on each running## instance's own socket. Each kit is run best-effort so one missing component (or a## failing egress probe) never truncates the sweep.status-web:@printf"══ edge (Caddy + ACME) ═══════════════════════════════════════════════\n"@./scripts/setup/frontend.shstatus||true@printf"\n══ egress proxy (Squid) ══════════════════════════════════════════════\n"@./scripts/setup/egress.shstatus||true@printf"\n══ dynamic DNS ═══════════════════════════════════════════════════════\n"@./scripts/setup/ddns.shstatus||true@printf"\n══ kernel egress firewall (nftables) ═════════════════════════════════\n"@./scripts/setup/firewall.shstatus||true@printf"\n══ mail relay (loopback SMTP) ════════════════════════════════════════\n"@./scripts/setup/smtp.shstatus||true@printf"\n══ auth service ══════════════════════════════════════════════════════\n"@./scripts/setup/auth.shstatus||true@printf"\n══ message spool (user↔staff threads) ════════════════════════════════\n"@./scripts/setup/msg.shstatus||true@printf"\n══ public site (signed-out content) ══════════════════════════════════\n"@./scripts/setup/site.shstatus||true@printf"\n══ per-user tier (collaborators + their web shells) ══════════════════\n"@./scripts/setup/user.shstatus||true