[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-2862":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":16,"stars30d":17,"stars90d":14,"forks30d":14,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":30,"readmeContent":31,"aiSummary":32,"trendingCount":14,"starSnapshotCount":14,"syncStatus":33,"lastSyncTime":34,"discoverSource":35},2862,"ph-intercept","m00grin\u002Fph-intercept","m00grin","A Pi-hole DNS visual dashboard. Blocking is represented by an interceptor ship destroying 'blocked query' baddies, space invaders style. Now supports AdGuard!","",null,"JavaScript",128,5,0,1,4,23,3,48.63,"MIT License",false,"main",[24,25,26,27,28,29],"dashboard","docker","open-source","pihole","self-hosted","webapp","2026-06-12 04:00:16","# ph-intercept\n\nA Pi-hole DNS dashboard that runs as a standalone Docker container. Streams live DNS query events from your Pi-hole v6 API and renders them as pixel-art friendlies and enemies. Blocked queries are destroyed by the ship, allowed queries fly through. Toggle blocking, set timed blocks, trigger gravity updates, and switch ships from the HUD.\n\nDesigned to be dropped in alongside an existing Pi-hole v6 setup with no extra dependencies.\n\n\u003Cimg width=\"1713\" height=\"1254\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F791ba70f-c6cd-4495-8135-0e0d2286668e\" \u002F>\n\n---\n\n> **AdGuard Home user?** See the [AdGuard setup guide](adguard\u002FREADME.md).\n\n## Quick Start\n\n**1.** Get your Pi-hole **app password** (not your web login password): from the Pi-hole admin panel, go to **Settings → Web interface \u002F API → Configure app password**.\n\n- **CLI users:** Create a `.env` file in the same directory as your `compose.yaml`:\n\n  ```env\n  PIHOLE_PASSWORD=your_pihole_app_password\n  ```\n\n- **Portainer users:** Skip the `.env` file. Add `PIHOLE_PASSWORD` as an environment variable directly in the Portainer stack config.\n\n**2.** Create a `compose.yaml` (copy the example below or grab [`compose.yaml`](compose.yaml) from the repo) and update `PIHOLE_URL` to your Pi-hole's address:\n\n```yaml\nservices:\n  ph-intercept:\n    image: ghcr.io\u002Fm00grin\u002Fph-intercept:latest\n    hostname: ph-intercept\n    container_name: ph-intercept\n    restart: unless-stopped\n    deploy:\n      resources:\n        limits:\n          memory: 128m\n          cpus: \"1\"\n          pids: 20\n\n    environment:\n      # REQUIRED: Pi-hole v6 API endpoint\n      # Example: \"http:\u002F\u002F192.168.1.2:80\u002Fapi\"\n      PIHOLE_URL: \"http:\u002F\u002FCHANGE.ME:PORT\u002Fapi\"\n\n      # CLI users: Create a .env file in the same dir as this compose file with:\n      #  PIHOLE_PASSWORD=your_pihole_app_password\n      # Portainer Web users: Add the environment variable: PIHOLE_PASSWORD=your_pihole_app_password\n      PIHOLE_PASSWORD: ${PIHOLE_PASSWORD}\n\n      # Optional: where ESC navigates to (like your homelab dashboard or homepage)\n      # Accepts http:\u002F\u002F, https:\u002F\u002F, protocol-relative (\u002F\u002F), relative paths, and custom app schemes\n      # Leave blank (\"\") to disable ESC entirely\n      RETURN_URL: \"\"\n\n      # Background style: starfield | dark | nebula\n      BG_MODE: starfield\n\n      # Sky region shown when BG_MODE=starfield:\n      #   summer_triangle | orion | scorpius | southern_cross\n      SKY_PRESET: summer_triangle\n\n      # Set BG_IMAGE to use a custom background. URL for an image, or \u002Fbg\u002Fyour-filename.jpg\n      # If set, BG_IMAGE overrides BG_MODE entirely\n      BG_IMAGE: \"\"\n\n      # SSL certificate verification. Set to \"false\" if Pi-hole uses HTTPS\n      #  with a self-signed certificate. Leave as \"true\" for HTTP or valid HTTPS.\n      PIHOLE_VERIFY_SSL: \"true\"\n\n      # Optional: comma-separated regex patterns. Matching domains spawn no ships. Case-insensitive.\n      # PIHOLE_IGNORE_DOMAINS: .*\\.local$,.*\\.internal$\n\n    volumes:\n      # Portainer Web users: This will resolve to \u002Fdata\u002Fcompose\u002F\u003Cstack-id>\u002Fbg\u002F\n      - .\u002Fbg:\u002Fapp\u002Fstatic\u002Fbg\n\n    cap_drop:\n      - ALL\n\n    security_opt:\n      - \"no-new-privileges:true\"\n\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n\n    ports:\n      # Host port : container port. Change the left side if 4653 is taken\n      - \"4653:4653\"\n\n    # Optional: point DNS at your Pi-hole (if used for DNS resolution) or resolver directly (like Unbound)\n    # dns:\n    #   - your.dns.dockernet.ip\n\n    # Optional: only needed if you use static IPs on a custom Docker network\n    # Uncomment both networks blocks if you need this\n    # networks:\n    #   dns_net:\n    #     ipv4_address: this.container.dockernet.ip\n\n# networks:\n#   dns_net:\n#     external: true\n```\n\n**3.** Start the container:\n\n```bash\ndocker compose up -d\n```\n\nOpen `http:\u002F\u002Fyour-host:4653`.\n\n---\n\n## Image Tags\n\n| Tag | What it is |\n|-----|------------|\n| `:latest` | Latest stable release. |\n| `:X.Y.Z` | Pinned release (e.g. `1.2.0`). |\n| `:develop` | Built automatically on every push to the `develop` branch. May be unstable. Good for trying out what I'm working on. |\n\n---\n\n## Portainer Note for BG\n\n- Drop image files into `\u002Fdata\u002Fcompose\u002F\u003Cstack-id>\u002Fbg\u002F` on the Portainer host (where the `.\u002Fbg` bind mount resolves).\n\n## Entities\n\nEach DNS query spawns an entity. Tier scales with how many times that domain has queried while the entity is still on screen:\n\n**Allowed queries:** friendly ships traveling across the screen. Cache-answered queries move faster than upstream-answered ones.\n\n\u003Cimg width=\"487\" height=\"354\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F7c80bb93-6ccd-4ac2-b1c9-e34a1f54cc31\" \u002F>\n\n| Tier | Condition | Shape | Color |\n|------|-----------|-------|-------|\n| 1 | First query | Rounded shuttle · Delta wing · X-wing | Green · Blue · Lime |\n| 2 | Queried again while on screen | Heavy transport | Cyan |\n| 3+ | Three or more queries while on screen | Capital ship | Gold |\n\n**Blocked queries:** enemies the ship targets and destroys. A domain blocked again while still on screen mutates its sprite to the next tier in place.\n\n\u003Cimg width=\"621\" height=\"471\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fa2da33be-7015-4b34-9c51-6904b06573d0\" \u002F>\n\n| Tier | Condition | Shape | Color |\n|------|-----------|-------|-------|\n| 1 | First block | Crab invader · Squid | Red |\n| 2 | Blocked twice | Heavy drone | Orange |\n| 3+ | Three or more | Boss | Purple |\n\nShip weapon color tracks tier: green for tier 1, cyan for tier 2, gold for tier 3+.\n\n---\n\n## The ship\n\nThe ship targets and destroys blocked entities autonomously. At five on-screen threats a support drone launches and flanks; at ten a second drone deploys. Drones are recalled when the threat count drops.\n\nSeven ships are selectable from the HUD, shown in an 8-slot 4×2 grid: **Protector** (NSEA Protector, default), **Falcon** (Millennium Falcon), **Swordfish** (Swordfish II), **Enterprise** (NCC-1701), **Serenity** (Firefly), **Normandy** (Mass Effect), and **PES** (Planet Express Ship). Switching ships triggers a warp-out\u002Fwarp-in transition that pushes nearby entities aside.\n\n\u003Cimg width=\"370\" height=\"176\" alt=\"ships\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F694a3786-10b5-4427-8f35-7d160b28c67b\" \u002F>\n\n---\n\n## The HUD\n\nA strip across the bottom, divided into four panels:\n\n**INTERCEPT:** blocking status and toggle. Click to open a menu with timed-disable options (10 sec, 30 sec, 5 min) or a full disable. A countdown shows when a timed block is active; the timer survives navigation and syncs correctly if blocking is changed remotely.\n\n**STATS:** total queries, blocked, allowed, and block percentage. Updated live.\n\n**GRAVITY:** gravity list size. The arrow triggers a list update and confirms when done.\n\n**SHIPS:** active ship name. Click to open the ship selector.\n\n\u003Cimg width=\"1376\" height=\"105\" alt=\"download\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fcc4adf89-ac1e-402f-aed2-68ed282b49a3\" \u002F>\n\nA hamburger button at the left edge of the HUD opens the **Settings** panel, which includes:\n\n- **Friendlies** -- show or hide friendly (allowed) entities\n- **Client** -- show the requesting client (IP or hostname) as a label per entity\n- **Domain** -- show or hide the domain label beneath each entity\n- **Pi-hole** -- link to the Pi-hole admin panel\n\nDisplay settings are saved to `localStorage` and restored on next load.\n\n---\n\n## The background\n\nThree modes are available via `BG_MODE`:\n\n**`starfield` (default):** Renders a real section of the night sky from an accurate star catalog (~12,200 stars to magnitude 6.8, color-coded by spectral type). Positions use equatorial coordinates; what you see is where the stars actually are. The sky region is set by `SKY_PRESET`.\n\n\u003Cimg width=\"684\" height=\"487\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fd6a04374-9341-464b-8f24-71cafc8bbbeb\" \u002F>\n\nStar data is from the **HYG Database** by David Nash ([astronexus.com](https:\u002F\u002Fastronexus.com)), combining Hipparcos (ESA) and the Yale Bright Star Catalogue.\n\n**Planets:** Mars, Jupiter, Saturn (with ring), and the Moon are computed from real orbital elements and appear at their actual sky positions, updated hourly.\n\n**Transients:** occasional satellite passes and meteors, including the ISS.\n\n**`nebula`:** A procedurally generated nebula. Overlapping color lobes with value noise, dust lanes, and a synthetic star layer. Fully GPU-rendered, no catalog data.\n\n**`dark`:** Plain black background. No canvas rendering overhead.\n\n---\n\n## Configuration\n\nAll configuration is via environment variables in `compose.yaml`.\n\n### Required\n\n| Variable | Description |\n|----------|-------------|\n| `PIHOLE_PASSWORD` | Pi-hole app password. CLI: set in a `.env` file. Portainer: add as an environment variable in the stack. Get it from **Settings → Web interface \u002F API → Configure app password**. |\n| `PIHOLE_URL` | Pi-hole v6 API base URL, e.g. `http:\u002F\u002F192.168.1.x:8053\u002Fapi` |\n\n### Optional\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `RETURN_URL` | `\"\"` | URL that ESC navigates to. Accepts `http:\u002F\u002F`, `https:\u002F\u002F`, protocol-relative (`\u002F\u002F`), relative paths, and custom app schemes. Leave blank to disable ESC. |\n| `BG_MODE` | `starfield` | `starfield` · `dark` · `nebula` |\n| `SKY_PRESET` | `summer_triangle` | `summer_triangle` · `orion` · `scorpius` · `southern_cross` |\n| `BG_IMAGE` | `\"\"` | Image URL or `\u002Fbg\u002Ffilename.jpg`. Overrides `BG_MODE` when set. |\n| `PIHOLE_VERIFY_SSL` | `true` | Set to `false` if Pi-hole uses HTTPS with a self-signed certificate. |\n| `PIHOLE_IGNORE_DOMAINS` | _(unset)_ | Comma-separated regex patterns. Domains that match spawn no ships. Case-insensitive; escape literal dots (`\\.local$`). Example: `.*\\.local$,.*\\.internal$` |\n\n---\n\n## Requirements\n\n- Pi-hole v6 (v5 is not compatible)\n- Docker with Compose\n- Network route from the container to your Pi-hole\n- **Architecture:** `linux\u002Famd64` · `linux\u002Farm64` · `linux\u002Farm\u002Fv7` · `linux\u002Farm\u002Fv6` · `linux\u002F386` · `linux\u002Friscv64`\n\nThe container listens on port 4653. The compose file includes an optional static IP block for existing Docker networks.\n\n---\n\n## Testing\n\nI may have gone a little overboard.\n\nph-intercept is developed against a full test suite covering four layers:\n\n- **Pi-hole client** -- async unit tests for every auth path (including passwordless and lock contention), session management, query status classification, the broadcast queue, and the live query poller\n- **API layer** -- endpoint tests for every route, security headers, cache-control rules, and input validation\n- **Browser** -- Playwright integration tests across Chromium, Firefox, and WebKit covering page load, canvas sizing, SSE connection and reconnect, settings persistence, font loading, and bfcache reentry\n- **Devices** -- the same Playwright suite run against 40+ real device profiles: RPi touchscreens, Fire tablets, iPads, Android tablets, HiDPI displays, 4K, ultrawides, and orientation changes\n","ph-intercept 是一个 Pi-hole DNS 的可视化仪表盘，以太空入侵者风格展示DNS查询拦截情况。该项目采用JavaScript编写，支持Docker容器化部署，能够实时从Pi-hole v6 API获取DNS查询事件，并将其渲染为像素艺术形式的友好和敌对单位，被拦截的查询会被飞船摧毁，允许的查询则会穿过屏幕。用户可以通过界面切换拦截状态、设置定时拦截等操作。此外，它还兼容AdGuard Home。适用于已经使用Pi-hole进行DNS过滤且希望以更直观有趣方式查看拦截活动的家庭网络环境或小型办公网络中。",2,"2026-06-11 02:51:18","CREATED_QUERY"]