SlopScore
00 crowd

mypi

pihole consolidation dashboard and pihole sync
Open repo on GitHubgithub.com/theojamesvibes/mypi
Python · ★ 10 · 1 forks · MIT · paperwork by the Cap'mmostly ai (inferred)light human (inferred)works-on-my-machine (inferred)other
listed 46 minutes ago by theojamesvibes · last checked 46 minutes ago
The owner didn't write this. This repo never submitted itself. The Cap'm found it on a truffle trawl and wrote its paperwork from what GitHub already shows. Picked by hand by the Cap'm on 2026-10-05: pihole consolidation dashboard and pihole sync; its own README says "--- Vibe coded with Claude Code ( by Anthropic". 10 stars; MIT license. The owner did not submit this. Votes count; awards don't until the owner claims it.

I'm not calling your project slop! Geeze, it's a joke... Do you own this repo?

Log in with GitHub as theojamesvibes. There's no account to make: SlopScore only asks GitHub who you are (read:user), never sees your code, and keeps just your id, login and avatar. Then you can:

  • Keep it, on your terms. Commit your own slopscore.md (spec) and press Refresh. Your paperwork replaces the Cap'm's, and you can submit it for Slop of the Day.
  • Take it down. One click on Remove. It stays gone; the trawl never brings it back.

Log in with GitHub

Can't log in as the owner? Request a takedown. No login needed, and a trawled listing comes down right away.

GitHub says
pihole consolidation dashboard and pihole sync
created
2026-04-07 · pushed 1 hour ago · 255 commits · 3 contributors
release
v2.8.4 · 2026-09-20
languages
Python 81%JavaScript 10%HTML 6%CSS 2%Shell 1%Dockerfile 0%
paperwork
licensereadme 42% health
dependencies
⚠ 1 of 26 deps have known advisories · OSV.dev, checked 46 minutes ago

Disclosures, inferred by the Cap'm

slopbucket
vibe-coded
category
other
ai_generated
mostly
human_touch
light
status
works-on-my-machine
language (detected)
cssdockerfilehtmljavascriptmakopythonshell
license (detected)
mit

The Cap'm's log

The Cap'm wrote this paperwork, not the owner. This repo never submitted itself to SlopScore. The Cap'm picked it by hand: pihole consolidation dashboard and pihole sync; its own README says "--- Vibe coded with Claude Code ( by Anthropic". It carries the MIT license. The disclosures above are his best guess from what GitHub shows.

Is this yours? Commit a real slopscore.md and press Refresh to replace this, or remove the listing in one click. There's no account to make: you log in with GitHub.

README — the repo's own words, folded up so the grading fits on one screen

MyPi

build tests ui-tests version platform

⚠️ Vibe Code Disclosure This project was generated almost entirely through AI-assisted development (Claude Code / Anthropic). The code has been reviewed and iterated on collaboratively, but it has not been audited for production use. Deploy on trusted local networks only, review the code before relying on it, and proceed with the usual amount of healthy scepticism you'd apply to any AI-generated codebase.

Independent reviews. Two LLM-based audits have been applied as part of the hardening-review branch — by Google Gemini (adversarial security review) and Grok (full architecture + security review). Both are documented in Independent reviews below, along with what was verified as already-correct and what was changed in response. These reviews are not a substitute for a professional audit.

A self-hosted dashboard that consolidates up to 10 locally running Pi-hole v6 instances into a single screen, paired with a REST API designed for iOS app consumption.


Screenshots

mypi 1 1 2 mainscreen mypi 1 1 2 settings

Features

