SSH hardening (no root, no password) prevents pct exec and deploy access on dev/staging servers. Moved to security skill as production-only optional step. Kept: fail2ban, auto-updates, sysctl. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
303 lines
8.8 KiB
Markdown
303 lines
8.8 KiB
Markdown
---
|
|
name: proxmox-lxc
|
|
description: Proxmox LXC-Container erstellen, konfigurieren und Anwendungen darauf deployen
|
|
user_invocable: true
|
|
---
|
|
|
|
Du bist ein Proxmox-LXC-Agent. Du erstellst und verwaltest LXC-Container auf Proxmox-Servern und deployest Anwendungen darauf.
|
|
|
|
## KONFIGURATION
|
|
```
|
|
PVE_HOST=$PVE_HOST # Proxmox-Host (IP oder Hostname)
|
|
PVE_PORT=$PVE_PORT # API-Port (default: 8006)
|
|
PVE_USER=$PVE_USER # z.B. root@pam
|
|
PVE_TOKEN_ID=$PVE_TOKEN_ID # API Token ID
|
|
PVE_TOKEN_SECRET=$PVE_TOKEN_SECRET # API Token Secret
|
|
PVE_NODE=$PVE_NODE # Node-Name (default: pve)
|
|
PVE_STORAGE=$PVE_STORAGE # Storage (default: local-lvm)
|
|
LXC_TEMPLATE=$LXC_TEMPLATE # Template (default: debian-13-standard)
|
|
```
|
|
|
|
## LXC-CONTAINER ERSTELLEN
|
|
|
|
### Via Proxmox API
|
|
```bash
|
|
# Nächste freie VMID ermitteln
|
|
VMID=$(curl -sk "https://$PVE_HOST:$PVE_PORT/api2/json/cluster/nextid" \
|
|
-H "Authorization: PVEAPIToken=$PVE_USER!$PVE_TOKEN_ID=$PVE_TOKEN_SECRET" | jq -r '.data')
|
|
|
|
# Container erstellen
|
|
curl -sk -X POST "https://$PVE_HOST:$PVE_PORT/api2/json/nodes/$PVE_NODE/lxc" \
|
|
-H "Authorization: PVEAPIToken=$PVE_USER!$PVE_TOKEN_ID=$PVE_TOKEN_SECRET" \
|
|
-d "vmid=$VMID" \
|
|
-d "hostname=app-staging" \
|
|
-d "ostemplate=$PVE_STORAGE:vztmpl/$LXC_TEMPLATE.tar.zst" \
|
|
-d "storage=$PVE_STORAGE" \
|
|
-d "rootfs=$PVE_STORAGE:8" \
|
|
-d "memory=2048" \
|
|
-d "swap=512" \
|
|
-d "cores=2" \
|
|
-d "net0=name=eth0,bridge=vmbr0,ip=dhcp" \
|
|
-d "start=1" \
|
|
-d "unprivileged=1" \
|
|
-d "features=nesting=1" \
|
|
-d "ssh-public-keys=$(cat ~/.ssh/id_rsa.pub)"
|
|
```
|
|
|
|
### Via SSH auf Proxmox-Host (Alternative)
|
|
```bash
|
|
ssh root@$PVE_HOST << 'EOF'
|
|
# Template herunterladen falls nötig
|
|
pveam update
|
|
pveam download local debian-13-standard_12.7-1_amd64.tar.zst
|
|
|
|
# Container erstellen
|
|
pct create $VMID local:vztmpl/debian-13-standard_12.7-1_amd64.tar.zst \
|
|
--hostname app-staging \
|
|
--storage local-lvm \
|
|
--rootfs local-lvm:8 \
|
|
--memory 2048 \
|
|
--swap 512 \
|
|
--cores 2 \
|
|
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
|
|
--unprivileged 1 \
|
|
--features nesting=1 \
|
|
--start 1
|
|
|
|
# SSH-Key hinzufügen
|
|
pct exec $VMID -- mkdir -p /root/.ssh
|
|
pct push $VMID /root/.ssh/authorized_keys /root/.ssh/authorized_keys
|
|
EOF
|
|
```
|
|
|
|
## LXC-IP ERMITTELN
|
|
|
|
Nach dem Erstellen und Starten des Containers die IP auslesen. Diese IP wird für alle weiteren Schritte und für `/ssh-deploy` verwendet.
|
|
|
|
### Via Proxmox API
|
|
```bash
|
|
LXC_IP=$(curl -sk "https://$PVE_HOST:$PVE_PORT/api2/json/nodes/$PVE_NODE/lxc/$VMID/interfaces" \
|
|
-H "Authorization: PVEAPIToken=$PVE_USER!$PVE_TOKEN_ID=$PVE_TOKEN_SECRET" \
|
|
| jq -r '.data[] | select(.name=="eth0") | .["inet-address"]')
|
|
echo "LXC-IP: $LXC_IP"
|
|
```
|
|
|
|
### Via SSH auf Proxmox-Host
|
|
```bash
|
|
# Variante 1: lxc-info
|
|
LXC_IP=$(ssh root@$PVE_HOST "lxc-info -n $VMID -iH 2>/dev/null || pct exec $VMID -- hostname -I | awk '{print \$1}'")
|
|
echo "LXC-IP: $LXC_IP"
|
|
|
|
# Variante 2: pct exec
|
|
LXC_IP=$(ssh root@$PVE_HOST "pct exec $VMID -- ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'")
|
|
echo "LXC-IP: $LXC_IP"
|
|
```
|
|
|
|
### WICHTIG: Automatische Weitergabe an ssh-deploy
|
|
Nach der IP-Ermittlung wird SSH_HOST automatisch gesetzt. Kein manueller Eintrag in `project.env` nötig:
|
|
```bash
|
|
# SSH-Verbindung zum neuen LXC testen
|
|
ssh -i $SSH_KEY_PATH -o StrictHostKeyChecking=accept-new deploy@$LXC_IP "echo 'LXC erreichbar'"
|
|
```
|
|
|
|
Ab hier kann `/ssh-deploy` direkt mit `SSH_HOST=$LXC_IP` arbeiten.
|
|
|
|
## LXC FÜR WEBPROJEKT EINRICHTEN
|
|
|
|
### Basis-Setup (Node.js + Docker)
|
|
```bash
|
|
# In den Container verbinden (via Proxmox-Host)
|
|
ssh root@$PVE_HOST "pct exec $VMID -- bash -c '
|
|
apt update && apt upgrade -y
|
|
apt install -y curl git sudo wget gnupg2 ca-certificates
|
|
|
|
# Node.js 20 LTS
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
apt install -y nodejs
|
|
|
|
# Docker (in unprivileged LXC mit nesting)
|
|
curl -fsSL https://get.docker.com | sh
|
|
|
|
# PM2 für Prozess-Management
|
|
npm install -g pm2
|
|
|
|
# Deploy-User anlegen
|
|
useradd -m -s /bin/bash deploy
|
|
mkdir -p /home/deploy/.ssh
|
|
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
|
|
chown -R deploy:deploy /home/deploy/.ssh
|
|
echo \"deploy ALL=(ALL) NOPASSWD:ALL\" >> /etc/sudoers.d/deploy
|
|
|
|
# App-Verzeichnis
|
|
mkdir -p /var/www/app
|
|
chown deploy:deploy /var/www/app
|
|
|
|
# Firewall
|
|
apt install -y ufw
|
|
ufw allow 22
|
|
ufw allow 80
|
|
ufw allow 443
|
|
ufw --force enable
|
|
|
|
# --- SECURITY HARDENING (ohne SSH - stört beim Deploy) ---
|
|
|
|
# Fail2Ban gegen Brute-Force
|
|
apt install -y fail2ban
|
|
cat > /etc/fail2ban/jail.local << JAIL
|
|
[sshd]
|
|
enabled = true
|
|
port = ssh
|
|
filter = sshd
|
|
logpath = /var/log/auth.log
|
|
maxretry = 3
|
|
bantime = 3600
|
|
findtime = 600
|
|
JAIL
|
|
systemctl enable --now fail2ban
|
|
|
|
# Automatische Sicherheitsupdates
|
|
apt install -y unattended-upgrades
|
|
cat > /etc/apt/apt.conf.d/20auto-upgrades << AUTOUPD
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
AUTOUPD
|
|
|
|
# Kernel-Hardening (sysctl)
|
|
cat > /etc/sysctl.d/99-security.conf << SYSCTL
|
|
net.ipv4.conf.all.rp_filter = 1
|
|
net.ipv4.conf.default.rp_filter = 1
|
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.default.accept_redirects = 0
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.default.send_redirects = 0
|
|
SYSCTL
|
|
sysctl -p /etc/sysctl.d/99-security.conf
|
|
'"
|
|
```
|
|
|
|
### Mit Docker-Compose Setup
|
|
```bash
|
|
ssh root@$PVE_HOST "pct exec $VMID -- bash -c '
|
|
apt update && apt upgrade -y
|
|
apt install -y curl git sudo
|
|
|
|
# Docker + Compose
|
|
curl -fsSL https://get.docker.com | sh
|
|
apt install -y docker-compose-plugin
|
|
|
|
# Deploy-User mit Docker-Rechten
|
|
useradd -m -s /bin/bash deploy
|
|
usermod -aG docker deploy
|
|
mkdir -p /home/deploy/.ssh /var/www/app
|
|
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
|
|
chown -R deploy:deploy /home/deploy/.ssh /var/www/app
|
|
'"
|
|
```
|
|
|
|
### Nginx Reverse-Proxy Setup
|
|
```bash
|
|
ssh deploy@$LXC_IP << 'EOF'
|
|
sudo apt install -y nginx
|
|
sudo tee /etc/nginx/sites-available/app << 'NGINX'
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
location / {
|
|
proxy_pass http://localhost:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
|
|
location /api {
|
|
proxy_pass http://localhost:4000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
}
|
|
NGINX
|
|
sudo ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
|
|
sudo rm -f /etc/nginx/sites-enabled/default
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
EOF
|
|
```
|
|
|
|
## CONTAINER-VERWALTUNG
|
|
|
|
### Status & Info
|
|
```bash
|
|
# Via API
|
|
curl -sk "https://$PVE_HOST:$PVE_PORT/api2/json/nodes/$PVE_NODE/lxc/$VMID/status/current" \
|
|
-H "Authorization: PVEAPIToken=$PVE_USER!$PVE_TOKEN_ID=$PVE_TOKEN_SECRET" | jq '.data'
|
|
|
|
# Via SSH
|
|
ssh root@$PVE_HOST "pct status $VMID"
|
|
ssh root@$PVE_HOST "pct config $VMID"
|
|
ssh root@$PVE_HOST "pct list"
|
|
```
|
|
|
|
### Start / Stop / Restart
|
|
```bash
|
|
ssh root@$PVE_HOST "pct start $VMID"
|
|
ssh root@$PVE_HOST "pct stop $VMID"
|
|
ssh root@$PVE_HOST "pct reboot $VMID"
|
|
```
|
|
|
|
### Snapshot (Backup vor Änderungen)
|
|
```bash
|
|
ssh root@$PVE_HOST "pct snapshot $VMID snap-before-deploy --description 'Vor Deployment'"
|
|
# Rollback:
|
|
ssh root@$PVE_HOST "pct rollback $VMID snap-before-deploy"
|
|
```
|
|
|
|
### Ressourcen anpassen
|
|
```bash
|
|
ssh root@$PVE_HOST "pct set $VMID --memory 4096 --cores 4"
|
|
ssh root@$PVE_HOST "pct resize $VMID rootfs +10G"
|
|
```
|
|
|
|
### Container löschen (mit Bestätigung!)
|
|
```bash
|
|
ssh root@$PVE_HOST "pct stop $VMID && pct destroy $VMID --purge"
|
|
```
|
|
|
|
## DEPLOYMENT-WORKFLOW AUF LXC
|
|
|
|
### Komplett-Workflow: Neuen LXC + App deployen
|
|
```
|
|
1. LXC-Container auf Proxmox erstellen
|
|
2. Basis-Setup (Node.js/Docker, Deploy-User, Firewall)
|
|
3. Nginx Reverse-Proxy konfigurieren
|
|
4. Code deployen (via rsync oder git clone)
|
|
5. Dependencies installieren, Build, Service starten
|
|
6. Health-Check: curl http://$LXC_IP/health
|
|
```
|
|
|
|
### In bestehendem LXC deployen
|
|
Nutze `/ssh-deploy` mit der LXC-IP als SSH_HOST.
|
|
|
|
## SICHERHEIT
|
|
- Unprivileged Container bevorzugen
|
|
- SSH-Keys statt Passwörter
|
|
- Firewall (ufw) immer aktivieren
|
|
- Container NICHT als root betreiben - Deploy-User nutzen
|
|
- Snapshots vor kritischen Änderungen
|
|
- Nesting nur aktivieren wenn Docker im LXC benötigt wird
|
|
- NIEMALS Container löschen ohne explizite Bestätigung
|
|
|
|
## TEMPLATES (häufig genutzt)
|
|
| Template | Verwendung |
|
|
|----------|-----------|
|
|
| `debian-13-standard` | Standard für Webapps (Trixie) |
|
|
| `ubuntu-24.04-standard` | Alternative mit Ubuntu |
|
|
| `alpine-3.20-default` | Minimal, für einfache Services |
|