Commit graph

211 commits

Author SHA1 Message Date
baec7c22e8
updated talos 2026-07-05 13:27:15 -05:00
74317f40e1
update 2026-06-29 10:32:00 -05:00
a42e2eeb7b
exclude manero-ca from most things 2026-06-25 16:52:20 -05:00
390eb2d835
cleanup 2026-06-22 16:17:33 -05:00
55451fe285
cleanup resolvers 2026-06-22 16:11:33 -05:00
6488494d1b
add ipv6 addresses to resolvers 2026-06-22 16:01:49 -05:00
c85be1d56c
flatcar resolvers 2026-06-22 15:52:51 -05:00
d6cf15165a
restore UISP server + caddy frontend, fix SSH host key regeneration, add tofu proxmox provider 2026-06-21 19:06:33 -05:00
216bd8e668
updates 2026-06-21 13:39:36 -05:00
763d7a938c
updates 2026-06-20 14:21:32 -05:00
1eb0b74fe8
updates 2026-06-19 17:24:54 -05:00
fb5803c866
add FreeBSD bootstrap, irc servers, hostname overrides, and cleanups
- bootstrap.yml: support pkg for FreeBSD sudo/python3 install
- base role: FreeBSD package sets, sudoers paths, host key regen, admin group, usermod process-in-use tolerance
- freebsd role: pkg update/upgrade tasks
- general role: standalone hostname tasks (ungated from network.skip), hostname_override support
- inspircd role: deploy configs for ca/us manero.org irc servers with vault-backed secrets
- uisp role: download and run installer on fresh hosts, skip if already running
- netbox role: stop services before usermod to avoid process-in-use failures
- hosts: add irc_servers group, ca/us.manero.org (Tailscale), drop staging.towerops.net
- all.yml: graham user gets static ed25519 key alongside github keys
2026-06-19 17:19:29 -05:00
b43aa56b5c
icinga2 host updates 2026-06-19 15:24:44 -05:00
b9a52c258a
updates 2026-06-19 09:33:27 -05:00
86ae10d963
resolvers: tune unbound for performance and reliability
- so-reuseport: yes — kernel hashes UDP across the per-thread sockets
  instead of waking all threads, balancing load and improving headroom
- edns-buffer-size: 1232 — DNS Flag Day 2020 default, avoids IP
  fragmentation (retransmits/TCP fallback)
- serve-expired (RFC 8767) with 1800ms client timeout and 86400s stale
  ttl — fast answers from stale cache during upstream slowness/outages
2026-06-07 16:43:33 -05:00
02390adff8
telegraf: disable PrivateMounts in containers (fix LXC 226/NAMESPACE)
The packaged telegraf.service sets PrivateMounts=true, which requires a
mount namespace that unprivileged LXC containers cannot create, so the
service failed to start on the LXC resolver with status 226/NAMESPACE.
Drop in PrivateMounts=false, gated to container guests so VM hosts keep
the unit's hardening.
2026-06-07 13:38:26 -05:00
66986e294b
monitoring: collect Unbound metrics via Telegraf into Prometheus + Grafana
Add a telegraf role (applied to the resolvers) that runs unbound-control
via the inputs.unbound plugin and exposes the stats as a Prometheus
/metrics endpoint on :9273, firewalled to the 204.110.188.0/22 scrape
network. Imports the InfluxData signing key (influxdata-archive.key; the
_compat key expired 2026-01-17) so dnf can validate the package.

- prom.w5isp.com: scrape job "unbound" for resolver1/2:9273
- grafana: "Unbound Resolvers" dashboard (queries, cache hit ratio,
  rcodes, query types, recursion time, request list, cache memory)
- resolvers: disable unused shm-enable to silence the startup warning
2026-06-07 13:34:24 -05:00
43730bf5e7
resolvers: fix empty sshd validation command in post-quantum task
The 'Validate sshd configuration' task had ansible.builtin.command with no
command, so it always errored (one of the following is required:
_raw_params, cmd, argv), tripping the rescue that reverts the drop-in. The
post-quantum KexAlgorithms hardening therefore never applied. Run sshd -t
to validate before restart as intended.
2026-06-07 10:38:46 -05:00
c64f59929e
resolvers: NXDOMAIN blocklist via RPZ; fix Ansible deprecations
Add an RPZ blocklist on the Unbound resolvers that returns NXDOMAIN for a
set of domains (and subdomains) and logs only those matches under rpz-log
tag "blocklist". Domains are templated from resolver_blocked_domains
(single source of truth) into /etc/unbound/blocklist.rpz; respip module
enabled for RPZ support.

