# ============================================================================ # Сервис №3 по плану (docs/ai/migration-tofu.md, раздел 5): gitea. # Главный приз всей миграции — ради него она и затевалась. # # OLD vmid 141, узел cloud-pc, боевой адрес 192.168.1.25. # Эталон снят 2026-09-02: ssh cloud-pc sudo pct config 141 (и совпадающий # /etc/pve/lxc/141.conf): # # arch: amd64 # cmode: shell # cores: 2 # features: nesting=1,keyctl=1 # hostname: gitea # memory: 2048 # mp0: /opt/data/gitea,mp=/opt/gitea/data # nameserver: 1.1.1.1 # net0: name=eth0,bridge=vmbr0,firewall=1,gw=192.168.1.1,ip=192.168.1.25/24,type=veth # onboot: 1 # ostype: debian # rootfs: data:141/vm-141-disk-0.raw,size=32G # startup: order=50 # swap: 1024 # unprivileged: 1 # lxc.cgroup2.devices.allow: c 10:229 rwm # lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file # # vm_id 153 — новый VMID для blue-green переезда. IP ниже — TMPIP # 192.168.1.12, используется только до шага 4.5.15 (проверка перед cutover); # затем меняется на боевой 192.168.1.25, а OLD (141) останавливается и # остаётся откатом минимум неделю. # # --- Bind mount -> volume ---------------------------------------------------- # Эталонный mp0 — bind mount каталога НОДЫ (/opt/data/gitea на cloud-pc), а не # volume на datastore. Provider с root@pam мог бы воспроизвести и bind mount # буквально, но делать это НЕЛЬЗЯ: если по ошибке одновременно поднимутся # оба контейнера, два процесса Gitea будут писать в один и тот же каталог # хоста и повредят SQLite и репозитории. Поэтому mount_point ниже — volume на # том же datastore, что и rootfs ("data"), с независимым хранилищем. Данные # переносятся отдельно на шаге 4.4 (rsync/pct push), а не общим bind mount. # Размер volume — 32G по решению migration-tofu.md (раздел 5, пункт 3): # фактическое использование на 2026-09-02 — 2.3G (`sudo du -sh # /opt/data/gitea`), так что 32G — запас с большим множителем на рост # репозиториев, а не минимально достаточное значение. # # --- /dev/fuse: features.fuse, а не device_passthrough ---------------------- # Эталонные строки lxc.cgroup2.devices.allow + lxc.mount.entry для /dev/fuse — # это ручной обход, потому что Proxmox API не принимает сырые lxc.* ключи # (pve-gitea.yml добавляет их через lineinfile). У провайдера bpg/proxmox для # ровно этого эффекта есть штатный флаг PVE features.fuse — это подтверждено # на пилоте (tofu/README.md, раздел «Решение: root@pam по паролю») и явно # названо в комментарии tofu/pilot.tf.example: "У провайдера для этого есть # features.fuse — штатный флаг PVE, а не обход". device_passthrough (dev0:) # нужен только для сырых character-устройств вида /dev/net/tun; у gitea # такого устройства нет (в pct config нет строки dev0:), поэтому блока # device_passthrough в этом ресурсе нет вообще — только features.fuse. # ============================================================================ resource "proxmox_virtual_environment_container" "gitea" { node_name = "cloud-pc" vm_id = 153 unprivileged = true start_on_boot = true started = true tags = ["tofu"] initialization { hostname = "gitea" ip_config { ipv4 { address = "192.168.1.25/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 = 2048 swap = 1024 } disk { datastore_id = "data" size = 32 } # Эталон: features: nesting=1,keyctl=1 плюс ручной обход для /dev/fuse # (см. комментарий выше). 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" } # Bind mount ноды заменён на независимый volume — см. комментарий выше. # Тот же datastore, что и rootfs. mount_point { volume = "data" size = "32G" path = "/opt/gitea/data" } network_interface { name = "eth0" bridge = "vmbr0" firewall = true } startup { order = 50 } } output "gitea" { description = "Что проверять на узле после apply" value = { vmid = proxmox_virtual_environment_container.gitea.vm_id node = proxmox_virtual_environment_container.gitea.node_name verify = "ssh cloud-pc sudo pct config ${proxmox_virtual_environment_container.gitea.vm_id}" } }