Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
57f193e
build(deps): add terraform updates for infra directory
simonvanlierde Aug 19, 2026
05748de
docs: update RUNBOOK with OpenTofu management details for Cloudflare …
simonvanlierde Aug 19, 2026
ec260d2
fix: repair exemplar links, error-rate alert, and logs dashboard
simonvanlierde Aug 20, 2026
69493b8
feat: harden the stack and make collector buffering durable
simonvanlierde Aug 20, 2026
b0579ab
ci: validate infra and demo build, pin actions to SHAs
simonvanlierde Aug 20, 2026
d00b1c4
refactor: drop redundant dashboard, dead file, and default-restating …
simonvanlierde Aug 20, 2026
3691af9
build(demo): bump dependencies, pin python patch, drop root
simonvanlierde Aug 20, 2026
ffbe398
docs: catch up with the review fixes
simonvanlierde Aug 20, 2026
8ebea55
docs: record the hub-and-spoke target and take over the Relab handover
simonvanlierde Aug 20, 2026
a623e8f
fix: set OTLP out-of-order window, drop stale ONBOARDING reference
simonvanlierde Aug 27, 2026
e29ee45
feat: add ContainerRestarting and ContainerOOMKilled alerts
simonvanlierde Aug 27, 2026
01b77d8
feat: add ProjectTelemetrySilent keystone alert for relab
simonvanlierde Aug 27, 2026
0d8085c
fix: promote project, env and host.name onto every OTLP series
simonvanlierde Aug 27, 2026
1c10473
docs: assign handover owner, record the metric-name experiment
simonvanlierde Aug 27, 2026
cec2769
fix: retarget ProjectTelemetrySilent at labels that actually exist
simonvanlierde Aug 27, 2026
abe69d4
refactor(tempo): drop the metrics-generator, RED comes from app OTLP
simonvanlierde Sep 1, 2026
e2fac86
feat(alerting): move evaluation and delivery into Grafana
simonvanlierde Sep 1, 2026
79b2c81
feat(loki): index project, env and host.name as stream labels
simonvanlierde Sep 1, 2026
21e1189
feat(dashboards): add gpu, host & containers, and logs
simonvanlierde Sep 1, 2026
625bd3a
feat(onboarding): add bootstrap.sh and the vendored agent templates
simonvanlierde Sep 1, 2026
25a9d45
docs: close out the hub-and-spoke migration
simonvanlierde Sep 1, 2026
8ff57fa
fix(alerting): drop the removed config/alerts mount, assert provision…
simonvanlierde Sep 1, 2026
030a1f3
fix(alerting): key the coverage backstop on project and env
simonvanlierde Sep 1, 2026
a40012b
docs: correct claims the alerting and onboarding moves left stale
simonvanlierde Sep 1, 2026
d649c36
feat: drive compose overlays from COMPOSE_FILE in .env
simonvanlierde Sep 1, 2026
d072c6e
refactor: dedup configs and scripts, fix stale docs
simonvanlierde Sep 1, 2026
425d1b2
docs: update comments and docs
simonvanlierde Sep 1, 2026
7e74925
fix(security): fail closed on JWT auth and harden containers
simonvanlierde Sep 1, 2026
79a01cf
fix(alerting): key HighErrorRate on project and env
simonvanlierde Sep 1, 2026
0a59039
feat(telemetry): add memory limiter to the spoke Alloy agent
simonvanlierde Sep 1, 2026
7741446
refactor(devx): isolate smoke and demo, fold linting into check
simonvanlierde Sep 1, 2026
b165164
docs: update for the hardening pass, fold S3 stub into ADR 0001
simonvanlierde Sep 1, 2026
cd03c9a
feat(edge): rename the ingestion hostname to otel.
simonvanlierde Sep 5, 2026
2917479
feat(telemetry): label every signal with its department
simonvanlierde Sep 5, 2026
02436db
docs: record the otel. hostname and the department label
simonvanlierde Sep 5, 2026
1f5a310
feat(infra): add script to generate OpenTofu import blocks for existi…
simonvanlierde Sep 6, 2026
a39904b
fix(templates): declare the egress network the spoke overlay joins
simonvanlierde Sep 6, 2026
00987bb
fix(infra): scope the Access app import id to accounts/
simonvanlierde Sep 6, 2026
d919a3b
feat(security): isolate the backends on an internal network, add pids…
simonvanlierde Sep 6, 2026
6cbc62e
fix(dashboards): key the GPU host picker on host_name, refresh variab…
simonvanlierde Sep 6, 2026
25f0804
test(check): exercise the exposure guards and spoke overlays, prove d…
simonvanlierde Sep 6, 2026
299e475
docs: upgrade steps for the import script and the Alertmanager volume
simonvanlierde Sep 6, 2026
242cb1b
feat(security): read-only root for cloudflared
simonvanlierde Sep 6, 2026
b3f8633
docs(runbook): inventory every secret, rotate the rest, mark tfstate …
simonvanlierde Sep 6, 2026
423b719
ci(dependabot): group patch bumps per directory and the demo into one PR
simonvanlierde Sep 6, 2026
72d9e78
docs: update phrasing across repo
simonvanlierde Sep 6, 2026
5e62b55
fix(lint): update ruff configuration to remove ignored rules and set …
simonvanlierde Sep 6, 2026
a29d25b
style(logging): format logging configuration for better readability
simonvanlierde Sep 6, 2026
2d7e3de
fix(infra): treat a failed DNS API call as an error, not an absent re…
simonvanlierde Sep 6, 2026
796c2de
ci: split lint from validate, assert the data paths in smoke, add git…
simonvanlierde Sep 6, 2026
93a99a6
feat(alerting): warn on disk fill rate and series cardinality
simonvanlierde Sep 6, 2026
fd0459f
ci: gate by path, move lint to the push hook, group security updates
simonvanlierde Sep 6, 2026
a3e45a5
ci: run on pull requests only; main advances through them
simonvanlierde Sep 6, 2026
f54507b
docs: describe the pull-request-only CI
simonvanlierde Sep 6, 2026
d4c2726
docs(changelog): consolidate the unreleased notes into 1.0.0
simonvanlierde Sep 6, 2026
5f8f83e
fix(security): keep the ingest token off argv, guard bootstrap's Graf…
simonvanlierde Sep 6, 2026
ed76cef
docs(changelog): release as 0.3.0, not 1.0.0
simonvanlierde Sep 6, 2026
be20364
docs(infra): the ingestion rename is applied; say so in the import sc…
simonvanlierde Sep 6, 2026
7ef9e20
docs: update repo documentation
simonvanlierde Sep 6, 2026
361c158
fix(security): keep the ingest token out of the container's argv, ref…
simonvanlierde Sep 6, 2026
11e45bd
fix(alerting): compare with bool so threshold rules can fire
simonvanlierde Sep 6, 2026
5562601
test(smoke): stop a scrape target and wait for TargetDown behind SMOK…
simonvanlierde Sep 6, 2026
484d38a
fix(backup): fail on tar errors, pin restore-check's compose files
simonvanlierde Sep 6, 2026
2264c87
build: digest-pin the lint, demo and curl images
simonvanlierde Sep 6, 2026
c3c54b8
fix(bootstrap): report a missing or rejected admin password
simonvanlierde Sep 6, 2026
edc664a
fix(infra): surface DNS API failures from the call sites, page polici…
simonvanlierde Sep 6, 2026
0ed7ff9
fix(dashboards): scope stack-health host panels to the hub's node job
simonvanlierde Sep 6, 2026
04d107e
docs: five exposure guards, the new alerts, the smoke alert round trip
simonvanlierde Sep 6, 2026
d4d7047
fix(ci): update .env file permissions to prevent exposure guards reje…
simonvanlierde Sep 6, 2026
66b1a30
refactor(stack): deduplicate compose, rules and the justfile
simonvanlierde Sep 6, 2026
48519d7
perf(prometheus): drop unread series from the hub's own scrapes
simonvanlierde Sep 6, 2026
a87e4a0
perf(alloy): ship only the container metrics the stack reads
simonvanlierde Sep 6, 2026
fa3dd2f
feat(alerting): count ingest per project at the gateway
simonvanlierde Sep 6, 2026
7618ec1
fix(docs): require project in every sender's resource attributes
simonvanlierde Sep 6, 2026
d3ee7bd
docs: record the series cut and the per-project ingest counters
simonvanlierde Sep 6, 2026
df888d2
fix(bootstrap): verify a new spoke with the gateway's ingest counter
simonvanlierde Sep 6, 2026
02b6b26
feat(infra): output the Zero Trust team domain
simonvanlierde Sep 6, 2026
9d137b4
docs(collector): name the real trigger for a department variable
simonvanlierde Sep 6, 2026
ce15637
docs(templates): keep GPU_METRICS as the documented spoke-side switch
simonvanlierde Sep 6, 2026
2c5b8ba
docs: changelog the Zero Trust team-domain output
simonvanlierde Sep 6, 2026
25daa4d
refactor: drop duplicate image lookup and widen the ingest-counter se…
simonvanlierde Sep 6, 2026
37c20ce
docs: changelog the ingest-counter selector
simonvanlierde Sep 6, 2026
c66b943
docs(infra): name the token permission the team-domain data source needs
simonvanlierde Sep 6, 2026
8cd8126
fix(volumes): keep the collector queue volume under compose management
simonvanlierde Sep 7, 2026
d7444c3
docs: changelog the queue init service
simonvanlierde Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
# Copy to .env and fill in. The .env file is gitignored.

