-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
237 lines (190 loc) · 12.6 KB
/
Copy pathMakefile
File metadata and controls
237 lines (190 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# [FORCA] Command Grid — operator command surface.
# Thin, discoverable wrappers over docker compose + the scripts/ helpers.
# Production targets use docker-compose.prod.yml; dev targets use docker-compose.yml.
#
# Quick start: make setup (creates .env) → edit .env → make deploy → make bootstrap → make health
.DEFAULT_GOAL := help
# docker compose (v2) or legacy docker-compose, whichever exists.
DC := $(shell docker compose version >/dev/null 2>&1 && echo "docker compose" || echo "docker-compose")
PROD := docker-compose.prod.yml
DEV := docker-compose.yml
.PHONY: help setup build deploy update migrate collectstatic bootstrap bootstrap-sample \
import-sde import-assets prices health logs ps down restart shell dbshell \
backup restore create-admin dev dev-down dev-logs cert config-check \
lint lint-fix test test-fast test-scripts check audit audit-deps audit-image \
audit-image-os scan-images install-scan-timer frontend-check sbom rollback
help: ## Show this help
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN{FS=":.*?## "}{printf " \033[1;36m%-18s\033[0m %s\n", $$1, $$2}'
# --- first-run ---------------------------------------------------------------
setup: ## Create .env from the template if it does not exist
@if [ -f .env ]; then echo ".env already exists — leaving it untouched."; \
else cp .env.example .env && echo "Created .env from .env.example. Edit it, then run 'make deploy'."; fi
# --- production lifecycle ----------------------------------------------------
build: ## Build the production images
$(DC) -f $(PROD) build
deploy: ## Build, audit, migrate, then start the prod stack (in that order — see below)
# Order matters. Starting the new code BEFORE migrating runs it against the old schema,
# and any new column on a hot table then 500s every session-bearing request until the
# migration lands. So: build, bring up only the data services, migrate and collect static
# on the new image, and only then swap the app containers. nginx restarts last because it
# caches the web container's IP and serves 502s until it is told to look again.
$(DC) -f $(PROD) build
# Audit the image we just built, BEFORE anything is started or migrated: a vulnerable
# build is caught while the previous stack is still serving, and the cost of refusing is
# a deploy that did not happen rather than one that has to be rolled back. Fails the
# deploy; SKIP_DEPENDENCY_AUDIT=1 is the deliberate, documented override.
@bash scripts/audit-image.sh
# ...and the OS packages of every image about to start. audit-image.sh sees Python
# distributions only, so it is structurally blind to the nginx/Postgres/Redis images
# and to the application image's own Debian layer — where the worst vulnerability this
# install ever carried lived (a CRITICAL OpenSSL inside a frozen nginx:1.27-alpine).
# Only FIXABLE findings fail; SKIP_IMAGE_SCAN=1 is the deliberate override.
@bash scripts/audit-image-os.sh
$(DC) -f $(PROD) up -d postgres redis
$(DC) -f $(PROD) exec -T postgres sh -c 'until pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" >/dev/null 2>&1; do sleep 2; done'
$(DC) -f $(PROD) run --rm --no-deps -T web python manage.py migrate --noinput
$(DC) -f $(PROD) run --rm --no-deps -T web python manage.py collectstatic --noinput
$(DC) -f $(PROD) up -d
@bash scripts/wait-for-services.sh
$(DC) -f $(PROD) restart nginx
# Re-audit what is now RUNNING, so a release that fixes a CVE retires its own finding
# within minutes. Without this, a fixed vulnerability keeps showing as open until the
# next scheduled scan — which is how a director surface learns to be ignored. Both are
# report-only: the deploy already happened, and failing it here would help nobody.
-$(DC) -f $(PROD) exec -T web python manage.py audit_dependencies --exit-zero --trigger deploy
-@bash scripts/scan-running-images.sh --trigger deploy --exit-zero
@echo "Deploy complete. Next: 'make bootstrap' (first install) then 'make health'."
rollback: ## Roll back to an earlier revision (REF=v1.0.0 [DUMP=./backups/....sql.gz] [DRIFT=1])
# A code-only rollback (no DUMP) is refused if the schema it leaves behind has columns the
# older code cannot write to — see scripts/rollback.sh. DRIFT=1 overrides that refusal.
@bash scripts/rollback.sh $(REF) $(if $(DUMP),--restore $(DUMP),) $(if $(DRIFT),--accept-schema-drift,)
update: ## Pull latest code, rebuild, migrate (safe upgrade path)
@bash scripts/update.sh
migrate: ## Apply database migrations
$(DC) -f $(PROD) exec -T web python manage.py migrate --noinput
collectstatic: ## Collect static assets
$(DC) -f $(PROD) exec -T web python manage.py collectstatic --noinput
# --- data & assets -----------------------------------------------------------
bootstrap: ## Load EVE reference data (full SDE + PI + referenced images)
@bash scripts/bootstrap-data.sh
bootstrap-sample: ## Load the tiny bundled sample SDE (dev/CI only)
@bash scripts/bootstrap-data.sh --sample --no-images
import-sde: ## Import the full Static Data Export from Fuzzwork
$(DC) -f $(PROD) exec -T web python manage.py import_sde_fuzzwork
import-assets: ## Mirror referenced EVE type images locally
$(DC) -f $(PROD) exec -T web python manage.py mirror_type_images --referenced-only
prices: ## Price referenced types from Jita (first pass)
$(DC) -f $(PROD) exec -T web python manage.py price_types
create-admin: ## Ensure a Django superuser (EMAIL=you@example.com)
@bash scripts/create-admin.sh "$(EMAIL)"
# --- operations --------------------------------------------------------------
health: ## Run the full health check
@bash scripts/healthcheck.sh
logs: ## Tail logs for all prod services (Ctrl-C to stop)
$(DC) -f $(PROD) logs -f --tail=100
ps: ## Show prod container status
$(DC) -f $(PROD) ps
restart: ## Restart the prod stack
$(DC) -f $(PROD) restart
down: ## Stop the prod stack (data volumes preserved)
$(DC) -f $(PROD) down
shell: ## Open a Django shell in the web container
$(DC) -f $(PROD) exec web python manage.py shell
dbshell: ## Open a psql shell
$(DC) -f $(PROD) exec postgres sh -c 'psql -U "$$POSTGRES_USER" "$$POSTGRES_DB"'
backup: ## Dump the database to ./backups
@bash scripts/backup.sh
restore: ## Restore the DB from a dump (FILE=./backups/forca-....sql.gz)
@bash scripts/restore.sh "$(FILE)"
cert: ## Obtain/renew TLS cert (DOMAIN=... EMAIL=...); run with sudo
sudo bash scripts/cert-init.sh "$(DOMAIN)" "$(EMAIL)"
config-check: ## Validate the compose files parse
$(DC) -f $(PROD) config -q && echo "prod compose OK"
$(DC) -f $(DEV) config -q && echo "dev compose OK"
# --- local development -------------------------------------------------------
dev: ## Start the dev stack (runserver + autoreload)
$(DC) -f $(DEV) up -d --build
@echo "Dev app: http://127.0.0.1:8000 — run 'make bootstrap-sample' for seed data."
dev-down: ## Stop the dev stack
$(DC) -f $(DEV) down
dev-logs: ## Tail dev logs
$(DC) -f $(DEV) logs -f --tail=100
# --- quality gates -----------------------------------------------------------
# These mirror .github/workflows/ci.yml so a contributor can reproduce CI locally.
# They run inside the dev stack's web container, which already has requirements-dev.
#
# --ds is passed explicitly: the dev compose file exports
# DJANGO_SETTINGS_MODULE=config.settings.dev, and that environment variable takes
# precedence over pyproject.toml's [tool.pytest.ini_options] value. Without --ds the
# suite would run against dev settings and fail trying to reach Redis.
TEST_DS := --ds=config.settings.test
messages: ## Re-extract translatable strings into locale/<lang>/LC_MESSAGES/django.po
# These flags must match tests/test_i18n_catalogue_freshness.py, which re-extracts and
# fails the build if a string marked in the code never reached a catalogue. Extracting
# differently here would hand you catalogues that CI then rejects.
$(DC) -f $(DEV) run --rm --no-deps web python manage.py makemessages --all \
--no-obsolete --add-location file --extension py,html \
--ignore '.venv/*' --ignore 'staticfiles/*' --ignore 'node_modules/*'
@echo "Catalogues updated. Translate the new entries, then run 'make compile-messages'."
@echo "Never translate a protected EVE term — see core/i18n/data/protected-terms.yml."
compile-messages: ## Compile the catalogues to .mo (CI and the image build do this too)
$(DC) -f $(DEV) run --rm --no-deps web python manage.py compilemessages
lint: ## Run the ruff linter (same checks as CI)
$(DC) -f $(DEV) run --rm --no-deps web ruff check .
lint-fix: ## Auto-fix what ruff can fix
$(DC) -f $(DEV) run --rm --no-deps web ruff check . --fix
test: ## Run the full pytest suite (needs the dev stack's postgres)
$(DC) -f $(DEV) run --rm web pytest -q $(TEST_DS)
test-fast: ## Run pytest, stopping at the first failure
$(DC) -f $(DEV) run --rm web pytest -q -x $(TEST_DS)
test-scripts: ## Test the deploy/scan scripts (host-side; needs no database)
# These live under scripts/ beside the code they cover, which puts them outside
# pyproject's default testpaths — hence a target of their own. They need bash and
# python3 on the HOST and nothing else: no docker, no trivy, no database. They exist
# because the last escape-hatch bug (SKIP_DEPENDENCY_AUDIT=0 silently DISABLING the
# gate) shipped for the simple reason that nobody ever ran it with that value.
# --no-deps: nothing here touches Postgres or Redis, so do not start them (and do not
# collide with a concurrent suite over the shared test database).
$(DC) -f $(DEV) run --rm --no-deps web pytest scripts/tests -q $(TEST_DS)
check: ## Django system checks (add --deploy for prod-hardening warnings)
$(DC) -f $(DEV) run --rm --no-deps web python manage.py check
audit: ## Scan the requirements FILE for known vulnerabilities (intent)
$(DC) -f $(DEV) run --rm --no-deps web pip-audit -r requirements.txt --progress-spinner off
audit-image: ## Scan the packages INSTALLED in the built prod image (reality) — gates deploy
# Not a duplicate of `audit`. That one audits requirements.txt, which carries lower
# bounds, so it stays green against an image built weeks ago that never picked the
# fixed version up — exactly how a running container here once carried 24 Pillow CVEs
# while CI was green. This audits the container's own site-packages.
@bash scripts/audit-image.sh
audit-deps: ## Re-run the dependency-CVE loop in the RUNNING web container (report only)
# Scan, refresh the director finding, relay a change to leadership. Use this after a
# fix ships out-of-band so the standing alert stops claiming vulnerabilities that are
# already gone — a stale alarm is what trains people to ignore the next real one.
$(DC) -f $(PROD) exec -T web python manage.py audit_dependencies --exit-zero --trigger manual
audit-image-os: ## Scan the OS packages of the images a deploy would start — gates deploy
# The layer nothing else covers. pip-audit sees Python distributions; this reads each
# image's own system-package database, including nginx/Postgres/Redis where we ship no
# Python at all. Only FIXABLE HIGH/CRITICAL findings fail, so an unactionable upstream
# advisory cannot turn the gate permanently red (and thus get it switched off).
@bash scripts/audit-image-os.sh
scan-images: ## Scan the images the RUNNING containers are actually using, and report them
# Different question from `audit-image-os`, and the more important one: that gate asks
# "is what we are about to start clean?", this asks "is what has been serving traffic
# for six weeks still clean?". Images rot on the world's schedule — a frozen upstream
# tag accumulated 37 HIGH/CRITICAL here without a single byte changing in this repo.
# Runs on the HOST (never mount the docker socket into an app container) and hands the
# result to the app, which relays it to directors over the existing Pingboard channels.
@bash scripts/scan-running-images.sh --trigger manual
install-scan-timer: ## Install the daily running-image scan systemd timer (needs sudo)
sudo bash scripts/install-image-scan-timer.sh
frontend-check: ## Fail if static/css/app.css + vendored JS are stale vs. their sources
# Runs on the HOST, not in a container: the build tooling is Node and lives in
# frontend/, and the application image deliberately has no Node in it. Production
# never rebuilds these — it ships the committed bytes — so this is the only thing
# standing between a new Tailwind class and four days of silently dead styles.
@bash scripts/check-frontend-build.sh
sbom: ## Write a CycloneDX SBOM of the runtime dependencies to ./sbom.cdx.json
@$(DC) -f $(DEV) run --rm --no-deps -T web \
pip-audit -r requirements.txt -f cyclonedx-json --progress-spinner off 2>/dev/null > sbom.cdx.json
@echo "Wrote sbom.cdx.json"