Dashboard

  • Unified stat cards — Total queries, blocked count, % blocked, and domains on blocklist aggregated across all instances; blocklist card turns red if instances disagree
  • Stat card footer links — each card has a Pi-hole–style footer: unique client count (→ Unique Clients view), List blocked queries, List all queries, Manage lists (→ master Pi-hole admin in new tab)
  • Time range selector — Last 15 minutes, Last 1 hour, Today, Last 24 hours (default), 48 hours, 7 days, 30 days; chart bucket granularity scales automatically with the window
  • DNS Queries over Time chart — configurable bucket size per time range, from actual query log data (not cumulative counters)
  • Query type breakdown — Doughnut chart (Forwarded / Cached / Blocked / Other)
  • Per-system panel — Each Pi-hole shown individually with its own stats and online/offline badge
  • Top Permitted Domains, Top Blocked Domains, Top Clients — Clickable rows open a drill-down modal with all matching queries for that domain or client
  • Blocked by List — ranks the window's blocks by the source blocklist that caught them; lists in a designated Pi-hole security group (for dedicated malware/phishing feeds like HaGeZi TIF or URLhaus) are flagged with a red "threat" badge, so security blocks stand out from ad/tracker noise. Configurable via SECURITY_GROUP_NAME. Attribution is resolved live via Pi-hole's /api/search (Pi-hole's per-query list_id doesn't attribute gravity blocks), and cached per scope.
  • Which-list drill-down — click any blocked domain (in the query log or the Top Blocked table) to see exactly which adlist(s) block it: list name, source URL, security flag, and how often it was blocked in the window. The manage-domain (shield) modal shows the same attribution next to its allow/deny controls.
  • Clickable MyPi logo in the sidebar — navigates to the dashboard from any other page; on the dashboard itself it refreshes the data in place without a full page reload
  • Site name in page titles — on multi-site deployments, per-slug pages show the current site name in both the browser tab title and the in-page heading (e.g. Dashboard: WTR)

How blocklist attribution works

Pi-hole v6's query API does not record which adlist caught a gravity block — the per-query list_id is null (or a negative sentinel) for gravity blocks, so it can't be joined back to a list. MyPi therefore resolves attribution on demand: it calls the relevant Pi-hole master's live GET /api/search/{domain}, which looks the domain up against gravity plus the exact/regex domainlist and returns the matching adlist(s).

  • List names come from each adlist's Pi-hole comment. Set a short name on your lists in the Pi-hole admin UI (Lists → comment, e.g. HaGeZi Pro, StevenBlack) and MyPi uses it everywhere a list is shown. A list with no comment falls back to a label derived from its source URL.
  • Where attribution appears: the which-list drill-down (click a blocked domain), the Blocked by List dashboard card (attributes the window's busiest blocked domains via /api/search and sums by list — cached per scope so a left-open dashboard doesn't hammer the master), and the manage-domain modal.
  • Security flag: an adlist assigned to the Pi-hole group named by SECURITY_GROUP_NAME is badged as a threat feed.
  • Scope: on a per-site page the search runs against that site's master; on the all-sites pages it uses any active master (gravity lists are typically identical across a household). No extra columns or backfill are stored — attribution is always live and current.

Combined Information (multi-site)

  • Shown only when ≥2 active sites are configured. Aggregates every active Pi-hole instance across every active site into a single screen.
  • Same four headline stat cards summed across sites; single aggregate DNS Queries over Time chart; Query Types pie.
  • Pi-hole Systems table lists every instance across all sites with a colored site pill, so you can see which instance belongs to which site at a glance.
  • Top Permitted / Top Blocked panels — merged by domain across sites (Top Clients is intentionally omitted because IP collisions between sites refer to different physical machines).
  • Live Activity ticker — the 15 most recent queries across all sites, color-tagged by site, new rows animated in every ~3 seconds.
  • Read-only: actions (sync, instance enable/disable, etc.) remain on the per-site pages.