# Which compose overlays this host runs; every `just` recipe acts on the same
# set. Unset = the core stack only.
# production: COMPOSE_FILE=compose.yml:compose.tunnel.yml
# local demo: COMPOSE_FILE=compose.yml:compose.demo.yml
#COMPOSE_FILE=

GRAFANA_ADMIN_PASSWORD=change-me

# Set to the public URL Cloudflare Tunnel exposes Grafana on, e.g.:
# GRAFANA_ROOT_URL=https://grafana.example.com
GRAFANA_ROOT_URL=http://localhost:3000

# Set to true whenever GRAFANA_ROOT_URL is https; `just up` with the tunnel
# overlay refuses to run without it. Keep false for plain-http localhost use,
# or logins break.
GRAFANA_COOKIE_SECURE=false

# Bearer token every telemetry sender must present (Authorization: Bearer <token>).
# The default only suits local use — generate a real one for production, e.g.: openssl rand -hex 32
# The default only suits local use. Generate a real one for production:
# openssl rand -hex 32
OTLP_AUTH_TOKEN=local-dev-token

# Where Alertmanager delivers alert notifications (any webhook: ntfy, Slack, …).
# Leave empty to run without delivery; failures are logged and harmless.
# Where Grafana delivers alert notifications (any webhook: ntfy, Slack, …).
# Not optional: an empty value drops every alert silently while the heartbeat
# below keeps reporting healthy. With the tunnel overlay active, `just up`
# refuses to start without it.
ALERT_WEBHOOK_URL=

