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-plexlxc-smbvps-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:
- No publiquis claus SSH completes (
ssh-rsa ...). - No publiquis contrasenyes (
COMMON_PASSWORD=...). - No publiquis tokens (
FREEDNS_TOKEN, API keys, PSK de VPN). - No publiquis IPs/hosts interns reals si no cal.
- 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
- Busca possibles secrets:
rg -n "(PASSWORD|TOKEN|PSK|ssh-rsa|ssh-ed25519|api[_-]?key|passwd:)" . - Revisa manualment fitxers
*.sh,*.yaml,*.yml,*.env. - Substitueix dades reals per placeholders consistents.
- 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
pcti provisionament intern estil cloud-init. - Per VPS/VM, aprofita
cloud-initnadiu 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.