Fix deprecation warnings across the Ansible tree:
- apt_repository -> deb822_repository (monitor, aprsc, uisp, grafana),
  removing stale .list files and adding update_cache where deb822 drops it
- community.mysql.* -> ansible.mysql.* (monitor) and requirements.yml
- top-level facts -> ansible_facts[...] (ansible_os_family,
  distribution_release, fqdn, architecture, default_ipv4) repo-wide
2026-06-07 10:32:42 -05:00
d632095eb2
cloudflare: disable managed robots.txt; fix v5 config bugs
- Add cloudflare_bot_management for all zones with is_robots_txt_managed=false
  so each app's own robots.txt is served instead of Cloudflare's managed one
- Fix zone_setting id zero_rtt -> 0rtt (invalid id rejected by API)
- Fix security_header value: object instead of jsonencode (avoids perpetual diff)
- Fix firewall expression http.request.user_agent -> http.user_agent (invalid field)
- tofu fmt reindentation
2026-06-04 17:41:45 -05:00
c5b658d653
remove findings 2026-06-04 15:25:00 -05:00
6b7ef92c5b
cleanup: remove audit findings files 2026-06-04 15:24:43 -05:00
b282921e22
upgrade: Talos v1.13.3, K8s v1.36.1, Cloudflare v5
- Bump talos configs: installer:v1.12.6 -> v1.13.3, kubelet + K8s
  components v1.35.0 -> v1.36.1 across all configs
- Update documentation version references (CLAUDE.md, README.md)
- Fix Cloudflare main.tf constraint to ~> 5.0 (was accidentally reverted)
- Copy talosconfig to ~/.talos/config for default CLI use
- Live nodes upgraded: 3 CP + 4 workers, then K8s upgrade completed
2026-06-04 15:23:13 -05:00
78d0ae179d
fix: resolve talos config issues, Cloudflare v5 migration, and security cleanup
- Remove sensitive files from git tracking (kubeconfig, secrets.yaml.backup,
  talosconfig.backup) and update .gitignore