# Dead man's switch ping target (e.g. https://hc-ping.com/<uuid>). The Watchdog
# alert posts here every 5m; alert externally when pings stop.
HEARTBEAT_URL=

# Only needed for `just up-tunnel` (production exposure via Cloudflare Tunnel).
# From: Cloudflare Zero Trust → Networks → Tunnels → <your tunnel> → Configure → token
# Optional. Lets `./bootstrap.sh` create a project's healthchecks.io checks.
# Must be the project's READ-WRITE API key (Project Settings -> API keys).
HEALTHCHECKS_API_KEY=

# Only needed with the tunnel overlay: cd infra && tofu output -raw tunnel_token
CLOUDFLARE_TUNNEL_TOKEN=

# Set to true to let Cloudflare Access sign users into Grafana individually.
# Needs both values below; `just up` with the tunnel overlay enforces that.
GRAFANA_JWT_AUTH=false
# Your Zero Trust team name, the <team> in https://<team>.cloudflareaccess.com:
# cd infra && tofu output -raw grafana_access_team_domain
CF_ACCESS_TEAM_DOMAIN=
# The Grafana Access application's aud tag: cd infra && tofu output -raw grafana_access_aud
CF_ACCESS_AUD=
35 changes: 32 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
version: 2
updates:
# Hub images: patch bumps ride together; a minor or major comes on its own,
# so a red PR names the one image that broke.
- package-ecosystem: "docker-compose"
directory: "/"
schedule:
interval: "weekly"
groups:
monitoring-stack-updates:
patterns:
- "*"
hub-patches:
update-types: ["patch"]

