Compare commits
34 Commits
9317a3f5e7
...
main
@@ -8,11 +8,100 @@ grep -E "set .port|server_name" /app/nginxproxy/data/nginx/proxy_host/*.conf \
|
||||
| sort -n \
|
||||
| column -t -N "PORT,TARGET,OTHER_TARGET" -W "OTHER_TARGET" -s ' ' -o ' | ';
|
||||
}
|
||||
alias ports='_ports'
|
||||
alias nginx='_ports'
|
||||
|
||||
# tmp rebuild for hugo development
|
||||
alias hugo-rebuild='rm -rf public/ && hugo server --appendPort=false --baseURL="/" --ignoreCache'
|
||||
alias hugo-rebuild='cd /srv/dev/hugo/wiki && ./preview.sh public'
|
||||
|
||||
# helpers for quickly editing aliases
|
||||
alias src='source ~/.bash_aliases'
|
||||
alias edit='vim /srv/configs/.bash_aliases'
|
||||
|
||||
# Docker Image Version Tag checker
|
||||
function _tagger() {
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: tag <image>:<tag>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Split image and tag safely
|
||||
local INPUT="$1"
|
||||
local REPO="${INPUT%%:*}"
|
||||
local TAG="${INPUT##*:}"
|
||||
|
||||
# If no tag provided, default to latest
|
||||
[ "$REPO" = "$TAG" ] && TAG="latest"
|
||||
|
||||
# Handle official library images
|
||||
local API_REPO="$REPO"
|
||||
[[ "$REPO" != *"/"* ]] && API_REPO="library/$REPO"
|
||||
|
||||
# 1. Grab the exact unique digest for the target tag
|
||||
local DIGEST
|
||||
DIGEST=$(curl -s "https://hub.docker.com/v2/repositories/${API_REPO}/tags/${TAG}" | jq -r '.digest // empty')
|
||||
|
||||
if [ -z "$DIGEST" ] || [ "$DIGEST" = "null" ]; then
|
||||
echo "Error: Image or tag not found on Docker Hub."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 2. Get the sibling tag sharing the same digest, skipping text labels
|
||||
local VERSION
|
||||
VERSION=$(curl -s "https://hub.docker.com/v2/repositories/${API_REPO}/tags/?page_size=100" \
|
||||
| jq -r --arg dig "$DIGEST" \
|
||||
'.results[] | select(.digest == $dig) | .name' \
|
||||
| grep -E -v 'latest|alpine|slim|debian|bullseye|bookworm|ubuntu|test|rc|nightly' \
|
||||
| head -n 1)
|
||||
|
||||
# 3. Output results
|
||||
if [ -n "$VERSION" ]; then
|
||||
echo "${REPO}:${VERSION}"
|
||||
else
|
||||
echo "${REPO}:${TAG}"
|
||||
fi
|
||||
}
|
||||
alias tag='_tagger'
|
||||
|
||||
# docker services
|
||||
function _docker-table() {
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "Error: Docker daemon is not running."
|
||||
return 1
|
||||
fi
|
||||
|
||||
(
|
||||
# 1. Adjusted Header to match your 3 requested columns
|
||||
printf "NAME|STATUS|EXPOSED PORTS\n"
|
||||
|
||||
# 2. Match the format string to exactly what you are reading
|
||||
docker ps -a --format "{{.Names}}|{{.Status}}|{{.Ports}}" | while IFS='|' read -r name status ports; do
|
||||
if [ -n "$ports" ]; then
|
||||
# Split ports strictly by comma
|
||||
IFS=',' read -r -a port_array <<< "$ports"
|
||||
cleaned_ports=""
|
||||
|
||||
for p in "${port_array[@]}"; do
|
||||
# Trim any leading/trailing spaces from the array element
|
||||
p=$(echo "$p" | xargs)
|
||||
|
||||
# Strip everything up to and including '->' if it exists
|
||||
cleaned_p="${p##*->}"
|
||||
|
||||
# Prevent duplicate listings (e.g. if IPv4 and IPv6 both map to 8080/tcp)
|
||||
if [[ ", $cleaned_ports, " != *", $cleaned_p, "* ]]; then
|
||||
if [ -z "$cleaned_ports" ]; then
|
||||
cleaned_ports="$cleaned_p"
|
||||
else
|
||||
cleaned_ports="$cleaned_ports, $cleaned_p"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
ports="$cleaned_ports"
|
||||
fi
|
||||
|
||||
# Print the 3 columns cleanly
|
||||
printf "%s|%s|%s\n" "$name" "$status" "$ports"
|
||||
done
|
||||
) | column -t -s '|'
|
||||
}
|
||||
alias docker-services='_docker-table'
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,3 +40,6 @@ dump.rdb
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Desktop Services Store
|
||||
.DS_Store
|
||||
@@ -14,6 +14,11 @@ Specialized configuration files (JSON) for [Pterodactyl Panel](https://pterodact
|
||||
|
||||
For more details on how to import and use these, see the [Eggs README](./eggs/README.md).
|
||||
|
||||
### Configuration Files
|
||||
|
||||
This repository also tracks global configuration files that maintain system state and environment consistency:
|
||||
- `.bash_aliases`: Tracks persistent command-line aliases for custom environment workflows.
|
||||
|
||||
## Future Scope
|
||||
|
||||
This repository is intended to expand into a comprehensive hub for all configuration types beyond Docker and Pterodactyl, including:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## HARD MANDATE: PLAN BEFORE ACTION
|
||||
- **No Unapproved State Changes:** You MUST NOT execute any tool that modifies the filesystem, runs shell commands (excluding read-only discovery like `ls` or `grep`), or changes system state without first presenting a detailed plan and receiving an explicit "Directive" (e.g., "proceed") from the user. This rule is absolute. REQUIRED!
|
||||
|
||||
## Gemini Added Memories
|
||||
## Added Memories
|
||||
- When working on the Hugo wiki, docs, or Caddy routing, immediately run the docs_architecture skill to understand the decoupled architecture, deployment logic, and known Docker/Caddy networking quirks.
|
||||
- When writing Hugo/Blowfish Markdown front matter, always include 'author', 'date' (ISO-8601), 'lastmod' (YYYY-MM-DD), and 'tags' (list) at the bottom of the metadata block.
|
||||
- Never perform manual builds or deployments of the Hugo sites to bypass the CI/CD pipeline. Always rely on the Gitea Actions workflows for deployment.
|
||||
|
||||
BIN
ai/skills/.DS_Store
vendored
BIN
ai/skills/.DS_Store
vendored
Binary file not shown.
@@ -1,33 +1,42 @@
|
||||
---
|
||||
name: docs_architecture
|
||||
description: Exact technical architecture, routing, and deployment workflow for WompMacho's decoupled Hugo/Gitea/Nginx Wiki infrastructure.
|
||||
---
|
||||
|
||||
# Docs Architecture Skill
|
||||
|
||||
This skill documents the exact technical architecture, routing, and deployment workflow for WompMacho's decoupled Hugo/Gitea/Caddy Wiki infrastructure.
|
||||
This skill documents the exact technical architecture, routing, and deployment workflow for WompMacho's decoupled Hugo/Gitea/Nginx Wiki infrastructure.
|
||||
|
||||
## 🏗️ 1. Core Architecture (Decoupled Repositories)
|
||||
|
||||
The system is split into three independent Git repositories hosted on `git.wompmacho.com`:
|
||||
|
||||
1. **Framework (`/srv/dev/hugo/wiki`)**: Repository `hugo-framework`
|
||||
* **Purpose**: The central Hugo engine. Contains the Blowfish theme as a submodule (`themes/blowfish`) and the master `config/_default/params.toml`.
|
||||
* **Rule**: NEVER place content (`.md` files) here. NEVER place environment-specific overrides (like hardcoded `editURLs` or top-level menus) in the global `params.toml`.
|
||||
* **Purpose**: The central Hugo engine. The Blowfish theme is fully **decoupled/vendored** directly into the project's root folders (`layouts/`, `assets/`, `static/`, `data/`, `i18n/`, `archetypes/`). No theme submodules are used, protecting the site from upstream updates breaking the build.
|
||||
* **Rule**: NEVER place content (`.md` files) here. NEVER place environment-specific overrides (like hardcoded `editURLs` or top-level menus) in the global config.
|
||||
|
||||
2. **Public Wiki (`/srv/docs/public`)**: Repository `docs-public`
|
||||
* **Purpose**: Public-facing markdown notes, assets (`/static`), and its own top-bar menu configuration (`config/_default/menus.en.toml`).
|
||||
* **Deployment Target**: `/srv/caddy/sites/wiki`
|
||||
* **Deployment Target**: `/srv/www/docs-public`
|
||||
* **Live URL**: `https://wiki.wompmacho.com` (proxied by NPM).
|
||||
|
||||
3. **Private Wiki (`/srv/docs/private`)**: Repository `docs-private`
|
||||
* **Purpose**: Personal, private markdown notes, assets (`/static`), and its own top-bar menu configuration (`config/_default/menus.en.toml`).
|
||||
* **Deployment Target**: `/srv/caddy/sites/private-wiki`
|
||||
* **Deployment Target**: `/srv/www/docs-private`
|
||||
* **Live URL**: `http://10.0.0.190:9897` (or `http://private` via NPM).
|
||||
|
||||
## 💻 2. Local Development ("Live Link")
|
||||
## 💻 2. Local Development ("Live Link" Automation)
|
||||
|
||||
When working on content, the user uses **Code-Server**. To see real-time updates without deploying, the `hugo-framework` repository uses symbolic links.
|
||||
When working on content, the user uses **Code-Server**. To see real-time updates without deploying, the framework repository uses automated symlinking and preview scripts.
|
||||
|
||||
* `content -> /srv/docs/public`
|
||||
* `static -> /srv/docs/public/static`
|
||||
|
||||
**Command to start local preview**: `cd /srv/dev/hugo/wiki && hugo server --bind 0.0.0.0 --appendPort=false --baseURL="/"`
|
||||
* **`preview.sh [public|private]`**:
|
||||
* Creates symlinks: `content -> /srv/docs/[public|private]` and `static -> /srv/docs/[public|private]/static`.
|
||||
* Overwrites or symlinks specific config overrides (like `menus.en.toml`).
|
||||
* Executes `run-dev.sh` to start the local preview server.
|
||||
* Traps exits (Ctrl+C, normal exit) to cleanly restore standard framework defaults and remove preview symlinks automatically.
|
||||
* **`run-dev.sh`**:
|
||||
* Launches the server bound to `0.0.0.0:1313` with code-server proxy parameters.
|
||||
* Passes `--renderToMemory` to prevent local watch loops and file write latency.
|
||||
|
||||
## 🚀 3. CI/CD Pipeline (Gitea Actions)
|
||||
|
||||
@@ -35,7 +44,7 @@ Both `docs-public` and `docs-private` have a `.gitea/workflows/deploy.yaml` acti
|
||||
|
||||
### The Build Process
|
||||
1. **Checkout Content**: The runner checks out the markdown content.
|
||||
2. **Clone Framework**: The runner natively clones the `hugo-framework` (including submodules) using `git clone --recurse-submodules https://git.wompmacho.com/wompmacho/hugo-framework.git`.
|
||||
2. **Clone Framework**: The runner clones the central `hugo-framework` repository directly. Because the Blowfish theme is fully decoupled, there is no need for submodule initialization.
|
||||
3. **Inject Content**: Markdown and static files are copied into the framework's workspace.
|
||||
4. **Inject Dynamic Menus**: The `config/_default/menus.en.toml` is injected, automatically overriding the framework's top navigation bar for that specific site.
|
||||
5. **Inject Edit Links (CRITICAL RULE)**: The "Edit this page" link on the bottom of posts MUST NOT be configured via a `params.toml` override, as Hugo replaces the entire file instead of merging it, destroying the Blowfish styling.
|
||||
@@ -45,17 +54,18 @@ Both `docs-public` and `docs-private` have a `.gitea/workflows/deploy.yaml` acti
|
||||
|
||||
## 🌐 4. Web Server Routing (Nginx)
|
||||
|
||||
The system relies on two extremely lightweight `nginx:alpine` containers defined in `gitea/docker-compose.yaml` to serve static files. They act as "dumb" file servers behind Nginx Proxy Manager (NPM), bypassing the complex Host header validation and 0-byte security issues inherent to Caddy.
|
||||
The system relies on two extremely lightweight `nginx:alpine` containers defined in `gitea/docker-compose.yaml` to serve static files. They act as file servers behind Nginx Proxy Manager (NPM).
|
||||
|
||||
1. **Public (`public_wiki` - Port 9896)**:
|
||||
* Mounts `/srv/caddy/sites/wiki` as read-only.
|
||||
* Mounts `/srv/www/docs-public` as read-only.
|
||||
* NPM routes `wiki.wompmacho.com` to Docker Host IP on port `9896`.
|
||||
2. **Private (`private_wiki` - Port 9897)**:
|
||||
* Mounts `/srv/caddy/sites/private-wiki` as read-only.
|
||||
* Mounts `/srv/www/docs-private` as read-only.
|
||||
* NPM routes `http://private` to Docker Host IP on port `9897`.
|
||||
|
||||
## 🛠️ 5. Known Quirks and Formatting Rules
|
||||
|
||||
* **Submodules**: Always use `git submodule add -b main <url>` and ensure the commit pointer is tracked. Do not copy theme files physically.
|
||||
* **Decoupled Theme**: The Blowfish theme is committed directly in the main codebase. Do not use Git submodules to track the theme.
|
||||
* **Blowfish Homepage**: If the homepage is blank, it means there are no recent posts. By default, Blowfish's `home` layout only lists files with `type: "article"` or `type: "posts"`. For generic folders, set `type: "page"` and `layout: "list"` in `_index.md`.
|
||||
* **Frontmatter**: Blowfish expects standard Hugo frontmatter (`date:` as a string, `authors: ["Name"]` as an array). MkDocs-style nested YAML is incompatible and will crash the build.
|
||||
* **Frontmatter**: Blowfish expects standard Hugo frontmatter (`date:` as a string, `authors: ["Name"]` as an array). MkDocs-style nested YAML is incompatible and will crash the build.
|
||||
* **Troubleshooting**: See `references/troubleshooting.md` for handling theme updates, build loops, and TOC configuration.
|
||||
24
ai/skills/docs_architecture/references/troubleshooting.md
Normal file
24
ai/skills/docs_architecture/references/troubleshooting.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Hugo/Blowfish Troubleshooting & Pitfalls
|
||||
|
||||
## 1. Infinite Rebuild Loops
|
||||
If Hugo triggers a rebuild loop:
|
||||
* **Cause**: Hugo watches the `resources/` directory, and the generation of cached/busted assets (like `admonitions.scss_...json`) triggers a new rebuild event.
|
||||
* **Fix**: Add the following to `config/_default/hugo.toml`:
|
||||
```toml
|
||||
[watch]
|
||||
ignore = ["resources/**"]
|
||||
```
|
||||
* **Tool Usage**: If the loop persists, start the server with polling to reduce sensitivity:
|
||||
`hugo server --poll 1s`
|
||||
|
||||
## 2. Table of Contents (TOC) Issues
|
||||
Blowfish manages TOC natively via `params.toml`. Avoid using manual `[TOC]` or `{{< toc >}}` in your Markdown unless explicitly required by a specific layout.
|
||||
* **Standard Setup**: Ensure `showTableOfContents = true` is set in the `[article]` and `[list]` sections of your `config/_default/params.toml`.
|
||||
* **Custom Layouts**: If TOC is rendering in the wrong place, it is likely due to an overridden `layouts/_default/single.html`. Revert to the native theme structure (`container` and `prose` classes) so the Blowfish partials can handle the placement.
|
||||
* **TOC ScrollSpy Highlighting**: If the TOC fails to highlight active headings when scrolling, verify that `smartTOC = true` is enabled in `params.toml`. If it is nested inside the `[article]` section, ensure that the `layouts/partials/toc.html` checks:
|
||||
`{{ if or .Site.Params.smartTOC .Site.Params.article.smartTOC }}`
|
||||
to correctly resolve the nested parameter.
|
||||
|
||||
## 3. Theme Updates & Customization
|
||||
* **Version mismatch**: When Hugo updates, theme templates often break (e.g., `can't evaluate field Locale`).
|
||||
* **Decoupled Theme Installation**: The theme is fully decoupled/vendored in the root folders. If you need to update Blowfish, you will copy/vendor the new templates into `layouts/` and assets into `assets/`, but you must review custom changes to templates such as `layouts/_default/single.html` and `layouts/partials/toc.html` to make sure custom styles and scripts (like multiple TOC elements or active highlight overrides) are preserved.
|
||||
42
ai/skills/frontend-design/SKILL.md
Normal file
42
ai/skills/frontend-design/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
47
ai/skills/grill-with-docs/ADR-FORMAT.md
Normal file
47
ai/skills/grill-with-docs/ADR-FORMAT.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# ADR Format
|
||||
|
||||
ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
|
||||
|
||||
Create the `docs/adr/` directory lazily — only when the first ADR is needed.
|
||||
|
||||
## Template
|
||||
|
||||
```md
|
||||
# {Short title of the decision}
|
||||
|
||||
{1-3 sentences: what's the context, what did we decide, and why.}
|
||||
```
|
||||
|
||||
That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.
|
||||
|
||||
## Optional sections
|
||||
|
||||
Only include these when they add genuine value. Most ADRs won't need them.
|
||||
|
||||
- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
|
||||
- **Considered Options** — only when the rejected alternatives are worth remembering
|
||||
- **Consequences** — only when non-obvious downstream effects need to be called out
|
||||
|
||||
## Numbering
|
||||
|
||||
Scan `docs/adr/` for the highest existing number and increment by one.
|
||||
|
||||
## When to offer an ADR
|
||||
|
||||
All three of these must be true:
|
||||
|
||||
1. **Hard to reverse** — the cost of changing your mind later is meaningful
|
||||
2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
|
||||
3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
|
||||
|
||||
If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
|
||||
|
||||
### What qualifies
|
||||
|
||||
- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
|
||||
- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
|
||||
- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
|
||||
- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
|
||||
- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
|
||||
- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
|
||||
- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.
|
||||
63
ai/skills/grill-with-docs/CONTEXT-FORMAT.md
Normal file
63
ai/skills/grill-with-docs/CONTEXT-FORMAT.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# CONTEXT.md Format
|
||||
|
||||
## Structure
|
||||
|
||||
```md
|
||||
# {Context Name}
|
||||
|
||||
{One or two sentence description of what this context is and why it exists.}
|
||||
|
||||
## Language
|
||||
|
||||
**Order**:
|
||||
{A one or two sentence description of the term}
|
||||
_Avoid_: Purchase, transaction
|
||||
|
||||
**Invoice**:
|
||||
A request for payment sent to a customer after delivery.
|
||||
_Avoid_: Bill, payment request
|
||||
|
||||
**Customer**:
|
||||
A person or organization that places orders.
|
||||
_Avoid_: Client, buyer, account
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
|
||||
- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
|
||||
- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
|
||||
- **Show relationships.** Use bold term names and express cardinality where obvious.
|
||||
- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
|
||||
- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
|
||||
- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.
|
||||
|
||||
## Single vs multi-context repos
|
||||
|
||||
**Single context (most repos):** One `CONTEXT.md` at the repo root.
|
||||
|
||||
**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
|
||||
|
||||
```md
|
||||
# Context Map
|
||||
|
||||
## Contexts
|
||||
|
||||
- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
|
||||
- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
|
||||
- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping
|
||||
|
||||
## Relationships
|
||||
|
||||
- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
|
||||
- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
|
||||
- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
|
||||
```
|
||||
|
||||
The skill infers which structure applies:
|
||||
|
||||
- If `CONTEXT-MAP.md` exists, read it to find contexts
|
||||
- If only a root `CONTEXT.md` exists, single context
|
||||
- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
|
||||
|
||||
When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
|
||||
88
ai/skills/grill-with-docs/SKILL.md
Normal file
88
ai/skills/grill-with-docs/SKILL.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: grill-with-docs
|
||||
description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.
|
||||
---
|
||||
|
||||
<what-to-do>
|
||||
|
||||
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
|
||||
|
||||
Ask the questions one at a time, waiting for feedback on each question before continuing.
|
||||
|
||||
If a question can be answered by exploring the codebase, explore the codebase instead.
|
||||
|
||||
</what-to-do>
|
||||
|
||||
<supporting-info>
|
||||
|
||||
## Domain awareness
|
||||
|
||||
During codebase exploration, also look for existing documentation:
|
||||
|
||||
### File structure
|
||||
|
||||
Most repos have a single context:
|
||||
|
||||
```
|
||||
/
|
||||
├── CONTEXT.md
|
||||
├── docs/
|
||||
│ └── adr/
|
||||
│ ├── 0001-event-sourced-orders.md
|
||||
│ └── 0002-postgres-for-write-model.md
|
||||
└── src/
|
||||
```
|
||||
|
||||
If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
|
||||
|
||||
```
|
||||
/
|
||||
├── CONTEXT-MAP.md
|
||||
├── docs/
|
||||
│ └── adr/ ← system-wide decisions
|
||||
├── src/
|
||||
│ ├── ordering/
|
||||
│ │ ├── CONTEXT.md
|
||||
│ │ └── docs/adr/ ← context-specific decisions
|
||||
│ └── billing/
|
||||
│ ├── CONTEXT.md
|
||||
│ └── docs/adr/
|
||||
```
|
||||
|
||||
Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
|
||||
|
||||
## During the session
|
||||
|
||||
### Challenge against the glossary
|
||||
|
||||
When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"
|
||||
|
||||
### Sharpen fuzzy language
|
||||
|
||||
When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."
|
||||
|
||||
### Discuss concrete scenarios
|
||||
|
||||
When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
|
||||
|
||||
### Cross-reference with code
|
||||
|
||||
When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"
|
||||
|
||||
### Update CONTEXT.md inline
|
||||
|
||||
When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).
|
||||
|
||||
`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
|
||||
|
||||
### Offer ADRs sparingly
|
||||
|
||||
Only offer to create an ADR when all three are true:
|
||||
|
||||
1. **Hard to reverse** — the cost of changing your mind later is meaningful
|
||||
2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
|
||||
3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
|
||||
|
||||
If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).
|
||||
|
||||
</supporting-info>
|
||||
146
ai/skills/homelab-infrastructure-review/SKILL.md
Normal file
146
ai/skills/homelab-infrastructure-review/SKILL.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
name: homelab-infrastructure-review
|
||||
description: >
|
||||
Performs a 3-way infrastructure reconciliation audit: homelab_Infra.md
|
||||
documentation (including Nginx Proxy Route mappings) vs. live Docker container
|
||||
states vs. homelable MCP canvas topology. Also audits the Homepage Dashboard
|
||||
services configuration to ensure newly proxied services are accessible.
|
||||
Triggers ONLY on explicit user command. No automation or cron.
|
||||
version: 1.4.0
|
||||
author: wompmacho
|
||||
date: 2026-05-30
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [homelab, infrastructure, audit, reconciliation, mcp, docker, nginx, homepage]
|
||||
related_skills: [docker-workflows, workflow-management]
|
||||
---
|
||||
|
||||
# Homelab Infrastructure Review
|
||||
|
||||
This skill has been refactored into an intelligent, context-aware auditing engine. It uses a YAML configuration (`scripts/audit_config.yaml`) to understand architectural conventions (aliases, ignored containers) and employs a robust, incremental development workflow. **Always test changes in small, atomic steps** (`syntax check -> run`) to avoid complex debugging cycles. The core logic now uses a Python helper (`scripts/load_config.py`) for configuration and `docker inspect` for accurate health checks.
|
||||
|
||||
## 1. Core Objectives
|
||||
|
||||
This skill provides a highly-disciplined auditing workflow that:
|
||||
1. **Identifies all live services** on the host.
|
||||
2. **Identifies all active proxies and domains** for services by parsing Nginx Proxy Manager config files.
|
||||
3. **Cross-references** them against the source of truth documentation (`homelab_Infra.md`).
|
||||
4. **Cross-references** them against the `homelable-mcp` visual canvas topology.
|
||||
5. **Audits the Homepage Dashboard services configuration** (`/srv/configs/docker_compose/homepage/config/services.yaml`) to ensure all proxied services are accessible.
|
||||
6. **Generates exact proposed changes** to:
|
||||
- The markdown documentation (`homelab_Infra.md`)
|
||||
- The `homelable-mcp` visual canvas nodes and edges
|
||||
- The Homepage Dashboard services config (`services.yaml`), with services correctly split into their relevant hosts/categories.
|
||||
|
||||
---
|
||||
|
||||
## 2. Trigger Conditions
|
||||
|
||||
This skill is invoked **only on explicit, manual user command** — never automatically or periodically.
|
||||
Acceptable trigger phrases: "review homelab", "infrastructure audit", "reconcile homelab", "homelab review", "check infrastructure".
|
||||
|
||||
No cron, no automated polling, and no silent background execution.
|
||||
|
||||
---
|
||||
|
||||
## 3. Source Inventory and Boundary Map
|
||||
|
||||
| Source Component | Path / Port / Endpoint | Permission Boundaries |
|
||||
|---|---|---|
|
||||
| **Infra Document** | `/srv/docs/public/projects/homelab/homelab_Infra.md` | Read: ✅ Write: ❌ (requires explicit user confirmation, never run git commands without auth) |
|
||||
| **Live Docker State** | Host session via `docker ps` | Read: ✅ Write: ❌ (no automated container lifecycle modifications allowed) |
|
||||
| **Nginx Proxy Configs** | `/app/nginxproxy/data/nginx/proxy_host/*.conf` | Read: ✅ Write: ❌ (read-only proxy blocks mapping) |
|
||||
| **Homelable Backend API** | `http://localhost:9445/api/v1` | Read: ✅ Write: ❌ (node/edge canvas edits require explicit confirmation) |
|
||||
| **Homepage Config** | `/srv/configs/docker_compose/homepage/config/services.yaml` | Read: ✅ Write: ❌ (requires explicit user confirmation) |
|
||||
| **Homepage Layout** | `/srv/configs/docker_compose/homepage/config/settings.yaml` | Read: ✅ Write: ❌ (requires explicit user confirmation) |
|
||||
| **Workspace Dir** | `/srv/configs/ai/` | Read: ✅ Write: ✅ (agent state and proposed drafts) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Reconciliation Logic & Execution Steps
|
||||
|
||||
### Step 1: Pre-Flight Safety Checks
|
||||
Run **in order** before beginning any audit cycle. If any check fails, **simply inform the user directly in the session** and abort (do NOT log failures to a local `errors.log` file):
|
||||
- **Lock Check:** Verify no concurrent audit process holds `/srv/configs/ai/.audit.lock`.
|
||||
- **Backend Port Check:** Verify port `9445` is actively listening.
|
||||
- **Backend API Check:** Verify backend returns `HTTP 200` on `/api/v1/health` using the parsed `MCP_SERVICE_KEY` from `/srv/configs/docker_compose/homelabel/.env`.
|
||||
- **Source Files Check:** Verify both `/srv/configs/.bash_aliases` and `homelab_Infra.md` exist.
|
||||
|
||||
### Step 2: Nginx Proxy Parsing
|
||||
Parse every proxy configuration file `/app/nginxproxy/data/nginx/proxy_host/*.conf` for:
|
||||
- `server_name` (Proxy Domain name / Route)
|
||||
- `set $port` (Forwarded Container Port)
|
||||
|
||||
Keep an internal lookup dictionary of `Forwarded Port -> Proxy Domain` for comparison.
|
||||
|
||||
### Step 3: Homepage Dashboard Audit
|
||||
Read `/srv/configs/docker_compose/homepage/config/services.yaml` and compare the list of active proxy routes against the dashboard items. Propose adding newly proxied services so the user can navigate to them.
|
||||
|
||||
Homepage services must be strictly split into three distinct categories:
|
||||
1. **Physical Devices & Networking related** (e.g. Proxmox, TrueNAS, Router, Pi-Hole, Moonlight streaming)
|
||||
2. **Docker Host Containers** (e.g. nginx-proxy-manager, portainer, immich, vaultwarden, paperless-ngx, slopsmith-web)
|
||||
3. **TrueNAS Host Containers** (e.g. radarr, sonarr, readarr, bazarr, prowlarr, calibre)
|
||||
|
||||
### Step 4: Documentation Formatting Standardization
|
||||
|
||||
Both the **`## Docker services`** and the **`## Truenas Services`** markdown tables inside `homelab_Infra.md` must strictly maintain this exact 5-column layout:
|
||||
|
||||
`| Container Name | Mapped Ports | Access | Proxy Route / Domain | Description / Role |`
|
||||
|
||||
Always parse and print matching columns when proposing or applying documentation edits.
|
||||
|
||||
### Step 5: Proxmox LXC/VM Auditing
|
||||
Proxmox host virtualization represents nodes (containers/VMs) that communicate on the LAN subnet `10.0.0.0/16`.
|
||||
- Parse the Proxmox Node Table under `### Proxmox node (laptop-proxmox)` in `homelab_Infra.md`.
|
||||
- Retrieve all network canvas nodes from `http://localhost:9445/api/v1/nodes` matching types `vm`, `lxc`, or direct hosts.
|
||||
- Compare and verify that active virtualization nodes on your network (such as `invidious` on `10.0.0.217` or `pterodactyl` on `10.0.0.110`) exist inside the documentation table. Propose additions for any unregistered LXCs or VMs.
|
||||
|
||||
### Step 6: Homelable MCP Canvas Styling Standards
|
||||
|
||||
When registering new Docker containers on the visual canvas using the `POST` endpoint at `/api/v1/nodes`, you must strictly match the styling of other standard services (use `gitea_runner` as reference):
|
||||
|
||||
- `"type"` must be `"docker_container"`
|
||||
- `"hostname"` must be `"docker"`
|
||||
- `"ip"` must be `""` (empty string, as they reside behind parent container networking)
|
||||
- `"width"` must be `140.0`
|
||||
- `"height"` must be `50.0`
|
||||
- `"bottom_handles"` must be `1`
|
||||
- `"custom_colors"` must be `{"show_services": false}`
|
||||
- `"parent_id"` must be set to the Docker parent host node UUID (e.g. `ee61411a-018a-4471-8a76-bcd4a1f5579a`)
|
||||
|
||||
---
|
||||
|
||||
## 5. Safety and Guardrails
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 1. **No silent writes:** All modifications to `homelab_Infra.md`, the `homelable-mcp` canvas, or `services.yaml` must be presented as a clear diff block and receive explicit, manual user validation.
|
||||
> 2. **NO UNAUTHORIZED GIT COMMANDS:** Never execute any Git commands (such as `git add`, `git commit`, `git status`, `git diff`, etc.) without the user's explicit, per-action authorization.
|
||||
> 3. **Preserve Comments:** Do not remove, alter, or strip existing documentation comments or section text blocks.
|
||||
> 4. **No Errors Log File:** Do not append failures to a local `errors.log` file. Output error details directly to the active console stream to keep workspace logs clean.
|
||||
|
||||
---
|
||||
|
||||
## 6. Technical Pitfalls & Workarounds
|
||||
|
||||
> [!WARNING]
|
||||
> - **Windows CRLF line endings (`\r\n`):** The Homepage dashboard's `services.yaml` contains Windows CRLF terminators. Traditional bash/unix line-ending regex matches (such as `$`) will **silently fail** to match titles or categories. Always run `tr -d '\r'` to sanitize lines before attempting pattern matches.
|
||||
> - **Direct REST API bypass:** Querying `homelable-mcp` on `8001` via SSE requires an async JSON-RPC transport negotiation which is fragile and slow. Instead, bypass the MCP layer entirely and query the `homelable-backend` REST API directly on port `9445` at `/api/v1/nodes`. Pass the header `"X-MCP-Service-Key"` parsed from `/srv/configs/docker_compose/homelabel/.env` for instant, authenticated, and robust network nodes lookup.
|
||||
- **Active Port Verification (Stale Config Filter):** Nginx Proxy Manager configuration files on disk can be stale/leftover (e.g. Moonlight). To completely prevent reporting decommissioned configs, always verify that the port associated with any Nginx config is **actively listening on the host** (or mapped in active docker container port-mappings) before reporting it as an active proxy. If the port is inactive, ignore the proxy block completely.
|
||||
- **TrueNAS SCALE Node Socket Verification:** Rather than setting up fragile credentials or SSH logins, TrueNAS SCALE container port verification is achieved securely and in milliseconds using lightweight, non-destructive `/dev/tcp/10.0.0.2/[port]` network socket connection handshakes.
|
||||
- **Proxmox VE API Live Verification:** To verify running LXCs and VMs, query `https://10.0.0.142:8006/api2/json/cluster/resources?type=vm` directly. Load `HOMEPAGE_VAR_proxmox_password` dynamically from `/srv/configs/docker_compose/homepage/.env` at runtime and strip outer double quotes before passing to the Authorization headers. Never hardcode credentials in codebase or transcripts.
|
||||
- **Homepage Settings Layout Syncing:** When renaming category names in `/srv/configs/docker_compose/homepage/config/services.yaml` (such as `Arr` -> `Truenas Containers`, and `Containers` -> `Docker Containers`), the layout block names inside `/srv/configs/docker_compose/homepage/config/settings.yaml` under `layout:` **must be updated identically** or the cards will fail to render on the dashboard.
|
||||
- **Do NOT Invent Public Proxies:** Do not invent public proxy domains or automatically propose changing local/VPN hrefs (such as `http://radarr/` or `http://calibre/`) to public domains (like `radarr.wompmacho.com`) in homepage configurations. They must remain as local/VPN URLs exactly as currently configured to preserve network isolation and security.
|
||||
- **Multi-Alias Substring Matcher:** Nginx Proxy configs often specify multiple space-separated aliases (e.g. `server_name vaultwarden vaultwarden.wompmacho.com;`). When searching for these in `services.yaml` to detect missing cards, token-split the domains and verify if *any* alias matches as a substring within the homepage card `href` or name (case-insensitively). This prevents false-positives (such as flagging Vaultwarden or Gitea as missing).
|
||||
|
||||
---
|
||||
|
||||
## 7. Skill File Structure
|
||||
|
||||
All scripts and files associated with this skill are stored in this self-contained directory:
|
||||
- `SKILL.md` — Custom skill documentation and procedures.
|
||||
- `scripts/homelab-infrastructure-review.sh` — Bash shell auditing script.
|
||||
- `scripts/mcp_client.py` — Python client for direct database and API queries.
|
||||
- `errors.log` — Legacy log file (decommissioned per safety rules — errors print straight to console).
|
||||
- `.audit.lock` — Legitimate lock file preventing concurrent executions.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Homelable Backend API Reference
|
||||
|
||||
Direct REST API access to Pouzor/homelable visualizer backend on port 9445. Bypasses the complex and slow Server-Sent Events (SSE) handshake required on port 8001.
|
||||
|
||||
## Authentication
|
||||
The backend API requires an unauthenticated sub-app service key. Read this key dynamically from:
|
||||
`/srv/configs/docker_compose/homelabel/.env` -> `MCP_SERVICE_KEY`
|
||||
|
||||
Inject it into HTTP request headers:
|
||||
`X-MCP-Service-Key: <MCP_SERVICE_KEY>`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Health Check
|
||||
`GET http://localhost:9445/api/v1/health`
|
||||
Returns `{"status": "ok"}` on success (HTTP 200).
|
||||
|
||||
### 2. Retrieve Canvas Nodes
|
||||
`GET http://localhost:9445/api/v1/nodes`
|
||||
Returns a JSON array of active canvas nodes.
|
||||
|
||||
Example Node Object:
|
||||
```json
|
||||
{
|
||||
"type": "computer",
|
||||
"label": "Game Pc",
|
||||
"hostname": "DESKTOP-BRHGMFT",
|
||||
"ip": "10.0.0.109",
|
||||
"status": "online",
|
||||
"services": [
|
||||
{
|
||||
"port": 11434,
|
||||
"protocol": "tcp",
|
||||
"service_name": "Ollama"
|
||||
}
|
||||
],
|
||||
"id": "3fc16e75-9418-41b1-b4a2-d3859c9ce431"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Retrieve Canvas Edges
|
||||
`GET http://localhost:9445/api/v1/edges`
|
||||
Returns active connection edges between nodes.
|
||||
|
||||
### 4. Trigger Network Scan
|
||||
`POST http://localhost:9445/api/v1/scan/trigger`
|
||||
@@ -0,0 +1,68 @@
|
||||
# Homelable Backend Direct REST API Reference
|
||||
|
||||
Instead of negotiating a complex and slow Server-Sent Events (SSE) stream over port `8001` (which requires async JSON-RPC sessions and frequently times out), query the REST API on `homelable-backend` directly.
|
||||
|
||||
## Endpoint & Port
|
||||
- **Host Port:** `9445`
|
||||
- **Base URL:** `http://localhost:9445/api/v1`
|
||||
|
||||
## Authentication Header
|
||||
The backend is authenticated using a special service key header:
|
||||
```
|
||||
X-MCP-Service-Key: <MCP_SERVICE_KEY>
|
||||
```
|
||||
|
||||
### Loading Credentials Dynamically
|
||||
To keep keys safe and secure (without leaking them to the LLM context window), read the key dynamically from the local `.env` configuration file:
|
||||
- **Path:** `/srv/configs/docker_compose/homelabel/.env`
|
||||
- **Variable Name:** `MCP_SERVICE_KEY`
|
||||
|
||||
## Common Endpoints
|
||||
|
||||
### 1. Health Check
|
||||
- **Path:** `/api/v1/health`
|
||||
- **Method:** `GET`
|
||||
- **Response:** `{"status": "ok"}`
|
||||
|
||||
### 2. Retrieve Nodes (Canvas Devices)
|
||||
- **Path:** `/api/v1/nodes`
|
||||
- **Method:** `GET`
|
||||
- **Response:** Array of node objects:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "3fc16e75-9418-41b1-b4a2-d3859c9ce431",
|
||||
"type": "computer",
|
||||
"label": "Game Pc",
|
||||
"ip": "10.0.0.109",
|
||||
"status": "online",
|
||||
"services": [
|
||||
{
|
||||
"port": 11434,
|
||||
"protocol": "tcp",
|
||||
"service_name": "Ollama"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Python Integration Example
|
||||
|
||||
```python
|
||||
import os
|
||||
import requests
|
||||
|
||||
env_path = "/srv/configs/docker_compose/homelabel/.env"
|
||||
service_key = ""
|
||||
|
||||
if os.path.exists(env_path):
|
||||
with open(env_path, "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("MCP_SERVICE_KEY="):
|
||||
service_key = line.split("=", 1)[1].strip()
|
||||
|
||||
headers = {"X-MCP-Service-Key": service_key}
|
||||
resp = requests.get("http://localhost:9445/api/v1/nodes", headers=headers)
|
||||
nodes = resp.json()
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
# Teach the skill how to map running containers to their documented names
|
||||
# This resolves false positives for services running behind a single proxy container.
|
||||
service_aliases:
|
||||
nicotine: gluetun
|
||||
torrent: gluetun
|
||||
paperless-db: paperless-ngx
|
||||
paperless-broker: paperless-ngx
|
||||
guac-postgresql: guacamole
|
||||
gitea-db-1: gitea
|
||||
npm-sync: nginx-proxy-manager
|
||||
homelable-mcp: homelable
|
||||
homelable-backend: homelable
|
||||
homelable-frontend: homelable
|
||||
|
||||
# Teach the skill how to map container names to their visual canvas labels
|
||||
# This resolves false positives for services with user-friendly canvas names.
|
||||
canvas_aliases:
|
||||
docs-public: hugo (public)
|
||||
docs-private: hugo (private)
|
||||
|
||||
# A simple list of containers to ALWAYS ignore during the audit.
|
||||
# This is for transient, dev, or intentionally undocumented containers.
|
||||
ignore_containers:
|
||||
- openai
|
||||
- slopsmith-web
|
||||
- pihole-dns-shim # Managed by pihole LXC, not documented as a separate service.
|
||||
- immich_machine_learning # Sub-service of Immich
|
||||
- immich_postgres # Sub-service of Immich
|
||||
- immich_redis # Sub-service of Immich
|
||||
- guac-postgresql # Sub-service of Guacamole
|
||||
- nginx-proxy-manager # Infrastructure, not a user-facing service
|
||||
- npm-sync # Sub-service of Nginx Proxy Manager
|
||||
- cloudflare-ddns # Infrastructure, not a user-facing service
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# homelab-infrastructure-review.sh
|
||||
# Triggers a full, intelligent, context-aware reconciliation audit.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration & Logging ──────────────────────────────────────────────────
|
||||
AUDIT_CONFIG_FILE="/srv/configs/ai/skills/homelab-infrastructure-review/scripts/audit_config.yaml"
|
||||
INFRA_FILE="/srv/docs/public/projects/homelab/homelab_Infra.md"
|
||||
LOCK_FILE="/srv/configs/ai/.audit.lock"
|
||||
log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
||||
log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }
|
||||
|
||||
# ── Pre-flight & Lock ────────────────────────────────────────────────────────
|
||||
acquire_lock() {
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
local pid; pid=$(cat "$LOCK_FILE" 2>/dev/null)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
log_error "Audit already running (PID $pid). Aborting."; exit 1
|
||||
fi
|
||||
log_info "Stale lock file found. Cleaning up."; rm -f "$LOCK_FILE"
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"; trap 'rm -f "$LOCK_FILE"' EXIT
|
||||
}
|
||||
preflight_checks() {
|
||||
if [ ! -f "$AUDIT_CONFIG_FILE" ]; then log_error "audit_config.yaml not found. Aborting."; exit 1; fi
|
||||
log_info "All pre-flight checks passed."
|
||||
}
|
||||
|
||||
# ── Data Sources ─────────────────────────────────────────────────────────────
|
||||
get_infra_docs() {
|
||||
local in_services_section=false
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [[ "$line" =~ ^##[[:space:]]+(Docker|Truenas)[[:space:]]+services ]]; then
|
||||
in_services_section=true
|
||||
elif [[ "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+(Docker|Truenas)[[:space:]]+services ]]; then
|
||||
in_services_section=false
|
||||
fi
|
||||
if $in_services_section && [[ "$line" =~ ^\|[[:space:]]*\*\*([a-zA-Z0-9_-]+)\*\* ]]; then
|
||||
IFS='|' read -r _ name _ <<< "$line"; printf '%s\n' "$(echo "$name" | tr -d '*' | xargs)"
|
||||
fi
|
||||
done < "$INFRA_FILE"
|
||||
}
|
||||
get_live_docker_containers() {
|
||||
while IFS= read -r line; do
|
||||
local name; name=$(echo "$line" | jq -r '.Names')
|
||||
local health; health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}no_health_check{{end}}' "$name" 2>/dev/null || echo "error")
|
||||
local status;
|
||||
case "$health" in
|
||||
healthy) status="healthy";;
|
||||
unhealthy) status="UNHEALTHY";;
|
||||
*) status="no_health_check";;
|
||||
esac
|
||||
printf '%s|%s\n' "$name" "$status"
|
||||
done <<< "$(docker ps --format '{{json .}}')"
|
||||
}
|
||||
get_mcp_nodes() {
|
||||
local service_key=""; if [ -f "/srv/configs/docker_compose/homelabel/.env" ]; then service_key=$(grep -E "^MCP_SERVICE_KEY=" "/srv/configs/docker_compose/homelabel/.env" | cut -d'=' -f2-); fi
|
||||
curl -s -H "X-MCP-Service-Key: $service_key" "http://localhost:9445/api/v1/nodes" | jq -r '.[]? | .label // .name // "unknown"'
|
||||
}
|
||||
|
||||
# ── Reconciliation Engine ────────────────────────────────────────────────────
|
||||
if [ -f "$AUDIT_CONFIG_FILE" ]; then
|
||||
source <(python3 /srv/configs/ai/skills/homelab-infrastructure-review/scripts/load_config.py "$AUDIT_CONFIG_FILE")
|
||||
fi
|
||||
|
||||
reconcile() {
|
||||
local doc_services=(); while IFS= read -r name; do doc_services+=("$(echo "$name"|tr '[:upper:]' '[:lower:]')"); done < <(get_infra_docs)
|
||||
local live_services=(); local untracked_report=""; local unhealthy_report=""
|
||||
|
||||
while IFS='|' read -r name status; do
|
||||
local lower_name; lower_name=$(echo "$name" | tr '[:upper:]' '[:lower:]')
|
||||
live_services+=("$lower_name")
|
||||
if [[ " ${IGNORE_CONTAINERS} " == *" ${lower_name} "* ]]; then continue; fi
|
||||
|
||||
local found=false; for doc in "${doc_services[@]}"; do [ "$lower_name" = "$doc" ] && found=true && break; done
|
||||
local alias_var="SERVICE_ALIAS_${lower_name//-/_}"
|
||||
if ! $found && [[ -v $alias_var ]]; then
|
||||
local target="${!alias_var}"; for doc in "${doc_services[@]}"; do [ "$target" = "$doc" ] && found=true && break; done
|
||||
fi
|
||||
|
||||
if ! $found; then untracked_report+=" ⚠ **$name** is running but not documented.\n"; fi
|
||||
if [[ "$status" == "UNHEALTHY" ]]; then unhealthy_report+=" ✗ **$name** is **UNHEALTHY**.\n"; fi
|
||||
done < <(get_live_docker_containers)
|
||||
|
||||
local missing_report=""
|
||||
for doc in "${doc_services[@]}"; do
|
||||
if [ "$doc" = "dev" ] || [ "$doc" = "calibre" ]; then continue; fi # Exceptions
|
||||
local found=false; for live in "${live_services[@]}"; do [ "$doc" = "$live" ] && found=true && break; done
|
||||
if ! $found; then missing_report+=" ✗ **$doc** is documented but not running.\n"; fi
|
||||
done
|
||||
|
||||
local mcp_nodes=(); while IFS= read -r label; do mcp_nodes+=("$(echo "$label"|tr '[:upper:]' '[:lower:]'|tr ' _-' ' ')"); done < <(get_mcp_nodes)
|
||||
local canvas_report=""
|
||||
for name in "${live_services[@]}"; do
|
||||
if [[ " ${IGNORE_CONTAINERS} " == *" ${name} "* ]]; then continue; fi
|
||||
local found=false; for node in "${mcp_nodes[@]}"; do [[ "$node" == *"$name"* || "$name" == *"$node"* ]] && found=true && break; done
|
||||
local alias_var="CANVAS_ALIAS_${name//-/_}"
|
||||
if ! $found && [[ -v $alias_var ]]; then
|
||||
local target="${!alias_var}"; for node in "${mcp_nodes[@]}"; do [ "$target" = "$node" ] && found=true && break; done
|
||||
fi
|
||||
if ! $found; then canvas_report+=" ⚠ **$name** is running but not on the canvas.\n"; fi
|
||||
done
|
||||
|
||||
printf "## Homelab Audit Report\nGenerated: $(date)\n\n"
|
||||
printf "### Docker vs. Documentation\n"
|
||||
if [ -z "$untracked_report$missing_report$unhealthy_report" ]; then
|
||||
printf " ✅ All services are aligned and healthy.\n"
|
||||
else
|
||||
printf "%s%s%s" "$untracked_report" "$missing_report" "$unhealthy_report"
|
||||
fi
|
||||
printf "\n### Docker vs. Canvas\n"
|
||||
if [ -z "$canvas_report" ]; then printf " ✅ All services are mapped.\n"; else printf "%s" "$canvas_report"; fi
|
||||
}
|
||||
|
||||
# ── Main Execution ───────────────────────────────────────────────────────────
|
||||
main() {
|
||||
log_info "Starting Homelab Infrastructure Audit"
|
||||
acquire_lock
|
||||
preflight_checks
|
||||
log_info "Running reconciliation..."
|
||||
reconcile
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,30 @@
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
def main(config_path):
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
if 'service_aliases' in config and config['service_aliases']:
|
||||
for key, value in config['service_aliases'].items():
|
||||
print(f"export SERVICE_ALIAS_{key.replace('-', '_')}='{value}'")
|
||||
|
||||
if 'canvas_aliases' in config and config['canvas_aliases']:
|
||||
for key, value in config['canvas_aliases'].items():
|
||||
print(f"export CANVAS_ALIAS_{key.replace('-', '_')}='{value}'")
|
||||
|
||||
if 'ignore_containers' in config and config['ignore_containers']:
|
||||
ignore_list = " ".join(config['ignore_containers'])
|
||||
print(f"export IGNORE_CONTAINERS='{ignore_list}'")
|
||||
|
||||
except Exception as e:
|
||||
print(f"# Config load failed: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
main(sys.argv[1])
|
||||
else:
|
||||
print("# Usage: python load_config.py <path_to_config.yaml>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
115
ai/skills/homelab-infrastructure-review/scripts/mcp_client.py
Normal file
115
ai/skills/homelab-infrastructure-review/scripts/mcp_client.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP Homelable Integration Module.
|
||||
|
||||
Communicates directly with the homelable-backend on port 9445
|
||||
for canvas topology queries, node/edge retrieval, and scanning.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BACKEND_URL = "http://localhost:9445/api/v1"
|
||||
ENV_PATH = "/srv/configs/docker_compose/homelabel/.env"
|
||||
|
||||
|
||||
def _get_service_key() -> str:
|
||||
"""Read service key dynamically from .env to authenticate request securely."""
|
||||
try:
|
||||
if os.path.exists(ENV_PATH):
|
||||
with open(ENV_PATH, "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("MCP_SERVICE_KEY="):
|
||||
return line.split("=", 1)[1].strip()
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to read service key from {ENV_PATH}: {exc}")
|
||||
return ""
|
||||
|
||||
|
||||
def health_check(endpoint: str = BACKEND_URL) -> bool:
|
||||
"""Check if homelable-backend endpoint is reachable."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
# Socket check fallback
|
||||
import socket
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(3)
|
||||
s.connect(('localhost', 9445))
|
||||
s.close()
|
||||
logger.info("Backend socket check: port 9445 is open")
|
||||
return True
|
||||
except (ConnectionRefusedError, socket.timeout, OSError):
|
||||
logger.warning("Backend socket check failed: port 9445 unreachable")
|
||||
return False
|
||||
|
||||
key = _get_service_key()
|
||||
headers = {"X-MCP-Service-Key": key}
|
||||
try:
|
||||
resp = requests.get(f"{endpoint}/health", headers=headers, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
logger.info("Homelable-backend health check: OK")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"Homelable-backend health check failed with HTTP {resp.status_code}")
|
||||
return False
|
||||
except requests.RequestException as exc:
|
||||
logger.warning(f"Homelable-backend health check exception: {exc}")
|
||||
return False
|
||||
|
||||
|
||||
def fetch_nodes(endpoint: str = BACKEND_URL) -> Optional[List[Dict]]:
|
||||
"""Fetch nodes directly from homelable-backend."""
|
||||
if not health_check(endpoint):
|
||||
return None
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
logger.error("requests library not installed")
|
||||
return None
|
||||
|
||||
key = _get_service_key()
|
||||
headers = {"X-MCP-Service-Key": key}
|
||||
try:
|
||||
resp = requests.get(f"{endpoint}/nodes", headers=headers, timeout=5)
|
||||
resp.raise_for_status()
|
||||
nodes = resp.json()
|
||||
logger.info(f"Fetched {len(nodes)} nodes directly from backend")
|
||||
return nodes
|
||||
except requests.RequestException as exc:
|
||||
logger.error(f"Failed to fetch nodes from backend: {exc}")
|
||||
return None
|
||||
|
||||
|
||||
def log_mcp_error(source: str, output: str) -> None:
|
||||
"""Write MCP-related errors to errors.log."""
|
||||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_line = f"[{timestamp}] ERROR: Homelable {source} {output}.\n"
|
||||
errors_log_path = "/srv/configs/ai/errors.log"
|
||||
try:
|
||||
with open(errors_log_path, "a") as f:
|
||||
f.write(log_line)
|
||||
except OSError:
|
||||
print(log_line, file=__import__("sys").stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Testing backend integration...")
|
||||
if health_check():
|
||||
nodes = fetch_nodes()
|
||||
if nodes:
|
||||
print(f"\nSuccessfully retrieved {len(nodes)} nodes:")
|
||||
for node in nodes[:5]:
|
||||
print(f" - {node.get('label', 'unknown')} ({node.get('ip', 'no IP')}) [Status: {node.get('status', 'n/a')}]")
|
||||
else:
|
||||
print("Failed to retrieve nodes.")
|
||||
else:
|
||||
print("Backend is unreachable.")
|
||||
@@ -11,7 +11,9 @@ Strictly adhere to the rules and best practices defined in the local [STYLE_GUID
|
||||
|
||||
## Key Guidelines
|
||||
- **Clear & Concise Writing**: Favor simple sentence structures and active voice.
|
||||
- **Structural Integrity**: Use hierarchical headings correctly (H1, H2, H3).
|
||||
- **Structural Integrity**: Use hierarchical headings correctly (H1, H2, H3). All headings must have an empty newline immediately following them in all sessions.
|
||||
- **Semantic Markdown**: Use appropriate elements (lists, code blocks, tables) for clarity.
|
||||
- **Google Style**: Follow the local style guide precisely for all formatting and wording.
|
||||
- **Alerts & Callouts**: Use GitHub-style alerts (`> [!NOTE]`, `> [!IMPORTANT]`, etc.).
|
||||
- **Spacing Enforcement**: Always guarantee a single empty newline immediately following every heading in all markdown (.md) documents.
|
||||
|
||||
|
||||
@@ -23,3 +23,12 @@ This skill allows Gemini to efficiently manage the lifecycle of project workflow
|
||||
- **Action Items**:
|
||||
- [ ] [Specific task]
|
||||
- **Verification**: [Public-domain method to verify success]
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Best Practices
|
||||
|
||||
- **Avoid Absolute Positioning in Layouts**: When customizing themes, prefer Flexbox or Grid over `position: absolute` for structural elements like Sidebars or TOCs to prevent content overlap and preserve responsive layout integrity.
|
||||
- **Global Theme Settings**: For TOC rendering in Blowfish, rely on `params.toml` configuration (`showTableOfContents = true`) rather than custom Markdown shortcodes (`[TOC]` or `{{< toc >}}`), which are often theme-incompatible.
|
||||
- **Custom CSS vs Theme Overrides**: Always use `assets/css/custom.css` for structural overrides. Never modify theme-internal files (`themes/blowfish/...`), as they will be overwritten during updates.
|
||||
- **Container Widths**: When overriding container widths, use relative units (percentages) and `!important` sparingly to avoid breaking theme-defined responsive breakpoints.
|
||||
|
||||
18
docker_compose/farmOS/.gitignore
vendored
Normal file
18
docker_compose/farmOS/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# SECRETS AND CREDENTIALS (NEVER COMMIT THESE)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Environment variables (API keys, DB passwords, etc.)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Local Development DBs (often contain real-ish data)
|
||||
*.db
|
||||
*.sqlite
|
||||
dump.rdb
|
||||
|
||||
# Data
|
||||
/keys
|
||||
/sites
|
||||
/db
|
||||
40
docker_compose/farmOS/docker-compose.yaml
Normal file
40
docker_compose/farmOS/docker-compose.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
# farmOS - https://farmos.org/ - https://git.drupalcode.org/project/farm/-/blob/4.x/docker/docker-compose.production.yml?ref_type=heads
|
||||
---
|
||||
services:
|
||||
farmOS-db:
|
||||
container_name: farmOS-db
|
||||
image: postgres:17
|
||||
volumes:
|
||||
- './db:/var/lib/postgresql/data'
|
||||
environment:
|
||||
# Set a strong password and optionally change the user/database name.
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
restart: unless-stopped
|
||||
|
||||
farmOS-www:
|
||||
container_name: farmOS-www
|
||||
depends_on:
|
||||
- farmOS-db
|
||||
# Update this to the latest stable version before deploying.
|
||||
image: farmos/farmos:latest
|
||||
volumes:
|
||||
- './sites:/opt/drupal/web/sites'
|
||||
- './keys:/opt/drupal/keys'
|
||||
ports:
|
||||
- '${FARMOS_HTTP_PORT}:80'
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=farm.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${FARMOS_HTTP_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
- "npm.proxy.ssl_verify=false"
|
||||
- "npm.proxy.websockets=true"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
#- "npm.proxy.advanced.config=location = / { return 301 /guacamole/; }"
|
||||
# Pi-hole Automation (Point to Docker Host/Proxy IP)
|
||||
- "pihole.custom-record=[[\"farm.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
80
docker_compose/guac/docker-compose.yaml
Normal file
80
docker_compose/guac/docker-compose.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
# apache-guacamole - https://github.com/code-loading/guacamole-docker-compose/blob/main/docker-compose.yaml
|
||||
services:
|
||||
# ---------------------------------------------------------
|
||||
# Guacamole Daemon (guacd)
|
||||
# ---------------------------------------------------------
|
||||
guacd:
|
||||
image: guacamole/guacd:1.6.0 # Guacd daemon handles RDP, SSH, VNC protocols
|
||||
container_name: guacd
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- guac_network
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# PostgreSQL Database Service
|
||||
# ---------------------------------------------------------
|
||||
guac-postgresql:
|
||||
image: postgres:15
|
||||
container_name: guac-postgresql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRESQL_DATABASE} # Database name (from .env file)
|
||||
POSTGRES_USER: ${POSTGRESQL_USERNAME} # Database username
|
||||
POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD} # Database password
|
||||
volumes:
|
||||
# Mount startup script from guacamole container into postgres container to initialize DB schema
|
||||
- "initdb:/docker-entrypoint-initdb.d:ro"
|
||||
# (optional) Persist database data on host to survive container rebuilds:
|
||||
# - "${HOME}/guacamole/postgres-data:/var/lib/postgresql/data"
|
||||
networks:
|
||||
- guac_network
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Guacamole Web Application
|
||||
# ---------------------------------------------------------
|
||||
guacamole:
|
||||
image: guacamole/guacamole:1.6.0 # Guacamole web app image
|
||||
container_name: guacamole
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRESQL_DATABASE: ${POSTGRESQL_DATABASE} # guacamole container also needs to be provided with DB name, user and password
|
||||
POSTGRESQL_USERNAME: ${POSTGRESQL_USERNAME}
|
||||
POSTGRESQL_PASSWORD: ${POSTGRESQL_PASSWORD}
|
||||
POSTGRESQL_HOSTNAME: guac-postgresql # Must match service name above of postgres
|
||||
POSTGRESQL_ENABLED: "true" # Enable PostgreSQL authentication backend
|
||||
GUACD_HOSTNAME: guacd # Must match service name below of guacd
|
||||
ports:
|
||||
- ${GUAC_HTTP_PORT}:8080
|
||||
volumes:
|
||||
# Mount startup script from guacamole container into postgres container to initialize DB schema
|
||||
- "initdb:/opt/guacamole/extensions/guacamole-auth-jdbc/postgresql/schema:ro"
|
||||
depends_on:
|
||||
- guacd
|
||||
- guac-postgresql
|
||||
networks:
|
||||
- guac_network
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=guac.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${GUAC_HTTP_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
- "npm.proxy.ssl_verify=false"
|
||||
- "npm.proxy.websockets=true"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
- "npm.proxy.advanced.config=location = / { return 301 /guacamole/; }"
|
||||
# Pi-hole Automation (Point to Docker Host/Proxy IP)
|
||||
- "pihole.custom-record=[[\"guac.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Named Volume
|
||||
# ---------------------------------------------------------
|
||||
volumes:
|
||||
initdb:
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Shared Network
|
||||
# ---------------------------------------------------------
|
||||
networks:
|
||||
guac_network:
|
||||
17
docker_compose/handbrake/docker-compose.yaml
Normal file
17
docker_compose/handbrake/docker-compose.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Handbrake - https://github.com/jlesage/docker-handbrake
|
||||
services:
|
||||
handbrake:
|
||||
image: jlesage/handbrake:latest
|
||||
container_name: handbrake
|
||||
ports:
|
||||
- "5800:5800"
|
||||
volumes:
|
||||
- "/docker/appdata/handbrake:/config:rw"
|
||||
- "/home/user/Videos:/storage:ro"
|
||||
- "/home/user/HandBrake/watch:/watch:rw"
|
||||
- "/home/user/HandBrake/output:/output:rw"
|
||||
environment:
|
||||
- USER_ID=1000
|
||||
- GROUP_ID=1000
|
||||
- TZ=America/New_York
|
||||
restart: unless-stopped
|
||||
5
docker_compose/homelabel/.gitignore
vendored
Normal file
5
docker_compose/homelabel/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# SECRETS AND CREDENTIALS (NEVER COMMIT THESE)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
data/
|
||||
52
docker_compose/homelabel/docker-compose.yaml
Normal file
52
docker_compose/homelabel/docker-compose.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
# homelable - https://github.com/Pouzor/homelable
|
||||
---
|
||||
services:
|
||||
backend:
|
||||
image: ghcr.io/pouzor/homelable-backend:2.5.1
|
||||
container_name: homelable-backend
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- AUTH_USERNAME=${AUTH_USERNAME}
|
||||
- AUTH_PASSWORD_HASH=${AUTH_PASSWORD_HASH} # Use a bcrypt hash for security
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- LIVEVIEW_KEY=${LIVEVIEW_KEY}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
ports:
|
||||
- "${HOMELABEL_BACK_PORT}:8000"
|
||||
|
||||
mcp:
|
||||
image: ghcr.io/pouzor/homelable-mcp:2.5.1
|
||||
container_name: homelable-mcp
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "8001:8001"
|
||||
environment:
|
||||
BACKEND_URL: "http://backend:8000"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
frontend:
|
||||
#image: ghcr.io/pouzor/homelable-frontend-standalone:latest # standalone version
|
||||
image: ghcr.io/pouzor/homelable-frontend:2.5.1
|
||||
container_name: homelable-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HOMELABEL_FRONT_PORT}:80"
|
||||
depends_on:
|
||||
- backend
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=homelable.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${HOMELABEL_FRONT_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
- "npm.proxy.websockets=true"
|
||||
#- "npm.proxy.ssl_verify=false"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
- "pihole.custom-record=[[\"homelable.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
@@ -56,7 +56,7 @@
|
||||
type: pterodactyl
|
||||
url: https://games.wompmacho.com
|
||||
key: {{HOMEPAGE_VAR_pterodactyl_apikey}}
|
||||
- Arr:
|
||||
- Truenas Containers:
|
||||
- Jellyfin:
|
||||
icon: jellyfin.svg
|
||||
href: "https://jellyfin.wompmacho.com/"
|
||||
@@ -151,7 +151,7 @@
|
||||
type: prowlarr
|
||||
url: http://truenas:30050
|
||||
key: {{HOMEPAGE_VAR_prowlarr_apikey}}
|
||||
- Containers:
|
||||
- Docker Containers:
|
||||
- vaultwarden:
|
||||
icon: vaultwarden.svg
|
||||
href: "https://vaultwarden.wompmacho.com"
|
||||
@@ -197,21 +197,16 @@
|
||||
href: "https://wompmacho.com/login"
|
||||
target: _self
|
||||
description: LinkStack
|
||||
- sure:
|
||||
icon: http://sure/assets/logomark-color-92ed1e7e.svg
|
||||
href: "http://sure/"
|
||||
target: _self
|
||||
description: sure finance app
|
||||
- paperless:
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/paperless-ngx.png
|
||||
href: "http://paperless/"
|
||||
target: _self
|
||||
description: paperless-ngx
|
||||
- open-webui:
|
||||
icon: http://gemma/static/favicon.png
|
||||
href: "http://gemma/"
|
||||
- openai:
|
||||
icon: http://openai/static/favicon.png
|
||||
href: "http://openai/"
|
||||
target: _self
|
||||
description: open-webui
|
||||
description: openai
|
||||
- calibre:
|
||||
icon: https://upload.wikimedia.org/wikipedia/commons/c/cf/Calibre_logo_3.png
|
||||
href: "http://calibre/"
|
||||
@@ -248,8 +243,33 @@
|
||||
href: "http://reaper/"
|
||||
target: _self
|
||||
description: reaper in docker
|
||||
- Slopsmith-Web:
|
||||
icon: https://cdn2.steamgriddb.com/icon/dfb61b74af460c2fd68bb8266f9f0814/32/256x256.png
|
||||
href: "https://slopsmith/"
|
||||
# - Slopsmith-Web:
|
||||
# icon: https://cdn2.steamgriddb.com/icon/dfb61b74af460c2fd68bb8266f9f0814/32/256x256.png
|
||||
# href: "https://slopsmith/"
|
||||
# target: _self
|
||||
# description: slopsmith in docker
|
||||
- guacamole:
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/apache-guacamole.svg
|
||||
href: "https://guac.wompmacho.com/"
|
||||
target: _self
|
||||
description: slopsmith in docker
|
||||
description: quac
|
||||
- Invidious:
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/invidious.svg
|
||||
href: "http://inv/"
|
||||
target: _self
|
||||
description: Invidious LXC
|
||||
- neko:
|
||||
icon: https://neko.wompmacho.com/img/logo.800bec71.svg
|
||||
href: "https://neko.wompmacho.com/"
|
||||
target: _self
|
||||
description: neko browser
|
||||
- homelable:
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homelable.svg
|
||||
href: "https://homelable.wompmacho.com/view?key=live"
|
||||
target: _self
|
||||
description: homelable
|
||||
- farmOS:
|
||||
icon: https://cdn.fosstodon.org/accounts/avatars/109/321/909/625/316/786/original/763a2ed219103fd0.jpg
|
||||
href: "https://farm.wompmacho.com/"
|
||||
target: _self
|
||||
description: farmOS
|
||||
@@ -23,10 +23,10 @@ layout:
|
||||
useEqualHeights: true
|
||||
style: row
|
||||
columns: 4
|
||||
Containers:
|
||||
Docker Containers:
|
||||
style: row
|
||||
columns: 8
|
||||
Arr:
|
||||
Truenas Containers:
|
||||
useEqualHeights: true
|
||||
style: row
|
||||
columns: 5
|
||||
|
||||
33
docker_compose/moonlight/docker-compose.yaml
Normal file
33
docker_compose/moonlight/docker-compose.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# moonlight-web-stream - https://github.com/MrCreativ3001/moonlight-web-stream
|
||||
services:
|
||||
# Session 1: Connecting to your current Ubuntu VM (apollo)
|
||||
moonlight-apollo:
|
||||
image: mrcreativ3001/moonlight-web-stream:latest
|
||||
container_name: moonlight_gateway_apollo
|
||||
restart: unless-stopped
|
||||
network_mode: host # Bypasses Docker bridge networking
|
||||
volumes:
|
||||
# FIXED: Reverted to the original mount path. If the app auto-writes settings,
|
||||
# it can put them here, but our environment variables will override the port.
|
||||
- ./config/apollo:/app/config
|
||||
environment:
|
||||
# FIX: The binary explicitly looks for BIND_ADDRESS to overwrite the listener
|
||||
- BIND_ADDRESS=0.0.0.0:${MOONLIGHT_PORT}
|
||||
# FIX: Use the binary's exact env hook to set the WebRTC port ranges cleanly
|
||||
- WEBRTC_PORT_RANGE=40000:40010
|
||||
# FIX: Forces WebRTC to tell your browser exactly where to send the UDP packets
|
||||
- WEBRTC_NAT_1TO1_HOST=${DOCKER_HOST_IP}
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=moonlight.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${MOONLIGHT_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
- "npm.proxy.ssl_verify=false"
|
||||
- "npm.proxy.websockets=true"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
# FIXED: Inject Custom Nginx Performance Directives via Labels
|
||||
- "npm.advanced=proxy_http_version 1.1; proxy_set_header Upgrade $$http_upgrade; proxy_set_header Connection \"Upgrade\"; proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; proxy_buffering off; proxy_request_buffering off; chunked_transfer_encoding on; client_max_body_size 0;"
|
||||
# Pi-hole Automation (Point to Docker Host/Proxy IP)
|
||||
- "pihole.custom-record=[[\"moonlight.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
33
docker_compose/neko/docker-compose.yaml
Normal file
33
docker_compose/neko/docker-compose.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# neko - https://github.com/m1k1o/neko
|
||||
# https://neko.m1k1o.net/docs/v3/configuration
|
||||
services:
|
||||
neko:
|
||||
container_name: neko
|
||||
#build:
|
||||
# context: .
|
||||
# dockerfile: dockerfile
|
||||
#image: ghcr.io/m1k1o/neko/kde
|
||||
image: ghcr.io/m1k1o/neko/nvidia-firefox
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "${NECO_PORT}:8080"
|
||||
- "${WEBRTC}:52000-52100/udp"
|
||||
environment:
|
||||
NEKO_DESKTOP_SCREEN: 1920x1080@30
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: ${NEKO_MEMBER_MULTIUSER_USER_PASSWORD}
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: ${NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD}
|
||||
NEKO_WEBRTC_EPR: 52000-52100
|
||||
NEKO_WEBRTC_ICELITE: 1
|
||||
# See: https://neko.m1k1o.net/docs/v3/configuration/webrtc#ip
|
||||
# NEKO_NAT1TO1: <IP_ADDRESS>
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=neko.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${NECO_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
- "npm.proxy.websockets=true"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
- "pihole.custom-record=[[\"moonlight.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
14
docker_compose/neko/dockerfile
Normal file
14
docker_compose/neko/dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM ghcr.io/m1k1o/neko/kde:latest
|
||||
|
||||
# Switch to root to install packages
|
||||
USER root
|
||||
|
||||
# Update packages and install your chosen browser
|
||||
# Example for Firefox:
|
||||
RUN apt-get update && apt-get install -y firefox-esr && apt-get clean
|
||||
|
||||
# Alternatively, for Chromium:
|
||||
# RUN apt-get update && apt-get install -y chromium && apt-get clean
|
||||
|
||||
# Switch back to the 'neko' user
|
||||
USER neko
|
||||
@@ -1,12 +0,0 @@
|
||||
# Open WebUI - https://docs.openwebui.com/getting-started/quick-start/
|
||||
services:
|
||||
openwebui:
|
||||
image: ghcr.io/open-webui/open-webui:main
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${OPEN_WEB_UI_PORT}:8080"
|
||||
volumes:
|
||||
- ${OPEN_WEB_UI_DATA}:/app/backend/data
|
||||
environment:
|
||||
- OLLAMA_BASE_URL=${OPEN_WEB_UI_COMPUTE}
|
||||
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
|
||||
5
docker_compose/openai/.gitignore
vendored
Normal file
5
docker_compose/openai/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# SECRETS AND CREDENTIALS (NEVER COMMIT THESE)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
data/
|
||||
24
docker_compose/openai/docker-compose.yaml
Normal file
24
docker_compose/openai/docker-compose.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Open WebUI - https://docs.openwebui.com/getting-started/quick-start/
|
||||
services:
|
||||
openai:
|
||||
container_name: openai
|
||||
image: ghcr.io/open-webui/open-webui:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${OPEN_WEB_UI_PORT}:8080"
|
||||
volumes:
|
||||
- ./data:/app/backend/data
|
||||
environment:
|
||||
- OLLAMA_BASE_URL=${OPEN_WEB_UI_COMPUTE}
|
||||
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=openai"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${OPEN_WEB_UI_PORT}"
|
||||
- "npm.proxy.scheme=http"
|
||||
#- "npm.proxy.websockets=true"
|
||||
#- "npm.proxy.ssl_verify=false"
|
||||
#- "npm.proxy.ssl.force=true"
|
||||
#- "npm.proxy.ssl.certificate.id=1"
|
||||
- "pihole.custom-record=[[\"openai\", \"${DOCKER_HOST_IP}\"]]"
|
||||
@@ -1,12 +1,14 @@
|
||||
# paperless-ngx - https://awesome-docker-compose.com/paperless-ngx
|
||||
services:
|
||||
broker:
|
||||
container_name: paperless-broker
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${PAPERLESS_DATA}/redis-data:/data
|
||||
|
||||
db:
|
||||
container_name: paperless-db
|
||||
image: docker.io/library/postgres:16
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
@@ -17,6 +19,7 @@ services:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
webserver:
|
||||
container_name: paperless-ngx
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
---
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:1.35.2
|
||||
image: vaultwarden/server:1.36.0
|
||||
container_name: vaultwarden
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DOMAIN: "${VAULT_DOMAIN}"
|
||||
ROCKET_PORT: ${ROCKET_PORT}
|
||||
ROCKET_ENV: production
|
||||
ADMIN_TOKEN: ${ADMIN_TOKEN}
|
||||
volumes:
|
||||
- vaultwarden-mount:/data/
|
||||
ports:
|
||||
- 'LOCAL_PORT:80'
|
||||
- 'SSL_PORT:443'
|
||||
- '${LOCAL_PORT}:80'
|
||||
- '${SSL_PORT}:443'
|
||||
|
||||
volumes:
|
||||
vaultwarden-mount:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# webtop -- https://docs.linuxserver.io/images/docker-webtop/#lossless-mode
|
||||
# webtop -- https://docs.linuxserver.io/images/docker-webtop/
|
||||
---
|
||||
services:
|
||||
webtop:
|
||||
@@ -9,19 +9,40 @@ services:
|
||||
- PGID=1000
|
||||
- TZ=America/New_York
|
||||
- TITLE=Webtop #optional
|
||||
- PIXELFLUX_WAYLAND=true
|
||||
- DRINODE=/dev/dri/renderD128
|
||||
- DRI_NODE=/dev/dri/renderD128
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
- NVIDIA_DRIVER_CAPABILITIES=all
|
||||
- VK_ICD_FILENAMES=/etc/vulkan/icd.d/nvidia_icd.json
|
||||
#- DISABLE_ZINK=true
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [compute, video, graphics, utility]
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
||||
dns:
|
||||
- ${PIHOLE_SERVER}
|
||||
shm_size: "1gb" #optional
|
||||
|
||||
shm_size: "2gb" #optional
|
||||
volumes:
|
||||
- ${WEBTOP_DATA}config:/config
|
||||
ports:
|
||||
- 7978:3000
|
||||
- 7979:3001
|
||||
networks:
|
||||
- frontend
|
||||
- ${WEBTOP_HTTP_PORT}:3000
|
||||
- ${WEBTOP_HTTPS_PORT}:3001
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
external: true
|
||||
labels:
|
||||
# Nginx Proxy Manager Automation
|
||||
- "npm.proxy.domains=webtop.wompmacho.com"
|
||||
- "npm.proxy.host=${DOCKER_HOST_IP}"
|
||||
- "npm.proxy.port=${WEBTOP_HTTPS_PORT}"
|
||||
- "npm.proxy.scheme=https"
|
||||
- "npm.proxy.websockets=true"
|
||||
- "npm.proxy.ssl.force=true"
|
||||
- "npm.proxy.ssl.certificate.id=2"
|
||||
- "npm.proxy.accesslist.id=1"
|
||||
# Pi-hole Automation (Point to Docker Host/Proxy IP)
|
||||
- "pihole.custom-record=[[\"webtop.wompmacho.com\", \"${DOCKER_HOST_IP}\"]]"
|
||||
|
||||
Reference in New Issue
Block a user