diff --git a/ubuntu-autoinstall/README.md b/ubuntu-autoinstall/README.md new file mode 100644 index 0000000..1f7988d --- /dev/null +++ b/ubuntu-autoinstall/README.md @@ -0,0 +1,151 @@ +# Ubuntu Server Autoinstall — Iron Legion G9 + +Headless, zero-touch Ubuntu 24.04 LTS deployment for GMKtec G9 mini-PCs. +Replaces MAAS for small batches. Fully autonomous after USB boot. + +## Files + +| File | Purpose | +|------|---------| +| `autoinstall.yaml` | Main config — save as `user-data` on USB | +| `meta-data` | Empty companion file (required by nocloud) | + +## Quick Start + +### 1. Create the USB stick + +1. Download **Ubuntu Server 24.04 LTS ISO** (not Desktop) +2. Flash to USB with **Rufus** (Windows) or `dd` (Linux) + - Partition scheme: GPT + - Target system: UEFI (non-CSM) +3. Mount the USB's writable partition + +### 2. Add autoinstall files + +Create these two files in the **root of the USB's writable partition**: + +**`user-data`** +``` +[paste entire contents of autoinstall.yaml here] +``` + +**`meta-data`** (can be empty) +```yaml +instance-id: iid-ironlegion01 +local-hostname: ubuntu +``` + +### 3. Configure GRUB for headless boot + +Edit `boot/grub/grub.cfg` on the USB. Find the first menuentry and add kernel params: + +```grub +set timeout=5 + +menuentry "Try or Install Ubuntu Server" { + linux /casper/vmlinuz autoinstall ds=nocloud\;s=/cdrom/ quiet --- + initrd /casper/initrd +} +``` + +**`timeout=5`** means 5-second countdown then auto-boots. Fully headless. + +### 4. Boot the G9 + +1. Plug USB into G9 +2. Power on +3. Walk away — 5 minutes later it's done +4. SSH in as `jarvis` / `ubuntu` + +## What Happens + +| Stage | Action | +|-------|--------| +| Boot | Ubuntu Server installer loads from USB | +| Storage | GPT on `/dev/nvme0n1` — 1GB boot + rest root (ext4) | +| Identity | Creates `jarvis` user with fleet SSH key | +| Hostname | Sets from MAC → `mk-33`, `mk-34`, `mk-39`, `mk-42`, or `g9-` | +| Packages | Installs Docker, Tailscale, git, curl, ufw, nfs-common | +| Docker | Adds `jarvis` to `docker` group | +| Tailscale | Installed, ready for `tailscale up` | +| Ansible | Clones `ansible-pull-deploy` repo to `~/.ansible-repo` | +| Network | Wildcard DHCP on all `en*` interfaces | +| SSH | Open on port 22, password auth enabled (fleet standard) | +| Firewall | UFW enabled — allows SSH only | +| Final | Reboots into fresh system | + +## Post-Install (one command per node) + +After the node is up: + +```bash +# Join Tailscale (interactive — generates auth URL) +sudo tailscale up + +# Or with pre-generated auth key: +sudo tailscale up --auth-key=tskey-auth-xxxxxxxx + +# Run ansible-pull for remaining fleet config +cd ~/.ansible-repo && ansible-pull -U https://gitea.nb.bobbysh.me/Iron-Legion/ansible-pull-deploy.git -d . +``` + +## MAC-to-Hostname Mapping + +| MAC | Hostname | +|-----|----------| +| `e0:51:d8:1c:5d:56` | `mk-33` | +| `e0:51:d8:1c:5c:75` | `mk-34` | +| `e0:51:d8:1c:5d:ca` | `mk-39` | +| `e0:51:d8:1c:5d:5c` | `mk-42` | +| other | `g9-` | + +## Tailscale Auth Key + +To auto-join without interaction, generate a reusable auth key: + +```bash +tailscale login # on Artemis or any node +tailscale web +tailscale authkeys create --reusable --preauthorized --tags=tag:g9 +``` + +Add the key to `late-commands` in `autoinstall.yaml`: +```yaml +- sudo tailscale up --auth-key=tskey-auth-xxxxxxxx +``` + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| Stops at boot menu | Check GRUB `timeout` is set, not `#` commented | +| Can't find `user-data` | Verify files are on writable partition root, not inside a folder | +| Wrong disk | Confirm G9 has 1TB NVMe in first slot (`/dev/nvme0n1`) | +| Hostname is `ubuntu` | Check MAC is correct; check `/etc/cloud/cloud-init.disabled` exists | +| No SSH | Verify `openssh-server` installed; check `ufw status` | + +## Differences from MAAS + +| | MAAS | Autoinstall | +|--|------|-------------| +| PXE server | Required (Shield) | Not needed | +| Subnet | Isolated `/27` required | Any network with DHCP | +| Internet during install | Not available | Available via your LAN | +| Time per node | 15-30 min | ~5 min | +| Touch required | Power on + rename in UI | Zero (headless) | +| Scale | Excellent for 10+ nodes | Ideal for 1-10 nodes | +| Phase 2 bootstrap | Required manually | Included in late-commands | + +## Notes + +- Password is fleet standard: `ubuntu` +- SSH key is the same fleet-wide key used on all nodes +- Cloud-init is disabled post-install to prevent hostname stomping +- If you get more G9s, just update the MAC→hostname mapping and re-flash + +## Version + +- Ubuntu 24.04 LTS (Noble Numbat) +- Docker CE latest +- Tailscale stable +- Ansible 2.x (pulled from repo) diff --git a/ubuntu-autoinstall/autoinstall.yaml b/ubuntu-autoinstall/autoinstall.yaml new file mode 100644 index 0000000..65d981a --- /dev/null +++ b/ubuntu-autoinstall/autoinstall.yaml @@ -0,0 +1,135 @@ +# Ubuntu Server Autoinstall — Iron Legion G9 Headless Deploy +# Usage: Place as 'user-data' on nocloud USB alongside empty 'meta-data' file +# See README.md for USB creation instructions + +version: 1 +autoinstall: + # ── Identity ── + identity: + hostname: ubuntu + username: jarvis + password: "$6$iypA63f5vLDzTGQ2$eOrvsyhnM6c4istoy65GUConWL4St.rzy28wFt8QxUWk7F3fSx7mHytwnjHosvIj7JAMBPeC4jkUctAZJeKDx/" + ssh: + install-server: true + allow-pw: true + authorized-keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPSBrRCROUHOiZX9IB3teEK89VFfghbdu7OF5NoJ1Y6g + + # ── Storage ── + storage: + config: + - type: disk + id: disk0 + ptable: gpt + wipe: superblock-recursive + path: /dev/nvme0n1 + - type: partition + id: boot-part + number: 1 + size: 1GB + device: disk0 + flag: boot + grub_device: true + - type: format + id: boot-format + fstype: ext4 + volume: boot-part + - type: mount + id: boot-mount + device: boot-format + path: /boot + - type: partition + id: root-part + number: 2 + size: -1 + device: disk0 + - type: format + id: root-format + fstype: ext4 + volume: root-part + - type: mount + id: root-mount + device: root-format + path: / + + # ── Networking ── + network: + version: 2 + ethernets: + en0: + match: + name: "en*" + dhcp4: true + dhcp6: true + + # ── Packages ── + packages: + - openssh-server + - curl + - git + - ca-certificates + - gnupg + - net-tools + - iputils-ping + - htop + - ufw + - nfs-common + + # ── First boot scripts ── + late-commands: + # Hostname from MAC address + - | + MAC=$(cat /sys/class/net/en*/address 2>/dev/null | head -1 | tr -d ':') + case "$MAC" in + e051d81c5d56) HOST="mk-33" ;; + e051d81c5c75) HOST="mk-34" ;; + e051d81c5dca) HOST="mk-39" ;; + e051d81c5d5c) HOST="mk-42" ;; + *) HOST="g9-$(echo $MAC | tail -c 5)" ;; + esac + hostnamectl set-hostname "$HOST" + echo "$HOST" > /etc/hostname + printf "127.0.1.1\t%s\n" "$HOST" >> /etc/hosts + + # Disable cloud-init re-run + - | + mkdir -p /etc/cloud/cloud.cfg.d + echo "preserve_hostname: true" > /etc/cloud/cloud.cfg.d/99_preserve_hostname.cfg + touch /etc/cloud/cloud-init.disabled + + # Install Docker + - | + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" > /etc/apt/sources.list.d/docker.list + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + usermod -aG docker jarvis + + # Install Tailscale + - | + curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null + curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list + apt-get update + apt-get install -y tailscale + + # Clone ansible-pull repo + - | + sudo -u jarvis bash -c 'mkdir -p ~/.ansible-repo && cd ~/.ansible-repo && git clone https://gitea.nb.bobbysh.me/Iron-Legion/ansible-pull-deploy.git . 2>/dev/null || true' + + # SSH hardening (keep PW auth enabled for fleet standard) + - | + sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config + sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config + systemctl restart ssh + + # Enable UFW basic rules + - | + ufw default deny incoming + ufw default allow outgoing + ufw allow 22/tcp + ufw --force enable + + # Reboot to finalize + - reboot