This repository manages automated Ubuntu Desktop installation using Canonical's autoinstall format. It includes a complete single-file configuration for deploying a production-ready system.
| Feature | Implementation |
|---|---|
| Desktop | ubuntu-desktop-minimal (installed on first boot) |
| Kubernetes | kubeadm, kubelet, kubectl v1.31, containerd |
| Shell Tools | Homebrew, Oh My Zsh, K9s |
| Security | Passwordless sudo, SSH password auth |
| Governance | Strict AI interaction modes (Approval vs Auto Pilot) |
This project adheres to strict interaction protocols defined in context/governance.md.
- Default: Approval Mode (Plan first, ask before executing).
- Auto Pilot: Available on request (Recommended or Aggressive profiles).
This project uses a 2-phase approach for reliable desktop installation:
┌─────────────────────────────────────────────────────────┐
│ PHASE 1 (Autoinstall) │
│ • Base Ubuntu Server │
│ • SSH + passwordless sudo │
│ • Kubernetes (containerd, kubeadm, kubelet, kubectl) │
│ • Creates Phase 2 systemd service │
└─────────────────────────────────────────────────────────┘
↓ Reboot
┌─────────────────────────────────────────────────────────┐
│ PHASE 2 (First Boot - Automatic) │
│ • Installs ubuntu-desktop-minimal │
│ • Installs Homebrew, Oh My Zsh, K9s │
│ • Reboots into GNOME desktop │
└─────────────────────────────────────────────────────────┘
Why 2-Phase? Installing ubuntu-desktop-minimal during autoinstall breaks SSH password authentication. The 2-phase approach installs desktop on first boot after the base system is working.
| File | Purpose |
|---|---|
context/autoinstall.example.yaml |
Template with all features |
context/autoinstall.yaml |
Your personalized config (gitignored) |
context/user_data.json |
VM connection info (gitignored) |
| Procedure | Description |
|---|---|
add_late_command/ |
Add late-command scripts to autoinstall.yaml |
e2e_autoinstall_test/ |
End-to-end testing with Packer/QEMU |
exclude_bundles/ |
Generate variant configs by excluding bundles |
init_autoinstall/ |
Initialize autoinstall.yaml from template |
init_change_log/ |
Initialize changelog with Keep a Changelog format |
init_user_data/ |
Initialize user_data.json with VM connection details |
maintain_docs/ |
Keep README synchronized with project structure |
passwordless_sudo/ |
Configure passwordless sudo access |
ssh_key_auth/ |
Set up SSH key authentication |
update_system/ |
Update system packages and bundles with tracking |
validate_config/ |
Validate autoinstall.yaml syntax |
verify_script/ |
Security analysis before script execution |
verify_vm/ |
Compare live VM state with configuration |
| Bundle | Components |
|---|---|
docker/ |
Docker CE (historical reference) |
kubeadm/ |
Kubernetes v1.31, containerd, nerdctl |
packer_qemu/ |
Packer + QEMU for E2E testing |
shell_tools/ |
Homebrew, Oh My Zsh, K9s |
cp context/autoinstall.example.yaml context/autoinstall.yamlEdit context/autoinstall.yaml:
identity:
hostname: your-hostname
username: your-username
password: "$6$..." # Generate: echo 'password' | openssl passwd -6 -stdinReplace all your-username placeholders in late-commands.
./scripts/validate_config.sh./context/procedures/e2e_autoinstall_test/files/run-e2e-test.shCreate bootable USB with autoinstall.yaml or use Packer to build VM image.
Heredocs (<< EOF) do NOT work in autoinstall late-commands due to shell escaping issues. Use simple echo commands instead:
# ❌ WRONG
- curtin in-target ... -- bash -c 'cat > /file << EOF
content
EOF'
# ✅ CORRECT
- curtin in-target ... -- bash -c 'echo "line1" > /file'
- curtin in-target ... -- bash -c 'echo "line2" >> /file'When creating autoinstall.yaml with shell, use single-quoted heredoc to prevent $ expansion:
cat > autoinstall.yaml << 'EOF'
password: "$6$salt$hash"
EOFAfter Phase 2 completes and system reboots:
k8s-init-single-node.sh# Check desktop
dpkg -l ubuntu-desktop-minimal
# Check Kubernetes
kubectl get nodes
kubectl get sc
# Check shell tools
brew --version
k9s version# Check status
systemctl status first-boot-phase2.service
# View logs
cat /var/log/phase2.log
# Re-run manually
sudo /usr/local/bin/first-boot-phase2.shsudo rm /var/lib/phase2-complete
sudo systemctl enable first-boot-phase2.service
sudo reboot