1 Commits

15 changed files with 398 additions and 432 deletions

View File

@@ -14,14 +14,24 @@ Each node runs `ansible-pull` every 5 minutes via cron. It clones this repo and
├── group_vars/
│ └── all.yml # Fleet-wide variables
├── host_vars/
│ ├── artemis.yml # Artemis (AI Foreman) specific
│ ├── mark44.yml # Mark44 (Hulkbuster) specific
│ ├── mark5.yml # Mark5 (Suitcase) specific
── bones.yml # Bones (Mark XLI) specific
└── roles/
── common/
└── tasks/
└── main.yml
│ ├── artemis.yml # Artemis (AI Foreman)
│ ├── cinnamint--elitebook.yml # Cinnamint-EliteBook (WSL2 workstation)
│ ├── hulkbuster.yml # Mark44 (GPU heavy)
── mark5.yml # Mark5 (GPU light / Suitcase)
│ ├── mark-vii.yml # Mark VII (Swarm manager + services)
── mission-control.yml # Mission-Control (WSL2 workstation)
│ ├── mk-33.yml # MK-33 Silver Centurion (Swarm worker)
│ ├── mk-34.yml # MK-34 (Swarm worker)
│ ├── mk-39.yml # MK-39 (Swarm worker)
│ ├── mk-42.yml # MK-42 Extremis (Swarm worker)
│ └── nebuchadnezzar.yml # Neo (Nextcloud + Vaultwarden)
├── new-build/
│ └── portainer/
│ └── docker-compose.yml # Portainer CE stack for Swarm manager
├── ubuntu-autoinstall/
│ └── autoinstall.yaml # Fleet-standard headless autoinstall
└── archive/
└── maas/
```
## Adding Node-Specific Tasks

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Ansible Pull — Iron Legion fleet baseline
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/var/lib/ansible/local
ExecStartPre=/bin/bash -c 'if [ ! -d /var/lib/ansible/local/.git ]; then git clone -b main https://gitea.nb.bobbysh.me/Iron-Legion/ansible-pull-deploy.git /var/lib/ansible/local; else git -C /var/lib/ansible/local pull origin main; fi'
ExecStart=/usr/bin/ansible-playbook /var/lib/ansible/local/local.yml
StandardOutput=journal
StandardError=journal

12
ansible-pull.service Normal file
View File

@@ -0,0 +1,12 @@
[Unit]
Description=Ansible Pull — Iron Legion fleet baseline
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/var/lib/ansible/local
ExecStartPre=/bin/bash -c 'if [ ! -d /var/lib/ansible/local/.git ]; then git clone -b main https://gitea.nb.bobbysh.me/Iron-Legion/ansible-pull-deploy.git /var/lib/ansible/local; else git -C /var/lib/ansible/local pull origin main; fi'
ExecStart=/usr/bin/ansible-playbook /var/lib/ansible/local/local.yml
StandardOutput=journal
StandardError=journal

10
ansible-pull.timer Normal file
View File

@@ -0,0 +1,10 @@
[Unit]
Description=Run ansible-pull every 15 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=15min
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -1,21 +0,0 @@
# MAAS Archive
**Status: ABANDONED — May 23, 2026**
These files are the final state of the MAAS PXE deployment effort for Iron Legion fleet nodes. MAAS was removed from Shield after repeated failures on isolated subnets, IP exhaustion, and general complexity. Replaced by USB autoinstall approach.
## Files
- `curtin_userdata_fleet_v5.yaml` — Fleet preseed targeting `/dev/nvme0n1`, no apt-get (isolated subnet safe), SSH enable only. Final working version before abandonment.
## Lessons Captured
1. MAAS ephemeral commissioning fails on subnets without internet (apt-get hangs)
2. IP exhaustion on /27 pools after failed deployments (sticky reservations)
3. G9 Intel I226-V NIC (enp5s0) drops link during Linux driver init
4. Linux enumerates first NVMe as `nvme0n1`, not `nvme1`
5. Cloud-init stomps hostname unless `preserve_hostname: true` is set
## Replacement
See `autoinstall/` in repo root for the USB-based replacement workflow.

View File

@@ -1,49 +0,0 @@
#cloud-config
# MAAS Fleet Preseed v5 — Iron Legion DR Standard
# No apt-get during late_commands (isolated subnet)
# Targets /dev/nvme0n1 (first NVMe, skip eMMC)
# Defer internet tasks to post-deploy on fleet LAN
# Corrected 2026-05-23: nvme1 → nvme0n1 (Linux enumeration)
debconf_selections:
maas: |
{{for line in str(curtin_preseed).splitlines()}}
{{line}}
{{endfor}}
storage:
swap:
size: 0
config:
- type: disk
id: root-disk
path: /dev/nvme0n1
ptable: gpt
wipe: superblock-recursive
- type: partition
id: root-partition
device: root-disk
size: -1
flag: boot
- type: format
id: root-format
volume: root-partition
fstype: ext4
- type: mount
id: root-mount
device: root-format
path: /
late_commands:
fleet_01_create_user: ["curtin", "in-target", "--", "sh", "-c", "useradd -m -s /bin/bash -G sudo jarvis && echo 'jarvis:ubuntu' | chpasswd"]
fleet_02_hostname: ["curtin", "in-target", "--", "sh", "-c", "hostnamectl set-hostname $(echo {{node.hostname}} | sed 's/[^a-zA-Z0-9-]//g') && echo $(echo {{node.hostname}} | sed 's/[^a-zA-Z0-9-]//g') > /etc/hostname"]
fleet_02b_preserve_hostname: ["curtin", "in-target", "--", "sh", "-c", "echo 'preserve_hostname: true' > /etc/cloud/cloud.cfg.d/99_preserve_hostname.cfg"]
fleet_03_enable_ssh: ["curtin", "in-target", "--", "systemctl", "enable", "ssh"]
fleet_04_start_ssh: ["curtin", "in-target", "--", "systemctl", "start", "ssh"]
fleet_05_ssh_dir: ["curtin", "in-target", "--", "sh", "-c", "mkdir -p /home/jarvis/.ssh && chmod 700 /home/jarvis/.ssh"]
fleet_06_auth_keys: ["curtin", "in-target", "--", "sh", "-c", "echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPSBrRCROUHOiZX9IB3teEK89VFfghbdu7OF5NoJ1Y6g Generated By Termius' > /home/jarvis/.ssh/authorized_keys"]
fleet_07_chmod: ["curtin", "in-target", "--", "chmod", "600", "/home/jarvis/.ssh/authorized_keys"]
fleet_08_chown: ["curtin", "in-target", "--", "chown", "-R", "jarvis:jarvis", "/home/jarvis/.ssh"]
fleet_09_sudo: ["curtin", "in-target", "--", "sh", "-c", "echo 'jarvis ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/jarvis && chmod 440 /etc/sudoers.d/jarvis"]
fleet_10_ansible_dirs: ["curtin", "in-target", "--", "sh", "-c", "mkdir -p /var/lib/ansible/local"]
maas: [wget, '--no-proxy', {{node_disable_pxe_url|escape.json}}, '--post-data', {{node_disable_pxe_data|escape.json}}, '-O', '/dev/null']

View File

@@ -1,27 +0,0 @@
---
# Bones (Mark XLI) — Headless CPU-only node
node_type: headless
has_gpu: false
# Headless essentials
extra_packages:
- cpufrequtils # CPU frequency management
- lm-sensors # Temperature monitoring
- smartmontools # Disk health monitoring
- hdparm # Disk performance tuning
- netdata # lightweight monitoring (optional)
# Services managed on Bones
managed_services:
- name: jarvis # Paperclip + Ollama + PostgreSQL stack
enabled: true
- name: ollama # CPU inference only
enabled: true
# Ollama config (CPU mode, very small models)
ollama_models:
- gemma3:1b # Ultra-tiny for CPU
# Node-specific vars
bones_storage: "256GB SSD"
jvm_heap: "512m"

17
iventoy.service Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=iVentoy PXE Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/iventoy
Environment=LD_LIBRARY_PATH=/opt/iventoy/lib/lin64
ExecStart=/opt/iventoy/lib/iventoy
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -33,7 +33,6 @@
- vim
- python3-pip
- cifs-utils
- nfs-common
state: present
when: ansible_os_family == "Debian"
tags: [baseline]

View File

@@ -1,35 +0,0 @@
version: '3.8'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
ports:
- "8000:8000" # Edge agent port
- "9000:9000" # HTTP web UI
- "9443:9443" # HTTPS web UI (optional)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- portainer_data:/data
environment:
- TZ=America/New_York
networks:
- portainer_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.nb.bobbysh.me`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls=true"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
# Optional: disable Traefik if not using it yet
# - "traefik.enable=false"
volumes:
portainer_data:
driver: local
networks:
portainer_net:
driver: bridge
attachable: true

