-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
371 lines (319 loc) · 15.5 KB
/
Copy pathMakefile
File metadata and controls
371 lines (319 loc) · 15.5 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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
.PHONY: local local-db local-down dev dev-api dev-dashboard down net test check fmt clippy migrate new-migration schema sqlx-prepare check-sqlx mock-target install-hooks \
tofu-init tofu-fmt tofu-validate tofu-plan tofu-apply tofu-destroy \
infra-shutdown infra-resume worktree-clean \
dashboard-static web-build web build install \
logs logs-deploy \
shortener-dev shortener-down shortener-deploy \
deploy db-shell \
e2e e2e-up e2e-down
COMPOSE := $(shell command -v podman-compose 2>/dev/null || command -v docker-compose 2>/dev/null || echo "docker compose")
ENGINE := $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null || echo docker)
# Cross-project network bridging Overfolder ↔ Overslash (see docker/docker-compose.dev.yml).
SHARED_NET := overfolder-shared
TOFU := $(shell command -v tofu 2>/dev/null || command -v terraform 2>/dev/null)
TOFU_DIR := infra
ENV ?= dev
TF_VAR_FILE := $(TOFU_DIR)/env/$(ENV).tfvars
# Load .env.local overrides if present (worktree isolation).
# Used by non-compose targets like `test` and `migrate` that read DATABASE_URL.
# Compose targets re-source .env.local inline below to handle the first-run case
# where the file is created by bin/worktree-env.sh just before being read.
-include .env.local
export
# Install prefix (default: ~/.local). Override: PREFIX=/usr/local make install
PREFIX ?= $(HOME)/.local
# Colors
GREEN := \033[0;32m
RED := \033[0;31m
YELLOW := \033[0;33m
NC := \033[0m
# Shell snippet: run worktree-env.sh, source .env.local (if created), then
# build PROJ_FLAG. In worktrees, this becomes `--project-name overslash-wt-XXX`,
# which overrides `name: overslash` in docker-compose.dev.yml (podman-compose
# 1.0.6 does NOT honor COMPOSE_PROJECT_NAME env var when `name:` is set in the
# file, so we must pass the flag explicitly). In the main repo, .env.local is
# not created and PROJ_FLAG is empty, so compose uses `name: overslash`.
WT_ENV = bash bin/worktree-env.sh && set -a && { [ -f .env.local ] && . ./.env.local; }; set +a; \
PROJ_FLAG=$${COMPOSE_PROJECT_NAME:+--project-name $$COMPOSE_PROJECT_NAME}
# Ensure the cross-project podman network exists (idempotent). The Overfolder
# backend attaches to this same network to reach the API by the `overslash`
# alias — see docker/docker-compose.dev.yml.
net:
@$(ENGINE) network inspect $(SHARED_NET) >/dev/null 2>&1 || { \
echo -e "$(GREEN)Creating shared network $(SHARED_NET)...$(NC)"; \
$(ENGINE) network create $(SHARED_NET); }
# Start the full local dev stack (postgres + api with cargo-watch + dashboard).
# Creates the shared network first so Overfolder can reach this API. `dev` is
# kept as an alias of `local`.
local dev: net
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null; \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up --build
# Stop the full local dev stack (alias of `down`).
local-down: down
# Start local infra only (postgres) — used by e2e-up.sh and worktree isolation.
local-db:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up -d postgres
# Start only the API (postgres + api)
dev-api:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null; \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up --build postgres api
# Start only the dashboard dev server (no container)
dev-dashboard:
cd dashboard && npm run dev
# Build the SvelteKit dashboard with adapter-static. Output: dashboard/build/.
# Required before `make web-build` so rust-embed has assets to embed.
dashboard-static:
cd dashboard && npm install && npm run build:static
# Build the self-hosted single-binary release with embedded dashboard and MCP.
# Produces target/release/overslash. Run `overslash web` to start it.
build: dashboard-static
SQLX_OFFLINE=1 cargo build --release -p overslash-cli --features embed-dashboard
# Alias kept for backward compatibility.
web-build: dashboard-static
SQLX_OFFLINE=1 cargo build --release -p overslash-cli --features embed-dashboard
# Install overslash to $(PREFIX)/bin (default: ~/.local/bin).
# Override: PREFIX=/usr/local make install
install: build
install -d $(PREFIX)/bin
install -m 755 target/release/overslash $(PREFIX)/bin/overslash
@echo -e "$(GREEN)Installed:$(NC) $(PREFIX)/bin/overslash"
@echo "Make sure $(PREFIX)/bin is in your PATH, then run: overslash web"
# Build + run the self-hosted binary directly (foreground).
web: build
./target/release/overslash web
# Stop services
down:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans
# Bring up the full e2e stack: Postgres → overslash-fakes → API → dashboard
# preview, all on dynamic ports written into a per-worktree .e2e/ state dir.
# Multiple worktrees can run concurrently without colliding.
e2e-up:
@bash scripts/e2e-up.sh
# Tear down the e2e stack started by `make e2e-up`. Postgres is left running;
# use `make worktree-clean` for a full teardown.
e2e-down:
@bash scripts/e2e-down.sh
# Boot the e2e stack, run Playwright, then tear down regardless of result.
# The env file the harness writes (API_URL, DASHBOARD_URL, ...) is sourced
# into the playwright invocation so worker subprocesses see them — setting
# them only inside playwright.config.ts isn't enough.
e2e:
@bash scripts/e2e-up.sh
@STATE_DIR="$${WORKTREE_STATE_DIR:-$$(pwd)}/.e2e"; \
status=0; ( cd dashboard && set -a && . "$$STATE_DIR/dashboard.env" && set +a && npx playwright test ) || status=$$?; \
bash scripts/e2e-down.sh; \
exit $$status
# Start the oversla.sh shortener dev stack (valkey + shortener on :8081)
shortener-dev:
$(COMPOSE) -f docker/docker-compose.shortener.yml up --build
# Stop the shortener dev stack
shortener-down:
$(COMPOSE) -f docker/docker-compose.shortener.yml down --remove-orphans
# Build, push, and deploy the oversla.sh shortener from local.
# Mirrors the cloud-build-shortener pipeline: builds crates/oversla-sh/Dockerfile,
# pushes :$(SHA) + :latest to Artifact Registry, then `gcloud run deploy`s.
# Usage:
# make shortener-deploy ENV=prod # prod with current HEAD sha
# make shortener-deploy ENV=prod SHA=v0.1.0 # override tag
# DEPLOY_AUTO_APPROVE=1 make shortener-deploy ENV=prod # skip prod confirm
shortener-deploy:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@command -v gcloud >/dev/null || (echo -e "$(RED)gcloud CLI not found.$(NC)" && exit 1)
@command -v docker >/dev/null || (echo -e "$(RED)docker CLI not found.$(NC)" && exit 1)
@test -n "$(GCP_PROJECT)" || (echo -e "$(RED)Could not read project_id from $(TF_VAR_FILE)$(NC)" && exit 1)
$(eval BASE_PREFIX := overslash-$(ENV))
$(eval REPO := $(BASE_PREFIX)-registry)
$(eval SERVICE := $(BASE_PREFIX)-shortener)
$(eval AR_HOST := $(REGION)-docker.pkg.dev)
$(eval IMAGE := $(AR_HOST)/$(GCP_PROJECT)/$(REPO)/oversla-sh)
$(eval SHA ?= $(shell git rev-parse --short HEAD))
@if [ "$(ENV)" = "prod" ] && [ "$(DEPLOY_AUTO_APPROVE)" != "1" ]; then \
echo -e "$(RED)About to deploy $(SERVICE) ($(IMAGE):$(SHA)) to PRODUCTION ($(GCP_PROJECT))$(NC)"; \
echo -n "Type 'prod' to confirm: "; \
read confirm && [ "$$confirm" = "prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)[1/3] docker build -> $(IMAGE):$(SHA)$(NC)"
docker build \
-f crates/oversla-sh/Dockerfile \
-t $(IMAGE):$(SHA) \
-t $(IMAGE):latest \
.
@echo -e "$(GREEN)[2/3] docker push (tags: $(SHA), latest)$(NC)"
docker push $(IMAGE):$(SHA)
docker push $(IMAGE):latest
@echo -e "$(GREEN)[3/3] gcloud run deploy $(SERVICE) --image $(IMAGE):$(SHA)$(NC)"
gcloud run deploy $(SERVICE) \
--image $(IMAGE):$(SHA) \
--region $(REGION) \
--project $(GCP_PROJECT) \
--quiet
@echo -e "$(GREEN)Deployed $(SERVICE) at $(IMAGE):$(SHA)$(NC)"
# Build, push, and deploy a Cloud Run service or Job from local — bypasses
# Cloud Build entirely. Useful when CB is degraded (incidents, weekends) or
# when you want to skip the GitHub-trigger round-trip. Wakes Cloud SQL first
# so it works after the night scheduler has paused the dev instance.
#
# Usage:
# make deploy SVC=api # api → dev
# make deploy SVC=metrics-exporter ENV=dev # exporter Cloud Run Job
# make deploy SVC=shortener ENV=prod # prod (asks for confirmation)
# make deploy SVC=api SHA=v1.2.3 # override image tag
# DEPLOY_AUTO_APPROVE=1 make deploy SVC=api ENV=prod
# CONTAINER_RUNTIME=podman make deploy SVC=api
deploy:
ifndef SVC
@echo -e "$(RED)SVC is required. Usage: make deploy SVC=api|metrics-exporter|shortener [ENV=dev|prod]$(NC)"
@exit 1
endif
@bin/deploy-cloudrun.sh $(SVC) $(ENV)
# Open a psql shell against the Cloud SQL instance via the Auth Proxy.
# No public IP whitelisting needed — the proxy authenticates as your gcloud
# identity. Wakes the instance first if the night scheduler has paused it.
#
# Usage:
# make db-shell # dev
# make db-shell ENV=prod # prod (asks for confirmation)
# READONLY=1 make db-shell ENV=prod # read-only session
db-shell:
@bin/db-shell.sh $(ENV)
# Remove worktree containers and volumes
worktree-clean:
@$(WT_ENV); \
if [ -n "$${COMPOSE_PROJECT_NAME:-}" ]; then \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down -v; \
else \
echo "Not in a worktree — nothing to clean."; \
fi
# Run all tests
test:
cargo test --workspace
# CI check: fmt + clippy + test
check:
cargo fmt --check
cargo clippy --workspace -- -D warnings
cargo test --workspace
# Format
fmt:
cargo fmt
# Lint
clippy:
cargo clippy --workspace -- -D warnings
# Run migrations
migrate:
cd crates/overslash-db && cargo sqlx migrate run
# Create new migration
new-migration:
@read -p "Migration name: " name; \
cd crates/overslash-db && cargo sqlx migrate add -r "$$name"
# Regenerate SCHEMA.sql
schema:
pg_dump --schema-only --no-owner --no-acl --schema=public --exclude-table=_sqlx_migrations "$${DATABASE_URL}" > SCHEMA.sql
# Regenerate sqlx offline caches
sqlx-prepare:
cargo sqlx prepare --workspace -- --tests
# Verify sqlx offline cache is up-to-date
check-sqlx:
cargo sqlx prepare --workspace --check -- --tests
# Start mock target
mock-target:
cargo run -p mock-target
# Install git hooks
install-hooks:
git config core.hooksPath .githooks
@echo "Git hooks installed."
# ---------------------------------------------------------------------------
# Infrastructure (OpenTofu / Terraform)
# Usage: make tofu-plan ENV=dev (default)
# make tofu-plan ENV=prod
# ---------------------------------------------------------------------------
tofu-init:
@echo -e "$(GREEN)Initializing tofu ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) init
tofu-fmt:
@echo -e "$(GREEN)Checking tofu formatting...$(NC)"
cd $(TOFU_DIR) && $(TOFU) fmt -check -recursive
tofu-validate: tofu-init
@echo -e "$(GREEN)Validating tofu configuration...$(NC)"
cd $(TOFU_DIR) && $(TOFU) validate
tofu-plan:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@echo -e "$(GREEN)Running tofu plan ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select -or-create $(ENV) && $(TOFU) plan -var-file=env/$(ENV).tfvars -out=$(ENV).tfplan
tofu-apply:
@test -f $(TOFU_DIR)/$(ENV).tfplan || (echo -e "$(RED)No plan file found. Run 'make tofu-plan ENV=$(ENV)' first.$(NC)" && exit 1)
@if [ "$(ENV)" = "prod" ] && [ "$(TF_AUTO_APPROVE)" != "1" ]; then \
echo -e "$(RED)You are about to apply to PRODUCTION (project: overslash)$(NC)"; \
echo -n "Type 'prod' to confirm: "; \
read confirm && [ "$$confirm" = "prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)Applying tofu plan ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select $(ENV) && $(TOFU) apply $(ENV).tfplan
tofu-destroy:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@if [ "$(ENV)" = "prod" ]; then \
echo -e "$(RED)You are about to DESTROY production (project: overslash)$(NC)"; \
echo -n "Type 'destroy prod' to confirm: "; \
read confirm && [ "$$confirm" = "destroy prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)Destroying tofu resources ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select $(ENV) && $(TOFU) destroy -var-file=env/$(ENV).tfvars
# ---------------------------------------------------------------------------
# Infra scheduler — manual shutdown / resume
# Usage: make infra-shutdown ENV=prod
# make infra-resume ENV=prod
# ---------------------------------------------------------------------------
GCP_PROJECT = $(shell grep '^project_id' $(TF_VAR_FILE) 2>/dev/null | sed 's/.*= *"\(.*\)"/\1/')
SQL_INSTANCE = overslash-$(ENV)-db
infra-shutdown:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found.$(NC)" && exit 1)
@echo -e "$(GREEN)Shutting down infra ($(ENV), project: $(GCP_PROJECT))...$(NC)"
gcloud sql instances patch $(SQL_INSTANCE) --activation-policy=NEVER --project=$(GCP_PROJECT) --quiet
@echo -e "$(GREEN)Cloud SQL stopped.$(NC)"
infra-resume:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found.$(NC)" && exit 1)
@echo -e "$(GREEN)Resuming infra ($(ENV), project: $(GCP_PROJECT))...$(NC)"
gcloud sql instances patch $(SQL_INSTANCE) --activation-policy=ALWAYS --project=$(GCP_PROJECT) --quiet
@echo -e "$(GREEN)Cloud SQL started.$(NC)"
# ---------------------------------------------------------------------------
# Cloud Run logs
# Usage: make logs (tail api)
# make logs ENV=prod (tail prod api)
# make logs SVC=api SINCE=30m (last 30m of history, then tail)
# make logs SVC=api,worker (multiple services, comma-separated)
# ---------------------------------------------------------------------------
REGION ?= europe-west1
SVC ?= api
logs:
@SVC_FILTER=$$(echo "$(SVC)" | sed 's/[^,]\+/resource.labels.service_name="overslash-$(ENV)-&"/g; s/,/ OR /g'); \
FILTER="resource.type=\"cloud_run_revision\" AND ($$SVC_FILTER) AND logName:\"stdout\""; \
STRIP_ANSI='s/\x1B\[[0-9;]*[A-Za-z]//g'; \
FMT='value(timestamp.date("%Y-%m-%d %H:%M:%S"), severity, resource.labels.service_name, jsonPayload.level, jsonPayload.target, jsonPayload.span.name, jsonPayload.message, jsonPayload.fields, textPayload)'; \
if [ -n "$(SINCE)" ]; then \
echo -e "$(GREEN)Reading Cloud Run logs: $(SVC) ($(ENV)) — last $(SINCE), then tailing$(NC)"; \
( set -x; \
gcloud logging read "$$FILTER" \
--project=$(GCP_PROJECT) --freshness=$(SINCE) --limit=10000 \
--format="$$FMT" \
) | tac | sed -E "$$STRIP_ANSI"; \
fi; \
echo -e "$(GREEN)Tailing Cloud Run logs: $(SVC) ($(ENV))$(NC)"; \
set -x; \
gcloud beta logging tail "$$FILTER" \
--project=$(GCP_PROJECT) --buffer-window=3s \
--format="$$FMT" \
| sed -uE "$$STRIP_ANSI"
# View Cloud Build deploy logs (last build per service)
# Usage: make logs-deploy (api only)
# make logs-deploy SVC=api,worker (multiple services)
logs-deploy:
@for svc in $$(echo "$(SVC)" | tr ',' ' '); do \
echo -e "$(GREEN)Latest deploy log: overslash-$(ENV)-$$svc$(NC)"; \
BUILD_ID=$$(gcloud builds list --project=$(GCP_PROJECT) --region=$(REGION) \
--filter="substitutions._SERVICE_NAME=overslash-$(ENV)-$$svc" \
--sort-by=~createTime --limit=1 --format="value(id)"); \
if [ -n "$$BUILD_ID" ]; then \
gcloud builds log "$$BUILD_ID" --project=$(GCP_PROJECT) --region=$(REGION); \
else \
echo -e "$(YELLOW)No builds found for overslash-$(ENV)-$$svc$(NC)"; \
fi; \
done