Query Log

  • Consolidated query log across all instances with instance badge per row
  • Column sorting (click any header), pagination, and a Live View toggle that refreshes every 2 seconds
  • Filter by instance, domain, client, blocked/permitted status, and time range
  • Block / Unblock domains — every row has an inline button: blocked-status queries show Unblock, all others show Block. One click adds or removes the domain from the master Pi-hole's exact deny list and triggers a gravity sync to all replicas in the background. The button toggles in-place without a page reload.
  • Unique Clients view — Show dropdown option that switches to a per-client aggregate table (total queries, blocked count, % blocked, last seen); accessible directly from the dashboard stat card
  • Deep-link URL params: /queries?blocked=true, /queries?blocked=false, /queries?show=clients pre-set the Show filter automatically
  • Last-updated timestamp shown in the topbar after each refresh

Pi-hole Sync

  • Push full configuration from a designated master Pi-hole to all replicas via the Pi-hole v6 teleporter API
  • Sync order: master runs gravity first (fetches fresh blocklists) → exports teleporter zip → replicas import → replicas run gravity
  • Per-key exclusions — pin settings each replica keeps as its own (local DNS records, CNAMEs, upstreams, DHCP range …) so a config sync can't replace them with the master's
  • Selectable import options: configuration settings, gravity (adlists/blocklists/domains/clients), DHCP leases
  • Configurable automatic sync interval: 15 min / 30 min / 1 hr / 6 hr / 24 hr, or manual-only
  • Auto-sync on gravity change — detects when the master's blocklist count changes and triggers an immediate sync
  • Schedule and last sync result persist across container restarts (stored in PostgreSQL)
  • Dashboard shows "Pi synced: <time>" whenever a sync has run; time turns red if last sync was more than 24 hours ago

Pushover Notifications

  • Push alerts to any device via Pushover (iOS, Android, desktop)
  • Configurable alerts: sync failure, instance offline/back online, high block rate, VIP transfer
  • Stalled-state alerts — fires when a Pi-hole's admin API still answers but its query log has stopped advancing (the "split-state" failure that can follow a Pi-hole upgrade where FTL comes back half-up). Suggests systemctl restart pihole-FTL in the alert body. Suppressed for instances marked vip_master / vip_replica (idle is normal on a standby) — replaced by a single group-level alert that fires only if every node in the cluster goes flat at once.
  • VIP transfer alerts — when the active node in a vip_master / vip_replica cluster shifts, MyPi fires a "now serving from X (was Y)" notification. Off by default; opt in under Settings → Pushover.
  • High block rate alert requires ≥7 days of data to establish a baseline before firing
  • No-logs and block-rate thresholds are configurable in Settings
  • Credentials (App Token + User Key) stored encrypted in PostgreSQL, survive restarts
  • Validate credentials and send a test notification directly from the Settings page

Settings

  • Appearance — Light / Dark / System theme selector; preference stored in the browser and applied before first paint (no theme flash); Dark mode covers all UI surfaces including charts
  • API key management (create / revoke) for iOS app authentication
  • Instance list showing all active Pi-hole instances with online/offline badge, master indicator, and clickable URL links (open Pi-hole web UI in new tab)
  • Orphaned instance cleanup — when an instance is renamed or removed from pihole_instances.yml, the old record is detected and shown with an option to permanently remove it along with all associated stats and query log data, individually or in bulk
  • Software versions — Pi-hole (core), FTL, and web interface versions shown as columns in the instances table; fetched on each stats poll and persisted to the database so they survive restarts; color-coded green (up to date) or red (update available)
  • Sync panel: import options, per-key keep-local exclusions, schedule configuration, live sync result with per-replica status
  • Session Timeout panel: configure how long the web UI session stays active
  • Pushover panel: credentials, master enable toggle, per-alert toggles, thresholds
  • Version Check panel — shows running vs latest version, last check time; version badge in topbar turns green/red; checks GitHub once per hour (can be disabled)

API & Auth

  • Full REST API under /api/ with auto-generated OpenAPI docs (Swagger UI at /docs, ReDoc at /redoc — opt-in via ENABLE_API_DOCS=true)
  • Username/password login for the web UI (JWT session cookie)
  • API key auth (X-API-Key header) for mobile clients and automation
  • Read-only API keys — mark a key read-only on creation; the require_mutation dependency rejects it from every mutation endpoint with 403. Session cookies and bearer JWTs are always full-access
  • Version badge in topbar is green (up to date) or red (update available), links to GitHub releases