# The spoke images every project host runs.
- package-ecosystem: "docker-compose"
directory: "/templates"
schedule:
interval: "weekly"
groups:
spoke-patches:
update-types: ["patch"]

# The demo: one PR per month for all of it.
- package-ecosystem: "docker"
directory: "/demo"
schedule:
interval: "monthly"
groups:
demo:
patterns: ["*"]

- package-ecosystem: "pip"
directory: "/demo"
schedule:
interval: "monthly"
groups:
demo:
patterns: ["*"]
# `groups` only cover version updates; without this every advisory opens
# its own PR. Only pip gets Dependabot security advisories here.
demo-security:
applies-to: security-updates
patterns: ["*"]

# Cloudflare provider for infra/. Dependabot bumps the constraint in
# main.tf but not the hashes in .terraform.lock.hcl; see the runbook.
- package-ecosystem: "terraform"
directory: "/infra"
schedule:
interval: "monthly"

- package-ecosystem: "github-actions"
directory: "/"
Expand Down
48 changes: 35 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,51 @@
# Thin wrapper around the `just` contract: everything CI runs, you can run
# locally with the same command.
# Everything CI runs, you can run locally with the same `just` command. `lint`
# has its own job so a lint failure reports without pulling the stack images.
name: CI

# Pull requests only: main moves through PRs, so a push run would repeat the
# check. No path filter: gitleaks must see every PR, and a skipped workflow
# never reports the checks a PR needs. infra/ also has its own (infra.yml).
on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

# A push to an open PR supersedes the run in flight.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
check:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: extractions/setup-just@v4
- run: cp .env.example .env
- run: just check
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# gitleaks scans git history; a shallow clone would pass vacuously.
with:
fetch-depth: 0
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0
# 600, or the exposure guards refuse it as world-readable.
- run: install -m 600 .env.example .env
- run: just lint

smoke:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: extractions/setup-just@v4
- run: cp .env.example .env
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0
# 600, or the exposure guards refuse it as world-readable.
- run: install -m 600 .env.example .env
- run: just validate
# SMOKE_ALERTS adds the TargetDown round trip (about 3 minutes).
- run: just smoke
- run: just down
env:
SMOKE_ALERTS: "1"
# The smoke failure messages point at these logs.
- if: failure()
run: just smoke-logs
- if: always()
run: just smoke-down
25 changes: 25 additions & 0 deletions .github/workflows/infra.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# OpenTofu validation for infra/, on its own trigger: it downloads the
# provider every run and nothing outside infra/ can change its result.
name: infra

on:
pull_request:
paths: ["infra/**"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0
- run: install -m 600 .env.example .env # 600 or the exposure guards reject it
- run: just infra-validate
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
backups/
infra/.terraform/
infra/terraform.tfstate*
infra/imports.tf
infra/*.tfvars
!infra/*.tfvars.example
38 changes: 38 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Installed by `just hooks` (prek).
# pre-commit gitleaks on the staged diff
# commit-msg Conventional Commits shape on the first line
# pre-push `just check`, plus `just infra-validate` when infra/ changed
# `prek run` runs the pre-commit stage on demand; `--hook-stage pre-push` the rest.
repos:
- repo: local
hooks:
- id: gitleaks
name: gitleaks (staged)
entry: just _gitleaks-staged
language: system
pass_filenames: false
always_run: true
stages: [pre-commit]
- id: commit-msg
name: conventional commit message
entry: >-
sh -c 'head -n1 "$1"
| grep -Eq "^((build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._/-]+\))?!?:
[a-z].{0,81}|(Merge|Revert|fixup!|squash!) .*)$"
|| { echo "commit message must be type(scope): lower-case summary, at most 82 chars" >&2; exit 1; }' --
language: system
stages: [commit-msg]
- id: check
name: just check
entry: just check
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
- id: infra-validate
name: just infra-validate
entry: just infra-validate
language: system
pass_filenames: false
files: ^infra/
stages: [pre-push]
6 changes: 6 additions & 0 deletions .yamlfmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Keep line breaks inside folded (>) block scalars: the default re-joins them
# onto one line, which un-wraps the long PromQL exprs and alert descriptions
# in config/grafana/alerting/ every time yamlfmt runs.
formatter:
scan_folded_as_literal: true
retain_line_breaks_single: true
Loading