View File

@@ -0,0 +1,9 @@
#!/bin/bash
export SUDO_ASKPASS=/tmp/askpass/askpass.sh
sudo -A pkill -f "iventoy" || true
sleep 2
cd /opt/iventoy
export LD_LIBRARY_PATH=/opt/iventoy/lib/lin64
nohup ./lib/iventoy > /dev/null 2>&1 &
sleep 3
pgrep -f iventoy

160
ssh-config Normal file
View File

@@ -0,0 +1,160 @@
# Read more about SSH config files: https://linux.die.net/man/5/ssh_config
# Core Services
# Reverse Proxy
#***********#
# Local Net #
#***********#
Host artemis
HostName 192.168.15.182
User jarvis
IdentityFile ~/.ssh/artemis_key
Host mark5
HostName 192.168.6.5
User jarvis
IdentityFile ~/.ssh/artemis_key
Host mark44
HostName 192.168.5.214
User jarvis
IdentityFile ~/.ssh/artemis_key
#========================#
# Tailscale alternatives #
#========================#
Host ts-artemis
HostName 100.100.97.18
User jarvis
IdentityFile ~/.ssh/artemis_key
Host ts-mark44
HostName 100.75.26.83
User jarvis
IdentityFile ~/.ssh/artemis_key
Host ts-mark5
HostName 100.118.67.105
User jarvis
IdentityFile ~/.ssh/artemis_key
#======================#
# Netbird alternatives #
#======================#
Host nb-artemis
HostName 100.100.97.18
User jarvis
IdentityFile ~/.ssh/artemis_key
Host nebuchadnezzar
HostName 100.99.123.16
User jarvis
IdentityFile ~/.ssh/id_nebuchadnezzar
IdentitiesOnly yes
Host gitea.nb.bobbysh.me
HostName gitea.nb.bobbysh.me
User git
IdentityFile ~/.ssh/gitea_api_key
StrictHostKeyChecking no
IdentitiesOnly yes
Host cinnamint
HostName 100.99.65.75
User jarvis
IdentityFile ~/.ssh/artemis_key
IdentitiesOnly yes
Host shield
HostName 192.168.27.205
User jarvis
IdentityFile ~/.ssh/artemis_key
IdentitiesOnly yes
Host mk7
HostName 192.168.7.7
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk7
HostName 100.66.70.51
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-33 Silver Centurion
Host mk33
HostName 192.168.0.190
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk33
HostName 100.125.155.41
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-34 Southpaw
Host mk34
HostName 192.168.0.123
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk34
HostName 100.94.190.43
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-39 Gemini
Host mk39
HostName 192.168.0.106
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk39
HostName 100.125.155.41
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-42 Extremis
Host mk42
HostName 192.168.0.196
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk42
HostName 100.94.190.43
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# Shield (iVentoy PXE Server, formerly Bones)
Host ts-shield
HostName 100.109.254.36
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# Igor (ZimaOS NAS, Mark XXXVIII)
Host igor
HostName 192.168.10.211
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# TrueNAS (beelink-tns)
Host truenas
HostName 192.168.16.254
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host mission-control
HostName 100.96.128.121
User jarvis
IdentityFile ~/.ssh/id_ed25519_windows
IdentitiesOnly yes