Security & Hardening

  • Security headers on every response — X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: same-origin, a Content-Security-Policy with no 'unsafe-inline' for scripts or styles (same-origin + jsdelivr only; /docs//redoc relax style-src for Swagger UI/ReDoc), and Strict-Transport-Security when SECURE_COOKIES=true
  • Rate limits on mutation endpoints — /api/sync (10/min), /api/domains/{deny,allow} (30/min each), /api/notifications/test (5/min), /api/notifications/validate (10/min), /api/auth/change-password (5/min)
  • Audit logging on mutations — every mutation handler logs user=<username> plus the action and target, so the application log doubles as an audit trail
  • Encrypted secrets at rest — Pi-hole API passwords and Pushover credentials are Fernet-encrypted in PostgreSQL using ENCRYPTION_KEY. Honest threat model: when ENCRYPTION_KEY is unset, the auto-generated key is stored in the same database (app_settings) as the ciphertext, so a database dump or backup contains both — the encryption then defends against nothing beyond casual observation. It only protects against DB/backup exposure once the key lives outside the data store: run docker compose exec app python scripts/rotate_encryption_key.py to generate a fresh key, re-encrypt stored secrets, remove the DB copy, and get the value to pin in .env. Treat pre-rotation backups as containing plaintext passwords.
  • Optional split secrets — JWT_SECRET_KEY and API_KEY_SALT let you rotate JWT signing keys without invalidating API keys (or vice versa); both fall back to SECRET_KEY when unset
  • Per-instance circuit breaker — a flap-prone Pi-hole is absorbed locally: after N consecutive connection failures, polling for that instance is suspended for a cooldown window, then one probe either closes the breaker or re-arms it. Defaults: 3 failures, 300 s cooldown, 2 s dedup (stats+queries share one connection). Tunable via CIRCUIT_FAIL_THRESHOLD / CIRCUIT_COOLDOWN_SECONDS / CIRCUIT_DEDUP_SECONDS
  • Container runs as non-root (UID 1000, no shell); image ships with a HEALTHCHECK against /api/health
  • Dependabot on a weekly schedule covers pip, github-actions, and docker ecosystems

Architecture

┌─────────────────────────────────────────────────┐
│  Pi-hole 1   Pi-hole 2   …   Pi-hole N          │
│  (Pi-hole v6, local network)                    │
└────────────────┬────────────────────────────────┘
                 │  HTTP or HTTPS (Pi-hole v6 REST API)
                 ▼
┌────────────────────────────────────────────────────────┐
│                    MyPi (Docker)                       │
│                                                        │
│  ┌─────────────┐   ┌────────────────────────────────┐ │
│  │  APScheduler│   │  FastAPI                       │ │
│  │  poll stats │──▶│  • Web UI (Jinja2 / Bootstrap) │ │
│  │  poll queries    │  • REST API (/api/*)           │ │
│  │  sync service    └──────────────┬────────────────┘ │
│  └─────────────┘                  │                   │
│                    ┌──────────────▼──────────────┐    │
│                    │  PostgreSQL 18               │    │
│                    │  stats · queries · users     │    │
│                    │  settings · sync schedule    │    │
│                    └─────────────────────────────┘    │
└────────────────────────────────────────────────────────┘
                 │  REST API (JWT / API key)
                 ▼
           iOS App (coming soon)

Tech Stack

Layer Technology
Web / API framework FastAPI + Uvicorn
Database PostgreSQL 18
ORM / migrations SQLAlchemy 2.0 async + Alembic
Background polling APScheduler (in-process)
HTTP client httpx (async)
Auth python-jose (JWT) + passlib (bcrypt)
Frontend Jinja2 + Bootstrap 5 + Chart.js
Config PyYAML + pydantic-settings

Getting Started

Prerequisites

  • Docker + Docker Compose
  • Pi-hole v6 instances running on your local network

No need to clone the repository. The app is published as a pre-built image on the GitHub Container Registry.

1. Download the config files

mkdir mypi && cd mypi

# Docker Compose
curl -fsSL https://raw.githubusercontent.com/theojamesvibes/mypi/main/docker-compose.yml -o docker-compose.yml

# Environment variables template
curl -fsSL https://raw.githubusercontent.com/theojamesvibes/mypi/main/.env.example -o .env

# Pi-hole instances template
curl -fsSL https://raw.githubusercontent.com/theojamesvibes/mypi/main/pihole_instances.yml.example -o pihole_instances.yml

2. Configure

Edit .env — at minimum set these two values:

POSTGRES_PASSWORD=change-me
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")

Edit pihole_instances.yml with your Pi-hole URLs and passwords (see Configuration below). Instances with no password set are supported — leave password empty or omit it.

3. Run

docker compose up -d

Important — stop timeouts: The provided docker-compose.yml sets stop_grace_period on both services (60s for Postgres, 30s for the app). Do not remove these. Without them Docker sends SIGKILL after only 10 seconds, which can interrupt a Postgres checkpoint mid-write and corrupt the database — requiring a table rebuild to recover.

Important — passing environment variables to the container: Compose's .env file is used for ${VAR} substitution inside docker-compose.yml, not automatically injected into the container. The security-relevant vars (SECURE_COOKIES, VERIFY_PIHOLE_SSL, ENABLE_API_DOCS, JWT_SECRET_KEY, API_KEY_SALT, ENCRYPTION_KEY) must either be listed explicitly under environment: or passed through with env_file:. If you customise the compose file, the safest pattern is:

  app:
    image: ghcr.io/theojamesvibes/mypi:latest
    env_file:
      - .env                    # pass every var from .env into the container
    environment:
      # keep only values that need compose-level substitution or validation
      DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-mypi}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-mypi}
      SECRET_KEY: ${SECRET_KEY:?SECRET_KEY is required}
      PIHOLE_CONFIG_PATH: /app/pihole_instances.yml

Without env_file:, setting SECURE_COOKIES=true in .env has no effect — cookies will be issued without the Secure flag and HSTS will not be sent, even behind a TLS reverse proxy.

Docker pulls the pre-built image automatically. The dashboard is available at http://localhost:8080 (or whichever APP_PORT you set in .env).

Log in with username admin (or INITIAL_ADMIN_USER if overridden). On first startup MyPi generates a random password for the admin account and prints it once to the container logs — retrieve it with:

docker compose logs app | grep -A2 "generated password"

You will be required to set a new password immediately before accessing the dashboard. To pin a known password instead of using the generated one, set INITIAL_ADMIN_PASSWORD in .env before the first startup (a password change is still forced on first login).


Building from source

git clone https://github.com/theojamesvibes/mypi.git && cd mypi
cp .env.example .env && cp pihole_instances.yml.example pihole_instances.yml
# edit both files, then:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

Configuration

Environment variables (.env)

Variable Default Description
POSTGRES_PASSWORD (required) PostgreSQL password
SECRET_KEY (required) JWT signing secret — generate with secrets.token_hex(32)
INITIAL_ADMIN_USER admin Username for the first admin account (created only on first startup, when no users exist yet)
INITIAL_ADMIN_PASSWORD (random, logged

Read the rest on GitHub

Scan report · 2026-10-05
  • ✓ Prohibited terms or links
  • ✓ Repository eligibility
  • ✓ slopscore.md paperwork
  • ✓ Content policy
  • ✓ Risk review — +10 owner has 0 followers

From the balcony · 0 of 3 clapped

    Schnitzel, Cap'm Slop and Princess read it and passed. Their reasons are on the balcony, with every other verdict.

    Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.

    0 comments

    log in to comment.

    report this listing — log in to report