home/common/ai/resources/codex/skills/migrate-lxc/SKILL.md
Migrate a non-NixOS LXC container to NixOS with impermanence, handling configuration extraction, secrets via agenix, data migration, and cross-host IP reference resolution. Provide origin SSH, target SSH (or "new"), service type, and description.
npx skillsauth add kamushadenes/nix migrate-lxcInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Migrate a non-NixOS LXC container to NixOS with impermanence.
Provide the following in your instructions:
origin_ssh -- SSH target for the source container (e.g., [email protected])new_ssh -- SSH target for the destination (e.g., [email protected]) or "new" to create a fresh LXCservice_type -- Service being migrated (e.g., postgresql, nginx, docker)description -- Human-readable descriptionWhen new_ssh is "new", a new LXC container is created via the new-lxc skill before migration.
| Item | Value | |-|-| | Proxmox Nodes | pve1: 10.23.5.10, pve2: 10.23.5.11, pve3: 10.23.5.12 |
Extract hostname from new_ssh (e.g., [email protected] -> web-01). If the target is an IP, ask the user for a machine name.
Test SSH to both hosts. If new_ssh is "new", skip new host validation.
ssh -o ConnectTimeout=5 -o BatchMode=yes <origin_ssh> "hostname && uname -a"
ssh -o ConnectTimeout=5 -o BatchMode=yes <new_ssh> "hostname && nixos-version"
If new_ssh is NOT "new", find the container across Proxmox nodes and add the nixos tag.
If new_ssh is "new":
new-lxc skill in migration mode with the calculated specsBased on service_type, identify config and data paths:
| Service | Config Paths | Data Paths |
|-|-|-|
| postgresql | /etc/postgresql/*/main/ | /var/lib/postgresql/ |
| mysql/mariadb | /etc/mysql/, /etc/my.cnf | /var/lib/mysql/ |
| nginx | /etc/nginx/ | /var/www/, /etc/letsencrypt/ |
| docker | /etc/docker/daemon.json | /var/lib/docker/ |
| redis | /etc/redis/ | /var/lib/redis/ |
| mosquitto | /etc/mosquitto/ | /var/lib/mosquitto/ |
| grafana | /etc/grafana/grafana.ini | /var/lib/grafana/ |
| prometheus | /etc/prometheus/prometheus.yml | /var/lib/prometheus/ |
| generic | Detect via systemd unit inspection | Parse from service |
ssh <origin_ssh> "cat /path/to/config 2>/dev/null"
ssh <origin_ssh> "systemctl cat <service> 2>/dev/null"
ssh <origin_ssh> "du -sh /var/lib/<service>/ 2>/dev/null"
private/migration/hosts-registry.nixSearch for password, secret, token, key, credential, apikey, auth, SSL private keys, and connection strings.
Encrypt each secret with BOTH the machine's SSH host key AND the main age key:
echo "$SECRET" | age -r "$MACHINE_KEY" -r "$MAIN_KEY" > private/nixos/secrets/<machine>/<secret-name>.age
Main key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGNSkXQmM7HTbNUvGnaiDZpRlCnqHtMPGSlW3cXYBEBf
Generate private/nixos/secrets/<machine>/secrets.nix with both keys. Run lxc-add-machine <machine> root@<ip> to register for global LXC secrets.
Create the following files:
nixos/hardware/<machine>.nix -- LXC hardware config with proxmox-lxc.nix importnixos/machines/<machine>.nix -- Service config with NixOS module settings, firewall rules, and agenix secretsflake.nix entry -- Add mkProxmoxHost with appropriate extraPersistPathsImport ${private}/nixos/lxc-management.nix and set age.identityPaths = [ "/nix/persist/etc/ssh/ssh_host_ed25519_key" ].
For services without native NixOS modules, ask the user: Docker container, systemd service, or manual config.
Present summary: files to create, detected secrets, IP mappings, data size, persistence paths, and warnings. Get user confirmation.
Stage new files (required for nix flakes). Private submodule files committed separately.
This is critical -- services create state during activation. Create bind mounts before rebuild:
ssh <new_ssh> "mkdir -p /nix/persist/{etc/nixos,etc/ssh,var/log,home,var/lib/<service>}"
rebuild -vL <machine>
ssh <origin_ssh> "systemctl stop <service>"
rsync -avz --progress <origin_ssh>:/var/lib/<service>/ <new_ssh>:/nix/persist/var/lib/<service>/
ssh <new_ssh> "chown -R <user>:<group> /nix/persist/var/lib/<service> && systemctl restart <service>"
Run service-specific health checks. Compare old vs new where possible. Report results and next steps (DNS update, decommission old container).
| Service | extraPersistPaths |
|-|-|
| postgresql | [ "/var/lib/postgresql" ] |
| mysql | [ "/var/lib/mysql" ] |
| nginx | [ "/var/www" "/etc/ssl/private" "/var/lib/letsencrypt" ] |
| docker | [ "/var/lib/docker" ] |
| redis | [ "/var/lib/redis" ] |
| mosquitto | [ "/var/lib/mosquitto" ] |
| grafana | [ "/var/lib/grafana" ] |
| prometheus | [ "/var/lib/prometheus" ] |
| Error | Recovery |
|-|-|
| SSH connection failed | Check connectivity, verify SSH keys |
| Service not found | Use generic systemd inspection, prompt user |
| Config syntax error | Run nix flake check, fix manually |
| Secret extraction failed | Prompt user for manual secret entry |
| Data sync failed | Retry rsync, check disk space |
| Health check failed | Check logs: journalctl -u <service> |
age.identityPaths configured or secrets fail silentlydata-ai
Show MemPalace status — room counts, storage usage, and palace health.
tools
Search your MemPalace — semantic search across all mined memories, projects, and conversations.
tools
Mine a project or conversation into your MemPalace — extract and store memories for later retrieval.
development
Initialize a new MemPalace — guided setup for your AI memory palace with ChromaDB backend.