Compare commits

..

4 Commits

Author SHA1 Message Date
Adyrem 7ba68de68d CI: trigger re-run to verify vault secret fix
Ansible Lint / lint (push) Successful in 4s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 19:57:39 +02:00
Adyrem d1d38df0e4 Track LXC container configs and fix network-interfaces
- proxmox/lxc/103.conf: Pi-hole LXC (firewall=0 to fix NAT via fwbr)
- proxmox/lxc/104.conf: Traefik LXC
- proxmox/network-interfaces: Proxmox network config with DNAT/MASQUERADE
  rules; fixes tpost-up typo so DNS DNAT persists across reboots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 19:52:43 +02:00
Adyrem 62e5027d24 Add Proxmox firewall configs and Grafana dashboards
- proxmox/firewall/host.fw: host firewall rules (SSH/Proxmox UI/SPICE
  from LAN, DNS from internal subnets, ICMP)
- proxmox/firewall/cluster.fw: cluster firewall with network aliases
- files/grafana-dashboards/node-exporter-full.json: Node Exporter Full
  (grafana.com/dashboards/1860)
- files/grafana-dashboards/loki-logs.json: Logs/App dashboard for Loki
  (grafana.com/dashboards/13639)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:35:36 +02:00
Adyrem 3d596b984b Add Claude Code VM (105) with cloud-init provisioning
Arch Linux cloud image, 4 cores/8GB, dev-net (10.10.2.10), autostart.
Cloud-init handles user/SSH key/network on first boot. Ansible role
installs Claude Code CLI, tmux, SSH keys, SSH config for all managed
VMs via ProxyJump, homelab repo clone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:41:52 +02:00
14 changed files with 15991 additions and 0 deletions
@@ -0,0 +1,2 @@
ansible_user: adyrem
ansible_ssh_common_args: "-o ProxyJump=root@192.168.1.10 -o StrictHostKeyChecking=no"
+5
View File
@@ -22,6 +22,11 @@ all:
hosts:
gitea:
claude_code_vms:
hosts:
claude-code:
ansible_host: 10.10.2.10
pihole_servers:
hosts:
pihole:
+8
View File
@@ -0,0 +1,8 @@
---
- name: Configure Claude Code VM
hosts: claude_code_vms
roles:
- common
- node_exporter
- promtail
- claude_code
+2
View File
@@ -0,0 +1,2 @@
files/homelab_ed25519
files/claude-code_ed25519
+64
View File
@@ -0,0 +1,64 @@
---
- name: Install Claude Code dependencies
community.general.pacman:
name:
- nodejs
- npm
- ansible
- python-pip
- tmux
- git
state: present
update_cache: true
- name: Install Claude Code CLI
community.general.npm:
name: "@anthropic-ai/claude-code"
global: true
state: present
- name: Deploy homelab SSH private key
ansible.builtin.copy:
src: homelab_ed25519
dest: /home/{{ admin_user }}/.ssh/homelab_ed25519
owner: "{{ admin_user }}"
group: "{{ admin_user }}"
mode: "0600"
- name: Deploy claude-code SFTP private key
ansible.builtin.copy:
src: claude-code_ed25519
dest: /home/{{ admin_user }}/.ssh/claude-code_ed25519
owner: "{{ admin_user }}"
group: "{{ admin_user }}"
mode: "0600"
- name: Write SSH client config
ansible.builtin.template:
src: ssh_config.j2
dest: /home/{{ admin_user }}/.ssh/config
owner: "{{ admin_user }}"
group: "{{ admin_user }}"
mode: "0600"
- name: Write tmux config
ansible.builtin.copy:
dest: /home/{{ admin_user }}/.tmux.conf
owner: "{{ admin_user }}"
group: "{{ admin_user }}"
mode: "0644"
content: |
set -g default-terminal "screen-256color"
set -g history-limit 50000
set -g mouse on
set -g base-index 1
set -g escape-time 10
bind r source-file ~/.tmux.conf \; display "Reloaded"
- name: Clone homelab repo
ansible.builtin.git:
repo: "http://10.10.1.125:3000/adyrem/homelab.git"
dest: /home/{{ admin_user }}/dev/homelab
version: main
become_user: "{{ admin_user }}"
become: true
@@ -0,0 +1,42 @@
# Proxmox host — jump host only, no shell
Host proxmox
HostName 192.168.1.10
User root
IdentityFile ~/.ssh/homelab_ed25519
StrictHostKeyChecking no
# Proxmox SFTP — write files to host workspace
Host proxmox-sftp
HostName 192.168.1.10
User claude-code
IdentityFile ~/.ssh/claude-code_ed25519
StrictHostKeyChecking no
# Infra VMs — via ProxyJump through Proxmox
Host gitea-vm
HostName 10.10.1.125
User adyrem
ProxyJump proxmox
IdentityFile ~/.ssh/homelab_ed25519
StrictHostKeyChecking no
Host monitoring-vm
HostName 10.10.1.137
User adyrem
ProxyJump proxmox
IdentityFile ~/.ssh/homelab_ed25519
StrictHostKeyChecking no
Host pihole-ct
HostName 10.10.1.2
User root
ProxyJump proxmox
IdentityFile ~/.ssh/homelab_ed25519
StrictHostKeyChecking no
Host traefik-ct
HostName 10.10.1.3
User root
ProxyJump proxmox
IdentityFile ~/.ssh/homelab_ed25519
StrictHostKeyChecking no
+248
View File
@@ -0,0 +1,248 @@
{
"annotations": {
"list": [
{
"$$hashKey": "object:75",
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "Log Viewer Dashboard for Loki",
"editable": false,
"gnetId": 13639,
"graphTooltip": 0,
"id": 1684514089902080,
"iteration": 1608932746420,
"links": [
{
"$$hashKey": "object:59",
"icon": "bolt",
"includeVars": true,
"keepTime": true,
"tags": [],
"targetBlank": true,
"title": "View In Explore",
"type": "link",
"url": "/explore?orgId=1&left=[\"now-1h\",\"now\",\"Loki\",{\"expr\":\"{job=\\\"$app\\\"}\"},{\"ui\":[true,true,true,\"none\"]}]"
},
{
"$$hashKey": "object:61",
"icon": "external link",
"tags": [],
"targetBlank": true,
"title": "Learn LogQL",
"type": "link",
"url": "https://grafana.com/docs/loki/latest/logql/"
}
],
"panels": [
{
"aliasColors": {},
"bars": true,
"dashLength": 10,
"dashes": false,
"datasource": "P8E80F9AEF21F6940",
"fieldConfig": {
"defaults": {
"custom": {},
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": false,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.0",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(count_over_time({job=\"$app\"} |= \"$search\" [$__interval]))",
"legendFormat": "",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:168",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
},
{
"$$hashKey": "object:169",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "P8E80F9AEF21F6940",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"gridPos": {
"h": 25,
"w": 24,
"x": 0,
"y": 3
},
"id": 2,
"maxDataPoints": "",
"options": {
"showLabels": false,
"showTime": true,
"sortOrder": "Descending",
"wrapLogMessage": false
},
"targets": [
{
"expr": "{job=\"$app\"} |= \"$search\" | logfmt",
"hide": false,
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "",
"transparent": true,
"type": "logs"
}
],
"refresh": false,
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allValue": null,
"current": {},
"datasource": "P8E80F9AEF21F6940",
"definition": "label_values(job)",
"hide": 0,
"includeAll": false,
"label": "App",
"multi": false,
"name": "app",
"options": [],
"query": "label_values(job)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": "",
"value": ""
},
"hide": 0,
"label": "String Match",
"name": "search",
"options": [
{
"selected": true,
"text": "",
"value": ""
}
],
"query": "",
"skipUrlSync": false,
"type": "textbox"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"hidden": false,
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Logs / App",
"uid": "sadlil-loki-apps-dashboard",
"version": 1
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
[OPTIONS]
enable: 1
[ALIASES]
lan 192.168.1.0/24
infra_net 10.10.1.0/24
dev_net 10.10.2.0/24
+12
View File
@@ -0,0 +1,12 @@
[OPTIONS]
enable: 1
log_level_in: nolog
log_level_out: nolog
[RULES]
IN ACCEPT -source 192.168.1.0/24 -p tcp -dport 22
IN ACCEPT -source 192.168.1.0/24 -p tcp -dport 8006
IN ACCEPT -source 192.168.1.0/24 -p tcp -dport 3128
IN ACCEPT -source 10.10.0.0/16 -p udp -dport 53
IN ACCEPT -source 10.10.0.0/16 -p tcp -dport 53
IN ACCEPT -p icmp
+12
View File
@@ -0,0 +1,12 @@
arch: amd64
cores: 1
features: nesting=1
hostname: pihole
memory: 512
nameserver: 8.8.8.8
net0: name=eth0,bridge=vmbr1,firewall=0,gw=10.10.1.1,hwaddr=BC:24:11:D6:DD:FF,ip=10.10.1.2/24,type=veth
onboot: 1
ostype: debian
rootfs: local-zfs:subvol-103-disk-0,size=4G
swap: 0
unprivileged: 1
+14
View File
@@ -0,0 +1,14 @@
arch: amd64
cores: 1
hostname: traefik
memory: 256
nameserver: 10.10.1.2
net0: name=eth0,bridge=vmbr1,firewall=1,gw=10.10.1.1,hwaddr=BC:24:11:8E:D3:36,ip=10.10.1.3/24,type=veth
onboot: 1
ostype: debian
rootfs: local-zfs:subvol-104-disk-0,size=2G
swap: 0
unprivileged: 1
[pve:pending]
nameserver: 8.8.8.8
+54
View File
@@ -0,0 +1,54 @@
auto lo
iface lo inet loopback
iface nic0 inet manual
auto vmbr0
iface vmbr0 inet static
address 192.168.1.10/24
gateway 192.168.1.1
bridge-ports nic0
bridge-stp off
bridge-fd 0
post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 80 -j DNAT --to-destination 10.10.1.3:80
post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 443 -j DNAT --to-destination 10.10.1.3:443
post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 80 -j DNAT --to-destination 10.10.1.3:80
post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 443 -j DNAT --to-destination 10.10.1.3:443
post-up iptables -t nat -A PREROUTING -i vmbr0 -p udp --dport 53 -j DNAT --to-destination 10.10.1.2:53
post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 53 -j DNAT --to-destination 10.10.1.2:53
post-up iptables -t nat -A POSTROUTING -p udp -d 10.10.1.2 --dport 53 -j MASQUERADE
post-up iptables -t nat -A POSTROUTING -p tcp -d 10.10.1.2 --dport 53 -j MASQUERADE
post-down iptables -t nat -D PREROUTING -i vmbr0 -p udp --dport 53 -j DNAT --to-destination 10.10.1.2:53
post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 53 -j DNAT --to-destination 10.10.1.2:53
post-down iptables -t nat -D POSTROUTING -p udp -d 10.10.1.2 --dport 53 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -p tcp -d 10.10.1.2 --dport 53 -j MASQUERADE
source /etc/network/interfaces.d/*
auto vmbr1
iface vmbr1 inet static
address 10.10.1.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up iptables -t nat -A POSTROUTING -s 10.10.1.0/24 -o vmbr0 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s 10.10.1.0/24 -o vmbr0 -j MASQUERADE
auto vmbr2
iface vmbr2 inet static
address 10.10.2.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up iptables -t nat -A POSTROUTING -s 10.10.2.0/24 -o vmbr0 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s 10.10.2.0/24 -o vmbr0 -j MASQUERADE
auto vmbr3
iface vmbr3 inet static
address 10.10.3.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up iptables -t nat -A POSTROUTING -s 10.10.3.0/24 -o vmbr0 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s 10.10.3.0/24 -o vmbr0 -j MASQUERADE
+23
View File
@@ -0,0 +1,23 @@
agent: enabled=1
bios: ovmf
boot: order=scsi0
cicustom: user=local:snippets/claude-code-userdata.yaml
citype: nocloud
cores: 4
cpu: host
efidisk0: local-zfs:vm-105-disk-0,efitype=4m,pre-enrolled-keys=0,size=1M
ide2: local-zfs:vm-105-cloudinit,media=cdrom
ipconfig0: ip=10.10.2.10/24,gw=10.10.2.1
machine: q35
memory: 8192
name: claude-code
nameserver: 10.10.1.2
net0: virtio=BC:24:11:E3:A1:D1,bridge=vmbr2,firewall=1
onboot: 1
ostype: l26
scsi0: local-zfs:vm-105-disk-1,iothread=1,size=20G
scsihw: virtio-scsi-single
searchdomain: homelab
smbios1: uuid=cc68aa1f-affe-42ee-8086-ba94b69573af
vga: qxl
vmgenid: 511710d7-9a40-43ef-901e-0a2de8344f5b