Files
infra/tofu/svc-grimmory.tf
DmitryandClaude Sonnet 5 a3fe8031fe feat: migrate all managed LXC provisioning to OpenTofu (blue-green)
Blue-green: a new container is created beside the old one, data is copied, the
IP is moved onto it, and the old container is kept stopped as rollback for at
least a week. Keeping the IP means only the VMID changes, and its consumers
(backup jobs, backup audit) already derive it from the registry.

Batch 1 (2026-09-02): emergency-bot 148->151, docker-test 145->152,
gitea 141->153, vaultwarden 140->154, monitoring 146->155, gyro 150->156,
grimmory 149->157.
Batch 2 (2026-09-03): adguard 144->158, mihomo 143->159, ovpn-mini 132->160.
All migratable LXC are now provisioner: tofu. hermes-ai (frozen) and pbs stay.

- tofu/services.tf + tofu/svc-*.tf: one resource per service, reproducing the
  pct-config etalon. /dev/fuse -> features.fuse; /dev/net/tun ->
  device_passthrough (first live use on mihomo and ovpn-mini); gitea bind mount
  -> datastore volume (data finally reaches PBS); console { type = "shell" }
  declared explicitly (provider tracks cmode there).
- services.yml: vmid + provisioner: tofu for every migrated service; features
  strings and device notes updated to the tofu representation; also drops the
  memoir-bot entry and adds homelab_reverse_proxy_image/_unit.
- pve-*.yml: configuration play target is `{{ pve_config_target | default(...) }}`
  so it can run against <name>-new on a temp address (a bare --limit zeroes the
  play instead of retargeting it). Container-creation plays are gated behind
  `provisioner != 'tofu'` / `pve_provisioning_enabled` (meta: end_play), so a
  stray run cannot pct start a stopped OLD VMID on a live IP. Override for
  intentional legacy rollback: -e pve_<svc>_legacy_provisioning_enabled=true.
- ssh_config: drop memoir-bot; ovpn-mini gets ProxyJump none (a jump via ru-vps
  would route through the very tunnel ovpn-mini terminates).
- gyro.yml / uptime-kuma.yml: same pve_config_target override.
- roles/uptime_kuma: only freeze homelab-monitoring when the unit actually
  exists (a fresh blue-green container never had it).
- offsite-restic-yadisk.yml: the gitea restic profile now runs inside the LXC
  (hosts: gitea), since the bind-mount host path is gone after the volume move;
  lost+found excluded (unreadable in an unprivileged LXC, restic exit 3).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uoq5AVK8mkBgg83Mq6o5V
2026-09-03 07:05:46 +03:00

