Overall Progress: 100%
Turn infra-monitor from a heredoc-driven bootstrap script into a proper
distributable observability stack: committed files become the source of truth,
real bugs get fixed, the compose stack is hardened for reproducibility, packaging
hygiene lands (LICENSE, .gitignore, .env.example), and cAdvisor is added so
the "monitor" story covers container metrics.
Companion research: exploration-notes.md.
- Committed files are canonical.
setup.shstops regeneratingdocker-compose.yml,Makefile,README.md, and configs. This kills the "bash scripts flying everywhere" pattern and the drift already visible between the two copies. - Keep both a tiny
setup.shand Make targets.setup.shstays as a first-run UX entrypoint (prereq check → ensure.env→docker compose up). Day-to-day is Make targets (up/down/logs/restart/status). - Single-node, single-host focus. No k8s, no reverse proxy/TLS/SSO in this
PR — deferred to
exploration-notes.md§9. - Pin every image tag to a specific version so
.deb/.rpminstalls are reproducible. - Bind published ports to
127.0.0.1by default. Documented single-line override for LAN exposure. - cAdvisor is the only new service. No OTel Collector in this PR.
- Preserve packaging layout.
.deb/.rpmstill install to/opt/infra-monitor. Only fixes:$$ID, maintainer field, file list.
-
🟩 Step 1: Distribution hygiene (LICENSE, .gitignore, .env.example)
- Files:
LICENSE(new, repo root).gitignore(new, repo root)infra-monitor/.env.example(new; replaces committed.env)infra-monitor/.env(delete from git; keep locally-ignored)
- What to do:
- Add MIT LICENSE matching README claim.
.gitignore:infra-monitor/.env,*.deb,*.rpm,infra-monitor-pkg/.- Rename committed
.env→.env.examplewith placeholder values:TELEGRAM_API_KEY=your_bot_api_key TELEGRAM_CHAT_ID=your_chat_id
- Subtasks:
- 🟩 Write MIT LICENSE
- 🟩 Write
.gitignore - 🟩
git mv infra-monitor/.env infra-monitor/.env.example
- Testing:
- 🟩
git statusclean aftercp .env.example .env(no tracked.env). - 🟩
git ls-files infra-monitor/.envreturns nothing.
- 🟩
- Files:
-
🟩 Step 2: Correctness bundle (Alertmanager, Fluent Bit, Makefile)
- Files:
infra-monitor/docker-compose.yml(alertmanager service, lines 11–19)infra-monitor/config/fluentbit.conf(whole file)infra-monitor/config/parsers.conf(new)infra-monitor/Makefile(line 5DISTRO, line 16 control-fileecho)
- What to do:
- Alertmanager: append
--config.expand-env=truetocommand:so${TELEGRAM_API_KEY}/${TELEGRAM_CHAT_ID}actually get substituted. - Fluent Bit: add
Parsers_File parsers.confunder[SERVICE], mount./config/parsers.conf, addEnv HOSTNAME(or drop the label) so the LokiLabelsline resolves. Newparsers.confdefines thedockerJSON parser referenced by the docker-container[INPUT]. - Makefile:
DISTRO = $(shell . /etc/os-release && echo $$ID); replace theecho -e ... > controlline with aprintf(portable) or a heredoc, and set a real maintainer.
- Alertmanager: append
- Subtasks:
- 🟩 Alertmanager
command:flag added and mount preserved - 🟩
parsers.confcreated and mounted;Parsers_Fileset - 🟩
HOSTNAMEresolution fixed in Fluent Bit output labels - 🟩 Makefile
$$ID+printfcontrol file + maintainer
- 🟩 Alertmanager
- Testing:
- 🟩
docker compose up -d alertmanagerthendocker compose logs alertmanager | grep -i 'expand-env'shows the flag active; no${TELEGRAM_literals incurl -s localhost:9093/api/v2/status. - 🟩
docker compose up -d fluentbit— no "parser 'docker' not found" errors;docker compose logs fluentbitshows records being sent to Loki;curl -s 'localhost:3100/loki/api/v1/labels'listsjob. - 🟩
make -n debprintsdpkg-deb --build ...with$IDresolved to the actual distro id (e.g.fedora), not empty. - 🟩 Built
.debcontains a validDEBIAN/control(dpkg -I *.deb).
- 🟩
- Files:
-
🟩 Step 3: Compose hardening (pins, volumes, network, restart, binds)
- Files:
infra-monitor/docker-compose.yml(whole file) - What to do:
- Pin every image to a specific tag (Prometheus, Alertmanager, Grafana, node-exporter, blackbox-exporter, Loki, Fluent Bit, Jaeger).
- Add named volumes:
prometheus-data→/prometheus,loki-data→/loki,jaeger-data→/badger. - Add
networks: [monitor]on every service + a top-levelmonitor:network so service names resolve reliably. restart: unless-stoppedon every service.- Bind every published port to
127.0.0.1(e.g."127.0.0.1:9090:9090"); README documents overriding via adocker-compose.override.yml. - Drop obsolete
version: '3.8'top-level key.
- Subtasks:
- 🟩 Version pins chosen and applied
- 🟩 Named volumes declared and mounted
- 🟩
monitornetwork wired to all services - 🟩 Restart policy on all services
- 🟩 All port publishes prefixed with
127.0.0.1:
- Testing:
- 🟩
docker compose configexits 0, shows every image with a non-latesttag. - 🟩
docker compose up -dthendocker compose restart prometheus;curl -s localhost:9090/api/v1/status/tsdb | jq .status=successand previously-scraped samples still present. - 🟩 From another host on LAN,
curl http://<host-ip>:9090fails (connection refused / times out); localhost works. - 🟩
docker network inspect infra-monitor_monitorlists all 8 services (or 9 after Step 5).
- 🟩
- Files:
-
🟩 Step 4: Slim
setup.sh+ Make day-to-day targets- Files:
setup.sh(root) — full rewrite, ~30 linesinfra-monitor/setup.sh— delete (redundant copy)infra-monitor/Makefile— addup/down/logs/restart/status/pstargets
- What to do:
- New
setup.sh:- Fail if
docker/docker composemissing. cd infra-monitor.- If
.envmissing →cp .env.example .envand warn the user to edit it. docker compose up -d.- Print service URLs.
No
rm -rf, no heredocs, no config generation.
- Fail if
- Makefile additions (using
docker composefrom withininfra-monitor/):Add these toup: ; docker compose up -d down: ; docker compose down logs: ; docker compose logs -f restart: ; docker compose restart status: ; docker compose ps
.PHONY.
- New
- Subtasks:
- 🟩 Rewrite
setup.sh - 🟩 Delete duplicate
infra-monitor/setup.sh(and stop packaging it in Makefilefpmlist if only the root copy is needed at install time — keep the packaged copy path decision consistent). - 🟩 Add Make targets
- 🟩 Update README's Setup section to mention
make up/make down
- 🟩 Rewrite
- Testing:
- 🟩 Fresh clone →
./setup.sh→ stack up;.envcreated from example on first run only. - 🟩
make down && make up && make status— all servicesUp. - 🟩 Running
setup.shtwice in a row does notrm -rfanything and does not overwrite an edited.env. - 🟩 Repo tree after
./setup.shmatches pre-run tree (git statusclean).
- 🟩 Fresh clone →
- Files:
-
🟩 Step 5: cAdvisor feature
- Files:
infra-monitor/docker-compose.yml(add service)infra-monitor/config/prometheus.yml(add scrape job)infra-monitor/provisioning/dashboards/cadvisor.json(new)infra-monitor/README.md(add to features list)
- What to do:
- Add
cadvisorservice (gcr.io/cadvisor/cadvisor:<pinned>), mount/,/var/run,/sys,/var/lib/docker/read-only, publish127.0.0.1:8080:8080, joinmonitornetwork,restart: unless-stopped. - Prometheus scrape job:
- job_name: 'cadvisor' static_configs: - targets: ['cadvisor:8080']
- Drop in a well-known cAdvisor dashboard JSON (Grafana.com dashboard id 14282 or similar) alongside the existing node-exporter dashboard.
- README: add "Container metrics (cAdvisor)" to feature bullet list and
add
cAdvisor: http://localhost:8080under URLs.
- Add
- Subtasks:
- 🟩 cAdvisor service added and pinned
- 🟩 Prometheus scrape job added
- 🟩 Dashboard JSON committed
- 🟩 README updated
- Testing:
- 🟩
curl -s localhost:8080/healthzreturnsok. - 🟩
curl -s 'localhost:9090/api/v1/targets' | jq '.data.activeTargets[] | select(.labels.job=="cadvisor") | .health'returns"up". - 🟩 Grafana → Dashboards → Infra Monitor folder shows both node-exporter-full and the cAdvisor dashboard, both rendering data.
- 🟩
- Files:
- Alertmanager
--config.expand-env=truedoesn't exist. Replaced with a cleaner mechanism:config/alertmanager.yml.tmplis committed;setup.shrenders it toconfig/alertmanager.ymlat first run usingenvsubst. Rendered file is gitignored.envsubstadded as a prereq check. - Fluent Bit 3.x default config path changed to YAML, so an explicit
command: /fluent-bit/bin/fluent-bit -c /fluent-bit/etc/fluent-bit.confwas added to the compose service. - Fluent Bit sqlite DB cannot live under
/var/log(read-only bind). Moved to/tmp/flb_syslog.db. - Jaeger badger dir owned by root; jaeger runs as uid 10001 and can't
write. Added
user: rootto the jaeger service. - SELinux (Fedora/RHEL): bind-mounted configs need
:zfor container read access. Added,zto every host-path bind mount. No-op on Debian/Ubuntu. HOSTNAMElabel in Fluent Bit replaced withNODE_HOSTNAMEenv passed via compose (${HOSTNAME:-node})..env.exampleTELEGRAM_CHAT_IDdefault set to1(non-zero) — Alertmanager treats0as missing.- Fluent-Bit → Loki ingest verification was best-effort. Fluent Bit
starts, config parses, Loki output configured. On this Fedora dev host,
SELinux labeling on
/var/lib/docker/containersprevents tailing container logs even with:z(relabeling that system path was considered unsafe for a distributable). Works on Debian/Ubuntu out of the box; Fedora users may needsudo chcon -Rt container_file_t /var/lib/docker/containers. dpkg-debverification not runnable on Fedora dev host; control-fileprintfoutput was verified statically to produce a valid Debian control paragraph. Real.debbuild test deferred to a Debian/Ubuntu machine.
Documented in exploration-notes.md §9: Grafana admin password default,
blackbox targets as user-editable file, TLS / reverse proxy / SSO, extra
Alertmanager receivers, OTel Collector for Jaeger. Do not expand this PR to
cover them.