160
ssh-config-artemis Normal file
View File

@@ -0,0 +1,160 @@
# Read more about SSH config files: https://linux.die.net/man/5/ssh_config
# Core Services
# Reverse Proxy
#***********#
# Local Net #
#***********#
Host artemis
HostName 192.168.15.182
User jarvis
IdentityFile ~/.ssh/artemis_key
Host mark5
HostName 192.168.6.5
User jarvis
IdentityFile ~/.ssh/artemis_key
Host mark44
HostName 192.168.5.214
User jarvis
IdentityFile ~/.ssh/artemis_key
#========================#
# Tailscale alternatives #
#========================#
Host ts-artemis
HostName 100.100.97.18
User jarvis
IdentityFile ~/.ssh/artemis_key
Host ts-mark44
HostName 100.75.26.83
User jarvis
IdentityFile ~/.ssh/artemis_key
Host ts-mark5
HostName 100.118.67.105
User jarvis
IdentityFile ~/.ssh/artemis_key
#======================#
# Netbird alternatives #
#======================#
Host nb-artemis
HostName 100.100.97.18
User jarvis
IdentityFile ~/.ssh/artemis_key
Host nebuchadnezzar
HostName 100.99.123.16
User jarvis
IdentityFile ~/.ssh/id_nebuchadnezzar
IdentitiesOnly yes
Host gitea.nb.bobbysh.me
HostName gitea.nb.bobbysh.me
User git
IdentityFile ~/.ssh/gitea_api_key
StrictHostKeyChecking no
IdentitiesOnly yes
Host cinnamint
HostName 100.99.65.75
User jarvis
IdentityFile ~/.ssh/artemis_key
IdentitiesOnly yes
Host shield
HostName 192.168.27.205
User jarvis
IdentityFile ~/.ssh/artemis_key
IdentitiesOnly yes
# Shield iVentoy PXE Server (Tailscale)
Host ts-shield
HostName 100.109.254.36
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host mk7
HostName 192.168.7.7
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk7
HostName 100.66.70.51
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-33 Silver Centurion
Host mk33
HostName 192.168.0.190
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk33
HostName 100.125.155.41
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-34 Southpaw
Host mk34
HostName 192.168.0.123
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk34
HostName 100.94.190.43
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-39 Gemini
Host mk39
HostName 192.168.0.106
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk39
HostName 100.125.155.41
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# mk-42 Extremis
Host mk42
HostName 192.168.0.196
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host ts-mk42
HostName 100.94.190.43
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# Igor (ZimaOS NAS, Mark XXXVIII)
Host igor
HostName 192.168.10.211
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
# TrueNAS (beelink-tns)
Host truenas
HostName 192.168.16.254
User jarvis
IdentityFile ~/.ssh/artemis_key
StrictHostKeyChecking accept-new
Host mission-control
HostName 100.96.128.121
User jarvis
IdentityFile ~/.ssh/id_ed25519_windows
IdentitiesOnly yes

View File

@@ -1,156 +0,0 @@
# 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`** — copy `autoinstall.yaml` and rename it to `user-data` (no extension):
```bash
cp autoinstall.yaml /media/usb/user-data
```
**`meta-data`** — create an empty file literally named `meta-data` (no extension):
```bash
touch /media/usb/meta-data
```
Optional content for `meta-data`:
```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-<last4>` |
| 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-<last4mac>` |
## 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)

View File

@@ -1,135 +0,0 @@
# 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