Add infrastructure packages: terraform-pve, ansible-push, wolfstack-keepalived, terraform-pve-vm
This commit is contained in:
67
artemis-backup-20260614-1920/README.md
Normal file
67
artemis-backup-20260614-1920/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Iron Legion Infra Backup — 20260614-1920
|
||||
## What's Included
|
||||
|
||||
| Folder | Purpose |
|
||||
|---|---|
|
||||
| `terraform-pve/` | LXC provisioning (Tailscale subnet routers) |
|
||||
| `terraform-pve-vm/` | VM migration (Artemis/Jarvis/Friday) |
|
||||
| `ansible-push/` | Fleet automation playbooks (includes wolfstack role) |
|
||||
| `wolfstack-keepalived/` | WolfStack VIP deploy scripts + configs |
|
||||
|
||||
## What's Excluded
|
||||
|
||||
- `.git/` history (not needed — this is a working copy)
|
||||
- `.terraform/` provider caches (regenerate via `terraform init`)
|
||||
- `__pycache__`, `venv`, `.venv`
|
||||
- **Identity files** — synced to TriliumNext (Project: A.R.T.E.M.I.S.), not included here
|
||||
|
||||
## Restore Procedure
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
```bash
|
||||
# Terraform
|
||||
wget -qO- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||
sudo apt update && sudo apt install terraform
|
||||
|
||||
# Ansible
|
||||
pip install ansible
|
||||
|
||||
# SSH key (must obtain separately)
|
||||
# Place fleet private key at ~/.ssh/artemis_key (mode 600)
|
||||
```
|
||||
|
||||
### 2. Init Terraform
|
||||
|
||||
```bash
|
||||
cd terraform-pve/terraform
|
||||
terraform init
|
||||
# State must be re-imported or recreated — .tfstate NOT included
|
||||
```
|
||||
|
||||
### 3. Run Ansible
|
||||
|
||||
```bash
|
||||
cd ansible-push
|
||||
ansible-playbook -i inventory.yml playbooks/main.yml
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
- Fleet credentials: `~/.hermes/credentials/fleet.env`
|
||||
- SSH key: `~/.ssh/artemis_key`
|
||||
- Tailscale auth key: `fleet.env` → `TS_AUTH_KEY_LXC_ALL`
|
||||
- Proxmox API token: `terraform-pve/terraform/terraform.auto.tfvars` (or fleet.env)
|
||||
|
||||
> **These are NOT in this tarball.** They must be provisioned separately.
|
||||
|
||||
## Notes
|
||||
|
||||
- Gitea repo (pull-deploy): `git@gitea.nb.bobbysh.me:Iron-Legion/ansible-pull-deploy.git`
|
||||
- Gitea repo (terraform-pve): `git@gitea.nb.bobbysh.me:Iron-Legion/terraform-pve.git`
|
||||
- Tailscale admin console: `https://login.tailscale.com/admin`
|
||||
- Proxmox nodes: MK33 (192.168.7.33), MK50 (192.168.50.41)
|
||||
|
||||
---
|
||||
Generated by Artemis on 20260614-1920
|
||||
4
artemis-backup-20260614-1920/ansible-push/ansible.cfg
Normal file
4
artemis-backup-20260614-1920/ansible-push/ansible.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
[defaults]
|
||||
stdout_callback = dense
|
||||
deprecation_warnings = False
|
||||
host_key_checking = False
|
||||
6
artemis-backup-20260614-1920/ansible-push/configure.sh
Executable file
6
artemis-backup-20260614-1920/ansible-push/configure.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
# Renamed to configure.sh and added playbooks to stack
|
||||
docker compose up -d
|
||||
|
||||
docker exec -it ansible ansible-playbook playbooks/main.yml -i inventory.yml
|
||||
|
||||
docker compose down
|
||||
14
artemis-backup-20260614-1920/ansible-push/docker-compose.yml
Normal file
14
artemis-backup-20260614-1920/ansible-push/docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
ansible:
|
||||
build: .
|
||||
container_name: ansible
|
||||
image: ansible
|
||||
environment:
|
||||
- ANSIBLE_HOST_KEY_CHECKING=false
|
||||
- ANSIBLE_PYTHON_INTERPRETER=/usr/bin/python3.12
|
||||
volumes:
|
||||
- .:/ansible
|
||||
- ./keys:/root/.ssh
|
||||
working_dir: /ansible
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command: ["tail -f /dev/null"]
|
||||
2
artemis-backup-20260614-1920/ansible-push/dockerfile
Normal file
2
artemis-backup-20260614-1920/ansible-push/dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM python:3.14.5-alpine3.23
|
||||
RUN pip install --no-cache-dir ansible==14.0.0 && apk add --no-cache curl openssh-client
|
||||
167
artemis-backup-20260614-1920/ansible-push/inventory.yml
Normal file
167
artemis-backup-20260614-1920/ansible-push/inventory.yml
Normal file
@@ -0,0 +1,167 @@
|
||||
# Iron Legion Fleet Inventory
|
||||
# Generated: 2026-06-03
|
||||
# Source: fleet documentation + live SSH config
|
||||
#
|
||||
# Usage with Ansible:
|
||||
# ansible all -m ping -i inventory.yml
|
||||
# ansible pve_workers -m setup -i inventory.yml
|
||||
# ansible swarm_manager -a "docker service ls" -i inventory.yml
|
||||
#
|
||||
# FIX: Group-specific variables (e.g. pve_workers:) were previously
|
||||
# placed outside `all:` scope, breaking inventory parsing.
|
||||
# All group vars are now merged into the group definitions below.
|
||||
|
||||
---
|
||||
|
||||
all:
|
||||
vars:
|
||||
ansible_ssh_private_key_file: /root/.ssh/id_ed25519
|
||||
children:
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Physical / Virtual Fleet Nodes
|
||||
# ──────────────────────────────────────────
|
||||
|
||||
fleet_nodes:
|
||||
children:
|
||||
|
||||
# Core fleet services
|
||||
core_services:
|
||||
hosts:
|
||||
mk7:
|
||||
ansible_host: 192.168.7.7
|
||||
ansible_user: jarvis
|
||||
node_role: swarm_manager
|
||||
docker_host: true
|
||||
description: "Swarm manager + Traefik + service stack host"
|
||||
|
||||
# PVE hosts nodes
|
||||
pve_hosts:
|
||||
vars:
|
||||
ansible_user: root
|
||||
ansible_ssh_pass: "proxmox12"
|
||||
ansible_become: true
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
hosts:
|
||||
mk33:
|
||||
ansible_host: 192.168.7.33
|
||||
node_role: pve_worker
|
||||
pve_api_url: "https://192.168.7.33:8006/"
|
||||
description: "PVE Silver Centurion"
|
||||
|
||||
mk34:
|
||||
ansible_host: 192.168.7.34
|
||||
node_role: pve_worker
|
||||
pve_api_url: "https://192.168.7.34:8006/"
|
||||
description: "PVE Southpaw"
|
||||
|
||||
mk39:
|
||||
ansible_host: 192.168.7.39
|
||||
node_role: pve_worker
|
||||
pve_api_url: "https://192.168.7.39:8006/"
|
||||
description: "PVE Gemini"
|
||||
|
||||
mk50:
|
||||
ansible_host: 192.168.50.41
|
||||
node_role: pve_worker
|
||||
pve_api_url: "https://192.168.50.41:8006/"
|
||||
description: "PVE MK50 Bleeding Edge"
|
||||
|
||||
# Active physical agents
|
||||
physical_agents:
|
||||
hosts:
|
||||
artemis:
|
||||
ansible_host: 192.168.15.182
|
||||
ansible_user: jarvis
|
||||
node_role: discord_gateway
|
||||
hermes_agent: true
|
||||
description: "Primary AI orchestrator + Discord gateway"
|
||||
|
||||
mark44:
|
||||
ansible_host: 192.168.5.214
|
||||
ansible_user: jarvis
|
||||
ansible_ssh_private_key_file: /root/.ssh/vscode_ed25519
|
||||
node_role: gpu_host
|
||||
gpu: true
|
||||
description: "Hulkbuster — GPU/Ollama standby"
|
||||
|
||||
mark5:
|
||||
ansible_host: 192.168.6.5
|
||||
ansible_user: jarvis
|
||||
node_role: tbd
|
||||
description: "Mark 5 — being repurposed"
|
||||
|
||||
mk42:
|
||||
ansible_host: 192.168.0.196
|
||||
ansible_user: jarvis
|
||||
ansible_become_pass: "ubuntu"
|
||||
node_role: swarm_worker
|
||||
description: "Swarm Extremis"
|
||||
|
||||
mk85:
|
||||
ansible_host: 100.75.79.47
|
||||
ansible_user: jarvis
|
||||
node_role: mobile_gamer
|
||||
ansible_connection: ssh
|
||||
description: "Mobile Gamer — Tailscale primary, no fixed LAN"
|
||||
|
||||
# Infrastructure / support nodes
|
||||
infrastructure:
|
||||
hosts:
|
||||
shield:
|
||||
ansible_host: 192.168.27.205
|
||||
ansible_user: jarvis
|
||||
ansible_become_pass: "ubuntu"
|
||||
node_role: pxe_server
|
||||
description: "iVentoy PXE deployment server"
|
||||
|
||||
igor:
|
||||
ansible_host: 192.168.10.211
|
||||
ansible_user: jarvis
|
||||
node_role: nas
|
||||
description: "ZimaOS NAS (MK-38)"
|
||||
|
||||
vars:
|
||||
nfs_shares:
|
||||
- src: "192.168.16.254:/mnt/Ice/Repo"
|
||||
path: "/home/jarvis/repo"
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Managed nodes (apt update/upgrade target)
|
||||
# Physical agents + core services, NOT PVE, NOT Neo, NOT igor, NOT ephemeral LXCs
|
||||
# ──────────────────────────────────────────
|
||||
managed_nodes:
|
||||
children:
|
||||
physical_agents:
|
||||
core_services:
|
||||
|
||||
# Wolfstack host nodes (includes PVE hosts for now)
|
||||
wolfstack:
|
||||
children:
|
||||
pve_hosts:
|
||||
|
||||
# Tailscale fallback aliases (uncomment if LAN fails)
|
||||
# tailscale_fallback:
|
||||
# hosts:
|
||||
# ts-mk7:
|
||||
# ansible_host: 100.66.70.51
|
||||
# ansible_user: jarvis
|
||||
# ts-mk33:
|
||||
# ansible_host: 100.125.155.41
|
||||
# ansible_user: jarvis
|
||||
# ts-mk34:
|
||||
# ansible_host: 100.94.190.43
|
||||
# ansible_user: jarvis
|
||||
# ts-nebuchadnezzar:
|
||||
# ansible_host: 100.99.123.16
|
||||
# ansible_user: jarvis
|
||||
|
||||
# Docker host targeting groups (uncomment when needed)
|
||||
# docker_hosts:
|
||||
# children:
|
||||
# swarm_manager:
|
||||
# hosts:
|
||||
# mk7:
|
||||
# standalone_docker:
|
||||
# hosts:
|
||||
# nebuchadnezzar:
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACD0ga0QkTlBzomV/SAd7XhCvPVRX4IW3buzheTaCdWOoAAAAKAofwxyKH8M
|
||||
cgAAAAtzc2gtZWQyNTUxOQAAACD0ga0QkTlBzomV/SAd7XhCvPVRX4IW3buzheTaCdWOoA
|
||||
AAAEBcve+GuhnjxJYBCNn+In+ZOzaGpDguXC6Kuhs1Neuo6PSBrRCROUHOiZX9IB3teEK8
|
||||
9VFfghbdu7OF5NoJ1Y6gAAAAFEdlbmVyYXRlZCBCeSBUZXJtaXVzAQIDBAUGBwgJ
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPSBrRCROUHOiZX9IB3teEK89VFfghbdu7OF5NoJ1Y6g Generated By Termius
|
||||
|
||||
49
artemis-backup-20260614-1920/ansible-push/keys/known_hosts
Normal file
49
artemis-backup-20260614-1920/ansible-push/keys/known_hosts
Normal file
@@ -0,0 +1,49 @@
|
||||
192.168.15.182 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEotq4gp1TaI6KY+ZlzU+Sp6wKOQxCsBOXcTedsscdnr
|
||||
192.168.5.214 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIA3LSBVGXwU8beehCbOnrXaF7v//nDbOzZJIo77KKAg
|
||||
192.168.0.196 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILyV6JsdRGtCcDuxk1Btf/6o21lPFUmxcKtGQQ0KGznf
|
||||
192.168.6.5 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICQ9ZfPqssWaCjx9WGZu23nN1uSyEpuHR1cFqy5x9cBv
|
||||
192.168.15.182 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7i2ToCbuFKb9w3cZLQstGM1XjsHpIUpWta68NN2XsRfjltfozGBcd+RkTyDwpzgb1ElEzJFoHMtKNmIX4ueavFdQeM6GdjKUD0pSjb3K+oBUDDAS2z3vyehbj9Nf4soFn5e3pP9OvxQ6Fkl5Fln8ySbCvvybA7Z61ws44Nk9Bt1GTH4aC0R1ZvzBoE2eXe0crg7x53i6EO3EzkUOebwaUiaTE4B/bIjLda0gQbr2u0idEaTY6aIAARWDnHwXD/KYtFxi/9RZ3SCHWksSBTVje4hHWYgwjCjRzPuZZ7FAmRtTTu6q90Ja1SSAk6myI4K5T/Gzo/HBsJ99OFs/NuTAXLjK4j+QnDsTfBlOIwneZeNw95L+4CUowP4LyaT3FA5ZKtMMnzfNKQcDyDu7vzQC216Vdio5WowazeQOUnOK3Af+t7oVH5RZ6IoafRZmTWBUVTSSHW5iTHa0gVGAerH8lakLo9pKzu9t+nv3iOQWL5rIOfWoAvNxDcGEdIh0SWfc=
|
||||
192.168.15.182 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPV51QE7y1uc+7vAnD2rm5GHlv9vJfqHcxlA7gy7mSb0VQ89TgyOnXEHy8yZJ5cRdCNvvX68CEt+FnZkaK8IjkQ=
|
||||
192.168.6.5 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDP0/X9xIy0R2jHPwBdM5KydJCk3qfelRiW+6vpFMKKUd7PL/QHEVmRfd28HMnPRoBuY3tzbx6/u2VTYwwsQQaW+5g1L7o3ju5qlBdKY1XFAvm0En6DVg6gz4RRBIlRJPx6DbSdRVdzYLsGCs9T4GFBZG4d39gnR2kWGEYfV6jCYKULGXf1CQBg6mFUdcjCORXX548CgYVj5jNmXC8BRVOezOTkPMaODUJN/fwcCB6Jasfwuv+4DKADhs91tDca4dGcWfdLH2jLT5sGkp3CkFNEu2XNoct6+tJEq7v212nSErFnAO9VMPIQagZGfuoUspjoPPNN0i0/AWSx0I3TwtGw4FdUwDb+WH5ZqPOgnwF5i9utEJETihJF2f8bac0gDKVPErd4EnyWjfT1wcqscNeJ/6Q9zl5Mj6VbxKRi62PSkpjHAk+qjO3GLlKZpVv5lUr9nz2efIUH2ZMSqKdrffkfDzatO4S53YLhpKJB54ka6W2l/eOA+vO+Q/BkoWp1DPE=
|
||||
192.168.6.5 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHh302+xQi3WZ05mCLW5oKwUOkCjos4BpOyfiCOnc6MmcV8aXoiOlueV/uqQlYuG2ZxJQ6OpRTjONOsSKSbCnhM=
|
||||
192.168.5.214 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEL4xa599zwNOKAxXvVbUhuuxRS5Acj0RHVUskJsqA5Ll5Poa1uFHZXExgZap98YLy5RT1NwYXLy6ElE2ly32STfiVRP2cX80LHspvtr8+xl65qTlE9oSiA/imY2vifd6NAxC0K+kfYMLO4l6/GBbd9OVrfJLju5x6aijzoF820wqm3OrlhuibMmgkAn8TmQnih+pQCYxrjgMCWZFbzv5i8C6EBBCtArW950NPJHwoTFrcvGydpZU1hlh6o6rKWcf1fJ2VYSXNTG7TFzhiB+ezZnmy+hgtF52E+tTBnFkPlSRfQIdAYxi93HIrsh4Vk0fyVuaffMRXH9FmH/2VPRqvplaTVO0fDrghufB/eiW/HhlYuRHtZsx5/ZFJQDeViBtxHn0lQevIaDQvCkrJio59IG1mzVa7Pf7yh1XmWV0ipbX47CUNs9O97y/Z1fl80qycLzr9L+dYpGKGrJpqKndMKsW026WSvaTeuvz6r3F58HJHa+Eu+L3FGE1/rTrI5S8=
|
||||
192.168.5.214 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKrxrlDM1kv3GtLl04xkDmXFKTQjmISt+v5Y3sVire/CJxVygzZei6xYeZcWgWzdNnTKccX10kKGiWUy9nqn71M=
|
||||
192.168.0.196 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDR8JGtc6afihCVPAKt8N4/UNhXqPikFzRkWrAlZTWcnVPKzExR/bu+d+wIRB4qdDxX/i/oG+Q+OwfFPeMn0trF0CBGvS/AbmQci+rw/eHgsd5kL+7CHG0vjxV5wXV+uhgEBtD6Js546G8pXkHUcbjIWFDPTOresepMhZIKQBUGMQOuuoOny95UUKt1P1lidi5GpQ4Uprc7brDDfORmskGN6ZUNRzwuhImdBS2mriJpQGdWtGuwEtOEu8bh1z2pxLl/6GPiq+eALi0MjdRSQT/EQ4qySRacDwmaFbYf/GjQ6lFoNi8mbSVXLa7Q7cNMSn0EK0iU6sqVb27q4VtyG4tPrd9mWT+ktlriddfMETKbcidyRZyNsMXcxd/vUCZ3i8zSU6iGZjl/zExRtlmoWbk4l8ei7Q6InSkfhm/CjoVYCzlj0puv9Gkts1hOaSkzBC27RVM8ozhRQiZK9RI7d0tw4ctMCDF9dQeiP337ERm9ySfpSgiUZorOfr15TN4a17s=
|
||||
192.168.0.196 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMnda1pPxmo0Y6P7w/zL91wuvogHQRkWtgpKP+EGkyiRxABQMfQeRQcHBA8iS1HlQGjJXXZBqLQ9O66GV8skn48=
|
||||
192.168.27.205 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOzGgg2/yT08NcAB3gFGAEBLpLhqvxlMat4jtd3rTaA
|
||||
192.168.10.211 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEi6WlACkMGgkVdb1VnIkO7VEuVbgP0DJ/D87moHWqOb
|
||||
192.168.7.33 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVK1+ZBO03DdZHI1OoMA8757P3Bm6QoxV04oZdVAuXm
|
||||
192.168.7.34 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDZJJyRDsLdUWMRTx7R8v8b5T1yoRhYmngJc6mp/TiMk
|
||||
192.168.7.39 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMp2w6b/5owCtWKKVHMoZ5fMU//HkJWSG2KVu8wDpmBe
|
||||
192.168.7.7 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFFlhwOsWFBDSkHhyO+Pi81bPLHul7fOV6EvObWT+qrD
|
||||
192.168.10.211 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCCGTIdi5wwNsJpFAk5cVd6I1keWD1Hfi6KVJkKnbqtrSeH0QIF55ne3SqUlMPZJfuKMXQXIhRgnsuHZZyneNBD/U4TULQWgoXriB/tgCyw1p5rHCv07+L/Lb9bBef9uoJcpNMfkhMEu8HCxy8DVTyV/uUmrNmduLCNNLAnRjoU/Fcq1qbKGHDUv2jPwCrD1ppbo2wktURqw78s2Ds5fqtt4EMriwAgh/lJQACySYxAX8nHK+yW9KwmRq1bjGJ3A09KR6Mqr70yFjjD4xdnoT9AL+91ij/u7kRR8ltaAjg80M+pxnwKcO6rgOf1a1Yr+ITVJGwnpiF9L8WDKhdrGphISR5bikPPektgwwxl2Iv1gRFB8Uh6UlgAxkuiyPAcdXgfcsG4vfqJzw81SsWMdYN0qKt68noEEekeuPMMZQrJ5nir7vjlf9lVVXPECvz7T3y/hBjj/7RK2wXuO2CiRPlxUZAE4J3t6u0dCkX7WkJPidre0HS4nkXAWkiugn+VSM=
|
||||
192.168.10.211 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK8/waWJ1Ygx0tyhz05kNrCKNC8lnPXPZzLXIkzpBo0haKr+FkRs3QiIX2HbNkGuWxoohaAqDyna/WfiTEATYZU=
|
||||
192.168.27.205 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDVsK1XVB+JXJcSLZVoXGRYURFqVLe5yBF5TU7ivHWDv9i6j/CQyYQ2Vlej2rSw6Ke/BEN+Oq/vSUDbdN6y1D+r7VEZ3PeMGEiYAX6aeTRpT998Jr5wdUELj9YV04SIVVXXVDXSiJbzXDx7q0klkTjiqWMK6VirV0PzmEvgrsz2c6hwvLgVlz+fUj2auukhBv3TOhcuvnCHrhfVnJxRtQkbZAj5D8hw95p3bia0rLCgcww/PlSgFKfbJzmyZ0SVBcq+bFx75C558hKL5x4EKqRcWdXYJnOQT/OvjuiqbgDQXO7BziDkc6Vpz7/dh26UhH63kexpgxQ//s0MzQyPm1HbTdblUjajzFOjfO7GtuLbbGdSN40Ve8n9qwQmiwtbpKPwmsP9/b4Vs1Lzd+GRV2miw4umweRGBLejq231audtOcZ6DPcbafUlv3/fhTP3q+PlepUWUPfsnxUCusmwivgRSA6gFbXMdaTqEzCJqlwP/fTPTdErFeEfpIhSWIXqIYs=
|
||||
192.168.27.205 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI7sl6SFn5qSEDjppViXdAFYV5AIQUaPXrqc7EwSYwUpMMO4sLzrXO2C2+LtzuDzw+K+bYmkOUVzlKu6o3ZmRP8=
|
||||
192.168.7.39 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCukRVDCkX9v56UBEZgPhfEifbaMJAdwAg/38PbV9zvJeM8M2+LFObhgjnlrU4rw977TbenjpifpBcenaI8bK/uPAgz6PGBxF/b1EQ9HwZDrq1K61Eq4BAQOWaXikAcrzALp7BlbRxXQ18mhT3CTpU8Mm0cEPTixNBIc84V2/5iONoNFlG9gGrA0pcJoffrcZQqzZNEu4z86NvT2nUm9UKbqOru2tzhNLrfksaiIkv6mdeAImZ+8IxNey4WmQM9hMwNKdkgXZ7xpDGQHbS3MqAgVaW5PzwJLqfRq2hEq2lxsxuI/ldg+SYmnUflA4FSjq6xcUCJo3jr62JcgG3qhzQQLGQ3m/Xxz50+SMF4KiFGwMTdas+7LoKhkX5iPRbTKymuGf+uc51p3jquv2sb3Eh08S1HGk3/v9MdpyoQsO9k/Wvtj2ykxsdPxY2chTirIC7FAlqUGqC4VIEVjV6Nxa2XS1DXe/1YkoqTRTTCNJ9RyAsmNiycHbctSgnmYYdm4VM=
|
||||
192.168.7.39 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD5mhLaDlpL9R3HEy3unZfBkdwRK3DIa0XGz2G3W9bmlVZ706DNzTW7/fAXaaI3OHOQ/+mwx5p0M6HA6aCSIWok=
|
||||
192.168.7.33 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClmcKiM9vjJ4qoSVb5Vcn0l2wXyNs/qDKZls4HFgMNx6Hy1c1RqBW7owxaL0BUdteU8Ar7/zFG4GwhmtoKQNBcMwo/2XkdHcL1IHHQ5lceczAhS1Qd8XLK4xK51SostxY3H8xmxxMNh9VF4F0D/lZvXC4Eb+aMPJEOe5xJb5JumvNCKX0kGLrd5FJ1g1taHSKZcPjtxcqBXgRuaPADh6PrBqHg9z/rgzwrJwTlSTON+Sy18LHgDpgCerUD8y1G8Iad9sfuIAeED6S9gFZV8oFl0+buK/Nfw5ju32nxM2KVrb5eES4q02v1Tbz3I3SXcK0tUDY9vCv/6k+Joxl3xUtG7WWq2qF0f2+a7I2vVH7VPpR8pxUIUgGosLPJdGuf5qVsj4R+YbxSklF1eoPPLXvUn7IIRa2+MwOW2ybU8vyQV4k1gCbof3JKpByiC+jfEKe7ZBqRXYRJ0ehop5Cu9eFYDyZCDFP2ygerVeJ9sLeMHAdGBxgIzUXbpuOD0AcW1nE=
|
||||
192.168.7.33 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFcv91R9UD8y+7ngkgaWDBu0apqSTHOXd8rUuBRWUFWW0CpvDl0TlwYGjxXEfAz6ca7cfEYavbVqgSjPc/RsR8o=
|
||||
192.168.7.34 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCreZi3n77vQBSXGUMbPj+G+DS/0YRowxqXH2o4AdfZEBmzgPa1O9u9XhKCjqeFqeQUMWp/a5EXwqmeqh80Ty/wrq66vaR0EyhxDFWvV45euSLv4WUz7oV7SO844ygt3iwc67UYPz7vk1y+UFxYW1n/Ya8tyXqL6aymHw1Sjy79+fWoTOM3eqmaoft5Q9MQmQT09ks7Wts1p2aHWdVs5dFahlF/Q4z+KC4EILiMcyr0U5VMkSpduAmSaopy/2qujUwZUynysP5UGx7virncSI0wuJSv/N63igeXTcfkYQKdw4iOkB4MWWeMwgKv1PjOvse2x4fH6bnRFtm4Zr2a79SlE7vqwzZCpqF0FAh8LGT3xgDVjfeRKIRiXTN43Hi5rNRvE5cgICKUMPX1ztOFTW0tUFoti21zTHRrMZGhsl7lhjolz9m8sD3Yv4Za1M8IZHCQET1VWJbNBmgeOtW1PvuIS6x/158cHRovj3BcHqh48nTv4oE7wdVAzMBlR7rYZv0=
|
||||
192.168.7.34 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH8AfCqibVctmBw8r9fQTUwsWMwXJX0sWLCNsG7OA3C4nGLaWW8Go7wrrPlcGADX4TfpjmvKOFO+LdvK4MZgB6w=
|
||||
192.168.7.7 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDOvAVwkT7DLu5u06fG14gEAjBnMLjUIkRVRSCvtxOi3EJcT7VrG9OkS65Q5seAKO1wORFAhhKeszf+d7SgpnPZ7di1KNE67gFodxlI0MqlD0l/k04wd/zIFTsWZCCMZZBCNcS0eL0xYT8mPjKiLF7kmPUmWNCwDn70V2jaWqsknAUHmvGqTbUgKyBoKyt/B5mB6DWND4tVlnMZqWHgDMCWhDszPLMN1R99C+5nWFwlWSsRLIOC6VzX5jEZrbDBf6+YYucqyhk+p7cd219tEMPWxhG3fmwhlGWURW0SRPdsY+kQkue3r5KPZF7fVeTiq7RxshuoUK3KKoHnP4cTWjkNUbDL/1ZT8wQe0ju/Zi7q7iEV4X/Cy/AXGsevPW/M9lJURcI6ML5laHWoLORj2G97+4nl9rZjjYAMjut+4q/8txrAa0Z4tNZ6Am6GfcpsiRzL0FZ/SbWj8qexoICb3o5Cy9KmiCMbbrl3a39nvlxgRYURl3bRoT4Gzci1l2nE+3s=
|
||||
192.168.7.7 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBxz4F9L3QBaNRo6FelZPfkptORSqkOxrpKKE0NH2f2oKILJLCmGA3tXUGTFDNdP0WD79YLphwzWsDOLJIrIPG4=
|
||||
192.168.32.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJxjNw/JPdhqFJNROddbG6gvpCu9RQKg6vycgtEy6Z4J
|
||||
192.168.32.11 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAj2HRnhG1KiHre5TAmTC0RB2d3seeud6VELaWwiEDPB
|
||||
192.168.32.12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEsqfgjH2aIqAUe70Tee1hCzYDAg9nWnlwJlDVZonLoD
|
||||
192.168.32.13 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOWBozwUgS3BjBsf4BnyqNlWpQVCmRAJ0sYjj/MrNLbw
|
||||
192.168.3.38 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQh3rwLm7zJLoKal82RCps3oClsCOxKaDWCW1fBF+PQ
|
||||
192.168.3.39 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBaYbJyvFt6YbrNs+Hw1xpNnTktBARZI7Bon3CY4oEu
|
||||
192.168.3.40 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE9+kfeKGhv7jIzNomyNpTDS8HJX3Ji/Plz8rssbS0YU
|
||||
192.168.3.42 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII85+SoGNmcuqgAPKAzyymDLL0vrmJxJ9kBpRuLekWzK
|
||||
192.168.3.41 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMkSIaJdPRfIRTZNDEECrQL5z+f225q6DRMccWEzfV8b
|
||||
192.168.12.34 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIOd3NnovP0Cx/pLKJbr5fiE2VERVe837k4D4RBqYSFW
|
||||
192.168.12.35 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF2j0uPxZGRIeKjxtmSA+dZtE/LhdczIOLkrp+nvgEhh
|
||||
192.168.12.36 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAt0h28NVbrnyt8yc8BjWxCdNiYNnCa9PlkJfEL/y1lu
|
||||
192.168.12.37 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINxfKbnPhXf009YJUIv641hqRmtEtfn2qwv1wD3IVqqn
|
||||
192.168.61.23 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWCUT7Xd0Ah91rmngKaFDYN/15/QWNVEfJAMpwHpjM6
|
||||
192.168.61.24 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIESllML+WHWWi58d0STdL6kJSZlV1xVcKHbtv/Dm7pVV
|
||||
192.168.61.25 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILezLcvmUXSL1chhCFaeZz4iSzlB700lsqEibPms38UK
|
||||
192.168.61.26 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1AkSyuJOf9yhV+D8/KJZMwxEq37OdIfHnqHLEI6EQC
|
||||
192.168.61.27 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWwK0LsPVo4l1/slWF+tcFveNzACJxQAeal+m9A7pV5
|
||||
192.168.50.41 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAX91JvjhqCG2/p7iBrXGhvR1UTnlqvEzEKR46JFRGN
|
||||
@@ -0,0 +1,28 @@
|
||||
192.168.15.182 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEotq4gp1TaI6KY+ZlzU+Sp6wKOQxCsBOXcTedsscdnr
|
||||
192.168.5.214 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIA3LSBVGXwU8beehCbOnrXaF7v//nDbOzZJIo77KKAg
|
||||
192.168.0.196 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILyV6JsdRGtCcDuxk1Btf/6o21lPFUmxcKtGQQ0KGznf
|
||||
192.168.6.5 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICQ9ZfPqssWaCjx9WGZu23nN1uSyEpuHR1cFqy5x9cBv
|
||||
192.168.15.182 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7i2ToCbuFKb9w3cZLQstGM1XjsHpIUpWta68NN2XsRfjltfozGBcd+RkTyDwpzgb1ElEzJFoHMtKNmIX4ueavFdQeM6GdjKUD0pSjb3K+oBUDDAS2z3vyehbj9Nf4soFn5e3pP9OvxQ6Fkl5Fln8ySbCvvybA7Z61ws44Nk9Bt1GTH4aC0R1ZvzBoE2eXe0crg7x53i6EO3EzkUOebwaUiaTE4B/bIjLda0gQbr2u0idEaTY6aIAARWDnHwXD/KYtFxi/9RZ3SCHWksSBTVje4hHWYgwjCjRzPuZZ7FAmRtTTu6q90Ja1SSAk6myI4K5T/Gzo/HBsJ99OFs/NuTAXLjK4j+QnDsTfBlOIwneZeNw95L+4CUowP4LyaT3FA5ZKtMMnzfNKQcDyDu7vzQC216Vdio5WowazeQOUnOK3Af+t7oVH5RZ6IoafRZmTWBUVTSSHW5iTHa0gVGAerH8lakLo9pKzu9t+nv3iOQWL5rIOfWoAvNxDcGEdIh0SWfc=
|
||||
192.168.15.182 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPV51QE7y1uc+7vAnD2rm5GHlv9vJfqHcxlA7gy7mSb0VQ89TgyOnXEHy8yZJ5cRdCNvvX68CEt+FnZkaK8IjkQ=
|
||||
192.168.6.5 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDP0/X9xIy0R2jHPwBdM5KydJCk3qfelRiW+6vpFMKKUd7PL/QHEVmRfd28HMnPRoBuY3tzbx6/u2VTYwwsQQaW+5g1L7o3ju5qlBdKY1XFAvm0En6DVg6gz4RRBIlRJPx6DbSdRVdzYLsGCs9T4GFBZG4d39gnR2kWGEYfV6jCYKULGXf1CQBg6mFUdcjCORXX548CgYVj5jNmXC8BRVOezOTkPMaODUJN/fwcCB6Jasfwuv+4DKADhs91tDca4dGcWfdLH2jLT5sGkp3CkFNEu2XNoct6+tJEq7v212nSErFnAO9VMPIQagZGfuoUspjoPPNN0i0/AWSx0I3TwtGw4FdUwDb+WH5ZqPOgnwF5i9utEJETihJF2f8bac0gDKVPErd4EnyWjfT1wcqscNeJ/6Q9zl5Mj6VbxKRi62PSkpjHAk+qjO3GLlKZpVv5lUr9nz2efIUH2ZMSqKdrffkfDzatO4S53YLhpKJB54ka6W2l/eOA+vO+Q/BkoWp1DPE=
|
||||
192.168.6.5 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHh302+xQi3WZ05mCLW5oKwUOkCjos4BpOyfiCOnc6MmcV8aXoiOlueV/uqQlYuG2ZxJQ6OpRTjONOsSKSbCnhM=
|
||||
192.168.5.214 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEL4xa599zwNOKAxXvVbUhuuxRS5Acj0RHVUskJsqA5Ll5Poa1uFHZXExgZap98YLy5RT1NwYXLy6ElE2ly32STfiVRP2cX80LHspvtr8+xl65qTlE9oSiA/imY2vifd6NAxC0K+kfYMLO4l6/GBbd9OVrfJLju5x6aijzoF820wqm3OrlhuibMmgkAn8TmQnih+pQCYxrjgMCWZFbzv5i8C6EBBCtArW950NPJHwoTFrcvGydpZU1hlh6o6rKWcf1fJ2VYSXNTG7TFzhiB+ezZnmy+hgtF52E+tTBnFkPlSRfQIdAYxi93HIrsh4Vk0fyVuaffMRXH9FmH/2VPRqvplaTVO0fDrghufB/eiW/HhlYuRHtZsx5/ZFJQDeViBtxHn0lQevIaDQvCkrJio59IG1mzVa7Pf7yh1XmWV0ipbX47CUNs9O97y/Z1fl80qycLzr9L+dYpGKGrJpqKndMKsW026WSvaTeuvz6r3F58HJHa+Eu+L3FGE1/rTrI5S8=
|
||||
192.168.5.214 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKrxrlDM1kv3GtLl04xkDmXFKTQjmISt+v5Y3sVire/CJxVygzZei6xYeZcWgWzdNnTKccX10kKGiWUy9nqn71M=
|
||||
192.168.0.196 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDR8JGtc6afihCVPAKt8N4/UNhXqPikFzRkWrAlZTWcnVPKzExR/bu+d+wIRB4qdDxX/i/oG+Q+OwfFPeMn0trF0CBGvS/AbmQci+rw/eHgsd5kL+7CHG0vjxV5wXV+uhgEBtD6Js546G8pXkHUcbjIWFDPTOresepMhZIKQBUGMQOuuoOny95UUKt1P1lidi5GpQ4Uprc7brDDfORmskGN6ZUNRzwuhImdBS2mriJpQGdWtGuwEtOEu8bh1z2pxLl/6GPiq+eALi0MjdRSQT/EQ4qySRacDwmaFbYf/GjQ6lFoNi8mbSVXLa7Q7cNMSn0EK0iU6sqVb27q4VtyG4tPrd9mWT+ktlriddfMETKbcidyRZyNsMXcxd/vUCZ3i8zSU6iGZjl/zExRtlmoWbk4l8ei7Q6InSkfhm/CjoVYCzlj0puv9Gkts1hOaSkzBC27RVM8ozhRQiZK9RI7d0tw4ctMCDF9dQeiP337ERm9ySfpSgiUZorOfr15TN4a17s=
|
||||
192.168.0.196 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMnda1pPxmo0Y6P7w/zL91wuvogHQRkWtgpKP+EGkyiRxABQMfQeRQcHBA8iS1HlQGjJXXZBqLQ9O66GV8skn48=
|
||||
192.168.27.205 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOzGgg2/yT08NcAB3gFGAEBLpLhqvxlMat4jtd3rTaA
|
||||
192.168.10.211 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEi6WlACkMGgkVdb1VnIkO7VEuVbgP0DJ/D87moHWqOb
|
||||
192.168.7.33 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVK1+ZBO03DdZHI1OoMA8757P3Bm6QoxV04oZdVAuXm
|
||||
192.168.7.34 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDZJJyRDsLdUWMRTx7R8v8b5T1yoRhYmngJc6mp/TiMk
|
||||
192.168.7.39 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMp2w6b/5owCtWKKVHMoZ5fMU//HkJWSG2KVu8wDpmBe
|
||||
192.168.7.7 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFFlhwOsWFBDSkHhyO+Pi81bPLHul7fOV6EvObWT+qrD
|
||||
192.168.10.211 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCCGTIdi5wwNsJpFAk5cVd6I1keWD1Hfi6KVJkKnbqtrSeH0QIF55ne3SqUlMPZJfuKMXQXIhRgnsuHZZyneNBD/U4TULQWgoXriB/tgCyw1p5rHCv07+L/Lb9bBef9uoJcpNMfkhMEu8HCxy8DVTyV/uUmrNmduLCNNLAnRjoU/Fcq1qbKGHDUv2jPwCrD1ppbo2wktURqw78s2Ds5fqtt4EMriwAgh/lJQACySYxAX8nHK+yW9KwmRq1bjGJ3A09KR6Mqr70yFjjD4xdnoT9AL+91ij/u7kRR8ltaAjg80M+pxnwKcO6rgOf1a1Yr+ITVJGwnpiF9L8WDKhdrGphISR5bikPPektgwwxl2Iv1gRFB8Uh6UlgAxkuiyPAcdXgfcsG4vfqJzw81SsWMdYN0qKt68noEEekeuPMMZQrJ5nir7vjlf9lVVXPECvz7T3y/hBjj/7RK2wXuO2CiRPlxUZAE4J3t6u0dCkX7WkJPidre0HS4nkXAWkiugn+VSM=
|
||||
192.168.10.211 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK8/waWJ1Ygx0tyhz05kNrCKNC8lnPXPZzLXIkzpBo0haKr+FkRs3QiIX2HbNkGuWxoohaAqDyna/WfiTEATYZU=
|
||||
192.168.27.205 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDVsK1XVB+JXJcSLZVoXGRYURFqVLe5yBF5TU7ivHWDv9i6j/CQyYQ2Vlej2rSw6Ke/BEN+Oq/vSUDbdN6y1D+r7VEZ3PeMGEiYAX6aeTRpT998Jr5wdUELj9YV04SIVVXXVDXSiJbzXDx7q0klkTjiqWMK6VirV0PzmEvgrsz2c6hwvLgVlz+fUj2auukhBv3TOhcuvnCHrhfVnJxRtQkbZAj5D8hw95p3bia0rLCgcww/PlSgFKfbJzmyZ0SVBcq+bFx75C558hKL5x4EKqRcWdXYJnOQT/OvjuiqbgDQXO7BziDkc6Vpz7/dh26UhH63kexpgxQ//s0MzQyPm1HbTdblUjajzFOjfO7GtuLbbGdSN40Ve8n9qwQmiwtbpKPwmsP9/b4Vs1Lzd+GRV2miw4umweRGBLejq231audtOcZ6DPcbafUlv3/fhTP3q+PlepUWUPfsnxUCusmwivgRSA6gFbXMdaTqEzCJqlwP/fTPTdErFeEfpIhSWIXqIYs=
|
||||
192.168.27.205 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI7sl6SFn5qSEDjppViXdAFYV5AIQUaPXrqc7EwSYwUpMMO4sLzrXO2C2+LtzuDzw+K+bYmkOUVzlKu6o3ZmRP8=
|
||||
192.168.7.39 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCukRVDCkX9v56UBEZgPhfEifbaMJAdwAg/38PbV9zvJeM8M2+LFObhgjnlrU4rw977TbenjpifpBcenaI8bK/uPAgz6PGBxF/b1EQ9HwZDrq1K61Eq4BAQOWaXikAcrzALp7BlbRxXQ18mhT3CTpU8Mm0cEPTixNBIc84V2/5iONoNFlG9gGrA0pcJoffrcZQqzZNEu4z86NvT2nUm9UKbqOru2tzhNLrfksaiIkv6mdeAImZ+8IxNey4WmQM9hMwNKdkgXZ7xpDGQHbS3MqAgVaW5PzwJLqfRq2hEq2lxsxuI/ldg+SYmnUflA4FSjq6xcUCJo3jr62JcgG3qhzQQLGQ3m/Xxz50+SMF4KiFGwMTdas+7LoKhkX5iPRbTKymuGf+uc51p3jquv2sb3Eh08S1HGk3/v9MdpyoQsO9k/Wvtj2ykxsdPxY2chTirIC7FAlqUGqC4VIEVjV6Nxa2XS1DXe/1YkoqTRTTCNJ9RyAsmNiycHbctSgnmYYdm4VM=
|
||||
192.168.7.39 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD5mhLaDlpL9R3HEy3unZfBkdwRK3DIa0XGz2G3W9bmlVZ706DNzTW7/fAXaaI3OHOQ/+mwx5p0M6HA6aCSIWok=
|
||||
192.168.7.33 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClmcKiM9vjJ4qoSVb5Vcn0l2wXyNs/qDKZls4HFgMNx6Hy1c1RqBW7owxaL0BUdteU8Ar7/zFG4GwhmtoKQNBcMwo/2XkdHcL1IHHQ5lceczAhS1Qd8XLK4xK51SostxY3H8xmxxMNh9VF4F0D/lZvXC4Eb+aMPJEOe5xJb5JumvNCKX0kGLrd5FJ1g1taHSKZcPjtxcqBXgRuaPADh6PrBqHg9z/rgzwrJwTlSTON+Sy18LHgDpgCerUD8y1G8Iad9sfuIAeED6S9gFZV8oFl0+buK/Nfw5ju32nxM2KVrb5eES4q02v1Tbz3I3SXcK0tUDY9vCv/6k+Joxl3xUtG7WWq2qF0f2+a7I2vVH7VPpR8pxUIUgGosLPJdGuf5qVsj4R+YbxSklF1eoPPLXvUn7IIRa2+MwOW2ybU8vyQV4k1gCbof3JKpByiC+jfEKe7ZBqRXYRJ0ehop5Cu9eFYDyZCDFP2ygerVeJ9sLeMHAdGBxgIzUXbpuOD0AcW1nE=
|
||||
192.168.7.33 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFcv91R9UD8y+7ngkgaWDBu0apqSTHOXd8rUuBRWUFWW0CpvDl0TlwYGjxXEfAz6ca7cfEYavbVqgSjPc/RsR8o=
|
||||
192.168.7.34 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCreZi3n77vQBSXGUMbPj+G+DS/0YRowxqXH2o4AdfZEBmzgPa1O9u9XhKCjqeFqeQUMWp/a5EXwqmeqh80Ty/wrq66vaR0EyhxDFWvV45euSLv4WUz7oV7SO844ygt3iwc67UYPz7vk1y+UFxYW1n/Ya8tyXqL6aymHw1Sjy79+fWoTOM3eqmaoft5Q9MQmQT09ks7Wts1p2aHWdVs5dFahlF/Q4z+KC4EILiMcyr0U5VMkSpduAmSaopy/2qujUwZUynysP5UGx7virncSI0wuJSv/N63igeXTcfkYQKdw4iOkB4MWWeMwgKv1PjOvse2x4fH6bnRFtm4Zr2a79SlE7vqwzZCpqF0FAh8LGT3xgDVjfeRKIRiXTN43Hi5rNRvE5cgICKUMPX1ztOFTW0tUFoti21zTHRrMZGhsl7lhjolz9m8sD3Yv4Za1M8IZHCQET1VWJbNBmgeOtW1PvuIS6x/158cHRovj3BcHqh48nTv4oE7wdVAzMBlR7rYZv0=
|
||||
192.168.7.34 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH8AfCqibVctmBw8r9fQTUwsWMwXJX0sWLCNsG7OA3C4nGLaWW8Go7wrrPlcGADX4TfpjmvKOFO+LdvK4MZgB6w=
|
||||
10
artemis-backup-20260614-1920/ansible-push/lxc-common.sh
Executable file
10
artemis-backup-20260614-1920/ansible-push/lxc-common.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# LXC Common Provisioning Runner (based on configure.sh)
|
||||
# Spins up persistent ansible container, runs lxc_common role, then stops
|
||||
|
||||
docker compose up -d
|
||||
|
||||
docker exec -it ansible ansible-playbook playbooks/main.yml -i terraform-prefill/inventory-lxc.yml --limit lxcs --tags lxc_common -v
|
||||
|
||||
docker compose down
|
||||
|
||||
33
artemis-backup-20260614-1920/ansible-push/playbooks/main.yml
Normal file
33
artemis-backup-20260614-1920/ansible-push/playbooks/main.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
- name: Prepare Systems
|
||||
hosts: physical_agents:core_services
|
||||
become: true
|
||||
roles:
|
||||
- prepare
|
||||
|
||||
- name: Install NFS client
|
||||
hosts: fleet_nodes:!pve_hosts:!igor
|
||||
become: false
|
||||
roles:
|
||||
- nfs_client
|
||||
|
||||
- name: Fleet update (apt update + upgrade)
|
||||
hosts: managed_nodes
|
||||
become: true
|
||||
tags:
|
||||
- fleet_update
|
||||
roles:
|
||||
- prepare
|
||||
|
||||
- name: LXC common provisioning (git + ansible)
|
||||
hosts: lxcs
|
||||
become: true
|
||||
tags:
|
||||
- lxc_common
|
||||
roles:
|
||||
- lxc_common
|
||||
|
||||
- name: Install Wolfstack and keepalived
|
||||
hosts: wolfstack
|
||||
become: true
|
||||
roles:
|
||||
- wolfstack
|
||||
@@ -0,0 +1,69 @@
|
||||
- name: Ensure apt cache is updated
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
become: true
|
||||
no_log: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install git
|
||||
ansible.builtin.apt:
|
||||
name: git
|
||||
state: present
|
||||
become: true
|
||||
no_log: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install Python pip (needed for ansible via pip)
|
||||
ansible.builtin.apt:
|
||||
name: python3-pip
|
||||
state: present
|
||||
become: true
|
||||
no_log: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Create jarvis user with UID 1000
|
||||
ansible.builtin.user:
|
||||
name: jarvis
|
||||
uid: 1000
|
||||
shell: /bin/bash
|
||||
create_home: true
|
||||
groups: sudo
|
||||
append: true
|
||||
become: true
|
||||
|
||||
- name: Ensure jarvis .ssh directory exists
|
||||
ansible.builtin.file:
|
||||
path: /home/jarvis/.ssh
|
||||
state: directory
|
||||
owner: jarvis
|
||||
group: jarvis
|
||||
mode: "0700"
|
||||
become: true
|
||||
|
||||
- name: Copy root authorized_keys to jarvis
|
||||
ansible.builtin.copy:
|
||||
src: /root/.ssh/authorized_keys
|
||||
dest: /home/jarvis/.ssh/authorized_keys
|
||||
owner: jarvis
|
||||
group: jarvis
|
||||
mode: "0600"
|
||||
remote_src: true
|
||||
become: true
|
||||
|
||||
- name: Ensure jarvis has passwordless sudo
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sudoers.d/jarvis
|
||||
line: "jarvis ALL=(ALL) NOPASSWD:ALL"
|
||||
create: true
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
become: true
|
||||
|
||||
- name: Install ansible via pip as jarvis
|
||||
ansible.builtin.pip:
|
||||
name: ansible
|
||||
state: present
|
||||
break_system_packages: true
|
||||
become: true
|
||||
no_log: true
|
||||
@@ -0,0 +1,59 @@
|
||||
- name: Install nfs-common
|
||||
ansible.builtin.apt:
|
||||
name: nfs-common
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Ensure NFS mount directories exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: jarvis
|
||||
group: jarvis
|
||||
become: true
|
||||
loop: "{{ nfs_shares }}"
|
||||
loop_control:
|
||||
label: "Directory: {{ item.path }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Create local repogroup matching TrueNAS GID 568
|
||||
ansible.builtin.group:
|
||||
name: repogroup
|
||||
gid: 568
|
||||
state: present
|
||||
become: true
|
||||
|
||||
- name: Add jarvis to repogroup
|
||||
ansible.builtin.user:
|
||||
name: jarvis
|
||||
groups:
|
||||
- repogroup
|
||||
append: true
|
||||
become: true
|
||||
|
||||
- name: Mount an NFS volume (root, because kernel mount)
|
||||
ansible.posix.mount:
|
||||
src: "{{ item.src }}"
|
||||
path: "{{ item.path }}"
|
||||
opts: "vers=4.2,proto=tcp,_netdev"
|
||||
state: mounted
|
||||
fstype: nfs
|
||||
become: true
|
||||
loop: "{{ nfs_shares }}"
|
||||
loop_control:
|
||||
label: "Mounted: {{ item.src }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Set mount permissions so jarvis (repogroup member) can write
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
mode: '0770'
|
||||
owner: root
|
||||
group: repogroup
|
||||
become: true
|
||||
loop: "{{ nfs_shares }}"
|
||||
loop_control:
|
||||
label: "Permission fix: {{ item.path }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
@@ -0,0 +1,12 @@
|
||||
- name: Run "apt update"
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Upgrade the OS (apt-get dist-upgrade)
|
||||
ansible.builtin.apt:
|
||||
upgrade: dist
|
||||
when:
|
||||
- ansible_os_family == "Debian"
|
||||
- not (gpu | default(false))
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Check for existing wolfstack installation
|
||||
ansible.builtin.command: which wolfstack
|
||||
register: wolfstack_exists
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Run Wolfstack installer
|
||||
ansible.builtin.shell: |
|
||||
curl -sSL https://raw.githubusercontent.com/wolfsoftwaresystemsltd/WolfStack/master/setup.sh | bash
|
||||
args:
|
||||
executable: /bin/bash
|
||||
when: wolfstack_exists.rc != 0
|
||||
|
||||
- name: Ensure wolfstack service is running and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: wolfstack
|
||||
state: started
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
|
||||
- name: Install keepalived package
|
||||
ansible.builtin.apt:
|
||||
name: keepalived
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ansible_os_family == "Debian"
|
||||
11
artemis-backup-20260614-1920/terraform-pve-vm/CHECKPOINT.md
Normal file
11
artemis-backup-20260614-1920/terraform-pve-vm/CHECKPOINT.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# CHECKPOINT — Terraform PVE VM Deployment
|
||||
**Session date:** 2026-06-12
|
||||
**Chunk:** 1 of 5 — PRD Draft written
|
||||
**Next chunk:** 2 — Await Bobby review on PRD decision points, then build Terraform files
|
||||
**Files modified:**
|
||||
- ~/documentation/PRD Drafts/terraform-pve-vm-deployment.md (new)
|
||||
**Blockers:** None — awaiting Bobby approval on:
|
||||
1. Cloud image download method (wget per node vs NAS)
|
||||
2. Disk format (raw vs qcow2)
|
||||
3. BIOS vs EFI
|
||||
4. QEMU Guest Agent enablement
|
||||
5
artemis-backup-20260614-1920/terraform-pve-vm/Dockerfile
Normal file
5
artemis-backup-20260614-1920/terraform-pve-vm/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM hashicorp/terraform:latest
|
||||
|
||||
RUN apk add --no-cache openssh-client
|
||||
|
||||
WORKDIR /workspace
|
||||
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
terraform:
|
||||
build: .
|
||||
container_name: terraform-pve-vm
|
||||
volumes:
|
||||
- ./terraform:/workspace
|
||||
- ~/.ssh/artemis_key.pub:/workspace/artemis_key.pub:ro
|
||||
working_dir: /workspace
|
||||
network_mode: host
|
||||
stdin_open: true
|
||||
tty: true
|
||||
command: ["version"]
|
||||
22
artemis-backup-20260614-1920/terraform-pve-vm/run.sh
Normal file
22
artemis-backup-20260614-1920/terraform-pve-vm/run.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
case "${1:-plan}" in
|
||||
init|validate|plan|apply|destroy)
|
||||
docker compose run --rm terraform "$1" "${@:2}"
|
||||
;;
|
||||
shell)
|
||||
docker compose run --rm terraform /bin/sh
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [init|validate|plan|apply|destroy|shell]"
|
||||
echo ""
|
||||
echo "Runtime overrides via -var flags:"
|
||||
echo " $0 apply -auto-approve \\"
|
||||
echo " -var=\"node=mk34\" \\"
|
||||
echo " -var=\"vmid_base=3035\""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,90 @@
|
||||
locals {
|
||||
# VM definitions: name, node, vmid, static IP
|
||||
vms = {
|
||||
for idx, suffix in var.name_suffixes : "vm-${suffix}" => {
|
||||
name = "${var.name_prefix}-${suffix}"
|
||||
node = element(["mk33", "mk34", "mk39"], idx)
|
||||
vmid = var.vmid_base + idx
|
||||
ipv4 = "${var.subnet_prefix}.30.${var.vmid_base + idx}/18"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_vm" "ubuntu_vm" {
|
||||
for_each = local.vms
|
||||
|
||||
name = each.value.name
|
||||
node_name = each.value.node
|
||||
vm_id = each.value.vmid
|
||||
|
||||
tags = concat(var.tags, [each.key])
|
||||
|
||||
# ── CPU / Memory ──────────────────────────
|
||||
cpu {
|
||||
cores = var.cores
|
||||
}
|
||||
|
||||
memory {
|
||||
dedicated = var.memory
|
||||
}
|
||||
|
||||
# ── QEMU Guest Agent ──────────────────────
|
||||
agent {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
# ── Boot / EFI ────────────────────────────
|
||||
bios = "ovmf"
|
||||
|
||||
efi_disk {
|
||||
datastore_id = var.storage
|
||||
type = "4m"
|
||||
}
|
||||
|
||||
machine = "q35"
|
||||
|
||||
# ── SCSI Controller ───────────────────────
|
||||
scsi_hardware = "virtio-scsi-single"
|
||||
|
||||
# ── Disk (Cloud-init image) ───────────────
|
||||
disk {
|
||||
datastore_id = var.storage
|
||||
file_id = var.cloud_image_id
|
||||
interface = "scsi0"
|
||||
iothread = true
|
||||
discard = "on"
|
||||
size = var.disk_size
|
||||
}
|
||||
|
||||
# ── Network ─────────────────────────────────
|
||||
network_device {
|
||||
bridge = var.bridge
|
||||
}
|
||||
|
||||
# ── VGA / Console ─────────────────────────
|
||||
vga {
|
||||
type = "std"
|
||||
}
|
||||
|
||||
# ── Cloud-Init ─────────────────────────────
|
||||
initialization {
|
||||
datastore_id = var.cloudinit_storage
|
||||
|
||||
user_account {
|
||||
username = "jarvis"
|
||||
password = "ubuntu"
|
||||
keys = [file(var.ssh_key_path)]
|
||||
}
|
||||
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = each.value.ipv4
|
||||
gateway = var.gateway
|
||||
}
|
||||
}
|
||||
|
||||
dns {
|
||||
servers = var.dns_servers
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
output "vm_names" {
|
||||
description = "Created VM names"
|
||||
value = [for vm in proxmox_virtual_environment_vm.ubuntu_vm : vm.name]
|
||||
}
|
||||
|
||||
output "vm_ids" {
|
||||
description = "Created VM IDs"
|
||||
value = [for vm in proxmox_virtual_environment_vm.ubuntu_vm : vm.vm_id]
|
||||
}
|
||||
|
||||
output "vm_nodes" {
|
||||
description = "PVE nodes hosting each VM"
|
||||
value = { for k, vm in proxmox_virtual_environment_vm.ubuntu_vm : k => vm.node_name }
|
||||
}
|
||||
|
||||
output "vm_ipv4" {
|
||||
description = "Static IPv4 addresses"
|
||||
value = { for k, vm in proxmox_virtual_environment_vm.ubuntu_vm : k => vm.initialization[0].ip_config[0].ipv4[0].address }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = "0.70.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "proxmox" {
|
||||
endpoint = var.pm_api_url
|
||||
api_token = "${var.pm_api_token_id}=${var.pm_api_token_secret}"
|
||||
insecure = true
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
variable "pm_api_url" {
|
||||
description = "Proxmox API URL"
|
||||
type = string
|
||||
default = "https://192.168.7.33:8006/api2/json"
|
||||
}
|
||||
|
||||
variable "pm_api_token_id" {
|
||||
description = "Proxmox API token ID"
|
||||
type = string
|
||||
default = "root@pam!terraform"
|
||||
}
|
||||
|
||||
variable "pm_api_token_secret" {
|
||||
description = "Proxmox API token secret"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "node" {
|
||||
description = "Target PVE node"
|
||||
type = string
|
||||
default = "mk33"
|
||||
}
|
||||
|
||||
variable "vmid_base" {
|
||||
description = "Starting VMID for the first VM"
|
||||
type = number
|
||||
default = 3034
|
||||
}
|
||||
|
||||
variable "vm_count" {
|
||||
description = "Number of VMs to create"
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
variable "name_prefix" {
|
||||
description = "Hostname prefix for VMs"
|
||||
type = string
|
||||
default = "vm"
|
||||
}
|
||||
|
||||
variable "name_suffixes" {
|
||||
description = "Per-VM name suffixes (artemis, jarvis, friday)"
|
||||
type = list(string)
|
||||
default = ["artemis", "jarvis", "friday"]
|
||||
}
|
||||
|
||||
variable "subnet_prefix" {
|
||||
description = "First two octets of IPv4"
|
||||
type = string
|
||||
default = "192.168"
|
||||
}
|
||||
|
||||
variable "cores" {
|
||||
description = "CPU cores per VM"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
description = "RAM in MB per VM"
|
||||
type = number
|
||||
default = 4096
|
||||
}
|
||||
|
||||
variable "disk_size" {
|
||||
description = "Disk size in GB"
|
||||
type = number
|
||||
default = 64
|
||||
}
|
||||
|
||||
variable "storage" {
|
||||
description = "Storage pool for VM disk"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "bridge" {
|
||||
description = "Network bridge"
|
||||
type = string
|
||||
default = "vmbr0"
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "IPv4 gateway"
|
||||
type = string
|
||||
default = "192.168.18.1"
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "DNS servers"
|
||||
type = list(string)
|
||||
default = ["192.168.7.7", "1.1.1.1"]
|
||||
}
|
||||
|
||||
variable "ssh_key_path" {
|
||||
description = "Path to SSH public key for jarvis user"
|
||||
type = string
|
||||
default = "artemis_key.pub"
|
||||
}
|
||||
|
||||
variable "cloud_image_id" {
|
||||
description = "Cloud-init image file ID on storage (storage:path)"
|
||||
type = string
|
||||
default = "local:iso/noble-server-cloudimg-amd64.img"
|
||||
}
|
||||
|
||||
variable "cloudinit_storage" {
|
||||
description = "Storage pool for cloud-init drive"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Base tags for all VMs"
|
||||
type = list(string)
|
||||
default = ["terraform", "cloudinit-vm"]
|
||||
}
|
||||
35
artemis-backup-20260614-1920/terraform-pve/CHECKPOINT.md
Normal file
35
artemis-backup-20260614-1920/terraform-pve/CHECKPOINT.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Tailscale Subnet Router LXC Deployment — CHECKPOINT
|
||||
|
||||
## Status: 2026-06-11 22:20 UTC
|
||||
|
||||
### Progress
|
||||
- ✅ **3030** (MK33): Online, advertising `192.168.0.0/18`, route approved
|
||||
- ✅ **3031** (MK33): FIXED — tailscaled running, route approved by Bobby in console
|
||||
- ✅ **Skill created:** `tailscale-lxc-subnet-router` — documents 3031 fix + critical route hijacking pitfall
|
||||
- ✅ **Artemis LAN partition FIXED:** `tailscale set --accept-routes=false` restored direct L2 routing
|
||||
- ✅ **Bobby can now SSH directly to Artemis** (was blocked by Tailscale routing table hijacking)
|
||||
- ⏳ **3032/3033** (MK50): Terraform ready — awaiting deploy signal
|
||||
|
||||
### Critical Discovery — Tailscale Route Hijacking
|
||||
**Root cause of Artemis "network partition":** `tailscale set --accept-routes=true` installed kernel routing rules (table 52) that redirected ALL `192.168.0.0/18` traffic through `tailscale0` instead of direct `eno1`.
|
||||
|
||||
| Before Fix | After Fix |
|
||||
|-----------|-----------|
|
||||
| `ip route get 192.168.50.41` → `dev tailscale0` | `ip route get 192.168.50.41` → `dev eno1` ✅ |
|
||||
| Bobby could NOT SSH to Artemis via LAN | Bobby CAN SSH to Artemis via LAN ✅ |
|
||||
| Artemis could NOT reach fleet nodes via LAN | Artemis routing table correct (pings still fail for some nodes — separate issues) |
|
||||
|
||||
**Standing rule:** Artemis must NOT `accept-routes` while advertising `192.168.0.0/18`. Subnet routers (3030/3031) handle tailnet→LAN; Artemis stays direct L2 for LAN peers.
|
||||
|
||||
### 3031 Fix Summary
|
||||
**Root cause:** `/etc/default/tailscaled` missing `PORT` variable → systemd unit `--port=${PORT}` expanded to `--port=` → `INVALIDARGUMENT`
|
||||
**Solution:** `pct exec` from MK33 host, wrote `PORT=41641` + `FLAGS=`, daemon-reload, restart, re-auth with route-capable key
|
||||
|
||||
### Credentials
|
||||
- Latest LXC auth key stored in `fleet.env` as `TAILSCALE_AUTH_KEY_LXC`
|
||||
- MK50 PVE API token exists in `fleet.env`
|
||||
|
||||
### Next Actions
|
||||
1. Deploy 3032/3033 on MK50 via Terraform when Bobby signals
|
||||
2. Verify all 4 LXCs advertise `192.168.0.0/18` and routes approved
|
||||
3. Investigate remaining LAN unreachability for MK50/MK33/Shield (may be their own accept-routes conflicts)
|
||||
6
artemis-backup-20260614-1920/terraform-pve/Dockerfile
Normal file
6
artemis-backup-20260614-1920/terraform-pve/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM hashicorp/terraform:latest
|
||||
|
||||
WORKDIR /workspace
|
||||
VOLUME ["/workspace"]
|
||||
|
||||
ENTRYPOINT ["terraform"]
|
||||
@@ -0,0 +1,55 @@
|
||||
# Phase 1 POC — Test Notes
|
||||
## Date: 2026-06-04
|
||||
## Status: COMPLETE
|
||||
|
||||
## Test Environment
|
||||
- **Node:** MK33 (192.168.7.33)
|
||||
- **PVE Cluster:** pve-swarm (3-node HA, quorate)
|
||||
- **Terraform:** v1.15.5 + bpg/proxmox v0.70.0 (Docker container)
|
||||
- **Auth:** API Token `root@pam!terraform` (privsep=0)
|
||||
- **Template:** `nas-ct-stor:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst`
|
||||
- **Storage:** `local` (dir-based, NOT local-lvm — verified via live API)
|
||||
|
||||
## What Was Tested
|
||||
1. Single LXC creation via Terraform plan → apply
|
||||
2. OS template extraction from NFS-shared storage
|
||||
3. Network: static IPv4 192.168.50.50/18, gateway, DNS
|
||||
4. Console authentication: root password + SSH key injection
|
||||
5. Features: unprivileged=true + nesting=true
|
||||
|
||||
## Results
|
||||
- ✅ LXC-5050 created successfully (4s)
|
||||
- ✅ OS loaded — Debian 12 console accessible
|
||||
- ✅ Root password "ubuntu" confirmed working
|
||||
- ✅ SSH key (artemis_key.pub) provisioned
|
||||
- ✅ No provider warnings or errors
|
||||
|
||||
## Fixes Applied During POC
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `local-lvm` storage didn't exist | Changed to `local` (verified via PVE API) |
|
||||
| Template auto-downloaded to local | Pointed to existing `nas-ct-stor` NFS share |
|
||||
| `.env` + Docker env mapping broken | Replaced with `terraform.auto.tfvars` (native Terraform) |
|
||||
| `run.sh` didn't forward flags | Fixed `${@:2}` handling for `-auto-approve` |
|
||||
| `features { nesting=true }` caused 500 | Combined with `unprivileged=true` — resolved |
|
||||
| No console login credentials | Added `password` to `user_account` block |
|
||||
| Provider schema errors | Verified all attributes live via `terraform providers schema` |
|
||||
|
||||
## Blockers / Tabled
|
||||
- **Pegaprox MK34/MK39 offline:** Tabled for Traefik VNC middleware fix
|
||||
- **Pegaprox console passthrough:** Requires Traefik middleware for SPICE/VNC websocket proxy
|
||||
|
||||
## Files in This Commit
|
||||
- `Dockerfile` — hashicorp/terraform base image
|
||||
- `docker-compose.yml` — volume mounts + working_dir
|
||||
- `run.sh` — wrapper script (executable)
|
||||
- `terraform/providers.tf` — bpg/proxmox provider with API token auth
|
||||
- `terraform/variables.tf` — all configurable parameters
|
||||
- `terraform/main.tf` — single LXC resource
|
||||
- `terraform/terraform.auto.tfvars` — live values (private Gitea)
|
||||
|
||||
## Next Phase
|
||||
Phase 2: Modular LXC deployment
|
||||
- Extract module: `modules/lxc/`
|
||||
- Auto-derive VMID, name, IP from count + vmid_base
|
||||
- Bulk creation: `count=4` → lxc-5050..5053
|
||||
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
terraform:
|
||||
build: .
|
||||
container_name: terraform-pve
|
||||
volumes:
|
||||
- ./terraform:/workspace
|
||||
- ~/.ssh/artemis_key.pub:/workspace/artemis_key.pub:ro
|
||||
- ../ansible-push:/ansible-push
|
||||
working_dir: /workspace
|
||||
network_mode: host
|
||||
stdin_open: true
|
||||
tty: true
|
||||
command: ["version"]
|
||||
24
artemis-backup-20260614-1920/terraform-pve/run.sh
Executable file
24
artemis-backup-20260614-1920/terraform-pve/run.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# How to use this file TF_VAR_vmid_base=3550 TF_VAR_lxc_count=4 ./run.sh apply -auto-approve
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Forward all TF_VAR_* environment variables into the container
|
||||
TF_VAR_ARGS=""
|
||||
while IFS='=' read -r name _; do
|
||||
TF_VAR_ARGS="$TF_VAR_ARGS -e $name"
|
||||
done < <(env | grep '^TF_VAR_')
|
||||
|
||||
case "${1:-plan}" in
|
||||
init|validate|plan|apply|destroy)
|
||||
docker compose run --rm $TF_VAR_ARGS terraform "$1" "${@:2}"
|
||||
;;
|
||||
shell)
|
||||
docker compose run --rm $TF_VAR_ARGS terraform sh
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [init|plan|apply|destroy|shell]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
45
artemis-backup-20260614-1920/terraform-pve/terraform/.terraform.lock.hcl
generated
Normal file
45
artemis-backup-20260614-1920/terraform-pve/terraform/.terraform.lock.hcl
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/bpg/proxmox" {
|
||||
version = "0.70.0"
|
||||
constraints = "0.70.0"
|
||||
hashes = [
|
||||
"h1:Vq+frhavl4SIBtt7BqGJ4XNjzvWl2lAhA0feK5i4ocg=",
|
||||
"zh:05fe0d04fa07c6f23028e08ebf14ed01f8cb6295c926bdc923b333c1b82263e7",
|
||||
"zh:2507482294e544990946c4ae660843c3c783dca0a104ba41156dadd9f99a6c03",
|
||||
"zh:3b34b8af4890a7c9785335e5367b96a5f2bacd39718ae242e392a2f476cdb47b",
|
||||
"zh:4c81279f32dee51ba760cdee546300c8bf5a00b1be8bafe0fe374e5aeedf38bd",
|
||||
"zh:6e91c968de930e6f1f234019426e5afdfdfbbf40df6cd552708224737ef6fcf4",
|
||||
"zh:8a4fbe6b16c216d3bc54d09eff3fc2812d9ae80fb612361428609fdff4763054",
|
||||
"zh:95c3784147c61ad938781dac3977660882c3018af98371583a55c3dbc5e007ed",
|
||||
"zh:9f781c90ec3e23b3aac9f7a08ba8b44f2909a65e8803ca3a034e8d138b414b4e",
|
||||
"zh:a9e8cbfc490a7ea41e0942498604e5b709ca8967fc38ee934cde23d7aed9762a",
|
||||
"zh:addeab5eb77b10f7d68df07919ca007acbc00ad71f256982d485bdc0a13d0191",
|
||||
"zh:ca111d70f0f365d4b406ff0a0a3477217538a1d1cdd004e8bacfab63af23a83e",
|
||||
"zh:d304a0a7334dce31d66eeaecd67607d2b84b7b37b2615335351268acd6cfd41f",
|
||||
"zh:e3a5e5ec75262320b14988d18f26acefe1e75a6da6a4509cfe5edc707527e465",
|
||||
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
|
||||
"zh:f770d33d287837b6295dc98165e24631f8f683e5d0e5f1a0200f30edc078f516",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/local" {
|
||||
version = "2.9.0"
|
||||
hashes = [
|
||||
"h1:9rBZCMNpxKwMlRbWH2QpwD3kqUCAejdOZQ/aiiDObXQ=",
|
||||
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
|
||||
"zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64",
|
||||
"zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90",
|
||||
"zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c",
|
||||
"zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8",
|
||||
"zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8",
|
||||
"zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b",
|
||||
"zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9",
|
||||
"zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195",
|
||||
"zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3",
|
||||
"zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPSBrRCROUHOiZX9IB3teEK89VFfghbdu7OF5NoJ1Y6g Generated By Termius
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
all:
|
||||
children:
|
||||
lxcs:
|
||||
hosts:
|
||||
%{ for host in hosts ~}
|
||||
${host.hostname}:
|
||||
ansible_host: ${host.ipv4_host}
|
||||
ansible_user: ${host.user}
|
||||
ansible_password: ${host.password}
|
||||
ansible_port: ${host.port}
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
ansible_python_interpreter: auto_silent
|
||||
%{ endfor ~}
|
||||
41
artemis-backup-20260614-1920/terraform-pve/terraform/main.tf
Normal file
41
artemis-backup-20260614-1920/terraform-pve/terraform/main.tf
Normal file
@@ -0,0 +1,41 @@
|
||||
locals {
|
||||
# Generate a list of VMIDs from vmid_base to vmid_base + lxc_count - 1
|
||||
vmids = [for i in range(var.lxc_count) : var.vmid_base + i]
|
||||
|
||||
# Derive IPv4 from each VMID: subnet_prefix + first2digits + last2digits
|
||||
# e.g. vmid 5052 -> 192.168.50.52/18
|
||||
ipv4s = {
|
||||
for vmid in local.vmids :
|
||||
vmid => "${var.subnet_prefix}.${floor(vmid / 100) % 100}.${vmid % 100}/18"
|
||||
}
|
||||
|
||||
# Derive hostnames: name_prefix + vmid
|
||||
hostnames = {
|
||||
for vmid in local.vmids :
|
||||
vmid => "${var.name_prefix}-${vmid}"
|
||||
}
|
||||
}
|
||||
|
||||
module "lxc" {
|
||||
source = "./modules/lxc"
|
||||
|
||||
for_each = toset([for v in local.vmids : tostring(v)])
|
||||
|
||||
vmid = tonumber(each.key)
|
||||
hostname = local.hostnames[tonumber(each.key)]
|
||||
ipv4 = local.ipv4s[tonumber(each.key)]
|
||||
|
||||
node = var.node
|
||||
cores = var.cores
|
||||
memory = var.memory
|
||||
storage = var.storage
|
||||
rootfs_size = var.rootfs_size
|
||||
gateway = var.gateway
|
||||
dns_servers = var.dns_servers
|
||||
ssh_key_path = var.ssh_key_path
|
||||
template_file_id = var.template_file_id
|
||||
password = var.password
|
||||
tags = concat(var.tags, ["vmid-${each.key}"])
|
||||
unprivileged = var.unprivileged
|
||||
devices = var.devices
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
resource "proxmox_virtual_environment_container" "lxc_test" {
|
||||
node_name = var.node
|
||||
vm_id = var.vmid
|
||||
unprivileged = true
|
||||
|
||||
description = "Terraform test LXC - Phase 1 (${var.hostname})"
|
||||
|
||||
cpu {
|
||||
cores = var.cores
|
||||
}
|
||||
|
||||
memory {
|
||||
dedicated = var.memory
|
||||
}
|
||||
|
||||
disk {
|
||||
datastore_id = var.storage
|
||||
size = var.rootfs_size
|
||||
}
|
||||
|
||||
network_interface {
|
||||
name = "eth0"
|
||||
bridge = "vmbr0"
|
||||
}
|
||||
|
||||
features {
|
||||
nesting = true
|
||||
}
|
||||
|
||||
initialization {
|
||||
hostname = var.hostname
|
||||
|
||||
dns {
|
||||
servers = var.dns_servers
|
||||
}
|
||||
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = var.ipv4
|
||||
gateway = var.gateway
|
||||
}
|
||||
}
|
||||
|
||||
user_account {
|
||||
keys = [file(var.ssh_key_path)]
|
||||
password = var.password
|
||||
}
|
||||
}
|
||||
|
||||
operating_system {
|
||||
template_file_id = var.template_file_id
|
||||
type = "debian"
|
||||
}
|
||||
|
||||
tags = ["terraform", "phase1", "test"]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = "0.70.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_container" "this" {
|
||||
node_name = var.node
|
||||
vm_id = var.vmid
|
||||
unprivileged = var.unprivileged
|
||||
|
||||
description = "Terraform batch LXC — ${var.hostname}"
|
||||
|
||||
cpu {
|
||||
cores = var.cores
|
||||
}
|
||||
|
||||
memory {
|
||||
dedicated = var.memory
|
||||
}
|
||||
|
||||
disk {
|
||||
datastore_id = var.storage
|
||||
size = var.rootfs_size
|
||||
}
|
||||
|
||||
network_interface {
|
||||
name = "eth0"
|
||||
bridge = "vmbr0"
|
||||
}
|
||||
|
||||
dynamic "features" {
|
||||
for_each = var.unprivileged ? [1] : []
|
||||
content {
|
||||
nesting = true
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "device_passthrough" {
|
||||
for_each = var.devices
|
||||
content {
|
||||
path = device_passthrough.value.path
|
||||
mode = device_passthrough.value.mode
|
||||
uid = device_passthrough.value.uid
|
||||
gid = device_passthrough.value.gid
|
||||
deny_write = device_passthrough.value.deny_write
|
||||
}
|
||||
}
|
||||
|
||||
initialization {
|
||||
hostname = var.hostname
|
||||
|
||||
dns {
|
||||
servers = var.dns_servers
|
||||
}
|
||||
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = var.ipv4
|
||||
gateway = var.gateway
|
||||
}
|
||||
}
|
||||
|
||||
user_account {
|
||||
keys = [file(var.ssh_key_path)]
|
||||
password = var.password
|
||||
}
|
||||
}
|
||||
|
||||
operating_system {
|
||||
template_file_id = var.template_file_id
|
||||
type = "debian"
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
output "vmid" {
|
||||
description = "LXC VMID"
|
||||
value = proxmox_virtual_environment_container.this.vm_id
|
||||
}
|
||||
|
||||
output "hostname" {
|
||||
description = "LXC hostname"
|
||||
value = var.hostname
|
||||
}
|
||||
|
||||
output "ipv4" {
|
||||
description = "LXC static IPv4 with CIDR"
|
||||
value = var.ipv4
|
||||
}
|
||||
|
||||
output "node" {
|
||||
description = "Target PVE node"
|
||||
value = var.node
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
variable "vmid" {
|
||||
description = "LXC VMID"
|
||||
type = number
|
||||
}
|
||||
|
||||
variable "hostname" {
|
||||
description = "LXC hostname"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "ipv4" {
|
||||
description = "Static IPv4 with CIDR"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "node" {
|
||||
description = "Target PVE node"
|
||||
type = string
|
||||
default = "mk33"
|
||||
}
|
||||
|
||||
variable "cores" {
|
||||
description = "CPU cores"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
description = "RAM in MB"
|
||||
type = number
|
||||
default = 2048
|
||||
}
|
||||
|
||||
variable "storage" {
|
||||
description = "Storage pool"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "rootfs_size" {
|
||||
description = "Root filesystem size in GB"
|
||||
type = number
|
||||
default = 8
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "IPv4 gateway"
|
||||
type = string
|
||||
default = "192.168.18.1"
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "DNS servers"
|
||||
type = list(string)
|
||||
default = ["192.168.7.7", "192.168.18.1", "1.1.1.1"]
|
||||
}
|
||||
|
||||
variable "ssh_key_path" {
|
||||
description = "Path to SSH public key for jarvis user"
|
||||
type = string
|
||||
default = "artemis_key.pub"
|
||||
}
|
||||
|
||||
variable "template_file_id" {
|
||||
description = "LXC OS template file ID (storage:path)"
|
||||
type = string
|
||||
default = "nas-ct-stor:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
||||
}
|
||||
|
||||
variable "password" {
|
||||
description = "Root password for console login"
|
||||
type = string
|
||||
sensitive = true
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to the LXC"
|
||||
type = list(string)
|
||||
default = ["terraform", "batch"]
|
||||
}
|
||||
|
||||
variable "unprivileged" {
|
||||
description = "Run as unprivileged container"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "devices" {
|
||||
description = "List of device passthrough configs (path, mode, uid, gid, deny_write)"
|
||||
type = list(object({
|
||||
path = string
|
||||
mode = optional(string, "0666")
|
||||
uid = optional(number, 0)
|
||||
gid = optional(number, 0)
|
||||
deny_write = optional(bool, false)
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# Timestamp for inventory filename during testing
|
||||
variable "inventory_timestamp" {
|
||||
description = "Timestamp suffix for inventory file (e.g. 2026-06-05-143022). Leave empty for static filename."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
locals {
|
||||
lxc_hosts = [
|
||||
for vmid in local.vmids : {
|
||||
hostname = module.lxc[tostring(vmid)].hostname
|
||||
ipv4 = module.lxc[tostring(vmid)].ipv4
|
||||
ipv4_host = split("/", module.lxc[tostring(vmid)].ipv4)[0]
|
||||
user = "root"
|
||||
password = var.password
|
||||
port = 22
|
||||
}
|
||||
]
|
||||
|
||||
inventory_filename = var.inventory_timestamp != "" ? "inventory-lxc-${var.inventory_timestamp}.yml" : "inventory-lxc.yml"
|
||||
inventory_content = templatefile("${path.module}/inventory-lxc.tmpl", { hosts = local.lxc_hosts })
|
||||
}
|
||||
|
||||
# Write timestamped inventory file (archive/history)
|
||||
resource "local_file" "inventory_timestamped" {
|
||||
filename = "/ansible-push/terraform-prefill/${local.inventory_filename}"
|
||||
content = local.inventory_content
|
||||
}
|
||||
|
||||
# Write latest inventory file (static name, always overwritten)
|
||||
resource "local_file" "inventory_latest" {
|
||||
filename = "/ansible-push/terraform-prefill/inventory-lxc.yml"
|
||||
content = local.inventory_content
|
||||
}
|
||||
|
||||
output "inventory_path" {
|
||||
description = "Path to generated Ansible inventory file (static latest)"
|
||||
value = local_file.inventory_latest.filename
|
||||
}
|
||||
|
||||
output "inventory_path_archive" {
|
||||
description = "Path to generated Ansible inventory file (timestamped archive)"
|
||||
value = local_file.inventory_timestamped.filename
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = "0.70.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "proxmox" {
|
||||
endpoint = var.pm_api_url
|
||||
api_token = "${var.pm_api_token_id}=${var.pm_api_token_secret}"
|
||||
insecure = true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
pm_api_url = "https://192.168.7.33:8006/api2/json"
|
||||
pm_api_token_id = "root@pam!terraform"
|
||||
pm_api_token_secret = "e0a2de73-261e-41ff-aeea-5e208ed5c2e1"
|
||||
node = "mk33"
|
||||
# vmid_base is passed via TF_VAR_vmid_base or -var at runtime
|
||||
# lxc_count is passed via TF_VAR_lxc_count or -var at runtime
|
||||
gateway = "192.168.18.1"
|
||||
dns_servers = ["192.168.7.7", "192.168.18.1", "1.1.1.1"]
|
||||
ssh_key_path = "artemis_key.pub"
|
||||
template_file_id = "nas-ct-stor:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
||||
password = "ubuntu"
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": 4,
|
||||
"terraform_version": "1.15.5",
|
||||
"serial": 112,
|
||||
"lineage": "3d6e1af6-7224-ba55-3a5b-7381460a1995",
|
||||
"outputs": {
|
||||
"inventory_path": {
|
||||
"value": "/ansible-push/terraform-prefill/inventory-lxc.yml",
|
||||
"type": "string"
|
||||
},
|
||||
"inventory_path_archive": {
|
||||
"value": "/ansible-push/terraform-prefill/inventory-lxc.yml",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [],
|
||||
"check_results": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": 4,
|
||||
"terraform_version": "1.15.5",
|
||||
"serial": 111,
|
||||
"lineage": "3d6e1af6-7224-ba55-3a5b-7381460a1995",
|
||||
"outputs": {
|
||||
"inventory_path": {
|
||||
"value": "/ansible-push/terraform-prefill/inventory-lxc.yml",
|
||||
"type": "string"
|
||||
},
|
||||
"inventory_path_archive": {
|
||||
"value": "/ansible-push/terraform-prefill/inventory-lxc.yml",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [],
|
||||
"check_results": null
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
variable "pm_api_url" {
|
||||
description = "Proxmox API URL"
|
||||
type = string
|
||||
default = "https://192.168.7.33:8006/api2/json"
|
||||
}
|
||||
|
||||
variable "pm_api_token_id" {
|
||||
description = "Proxmox API token ID"
|
||||
type = string
|
||||
default = "root@pam!terraform"
|
||||
}
|
||||
|
||||
variable "pm_api_token_secret" {
|
||||
description = "Proxmox API token secret"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "node" {
|
||||
description = "Target PVE node"
|
||||
type = string
|
||||
default = "mk33"
|
||||
}
|
||||
|
||||
# ── Batch / Dynamic Parameters ───────────────────────────────
|
||||
|
||||
variable "vmid_base" {
|
||||
description = "Starting VMID for the first LXC"
|
||||
type = number
|
||||
default = 5050
|
||||
}
|
||||
|
||||
variable "lxc_count" {
|
||||
description = "Number of LXCs to create"
|
||||
type = number
|
||||
default = 1
|
||||
}
|
||||
|
||||
variable "subnet_prefix" {
|
||||
description = "First two octets of IPv4 (e.g. 192.168)"
|
||||
type = string
|
||||
default = "192.168"
|
||||
}
|
||||
|
||||
variable "name_prefix" {
|
||||
description = "Hostname prefix for LXCs"
|
||||
type = string
|
||||
default = "lxc"
|
||||
}
|
||||
|
||||
# ── Static Per-LXC Parameters (fixed for POC) ────────────────
|
||||
|
||||
variable "cores" {
|
||||
description = "CPU cores per LXC"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
description = "RAM in MB per LXC"
|
||||
type = number
|
||||
default = 2048
|
||||
}
|
||||
|
||||
variable "storage" {
|
||||
description = "Storage pool"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "rootfs_size" {
|
||||
description = "Root filesystem size in GB"
|
||||
type = number
|
||||
default = 8
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "IPv4 gateway"
|
||||
type = string
|
||||
default = "192.168.18.1"
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "DNS servers"
|
||||
type = list(string)
|
||||
default = ["192.168.7.7", "192.168.18.1", "1.1.1.1"]
|
||||
}
|
||||
|
||||
variable "ssh_key_path" {
|
||||
description = "Path to SSH public key for jarvis user"
|
||||
type = string
|
||||
default = "artemis_key.pub"
|
||||
}
|
||||
|
||||
variable "template_file_id" {
|
||||
description = "LXC OS template file ID (storage:path)"
|
||||
type = string
|
||||
default = "nas-ct-stor:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
||||
}
|
||||
|
||||
variable "password" {
|
||||
description = "Root password for console login"
|
||||
type = string
|
||||
sensitive = true
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Base tags for all LXCs"
|
||||
type = list(string)
|
||||
default = ["terraform", "batch"]
|
||||
}
|
||||
|
||||
variable "unprivileged" {
|
||||
description = "Run as unprivileged container"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "devices" {
|
||||
description = "Device passthrough configs for LXC"
|
||||
type = list(object({
|
||||
path = string
|
||||
mode = optional(string, "0666")
|
||||
uid = optional(number, 0)
|
||||
gid = optional(number, 0)
|
||||
deny_write = optional(bool, false)
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
variable "pm_api_url" {
|
||||
description = "Proxmox API URL"
|
||||
type = string
|
||||
default = "https://192.168.7.33:8006/api2/json"
|
||||
}
|
||||
|
||||
variable "pm_api_token_id" {
|
||||
description = "Proxmox API token ID"
|
||||
type = string
|
||||
default = "root@pam!terraform"
|
||||
}
|
||||
|
||||
variable "pm_api_token_secret" {
|
||||
description = "Proxmox API token secret"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "node" {
|
||||
description = "Target PVE node"
|
||||
type = string
|
||||
default = "mk33"
|
||||
}
|
||||
|
||||
variable "vmid" {
|
||||
description = "LXC VMID"
|
||||
type = number
|
||||
default = 5050
|
||||
}
|
||||
|
||||
variable "hostname" {
|
||||
description = "LXC hostname"
|
||||
type = string
|
||||
default = "lxc-5050"
|
||||
}
|
||||
|
||||
variable "cores" {
|
||||
description = "CPU cores"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
description = "RAM in MB"
|
||||
type = number
|
||||
default = 2048
|
||||
}
|
||||
|
||||
variable "storage" {
|
||||
description = "Storage pool"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "rootfs_size" {
|
||||
description = "Root filesystem size in GB"
|
||||
type = number
|
||||
default = 8
|
||||
}
|
||||
|
||||
variable "ipv4" {
|
||||
description = "Static IPv4 with CIDR"
|
||||
type = string
|
||||
default = "192.168.50.50/18"
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "IPv4 gateway"
|
||||
type = string
|
||||
default = "192.168.18.1"
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "DNS servers"
|
||||
type = list(string)
|
||||
default = ["192.168.7.7", "192.168.18.1", "1.1.1.1"]
|
||||
}
|
||||
|
||||
variable "ssh_key_path" {
|
||||
description = "Path to SSH public key for jarvis user"
|
||||
type = string
|
||||
default = "artemis_key.pub"
|
||||
}
|
||||
|
||||
variable "template_file_id" {
|
||||
description = "LXC OS template file ID (storage:path)"
|
||||
type = string
|
||||
default = "nas-ct-stor:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
||||
}
|
||||
|
||||
variable "password" {
|
||||
description = "Root password for console login"
|
||||
type = string
|
||||
sensitive = true
|
||||
default = ""
|
||||
}
|
||||
106
artemis-backup-20260614-1920/wolfstack-keepalived/README.md
Normal file
106
artemis-backup-20260614-1920/wolfstack-keepalived/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# WolfStack Keepalived HA Setup
|
||||
|
||||
## Files
|
||||
|
||||
| File | Node | Priority | Intended State |
|
||||
|------|------|----------|---------------|
|
||||
| `keepalived-node1-primary.conf` | 192.168.5.214 | 150 | MASTER |
|
||||
| `keepalived-node2-secondary.conf` | `192.168.15.182` | BACKUP | 130 |
|
||||
| `keepalived-node3-tertiary.conf` | 192.168.50.41 | 110 | BACKUP |
|
||||
|
||||
Shared VIP: `192.168.63.254` on interface `eth0`
|
||||
|
||||
## Quick Install (per node)
|
||||
|
||||
```bash
|
||||
sudo apt install keepalived netcat-openbsd -y
|
||||
sudo cp keepalived-nodeX-*.conf /etc/keepalived/keepalived.conf
|
||||
sudo systemctl enable keepalived --now
|
||||
sudo systemctl status keepalived
|
||||
```
|
||||
|
||||
Watch the syslog:
|
||||
```bash
|
||||
sudo journalctl -u keepalived -f
|
||||
```
|
||||
|
||||
## Network Confirmation
|
||||
|
||||
All three nodes (`.5.x`, `.50.x`) and the VIP (`.63.254`) fall within the same `192.168.0.0/18` network (`192.168.0.0` – `192.168.63.255`). This means they share Layer 2/L3 broadcast domain and VRRP unicast failover works cleanly across all nodes — no VLAN bridging or routing tricks required. ✅
|
||||
|
||||
### What Happens In Practice
|
||||
|
||||
| Scenario | Outcome |
|
||||
|----------|---------|
|
||||
| Node 1 dies, Node 2 healthy | VIP moves to Node 2 ✅ |
|
||||
| Node 1 + Node 2 die, Node 3 alive | VIP moves to Node 3 ✅ (clients reach it directly) |
|
||||
| Node 3 is primary but 1 & 2 alive | Node 3 will NOT win because priority 110 < 150 / 130 ✅ |
|
||||
| All 3 nodes lose peer connectivity | **Split-brain risk** — each may claim VIP. Use fencing/STONITH if this is production-critical. |
|
||||
|
||||
## Track Script Explained
|
||||
|
||||
```bash
|
||||
script "/usr/bin/nc -z -w 2 127.0.0.1 8553"
|
||||
```
|
||||
|
||||
This checks whether the **local** WolfStack UI (port 8553) is accepting TCP connections.
|
||||
|
||||
- **If WolfStack crashes / stops** on the MASTER, keepalived subtracts `weight -40` from that node's priority.
|
||||
- Priority drops from 150 → 110, which is below Node 2 (130). Node 2 promotes to MASTER and takes the VIP.
|
||||
- If Node 1 comes back online *and* WolfStack is healthy, its priority returns to 150. The VIP will move back **unless** you keep `nopreempt`.
|
||||
|
||||
### Preempt Behaviour
|
||||
|
||||
| Setting | Behaviour |
|
||||
|---------|-----------|
|
||||
| `nopreempt` present | Once VIP is on a backup node, it stays there even when higher-priority node recovers. Good for stability. Manual failback required. |
|
||||
| `nopreempt` removed | Highest priority node reclaims VIP automatically. Good for deterministic primary/secondary but causes brief flap. |
|
||||
|
||||
## ⚠️ WolfStack-Specific Caveats (from research)
|
||||
|
||||
1. **No native UI HA**: WolfStack's web UI is single-node. If you point browsers at the VIP and Node 2 takes over, **users must re-authenticate** (sessions are local to each node).
|
||||
2. **No reverse-proxy awareness**: The UI doesn't have a "behind proxy" setting. If you terminate SSL on a proxy and forward to `http://backend:8553`, you may hit mixed-content or CSP issues.
|
||||
3. **WebSockets**: The dashboard uses websockets for the terminal and real-time graphs. Make sure your proxy (or keepalived direct-access path) supports websocket upgrades if not hitting the VIP directly.
|
||||
4. **WolfProxy**: WolfStack ships a sibling `WolfProxy` (NGINX-compatible), but that is a **separate** reverse proxy for *other services*, not for making the WolfStack UI highly available.
|
||||
|
||||
## Suggested Topology
|
||||
|
||||
```
|
||||
Clients / Browsers
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| VIP: 192.168.63.254 |
|
||||
| (keepalived floating) |
|
||||
+----------+----------+
|
||||
|
|
||||
+----------------+----------------+
|
||||
| |
|
||||
Node 1 (MASTER) Node 2 (BACKUP)
|
||||
192.168.5.214:8553 192.168.15.182:8553
|
||||
priority 150 priority 130
|
||||
| |
|
||||
WolfStack WolfStack
|
||||
WolfNet Mesh WolfNet Mesh
|
||||
+------------+ 192.168.50.41 +---+
|
||||
Node 3 (BACKUP)
|
||||
priority 110
|
||||
(same VLAN?)
|
||||
```
|
||||
|
||||
## Testing Failover
|
||||
|
||||
1. From any client, `curl -k https://192.168.63.254:8553` should show the WolfStack UI.
|
||||
2. On the MASTER node: `sudo systemctl stop wolfstack` (simulating WolfStack crash).
|
||||
3. Watch `journalctl -u keepalived -f` on Node 2 — it should promote to MASTER within ~6 seconds (3 x 2s intervals).
|
||||
4. `ip addr show eth0` on Node 2 should now show `192.168.63.254/32`.
|
||||
5. Client traffic should continue via the VIP.
|
||||
6. Restart WolfStack on Node 1. If `nopreempt` is enabled, VIP stays on Node 2. If removed, VIP moves back to Node 1.
|
||||
|
||||
## Rollback
|
||||
|
||||
```bash
|
||||
sudo systemctl stop keepalived
|
||||
sudo ip addr del 192.168.63.254 dev eth0 # if needed manually
|
||||
sudo systemctl disable keepalived
|
||||
```
|
||||
114
artemis-backup-20260614-1920/wolfstack-keepalived/deploy.sh
Normal file
114
artemis-backup-20260614-1920/wolfstack-keepalived/deploy.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# WolfStack Keepalived Deploy Script
|
||||
# Usage:
|
||||
# sudo bash deploy.sh [install] [node1-primary|node2-secondary|node3-tertiary]
|
||||
#
|
||||
# Examples:
|
||||
# sudo bash deploy.sh install # install + auto-detect
|
||||
# sudo bash deploy.sh node1-primary # deploy role, no install
|
||||
# sudo bash deploy.sh install node1-primary # install + deploy role
|
||||
# ============================================================
|
||||
|
||||
set -e
|
||||
|
||||
NODE_TYPE=""
|
||||
CONF_DIR="/home/jarvis/wolfstack-keepalived"
|
||||
KEEPALIVED_CONF="/etc/keepalived/keepalived.conf"
|
||||
|
||||
detect_node() {
|
||||
local ip
|
||||
ip=$(hostname -I | tr ' ' '\n' | grep -E '^192\.168\.' | head -n1)
|
||||
[[ -z "$ip" ]] && ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
case "$ip" in
|
||||
192.168.50.41)
|
||||
NODE_TYPE="node1-primary"
|
||||
;;
|
||||
192.168.5.214)
|
||||
NODE_TYPE="node2-secondary"
|
||||
;;
|
||||
192.168.15.182)
|
||||
NODE_TYPE="node3-tertiary"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Could not auto-detect node type from IP: $ip"
|
||||
echo "Please specify manually: node1-primary | node2-secondary | node3-tertiary"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "Detected IP: $ip -> $NODE_TYPE"
|
||||
}
|
||||
|
||||
install_keepalived() {
|
||||
if command -v apt-get &>/dev/null; then
|
||||
echo "Installing keepalived (Debian/Ubuntu)..."
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y keepalived netcat-openbsd
|
||||
elif command -v dnf &>/dev/null; then
|
||||
echo "Installing keepalived (RHEL/Fedora)..."
|
||||
sudo dnf install -y keepalived nc
|
||||
elif command -v zypper &>/dev/null; then
|
||||
echo "Installing keepalived (openSUSE)..."
|
||||
sudo zypper install -y keepalived netcat-openbsd
|
||||
elif command -v pacman &>/dev/null; then
|
||||
echo "Installing keepalived (Arch)..."
|
||||
sudo pacman -Sy --noconfirm keepalived openbsd-netcat
|
||||
else
|
||||
echo "ERROR: Unsupported package manager. Install keepalived manually."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_config() {
|
||||
local src="$CONF_DIR/keepalived-${NODE_TYPE}.conf"
|
||||
if [[ ! -f "$src" ]]; then
|
||||
echo "ERROR: Config not found: $src"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying $src -> $KEEPALIVED_CONF"
|
||||
sudo mkdir -p /etc/keepalived
|
||||
sudo cp "$src" "$KEEPALIVED_CONF"
|
||||
sudo chmod 644 "$KEEPALIVED_CONF"
|
||||
}
|
||||
|
||||
enable_service() {
|
||||
if systemctl is-active --quiet keepalived 2>/dev/null; then
|
||||
echo "Keepalived already running. Restarting to pick up new config..."
|
||||
sudo systemctl restart keepalived
|
||||
else
|
||||
echo "Enabling and starting keepalived..."
|
||||
sudo systemctl enable keepalived --now
|
||||
fi
|
||||
sleep 1
|
||||
sudo systemctl status keepalived --no-pager
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
|
||||
ARGS=("$@")
|
||||
|
||||
# Check if first arg is 'install'
|
||||
if [[ "${ARGS[0]:-}" == "install" ]]; then
|
||||
install_keepalived
|
||||
ARGS=("${ARGS[@]:1}") # remove 'install' from args
|
||||
fi
|
||||
|
||||
# If a node type arg remains, use it; otherwise auto-detect
|
||||
if [[ -n "${ARGS[0]:-}" ]]; then
|
||||
NODE_TYPE="${ARGS[0]}"
|
||||
echo "Using provided node type: $NODE_TYPE"
|
||||
else
|
||||
detect_node
|
||||
fi
|
||||
|
||||
deploy_config
|
||||
enable_service
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Keepalived deployed for $NODE_TYPE"
|
||||
echo "VIP should appear on this node if it is MASTER."
|
||||
echo "Check: ip addr show | grep 192.168.63.254"
|
||||
echo "========================================"
|
||||
@@ -0,0 +1,48 @@
|
||||
# ============================================================
|
||||
# WolfStack Keepalived — PRIMARY (Node 1 / mk50)
|
||||
# Host: 192.168.50.41
|
||||
# VIP : 192.168.63.254
|
||||
# Interface: nic0
|
||||
# ============================================================
|
||||
|
||||
global_defs {
|
||||
router_id WOLFSTACK_NODE1
|
||||
script_security
|
||||
enable_script_security
|
||||
}
|
||||
|
||||
vrrp_script check_wolfstack {
|
||||
script "/usr/bin/nc -z -w 2 127.0.0.1 8553"
|
||||
interval 2
|
||||
timeout 3
|
||||
fall 3
|
||||
rise 2
|
||||
weight -40
|
||||
}
|
||||
|
||||
vrrp_instance VI_WOLFSTACK {
|
||||
state MASTER
|
||||
interface nic0
|
||||
virtual_router_id 63
|
||||
priority 150
|
||||
advert_int 1
|
||||
nopreempt
|
||||
|
||||
authentication {
|
||||
auth_type PASS
|
||||
auth_pass Letmein1$
|
||||
}
|
||||
|
||||
unicast_peer {
|
||||
192.168.5.214
|
||||
192.168.15.182
|
||||
}
|
||||
|
||||
virtual_ipaddress {
|
||||
192.168.63.254
|
||||
}
|
||||
|
||||
track_script {
|
||||
check_wolfstack
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# ============================================================
|
||||
# WolfStack Keepalived — SECONDARY (Node 2 / mk44)
|
||||
# Host: 192.168.5.214
|
||||
# VIP : 192.168.63.254
|
||||
# Interface: eth0
|
||||
# ============================================================
|
||||
|
||||
global_defs {
|
||||
router_id WOLFSTACK_NODE2
|
||||
script_security
|
||||
enable_script_security
|
||||
}
|
||||
|
||||
vrrp_script check_wolfstack {
|
||||
script "/usr/bin/nc -z -w 2 127.0.0.1 8553"
|
||||
interval 2
|
||||
timeout 3
|
||||
fall 3
|
||||
rise 2
|
||||
weight -40
|
||||
}
|
||||
|
||||
vrrp_instance VI_WOLFSTACK {
|
||||
state BACKUP
|
||||
interface eth0
|
||||
virtual_router_id 63
|
||||
priority 130
|
||||
advert_int 1
|
||||
nopreempt
|
||||
|
||||
authentication {
|
||||
auth_type PASS
|
||||
auth_pass Letmein1$
|
||||
}
|
||||
|
||||
unicast_peer {
|
||||
192.168.50.41
|
||||
192.168.15.182
|
||||
}
|
||||
|
||||
virtual_ipaddress {
|
||||
192.168.63.254
|
||||
}
|
||||
|
||||
track_script {
|
||||
check_wolfstack
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# ============================================================
|
||||
# WolfStack Keepalived — TERTIARY (Node 3 / artemis)
|
||||
# Host: 192.168.15.182
|
||||
# VIP : 192.168.63.254
|
||||
# Interface: nic0
|
||||
# ============================================================
|
||||
|
||||
global_defs {
|
||||
router_id WOLFSTACK_NODE3
|
||||
script_security
|
||||
enable_script_security
|
||||
}
|
||||
|
||||
vrrp_script check_wolfstack {
|
||||
script "/usr/bin/nc -z -w 2 127.0.0.1 8553"
|
||||
interval 2
|
||||
timeout 3
|
||||
fall 3
|
||||
rise 2
|
||||
weight -40
|
||||
}
|
||||
|
||||
vrrp_instance VI_WOLFSTACK {
|
||||
state BACKUP
|
||||
interface nic0
|
||||
virtual_router_id 63
|
||||
priority 110
|
||||
advert_int 1
|
||||
nopreempt
|
||||
|
||||
authentication {
|
||||
auth_type PASS
|
||||
auth_pass Letmein1$
|
||||
}
|
||||
|
||||
unicast_peer {
|
||||
192.168.50.41
|
||||
192.168.5.214
|
||||
}
|
||||
|
||||
virtual_ipaddress {
|
||||
192.168.63.254
|
||||
}
|
||||
|
||||
track_script {
|
||||
check_wolfstack
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user