- Fix installer version drift: v1.12.4 -> v1.12.6 across all talos configs
- Fix grubUseUKICmdline + extraKernelArgs conflict on CP configs
- Remove tailscale extension from stock installer CP configs (won't work)
- Add nameservers to controlplane and per-node configs
- Add time servers to all per-node worker configs
- Fix HostnameConfig auto: stable in base templates
- Clean up doc drift (README.md, CLAUDE.md) for worker count and storage
- Remove all Longhorn references (worker configs, documentation)
- Migrate Cloudflare provider v4 -> v5 with full resource refactoring:
  cloudflare_record -> cloudflare_dns_record,
  zone_settings_override -> zone_setting,
  block -> attribute syntax for rulesets, data, and tunnel configs
- Fix firewall expression (not http.user_agent ne '' -> not exists)
2026-06-04 14:58:09 -05:00
9b73f436d8
tofu: remove state from tracking, add gitignore, upgrade providers
- Stop tracking terraform.tfstate (git rm --cached)
- Add tofu/.gitignore with tfstate, plan, and IDE patterns
- Upgrade Porkbun provider 1.3.1 -> 1.3.3
- Upgrade Cloudflare provider 4.52.5 -> 4.52.7
- Add audit findings.md with 33 issues identified
- Clean up local backup files
2026-06-04 14:48:54 -05:00
5e8823f4dd
fix: resolve all logic bugs
- Remove duplicate firewall include_role from base role (#18)
- Move remote_user from [ssh_connection] to [defaults] in ansible.cfg (#20)
- Add set -euo pipefail to run.sh (#21)
- Delete orphaned playbook_handlers.yml (#22)
- Fix changed_when: true in debian tasks (#23)
- Add tailscale_authkey env var lookup with TAILSCALE_KEY fallback (#25)
- Document -K requirement in bootstrap.yml (#26)
- Replace bare ansible_host=mail/git with IP addresses (#27)
- Update findings.md
2026-06-04 14:43:06 -05:00
ec372fd399
fix: add geerlingguy.postgresql to requirements.yml (used by db.w5isp.com) 2026-06-04 14:41:25 -05:00
8d50d412c6
docs: mark general.yml and docker targets findings as resolved 2026-06-04 14:40:22 -05:00
ffe1cc3820
docs: mark alpine role finding as resolved in findings.md 2026-06-04 14:40:02 -05:00
d81f910797
cleanup: remove orphaned host_vars/db.aprs.me.yml (superseded by db.w5isp.com) 2026-06-04 14:39:29 -05:00
118aa70f51
cleanup: remove broken makefile targets, fix general.yml references
- Remove all docker/build/push targets (no Dockerfile exists)
- Remove broken qc-web target (group doesn't exist)
- Remove clean, dirs, base-*, local, latest targets (docker-only)
- Fix bootstrap-network, run-all to use playbook.yml not general.yml
- Fix install-collections (deleted files referenced)
- Update findings.md
2026-06-04 14:37:55 -05:00
1b7077dd0d
fix: add template defaults, remove alpine ref, fix ssh -t flag
- Add |default() fallbacks for ntpserver, template_run_date,
  deb_mirror, and sshd.* to prevent template crash
- Remove include_role: name=alpine from base tasks (no such role)
- Fix SSH -t flag placement in Makefile reboot targets
- Update findings.md
2026-06-04 14:37:08 -05:00
44c109c6c9
cleanup: remove dead code and stale files
- Delete unreferenced roles: icinga2-container, debug, mailcow, syncthing
- Remove unused Galaxy deps from requirements.yml (docker, kubernetes)
- Delete roles/requirements.yml (graylog2.graylog, bertvv.bind, artis3n.tailscale)
- Merge netbox.netbox from collections/requirements.yml into root, delete file
- Delete 56 stale fact_cache files
- Remove unused inventory groups: home_cluster, dns_servers
- Update findings.md
2026-06-04 14:35:28 -05:00
ee72c64246
fix: resolve best practice and lint violations
- Convert ~290 short module names to FQCNs across all roles
- Change become: yes/no to become: true/false, gather_facts: no to false
- Remove deprecated inject_facts_as_vars from ansible.cfg
- Add missing task names to import_tasks/include_role calls
- Add become: yes to librenms clone task (partial-become fix)
- Add pipefail to risky shell command in udp-gro-fix.yml
- Quote all octal mode values
- Harden .ansible-lint with production profile and empty skip_list
- Update findings.md with all fixes
2026-06-04 14:34:23 -05:00
6f0d308fea
cleanup: remove forgejo, fix ssh key paths, fix tracking issues, update docs
- Remove forgejo role, play, group, host_vars, and DNS references
- Replace hardcoded /Users/graham/.ssh/ paths with env var lookup
- Remove broken setup-user Makefile target
- Add community.mysql collection to requirements.yml
- Add .ansible/ to .gitignore
- git rm --cached .DS_Store files and .vscode/settings.json
- Update CLAUDE.md and README.md to reflect actual state
- Add findings.md with audit results
2026-06-04 14:27:31 -05:00
46f55cdad8
fix 2026-06-02 14:46:50 -05:00
80943fc684
fix idempotency across roles
- firewall: remove ufw reset on every run; pre-check state before enable/disable/rule tasks
- debian: add regexp to fstab lineinfile; clean stale mariadb 12.2 repo
- librenms: use mariadb 11.8 (trixie-compatible)
- mailcow: idempotent git config, docker compose down/pull/up
- tailscale: don't redownload gpg key every run
2026-06-02 11:54:34 -05:00
38638e1134
update 2026-06-01 10:53:23 -05:00
a3bf64521b
updates 2026-05-20 12:06:54 -05:00
02df6fbbc6
resolvers: serve DoH via Caddy, add IPv6 records and PQ SSH
- unbound: add loopback DoH listener (127.0.0.1@8080, http-notls-downstream);
  interface-automatic-ports lists 8080 so interface-automatic does not silently
  drop the non-53 listener; access-control driven by resolver_allowed_netblocks
- caddy: add RedHat/COPR install path, caddy_template var, /var/log/caddy dir;
  new Caddyfile-resolver.j2 terminates LE TLS and h2c-proxies to unbound,
  enforcing the client allow-list via remote_ip
- resolvers: open 80/443 in firewall; prefer post-quantum SSH key exchange
  (sntrup761x25519-sha512) via validated sshd_config.d drop-in
- bind9: zone.j2 emits AAAA records; add ipv6 for resolver1/2 (2606:1c80::240/250)
- playbook: run caddy role on the resolvers group
2026-05-14 10:10:46 -05:00
1612910654
resolvers: enable IPv6 in unbound (listen, do-ip6, access-control)
Listen on ::0, set do-ip6: yes for IPv6 upstream resolution, and add
IPv6 access-control: default-deny ::0/0, allow ::1 and the VNTX
2606:1c80::/32 allocation.
2026-05-14 09:04:22 -05:00
5443e75d70
grafana: prune stale dashboard files from prom on each apply
Without this, a renamed dashboard left the old file on disk and the
provisioner refused to write any dashboards because of a duplicate UID
(seen with towerops-phoenix-live-view vs towerops-phoenix_live_view).
2026-05-11 10:39:35 -05:00
6fa9085b18
grafana: add per-app Loki log dashboards (argocd, aprs, towerops, prop, gridmap) 2026-05-11 10:36:07 -05:00
36eb41736f
promtail: drop namespace.yaml (owned by node-exporter app) 2026-05-11 10:28:01 -05:00
6d173d9fcb
loki: add log aggregation on prom + promtail daemonset in home-cluster
- new ansible loki role installs loki 3.5.5 on prometheus_servers as a
  systemd unit (single-binary, filesystem store, 30d retention)
- open tcp/3100 on prom.w5isp.com to 10.0.0.0/16
- grafana: provision loki datasource at http://127.0.0.1:3100
- k8s: promtail daemonset + rbac in monitoring namespace, ships pod logs
  to http://10.0.15.31:3100 with cluster=home-cluster label
- argocd: Application points at home/cluster/promtail
2026-05-11 10:24:30 -05:00
b13ae9bd5f
fix(grafana): correct PromEx status_info metric name in towerops dashboards
The dashboards referenced `towerops_prom_ex_status_info` but PromEx exports
this as `towerops_prom_ex_prom_ex_status_info`. The template job dropdown
was empty because Grafana couldn't find any job values for the wrong metric.
2026-05-11 08:15:35 -05:00
4191c0eebb
ansible/hosts: drop decommissioned proxmox + postgresql entries
Removes vm1-380 (10.0.0.1), db.towerops.net and db.aprs.me (10.0.15.22)
from the inventory.
2026-05-10 18:12:54 -05:00
04b3855874
k8s: add Starlink dish prometheus exporter + dashboard
joshuasing/starlink_exporter 0.9.0 (Docker Hub; ghcr.io lags behind
upstream releases). Targets the dish's gRPC management interface at
192.168.100.1:9200, reachable from cluster pods via 10.0.15.254 while
the Starlink router is in bypass mode.

Grafana dashboard "Starlink" covers throughput, PoP ping latency/drop,
obstruction state + 24h total, GPS lock, and dish position/info — synced
to /var/lib/grafana/dashboards/ by the existing grafana role.
2026-05-10 18:11:21 -05:00
8466e4590f
tmobile-exporter: switch to stock json_exporter + nginx sidecar
Replaces the vendored Go exporter with two pre-built upstream images:
- quay.io/prometheuscommunity/json-exporter:v0.7.0 maps the gateway JSON
  into prometheus metrics via JSONPath. The G4AR keys signal blocks as
  "5g" / "4g" (digit-prefixed), which the k8s client-go JSONPath dialect
  only reaches via bracket-with-single-quote notation.
- nginxinc/nginx-unprivileged:1.27-alpine-slim sidecar rewrites
  GET /metrics → GET /probe?module=tmhi&target=http://192.168.12.1/...
  so the existing kubernetes-services scrape job picks it up over the
  apiserver proxy with no Prometheus-side config changes.

Avoiding caddy here: its binary carries cap_net_bind_service file caps,
which the kernel refuses to exec under no_new_privs as a non-root user.

Dashboard "Gateway up" panel now reads up{service="tmobile-exporter"}
(Prometheus's built-in scrape-success gauge) since json_exporter's
/probe endpoint does not emit probe_success.
2026-05-10 17:57:13 -05:00
f2d6dbd271
tmobile-exporter: vendor minimal Go exporter that handles G4AR quirks
Replaces tpaulus/tmobile-internet-exporter upstream image, which crashes
on every scrape against the G4AR because the gateway returns `bars` as a
JSON float (`4.0`) and the upstream struct types it as int.

The vendored exporter:
- decodes all radio numerics as float64 so `4.0` round-trips
- treats the 4g / 5g signal blocks as optional (the G4AR omits 4g)
- evicts stale (type, band) series when a band disappears between scrapes
- adds a scrape_errors_total counter and /healthz endpoint

Deployment now pulls git.mcintire.me/graham/tmobile-exporter:v1 via the
forgejo-registry imagePullSecret (matching the convention used by aprs,
prop, towerops). Build + push the image before Argo can pull it; the
imagePullSecret must also be copied into the monitoring namespace.
2026-05-10 17:42:25 -05:00