بنقرة واحدة
new-homelab-guest
Add a new LXC guest to the homelab across both terraform and ansible repos
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add a new LXC guest to the homelab across both terraform and ansible repos
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Interact with the Fizzy kanban tool via its REST API. Use this skill whenever the user wants to read or modify anything in Fizzy — listing boards, finding or creating cards, moving cards between columns, closing cards, adding comments, checking activity, managing tags, or any other Fizzy operation. Trigger on requests like "show me my Fizzy boards", "create a card for X", "what's in the backlog?", "move card
Interact with a self-hosted Gitea instance via its REST API. Use this skill whenever the user wants to do anything with Gitea — creating or updating pull requests, checking PR status, listing branches, reading commit history, merging PRs, adding PR comments, or reading/writing wiki pages. Trigger on requests like "update the PR description", "what PRs are open?", "merge the PR", "what branches exist?", "add a comment to the PR", "what's the latest commit on main?", "add a wiki page for X", "what does the wiki say about Y", "update the wiki", or any time the user mentions a PR number, a repo wiki, or asks about repo state on Gitea.
Interact with a self-hosted Grist instance via its REST API. Use this skill whenever the user wants to read or modify data in Grist — listing orgs/workspaces/docs, finding tables and columns, querying or filtering records, adding/updating/deleting rows, running read-only SQL against a doc, or managing table schema. Trigger on requests like "what's in my Grist doc", "add a row to the Inventory table", "update the status column for record 12", "run a query against my budget doc", "what tables are in this workspace", or any mention of Grist docs, tables, or records.
Override Claude Code's built-in WebSearch tool to route web searches through a self-hosted SearXNG instance via curl instead
Create, increment (bump), and read counters using the Bumpkit API at bumpkit.tphummel.workers.dev. Use when you need a persistent named counter that can be incremented and queried by ID.
Use fd, rg, fzf, and bat as modern replacements for find, grep, interactive selection, and cat. Prefer these tools for faster, friendlier, and more ergonomic file searching, content searching, fuzzy filtering, and file viewing.
| name | new-homelab-guest |
| description | Add a new LXC guest to the homelab across both terraform and ansible repos |
| argument-hint | ["guest-name"] |
You are adding a new Proxmox LXC guest named $ARGUMENTS to the homelab infrastructure.
Before doing anything else, identify the terraform and ansible repos. Check in this order:
./terraform/ and ./ansible/ relative to the current working directory.~/Code for the homelab terraform repo: fd -t d terraform ~/Code --max-depth 3 then verify it contains main.tf with proxmox LXC resources (rg 'proxmox_lxc' <path>/main.tf).~/Code for the ansible repo: fd -t d ansible ~/Code --max-depth 3 then verify it contains homelab playbooks (rg 'hummel.casa' <path>/*.yml --max-count 1). There may be multiple ansible dirs; pick the one with active playbooks.Set $TERRAFORM_REPO and $ANSIBLE_REPO to the absolute paths of those directories. Use these variables for all subsequent file paths.
Ask the user for all of these before writing any files:
Before asking other questions, scan $TERRAFORM_REPO/main.tf to find all MAC addresses (0A:BC:DE:00:00:XX) and IP addresses (10.20.71.XXX) currently in use. Determine the next available values (highest in-use + 1 for each).
Ask the user for MAC and IP together in a single question using AskUserQuestion. Present:
0A:BC:DE:00:00:62 / 10.20.71.157) with a description noting these are the next availableThe user will frequently want to specify their own values rather than using the next sequential ones. After receiving the user's choice, verify the chosen MAC and IP are not already in use by grepping $TERRAFORM_REPO/main.tf for both values. If either is taken, inform the user and ask again.
All file paths are relative to $TERRAFORM_REPO/.
Add a new entry inside the locals.guests block, before the closing }. Follow the existing format:
guest_name = {
mac = "0A:BC:DE:00:00:XX"
dhcp_reservation = "10.20.71.XXX"
domain = "guest-name.hummel.casa"
target_node = "pveX"
cpu_cores = 1
ram_mib = 2048
root_fs_size = "16G"
playbook = "guest-name.yml"
}
Use underscores in the HCL key name (e.g., my_guest), hyphens in the domain (e.g., my-guest.hummel.casa).
The file must contain these four resources:
random_password.guest_name - 30 char password with _%@ specialsproxmox_lxc.guest_name - LXC container with:
depends_on = [null_resource.guest_name_unifi_client]ostemplate = local.debian_12_bookworm_lxc_templatelxc_guest_ssh, atlantis_root_2, gitea_hummel_casa, olivetinvar.atlantis_ansible_ssh_private_key_filelifecycle { ignore_changes = [ostemplate] }cloudflare_record.guest_name - A record for the domainnull_resource.guest_name_unifi_client - UniFi DHCP reservation with:
--insecure flag for curlIMPORTANT: Read an existing guest .tf file (like $TERRAFORM_REPO/ntfy.tf) before writing to ensure you match the exact current pattern, including the UniFi polling logic.
Terraform changes always go to a feature branch, never directly to main:
cd $TERRAFORM_REPO
git checkout main && git pull origin main
git checkout -b add-guest-name-guest
git add main.tf guest-name.tf
git commit -m "add guest-name guest"
git push -u origin add-guest-name-guest
All file paths are relative to $ANSIBLE_REPO/.
Read an existing playbook (e.g., $ANSIBLE_REPO/verdaccio.yml) to get the current dns_api_token value — do not hardcode it, copy it from there.
---
- hosts: localhost
become: yes
gather_facts: yes
vars:
domain: 'guest-name.hummel.casa'
dns_api_token: '<copy from existing playbook>'
tls_email: 'tls@hummel.casa'
# If container-based, add container vars:
container:
name: guest-name
description: "Description of the service"
image_name: 'dockerhub.hummel.casa/org/image'
image_tag: 'v1.0.0'
exposed_port: '8080'
roles:
- role: ansible-role-caddy-tls-dns
vars:
caddy_domain: '{{ domain }}'
caddy_tls_email: '{{ tls_email }}'
caddy_dns_api_token: '{{ dns_api_token }}'
caddy_target_port: "{{ container.exposed_port }}"
- role: geerlingguy.node_exporter
tasks:
# ... service-specific tasks
handlers:
# ... if needed
Podman-based service (default): Use $ANSIBLE_REPO/verdaccio.yml as the reference pattern. The key elements are:
Install podman via apt (no role needed, just a task):
- name: Install podman
apt:
name:
- podman
state: present
update_cache: yes
Systemd service file using podman directly (no daemon, no socket):
- name: Create systemd service file for {{ container.name }}
copy:
content: |
[Unit]
Description={{ container.description }}
After=network-online.target
Wants=network-online.target
[Service]
Restart=on-failure
RestartSec=5s
ExecStartPre=-/usr/bin/podman rm -f {{ container.name }}
ExecStart=/usr/bin/podman run --name {{ container.name }} \
--security-opt=no-new-privileges \
--pids-limit=100 \
--memory=512m \
--memory-swap=512m \
-p 127.0.0.1:{{ container.exposed_port }}:{{ container.exposed_port }} \
-v /path/on/host:/path/in/container:ro \
-v /path/on/host:/path/in/container \
-e TZ=America/Los_Angeles \
{{ container.image_name }}:{{ container.image_tag }}
ExecStop=/usr/bin/podman stop -t 10 {{ container.name }}
ExecStopPost=/usr/bin/podman rm -f {{ container.name }}
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/{{ container.name }}.service
mode: 0644
notify:
- Reload systemd
- Start service
Handlers for reload, restart, and image cleanup:
handlers:
- name: Reload systemd
systemd:
daemon_reload: yes
- name: Start service
systemd:
name: "{{ container.name }}"
enabled: yes
state: restarted
notify:
- Cleanup old images
- name: Cleanup old images
listen: Start service
shell: |
set -euo pipefail
repo="{{ container.image_name }}"
keep_tag="{{ container.image_tag }}"
keep_ref="${repo}:${keep_tag}"
echo "==> Evaluating images for repo: ${repo}"
podman image ls --format '{{'{{'}}.Repository{{'}}'}}:{{'{{'}}.Tag{{'}}'}}' "${repo}" || exit 0
echo "==> Keeping: ${keep_ref}"
old_images=$(podman image ls --format '{{'{{'}}.Repository{{'}}'}}:{{'{{'}}.Tag{{'}}'}}' "${repo}" \
| awk -v keep="${keep_ref}" '$0 != keep && $0 !~ /<none>:/ {print $0}')
if [ -z "$old_images" ]; then
echo "==> No old images found to remove."
else
echo "==> Will remove:"
echo "$old_images"
echo "$old_images" | xargs -r -n1 podman image rm -f
fi
echo "==> Pruning dangling layers..."
podman image prune -f --filter "dangling=true"
args:
executable: /bin/bash
register: cleanup_result
changed_when: true
notify:
- Show cleanup output
- name: Show cleanup output
debug:
var: cleanup_result.stdout_lines
Docker-based service (only if explicitly requested): Use the geerlingguy.docker role and read an existing Docker-based playbook (e.g., $ANSIBLE_REPO/ntfy.yml, $ANSIBLE_REPO/mealie.yml) for the pattern. Do NOT use Docker unless the user specifically asks for it.
Node.js service (requires curl installed first):
- name: Install required packages
apt:
name:
- curl
state: present
- name: Check current Node.js version
command: node --version
register: node_version
ignore_errors: yes
changed_when: false
- name: Remove Debian default nodejs if not vXX
apt:
name: nodejs
state: absent
purge: yes
when: node_version.rc == 0 and not node_version.stdout.startswith('vXX')
- name: Install Node.js XX LTS repository
shell: |
curl -fsSL https://deb.nodesource.com/setup_XX.x | bash -
when: node_version.rc != 0 or not node_version.stdout.startswith('vXX')
- name: Install Node.js XX
apt:
name: nodejs
state: latest
update_cache: yes
Note: Debian 12 ships Node 18 without npm. The nodesource package bundles npm. You must purge the Debian nodejs first or apt won't upgrade, and curl is not installed by default in LXC containers.
Conditional global npm install:
- name: Check if tool-name is installed
command: which tool-name
register: tool_check
ignore_errors: yes
changed_when: false
- name: Install tool-name globally if not on PATH
command: npm install -g tool-name@latest
when: tool_check.rc != 0
Docker image registries:
dockerhub.hummel.casa/ for DockerHub imagesghcr.hummel.casa/ for GitHub Container Registry imagesregistry.hummel.casa/ for custom imagesEdit $ANSIBLE_REPO/prometheus.yml to add the new guest to both scrape target lists:
"guest-name.hummel.casa:9100" to the node_exporter job's targets list (around line 62-96)"guest-name.hummel.casa:2019" to the caddy job's targets list (around line 101-130)Read the file first to find the exact insertion points. Add new entries at the end of each target list, before the closing ].
Edit $ANSIBLE_REPO/templates/start_website_index.html.j2 to add a link for the new guest. Choose the appropriate section:
Add a <dt> entry in the chosen section's <dl>:
<dt><a href="https://guest-name.hummel.casa">Guest Display Name</a></dt>
Ask the user which section the new guest belongs in.
Ansible playbooks are committed and pushed directly to main:
cd $ANSIBLE_REPO
git add guest-name.yml prometheus.yml templates/start_website_index.html.j2
git commit -m "add guest-name guest playbook"
git push origin main