Objectiu

Quan automatitzem infraestructures amb scripts, és molt fàcil que acabem publicant coses que no haurien de sortir mai: claus SSH completes, contrasenyes, tokens DDNS, dominis interns o IPs de LAN.

En aquest article deixo una manera neta de treballar cloud-init en dos casos habituals:

  • LXC (serveis interns tipus Plex o SMB)
  • VPS/VM (serveis públics tipus web, reverse proxy o VPN)

I ho faig amb exemples inspirats en tres patrons pràctics:

  • lxc-plex
  • lxc-smb
  • vps-www

Sempre amb placeholders i bones pràctiques de seguretat.

Diferència clau: LXC vs VPS amb cloud-init

No tots dos casos funcionen igual.

LXC (contenidor)

A Proxmox, la configuració base d’un LXC normalment es fa amb pct create + pct set + pct exec. En molts entorns, això és un “estil cloud-init” més que no pas cloud-init pur dins del contenidor.

Patró habitual:

pct create 801 local:vztmpl/debian-12-standard_amd64.tar.zst \
  --hostname smb.example.net \
  --cores 2 --memory 1024 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.10.21/24,gw=192.168.10.1 \
  --unprivileged 1 --onboot 1

pct start 801
pct exec 801 -- apt-get update
pct exec 801 -- apt-get install -y sudo cockpit samba

VPS/VM (màquina virtual)

Aquí sí: imatge cloud (qcow2) + snippet #cloud-config + qm set --cicustom.

Patró habitual:

qm create 913 --name www-example --memory 4096 --cores 2 --net0 virtio,bridge=vmbr0
qm importdisk 913 debian-13-genericcloud-amd64.qcow2 local-lvm
qm set 913 --virtio0 local-lvm:vm-913-disk-0
qm set 913 --ide2 local-lvm:cloudinit
qm set 913 --cicustom "user=local:snippets/www-cloudinit.yaml"
qm set 913 --ipconfig0 "ip=192.168.10.50/24,gw=192.168.10.1"
qm start 913

Política “publicable”: què no has de penjar mai

Abans de pujar un script o un post:

  1. No publiquis claus SSH completes (ssh-rsa ...).
  2. No publiquis contrasenyes (COMMON_PASSWORD=...).
  3. No publiquis tokens (FREEDNS_TOKEN, API keys, PSK de VPN).
  4. No publiquis IPs/hosts interns reals si no cal.
  5. No publiquis correus personals dins scripts d’automatització.

Substitueix-ho per placeholders:

COMMON_USER="adminuser"
COMMON_PASSWORD="CHANGEME_STRONG_PASSWORD"
SSH_PUBLIC_KEY="ssh-ed25519 AAAA...REPLACE_ME"
DDNS_TOKEN="REPLACE_DDNS_TOKEN"
DOMAIN_PUBLIC="example.net"
LAN_GW="192.168.10.1"

Exemple 1: LXC Plex (patró)

Aquest patró cobreix:

  • creació de contenidor
  • muntatge de dades (mp0)
  • instal·lació de Plex
  • hardening bàsic SSH
CTID="802"
CT_NAME="plex.example.net"
MEDIA_MP="/mnt/pve/zfs_media"

pct create "$CTID" local:vztmpl/debian-12-standard_amd64.tar.zst \
  --hostname "$CT_NAME" \
  --cores 2 --memory 2048 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.10.22/24,gw=192.168.10.1 \
  --unprivileged 0 --features nesting=1 --onboot 1

pct set "$CTID" --mp0 "$MEDIA_MP",mp=/srv/media,shared=1
pct start "$CTID"

pct exec "$CTID" -- apt-get update
pct exec "$CTID" -- apt-get install -y curl gnupg2 sudo
pct exec "$CTID" -- bash -lc "curl -fsSL https://downloads.plex.tv/plex-keys/PlexSign.key | gpg --dearmor -o /usr/share/keyrings/plex.gpg"
pct exec "$CTID" -- bash -lc "echo 'deb [signed-by=/usr/share/keyrings/plex.gpg] https://downloads.plex.tv/repo/deb public main' > /etc/apt/sources.list.d/plex.list"
pct exec "$CTID" -- apt-get update
pct exec "$CTID" -- apt-get install -y plexmediaserver

Exemple 2: LXC SMB (patró)

Aquest patró cobreix:

  • muntatges per apps, timemachine, media
  • Samba + Cockpit
  • comparticions base
CTID="801"
pct set "$CTID" --mp0 /mnt/pve/zfs_apps,mp=/srv/apps,shared=1
pct set "$CTID" --mp1 /mnt/pve/zfs_timemachine,mp=/srv/timemachine,shared=1
pct set "$CTID" --mp2 /mnt/pve/zfs_media,mp=/srv/media,shared=1

pct exec "$CTID" -- apt-get update
pct exec "$CTID" -- apt-get install -y samba cockpit wsdd avahi-daemon

pct exec "$CTID" -- bash -lc "cat > /etc/samba/smb.conf << 'SMBEOF'
[global]
workgroup = WORKGROUP
security = user
map to guest = Bad User

[Apps]
path = /srv/apps
browseable = yes
read only = no

[TimeMachine]
path = /srv/timemachine
browseable = yes
read only = no

[Media]
path = /srv/media
browseable = yes
guest ok = yes
read only = no
SMBEOF"

pct exec "$CTID" -- systemctl restart smbd

Exemple 3: VPS WWW amb cloud-init real (patró)

Aquí sí que té sentit un snippet complet #cloud-config.

#cloud-config
hostname: www-example
package_update: true
package_upgrade: true

packages:
  - qemu-guest-agent
  - nginx
  - firewalld
  - git
  - curl

users:
  - name: adminuser
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    lock_passwd: false
    passwd: "$6$REPLACE_WITH_HASH"
    ssh_authorized_keys:
      - "ssh-ed25519 AAAA...REPLACE_ME"

runcmd:
  - systemctl enable --now qemu-guest-agent
  - systemctl enable --now nginx
  - systemctl enable --now firewalld
  - firewall-cmd --permanent --add-service=http
  - firewall-cmd --permanent --add-service=https
  - firewall-cmd --reload
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
  - systemctl restart ssh

I després, al host Proxmox:

qm set 913 --cicustom "user=local:snippets/www-cloudinit.yaml"
qm set 913 --ipconfig0 "ip=192.168.10.50/24,gw=192.168.10.1"
qm start 913

Truc important: hash de contrasenya (mai en clar)

Per Debian/Ubuntu cloud-init, genera hash SHA-512:

openssl passwd -6 'CANVIA_AIXO_PER_UNA_CONTRASENYA_REAL'

I posa el resultat al camp passwd: del YAML.

Checklist final abans de publicar scripts

  1. Busca possibles secrets:
    rg -n "(PASSWORD|TOKEN|PSK|ssh-rsa|ssh-ed25519|api[_-]?key|passwd:)" .
    
  2. Revisa manualment fitxers *.sh, *.yaml, *.yml, *.env.
  3. Substitueix dades reals per placeholders consistents.
  4. Si cal, separa secrets a un fitxer local no versionat (.env.local) i afegeix-lo a .gitignore.

Resum

La idea és simple:

  • Per LXC, automatitza amb pct i provisionament intern estil cloud-init.
  • Per VPS/VM, aprofita cloud-init nadiu amb snippet YAML.
  • Per publicar, mai secrets reals: usa placeholders i hashes.

Amb aquest patró pots documentar infra real (Plex, SMB, WWW) sense comprometre ni la teva xarxa ni les teves credencials.