150 lines
6.5 KiB
Terraform
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ============================================================================
# Сервис №7 по плану (docs/ai/migration-tofu.md, раздел 5): grimmory.
# Самый болезненный шаг: MariaDB, Flyway-миграции, контракт OPDS/KOReader.
#
# OLD vmid 149, узел cloud-pc, боевой адрес 192.168.1.34.
# Эталон снят 2026-09-02: ssh cloud-pc sudo pct config 149 (и совпадающий
# дословно /etc/pve/lxc/149.conf):
#
# arch: amd64
# cmode: shell
# cores: 2
# features: nesting=1,keyctl=1
# hostname: grimmory
# memory: 4096
# nameserver: 1.1.1.1
# net0: name=eth0,bridge=vmbr0,firewall=1,gw=192.168.1.1,ip=192.168.1.34/24,type=veth
# onboot: 1
# ostype: debian
# rootfs: data:149/vm-149-disk-0.raw,size=64G
# startup: order=100
# swap: 1024
# unprivileged: 1
# lxc.cgroup2.devices.allow: c 10:229 rwm
# lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file
#
# Ни mpN, ни dev0 в эталоне нет — только /dev/fuse через сырые lxc.* строки.
# Значит правило про bind mount -> mount_point (как у gitea) здесь не
# применяется: mount_point в этом ресурсе не добавлен.
#
# vm_id 157 — новый VMID для blue-green переезда (назначен, не менять).
# TMPIP 192.168.1.16 ниже используется только на шаге 4.3 (проверка перед
# cutover; запись grimmory-new уже есть в hosts.yml/ssh_config — заведена
# координатором). После шага 4.5.15 адрес меняется на боевой 192.168.1.34,
# OLD (149) останавливается и остаётся откатом минимум неделю.
#
# --- /dev/fuse: features.fuse, а не device_passthrough ----------------------
# Эталонные lxc.cgroup2.devices.allow + lxc.mount.entry для /dev/fuse — это
# ручной обход (lineinfile по /etc/pve/lxc/149.conf в pve-grimmory.yml),
# потому что Proxmox API сырые lxc.* ключи не принимает.
#
# Заменяется штатным флагом PVE features.fuse, а НЕ device_passthrough.
# device_passthrough (dev0:) предназначен для сырых character-устройств вида
# /dev/net/tun; для /dev/fuse у PVE есть флаг, и именно он проверен на пилоте
# VMID 199 (`features: fuse=1,keyctl=1,nesting=1`), см. tofu/pilot.tf.example
# строки 17-21 и tofu/README.md. Формулировка «device_passthrough для каждого
# устройства из lxc.mount.entry» в migration-tofu.md п.4.2 была слишком
# широкой и уточнена там же 2026-09-02; так же поправлены svc-docker-test.tf
# и svc-vaultwarden.tf, так что все ресурсы tofu/ теперь единообразны.
#
# Следствие: pct config нового контейнера покажет features: fuse=1,keyctl=1,
# nesting=1 — текстуально иначе, чем эталон, при том же эффекте.
#
# --- MariaDB: только dump/restore, не копирование файлов --------------------
# /opt/grimmory/mariadb — это datadir MariaDB (grimmory-mariadb, БД grimmory).
# Копировать эти файлы между старым и новым контейнером НЕЛЬЗЯ: нужен
# mariadb-dump на старом контейнере (сервис приложения остановлен, MariaDB
# работает) и restore на новом. Рецепт dump уже есть и проверен в рабочем
# offsite-профиле restic (playbooks/offsite-restic-yadisk.yml, задача
# ../tasks/offsite-restic-profile.yml): docker exec -e MYSQL_PWD=...
# grimmory-mariadb mariadb-dump --single-transaction --routines --events
# --databases grimmory. legacy-warning.md и docs/ai/edge-cases.md запрещают
# code-only downgrade после Flyway-миграции — на новом контейнере обязан
# развернуться РОВНО ТОТ ЖЕ pinned образ v3.2.4 (проверено 2026-09-02:
# запущенный в CT 149 образ grimmory/grimmory:v3.2.4 имеет digest
# sha256:dfa7afdfcf25d649fd664497a62385dd00cd9678c37546e182c172e41c8e80cb —
# совпадает с реестром services.yml байт-в-байт, расхождения нет).
# ============================================================================
resource "proxmox_virtual_environment_container" "grimmory" {
node_name = "cloud-pc"
vm_id = 157
unprivileged = true
start_on_boot = true
started = true
tags = ["tofu"]
initialization {
hostname = "grimmory"
ip_config {
ipv4 {
address = "192.168.1.34/24"
gateway = "192.168.1.1"
}
}
dns {
servers = ["1.1.1.1"]
}
user_account {
keys = [trimspace(file(pathexpand("~/.ssh/id_ed25519_homelab.pub")))]
}
}
operating_system {
template_file_id = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst"
type = "debian"
}
cpu {
cores = 2
}
memory {
dedicated = 4096
swap = 1024
}
disk {
datastore_id = "data"
size = 64
}
# Эталон: features: nesting=1,keyctl=1 плюс /dev/fuse через сырые lxc.*
# строки. fuse=1 — штатная замена этого обхода, см. комментарий файла выше.
features {
nesting = true
keyctl = true
fuse = true
}
# roles/pve_lxc всем контейнерам ставит cmode=shell (pve_lxc_cmode), провайдер
# это отслеживает через блок console.type — без него на следующем apply
# откатит на дефолт Proxmox tty. Обнаружено 2026-09-02 на emergency-bot,
# см. tofu/README.md.
console {
type = "shell"
}
network_interface {
name = "eth0"
bridge = "vmbr0"
firewall = true
}
startup {
order = 100
}
}
output "grimmory" {
description = "Что проверять на узле после apply"
value = {
vmid = proxmox_virtual_environment_container.grimmory.vm_id
node = proxmox_virtual_environment_container.grimmory.node_name
verify = "ssh cloud-pc sudo pct config ${proxmox_virtual_environment_container.grimmory.vm_id}"
}
}