09 Apps - seadogger-tech/seadogger-homelab GitHub Wiki
User facing applications that are applied thru ArgoCD on top of the k3s tech stack
PiHole: Website: https://pi-hole.net
- Network-wide DNS sinkhole for ads and tracking.
- Blocks ads at the network level (including in-app and smart-TV).
- Web admin interface and API; ideal on Raspberry Pi or any Linux box.
- All
*.seadogger-homelabhostnames are staticdnsmasqrecords defined incore/deployments/pihole/pihole-values.yaml(dnsmasq.customDnsEntries) β add a new app's hostname there, not through the Pi-hole UI, so it survives a redeploy. The one exception ishermes-<user>.seadogger-homelab, whichansible/tasks/hermes_deploy.yml(Pro repo) writes to Pi-hole'sdns.hosts. -
Allowlist and adlists in
pihole-values.yamlare imported only on a fresh install (emptygravity.dbon the PVC). Changing them restarts the pod but leaves the live lists untouched, so apply every change in both places: edit the values file, thenkubectl -n pihole exec deploy/pihole -c pihole -- pihole allow <domain> --comment "<why>". Allow-type subscribed lists can't be declared there at all (the chart importsadlistsas block lists); the values file records the one in use and the command to restore it. -
30-Minute Pause button on the portal calls a small backend
(
deployments/pihole-toggle, Pro repo) that holds the Pi-hole admin password server-side and calls Pi-hole's v6 API (POST /api/dns/blocking) β the password never reaches the browser.
Home Assistant: Website: https://www.home-assistant.io
- Home automation hub, deployed via the community
pajikos/home-assistant-helm-chart
(
deployments/home-assistant+ansible/tasks/ha_deploy.yml) β chart version pinned explicitly. - Container mode, no Supervisor: Core + HACS only. There is no add-on store β anything you'd install as a Supervisor add-on (Mosquitto, Zigbee2MQTT, ESPHome) needs to run as its own pod instead.
-
No
hostNetwork, no node-pinning: standard pod networking, since no USB Zigbee/Z-Wave radio is attached. mDNS-based discovery (HomeKit, Chromecast, local device auto-discovery) will not work withouthostNetwork: trueβ revisit if that's ever needed. -
Reverse-proxy trust required: HA rejects requests from a reverse
proxy it doesn't recognize (
400 Bad Request, "not set-up for reverse proxies"). Since we route via our own TraefikIngressRouterather than the chart's ownIngress,ingress.external: true+configuration.enabled: truein the values file is what makes HA emit thehttp.use_x_forwarded_for/trusted_proxiesconfig β without it, every request 400s. -
configuration.yamlis only regenerated if missing β the chart's init container copies its templated config to the PVC once and never again once the file exists. After changingconfiguration.templateConfigvalues, you must delete/config/configuration.yamlfrom the running pod and restart it for the new template to actually apply. -
HACS install:
wget -O - https://get.hacs.xyz | bash -run inside the pod (kubectl exec ... -- bash -c "..."), then restart the pod. Not pre-installed by default. -
Dashboard editing via code: see 17-Runbooks β
"Edit a Home Assistant Dashboard via Code" for a reusable script
(
deployments/home-assistant/ha_dashboard_edit.py) that edits Lovelace dashboards through HA's own WebSocket API (lovelace/config/lovelace/config/save) instead of hand-editing the live.storagefile. -
RTSP cameras: the two Amcrest IP2M-844E cameras (front door, pool)
are wired in via the core ONVIF integration (manual IP entry β
WS-Discovery/UDP multicast doesn't reach the pod, so auto-discovery
finds nothing) rather than a standalone go2rtc app. Replaced the old
camerasapp (removed; see git history), which was a 2-3fps JPEG-polling stopgap. - Storage:
ceph-block-data(RBD), single replica β HA's SQLite recorder DB needs a single writer.
Mealie: Website: https://mealie.io
- Self-hosted recipe manager and meal planner (mealie-recipes/mealie).
- Plain hand-written manifests (
deployments/mealie+ansible/tasks/mealie_deploy.yml), not a Helm chart β no third-party chart for Mealie has real community adoption, so this follows the same self-contained-Kustomize pattern used elsewhere in this repo. - SQLite backend (default, no separate Postgres needed) on
ceph-block-data,Recreatedeploy strategy (single writer). - Image pinned (not
:latest) β bump deliberately, verify after.
- HA's built-in
mealieintegration (core since 2024.7) connects directly to Mealie's API β no HACS plugin needed. -
Use the in-cluster Service DNS, not the Pi-hole hostname. HA/Mealie
pods resolve DNS through k3s's CoreDNS, which has no record for
mealie.seadogger-homelab(that name only exists in Pi-hole'sdnsmasq.customDnsEntries, used by browsers/LAN clients). Configure the integration's host ashttp://mealie.mealie.svc.cluster.local:9000instead. - Exposes one
calendar.mealie_<type>entity per meal-plan entry type (breakfast/lunch/dinner/side/snack/drink/dessert) plus stat sensors (sensor.mealie_recipes, etc.). Calendars stay empty until a recipe is assigned to a date in Mealie's Meal Plan view. - The family dashboard's "Today's Meals" card
(
custom:calendar-card-pro,days_to_show: 1) reads these calendar entities directly β one color-coded row per meal type. See Meal-Planning Workflow below. - HA polls Mealie on its own interval; to force an immediate refresh after
a manual test, reload the config entry:
POST /api/config/config_entries/entry/{entry_id}/reload.
Mealie's AI provider is wired to the in-cluster
AWS Bedrock Access Gateway,
configured through Mealie's own Group Settings β AI Providers API
(/api/groups/ai-providers/*) β not env vars (that changed as of
Mealie v1.7+/v3.10+, which moved AI config out of OPENAI_* container
env vars and into per-group UI/API settings).
-
Endpoint:
http://bedrock-access-gateway-service.bedrock-gateway.svc.cluster.local:6880/api/v1(in-cluster Service DNS β same DNS caveat as above applies here too). -
Model:
us.anthropic.claude-haiku-4-5-20251001-v1:0β the only model tested that produces clean, schema-conformant JSON through this gateway. See "Model compatibility" below for why other models don't work as-is. -
API key: the gateway's own static key (
bedrock), not a real Anthropic/OpenAI key.
Mealie's AI client uses the OpenAI Python SDK's structured-output helper
(client.chat.completions.parse()), which sends a response_format: json_schema on every request and expects the model to return only
raw JSON matching that schema β no markdown, no preamble. As of upstream
commit 274b794e (2026-06-18), the AWS Bedrock Access Gateway silently
dropped response_format entirely β it was accepted by the request
schema but never translated into anything Bedrock understands, so every
model just free-texted its own natural style and Mealie's strict
json.loads() broke unpredictably (Claude sometimes wrapped output in
```json fences, sometimes didn't).
This is a confirmed, tracked upstream gap
(aws-samples/bedrock-access-gateway#162,
#255) β
not a config option we were missing. A complete, tested fix exists as an
open, unmerged PR (#255,
branch nijave:nv-response-format) that maps response_format.json_schema
to Bedrock's native outputConfig.textFormat β real schema enforcement
at the AWS API level, not a prompt trick.
core/.github/workflows/upstream-rebuild.yaml cherry-picks that PR's
single commit on top of a fresh checkout of aws-samples/main on every
scheduled rebuild (every 6h), so we still track all other upstream
changes automatically and don't freeze at a stale fork. If the cherry-pick
ever conflicts (upstream changed something the patch touches), the
workflow fails loudly rather than silently building without the fix β
check the Action run and rebase manually if that happens. Remove the
cherry-pick step once PR #255 actually merges upstream.
Verified post-patch: 5/5 consecutive real ingredient-parse calls through Mealie returned clean HTTP 200 JSON with no fences, no reasoning tags, correct schema shape.
Even with the gateway patch, only Claude is currently usable:
| Model | Result |
|---|---|
us.anthropic.claude-haiku-4-5-20251001-v1:0 |
Works β clean structured output via outputConfig.textFormat
|
openai.gpt-oss-20b-1:0 / 120b
|
Still wraps output in <think>...</think> reasoning tags before the JSON, even with structured output enabled β confirmed via direct testing post-patch. This is the model's native "harmony format" output; the gateway's reasoning-separation feature (which cleanly splits reasoning_content for Claude/DeepSeek) is not implemented for gpt-oss β reasoning_content is always null for this model family regardless of reasoning_effort. This is a separate, gpt-oss-specific gap, not fixed by PR #255. |
us.amazon.nova-lite-v1:0 |
Not re-tested after the patch; previously ignored the schema's object wrapper (returned a bare list instead of {"ingredients": [...]}) |
Note: AWS Bedrock does not host OpenAI's proprietary models (GPT-4o,
GPT-5, etc.) in any region β verified directly against the Bedrock
list-foundation-models API in 5 regions. Only OpenAI's open-weight
gpt-oss line is available on Bedrock; the closed GPT models remain
exclusive to OpenAI's own API / Azure OpenAI.
Custom prompt overrides also live in deployments/mealie/prompts/recipes/
and are mounted into the pod via a Kustomize configMapGenerator. These
predate the gateway patch above (added when prompt wording was the only
lever available) and are kept as a second layer of protection β cheap
insurance in case a future model or gateway regression stops enforcing
the schema again:
- ConfigMap
mealie-custom-promptsmounted at/app/custom-prompts/recipes/(Mealie expects this exact subdirectory β prompt names likerecipes.parse-recipe-ingredientsmap to<custom_dir>/recipes/parse-recipe-ingredients.txt). -
OPENAI_CUSTOM_PROMPT_DIR=/app/custom-promptsenv var on the Mealie deployment tells it to check there first, falling back to Mealie's built-in prompts if a file is missing. - Each of the 4 prompt files (ingredient parsing, image-to-recipe,
video-to-recipe, URL-scrape) opens and closes with an explicit,
repeated instruction: "first character of your response must be
{, no code fences, no<think>tags." - Kustomize's
configMapGeneratorcontent-hashes the ConfigMap name, so editing a prompt file and re-deploying automatically rolls the Mealie pod to pick up the change β no manual restart needed.
- Add a recipe. Either + Create β Import from URL (Mealie tries its normal scraper first; if the page lacks clean recipe markup, it automatically falls back to the AI provider above) or add one by hand.
- Assign it to a date. Mealie β Meal Plan β click a date β choose an entry type (Breakfast/Lunch/Dinner/etc.) β search/select the recipe β Save.
-
It appears on the dashboard automatically. HA polls the
mealieintegration periodically; the matchingcalendar.mealie_<type>entity updates, and the "Today's Meals" card on the family dashboard reflects it without any manual step.
Mealie's scraper-selection order is hardcoded server-side
(RecipeScraperPackage always tries first; the AI scraper only runs if
that returns nothing at all) β there is no request-level way to force AI
for a page the plain scraper can technically read but parses badly (e.g.
duplicated/mislabeled instruction steps from pages that interleave
step-number labels with restated ingredient lists). Today, the only
workaround is manual: fetch the page, extract just the visible recipe
text (no HTML/JSON-LD for the plain scraper to grab), and POST it to
/api/recipes/create/html-or-json directly via the API.
Two real, tested, unmerged upstream PRs fix this properly by adding actual UI controls:
- #7618 β a "Force OpenAI Scraper" checkbox on the Import-from-URL page
- #7825 β a dedicated "Create from Text" page
Both are cherry-picked into a custom image
(ghcr.io/seadogger-tech/mealie:v3.21.0-ai-import) built by
core/.github/workflows/mealie-rebuild.yaml, following the same
adopted-unmerged-PR pattern established for the Bedrock gateway β see
ADR-011.
Unlike the gateway, this pipeline is pinned to a specific upstream
release tag (v3.21.0), not the active mealie-next development
branch β Mealie holds real household data, so unattended tracking of
in-progress upstream commits would be inappropriate here. The pipeline
only rebuilds when the cherry-picked PRs change or UPSTREAM_TAG is
bumped manually.
OpenWebUI: Website: https://open-webui.com
- Self-hosted web UI for local/remote LLMs.
- Works offline; supports multiple LLM runners; chat, RAG, and extensions.
- Installable as a PWA for a smooth mobile experience.
AWS Bedrock Access Gateway: Website/Repo: https://github.com/aws-samples/bedrock-access-gateway
- Open-source gateway that exposes OpenAI-compatible REST APIs for Amazon Bedrock.
- Lets existing OpenAI SDKs/tools (e.g., OpenAI Python/JS, LangChain-OpenAI, AutoGen) work with Bedrock without code changes.
- Supports SSE streaming, Chat Completions, Embeddings, Tool/function calling, Multimodal, Models API, Cross-region inference, and Application Inference Profiles.
- Easy deployment: 1-click CloudFormation to ALB + Lambda or ALB + Fargate; also runs locally or in containers/Kubernetes.
- Regions & models: follows Bedrock-supported regions; use the Models API to discover availability.
- Automated Upstream Tracking: GitHub Actions workflow rebuilds image every 6 hours from aws-samples/bedrock-access-gateway
-
Multi-arch Support: Built for
linux/amd64andlinux/arm64(Raspberry Pi 5 compatible) -
Image Registry:
ghcr.io/seadogger-tech/aws-bedrock-gateway:latest -
Access: MetalLB LoadBalancer at
192.168.1.242:6880 -
Integration: Confirmed actively connected to OpenWebUI's chat
interface β verified via OpenWebUI's own stored connection config
(
openai.api_base_urlsin itswebui.db), pointed athttp://192.168.1.242:6880/api/v1with the same staticbedrockkey.
-
AWS Bedrock Model Access:
- Enable models in AWS Bedrock console for the deployment region (us-west-2)
- Cross-region inference profiles (
us.*prefix) require separate access grants - Example:
us.anthropic.claude-opus-4-1-20250805-v1:0requires both base model and inference profile access
-
IAM Permissions:
- User must have
AmazonBedrockFullAccesspolicy or equivalent - Credentials stored as Kubernetes secret in
bedrock-gatewaynamespace - Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION
- User must have
-
Gateway Authentication:
-
API_KEY:
bedrock(used by clients to authenticate to the gateway) - Clients must include header:
Authorization: Bearer bedrock - This is separate from AWS credentials (gateway β Bedrock authentication)
-
API_KEY:
-
Container Configuration:
- Container listens on port 8080 (upstream default as of 2025)
- Service exposes externally on port 6880 via MetalLB
-
Known Issues & Solutions:
- Model returns AccessDeniedException: Enable the specific model in AWS Bedrock console for us-west-2
-
Requests hang without response: Restart deployment to pull latest gateway image (
kubectl rollout restart deployment/bedrock-access-gateway -n bedrock-gateway) - Parameter validation errors: Upstream fixes auto-deployed (e.g., Claude Sonnet 4.5 temperature/top_p conflict fixed in latest)
-
Pod CrashLoopBackOff "API Key not configured": Ensure
API_KEYenv var is set (upstream removed default in Feb 2025) -
gpt-oss models leak
<think>...</think>reasoning tags intocontent: confirmed via direct testing (reasoning_contentfield alwaysnullforopenai.gpt-oss-20b-1:0/120b, regardless ofreasoning_effort), even withresponse_formatstructured output enabled (see below). The gateway's reasoning-separation feature only covers Claude and DeepSeek R1. Breaks any client (like Mealie) expecting strict JSON-only output. Use Claude instead until the gateway adds gpt-oss support. -
response_format/json_schemawas silently ignored (fixed as of 2026-07-25): upstream never implemented OpenAI's structured-output contract β accepted the field, never enforced it, so every model free-texted its own JSON style (Claude sometimes fenced in```json, breaking strict clients like Mealie). Tracked upstream as #162/#255, fixed here by cherry-picking the unmerged PR #255 inupstream-rebuild.yamlβ see Mealie Β§ Structured-output enforcement for full details.
upstream-rebuild.yaml cherry-picks 6 open, unmerged PRs on top of a
fresh checkout of aws-samples/main on every scheduled rebuild β see
ADR-011
for the full rationale and the reusable pattern this establishes.
Verified working end-to-end against the live deployed image
(2026-07-25):
| PR | Fixes | Verified via |
|---|---|---|
| #255 |
response_format/json_schema β real Bedrock structured output |
Direct curl test + 5/5 clean Mealie ingredient-parse calls |
| #246 | Drop orphan tool_use/tool_result pairs (OpenWebUI history-rewrite scenario) |
grep -n _sanitize_tool_pairs /app/api/models/bedrock.py on the live pod |
| #247 | Non-negative tool-call indexes in streaming | Present in cherry-picked commit history |
| #239 | Replayed tool blocks without a tools array (conversation compaction) |
Present in cherry-picked commit history |
| #198 | Return tool_calls instead of dropping them on max_tokens truncation |
Present in cherry-picked commit history |
| #249 | Claude Opus 4.7 adaptive-thinking request format |
grep -n ADAPTIVE_THINKING_MODELS /app/api/models/bedrock.py on the live pod |
#246/#247/#239/#198 protect OpenWebUI's tool/function-calling
path, which isn't enabled today (0 Tools/Functions configured in its
webui.db as of this writing) but would hit these exact bugs the
moment it is. #249 is a live gap regardless β Opus 4.7 is already
selectable in OpenWebUI's model dropdown.
Two of the six conflict with each other in small, known ways (both
#255 and #239 add a branch in _parse_request; #198 and #239
touch the same log line). Resolved by
.github/scripts/resolve_bedrock_gateway_conflicts.py, not inline in
the workflow YAML β see the script's docstring for exactly what each
resolver does and why.

NextCloud: Website: https://nextcloud.com
- Open-source, self-hosted content-collaboration and file-sync platform.
- Files & sharing, Office (collaborative editing), Calendar, Contacts, Talk (chat/video).
- Desktop & mobile clients; extensible via a large app ecosystem.
A tiny Python utility that pulls the HDHomeRun XMLTV guide and stores it locally.
It can be run with:
# Default (no target, saves to xmltv.xml in current directory)
./fetch_hdhomerun_guide.py \
--discover-url http://192.168.1.70/discover.json
# Save to a specific location
./fetch_hdhomerun_guide.py \
--discover-url http://192.168.1.70/discover.json \
--target /media/data/HomeMedia/files/Live_TV_Guide/xmltv.xmlThe script lives in seadogger-homelab-pro/core/useful_scripts/fetch_hdhomerun_guide.py and is documented in the wiki page HDHomeRun Guide Utility.
- Family road-trip planner β custom app (
deployments/ember-trail, Pro repo), a small Python state server behind Caddy, not a third-party project.
- Headless Signal messenger client (AsamK/signal-cli) exposed as an HTTP daemon, used by Hermes agents to send/receive Signal messages.
- Built via
core/deployments/signal-cli/Dockerfile, rebuilt automatically every time upstream cuts a release (core/.github/workflows/upstream-rebuild-signal-cli.yaml). - The daemon's own HTTP server rejects requests carrying a Kubernetes
Service
Hostheader (421), so an nginx sidecar rewrites theHostheader before proxying to it.
- Browser-accessible cluster shell:
ttyd serving a terminal with
kubectlpre-installed (copied in from thebitnami/kubectlimage at pod start), bound to aServiceAccountvia RBAC (deployments/terminal/rbac.yaml) rather than requiring a separate login.
JellyFin: Website: https://jellyfin.org
- Free, open-source, self-hosted media server.
- Lets you organize and stream movies, TV, music, and photos to many devices.
- Runs on Windows, Linux, macOS, Docker, and more.
- Web UI and apps; supports Live TV/DVR and DLNA.
- Hardware-accelerated transcoding via FFmpeg when available.
- 100% freeβno tracking and no premium tiers.
- 18-Setting-Up-n8n-Connections - N8N configuration guide
- 20-HDHomeRun-Guide - Jellyfin live TV setup
- 06-Storage-Rook-Ceph - Application storage backends
- 08-Security-and-Certificates - Application TLS certificates
Setup Guides: