From d670e8cd85edce479ff169bbd1a60ed5266dfb21 Mon Sep 17 00:00:00 2001 From: nearxos Date: Tue, 17 Feb 2026 15:09:54 +0200 Subject: [PATCH] Initial commit: Rina VM deployment (VLAN 40, br1.40, cloud-init) Co-authored-by: Cursor --- .gitignore | 7 + README.md | 117 +++++++++++++++ cloud-init/meta-data | 2 + cloud-init/network-config | 14 ++ cloud-init/user-data | 53 +++++++ deploy-rina-vm.sh | 114 +++++++++++++++ docs/vlan40-bridge-systemd-networkd.md | 190 +++++++++++++++++++++++++ setup-vlan40-bridge.sh | 58 ++++++++ 8 files changed, 555 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cloud-init/meta-data create mode 100644 cloud-init/network-config create mode 100644 cloud-init/user-data create mode 100644 deploy-rina-vm.sh create mode 100644 docs/vlan40-bridge-systemd-networkd.md create mode 100644 setup-vlan40-bridge.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76a04f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Cloud-init seed build +.cloud-init-seed/ +*.iso + +# Generated VM disk / definition (optional; remove if you want to track) +# *.qcow2 +# rina.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d30e43 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# Rina VM deployment (Debian on QEMU/libvirt) + +Deploy a **Debian 13 minimal** VM with cloud-init on a host running **QEMU** and **libvirt** (`virsh`). No interactive install: user, password, static IP, and swap are applied on first boot. + +## Why cloud-init (and not the netinst ISO)? + +| Approach | Pros | Cons | +|----------|------|------| +| **Cloud image + cloud-init** ✅ | Fully automated, minimal image, repeatable, set user/IP/swap in one go | Requires cloud image (no installer UI) | +| **Netinst ISO** | Familiar installer | Manual or preseed setup; slower; more steps | + +**Recommendation:** Use the provided **Debian generic cloud image + cloud-init** for a one-shot, reproducible VM that matches your specs. + +## Specs (as requested) + +- **CPU:** 2 cores +- **Memory:** 4 GB RAM + 4 GB swap +- **Storage:** 128 GB +- **IP:** 192.168.0.225 +- **User:** `rina` / **Password:** `rinapwd` +- **OS:** Latest Debian minimal (Debian 13 Trixie cloud image) + +## Requirements on the remote host + +- **libvirt** (virsh), **QEMU**, **virt-install**, **qemu-img** +- **cloud-image-utils** (for `cloud-localds`) or **genisoimage** (for seed ISO) +- Optional: **curl** (to download the cloud image) + +Install on Debian/Ubuntu: + +```bash +sudo apt install libvirt-daemon-system qemu-kvm virtinst libguestfs-tools cloud-image-utils +``` + +## Quick start + +1. Copy this directory to the remote machine (or clone there). + +2. **Optional:** Edit `cloud-init/user-data` and `cloud-init/network-config`: + - Change gateway (default `192.168.0.1`) or DNS if your LAN differs. + - Add your SSH public key under `ssh_authorized_keys` for key-based login. + - Network is also embedded in `user-data`; `network-config` is kept for reference. + +3. Run the deploy script (on the remote host): + + ```bash + chmod +x deploy-rina-vm.sh + ./deploy-rina-vm.sh + ``` + +4. Wait for first boot (~1–2 minutes). Then: + + ```bash + ssh rina@192.168.0.225 + # password: rinapwd + ``` + +## What the script does + +1. Downloads the Debian 13 generic cloud image (qcow2) once to `/var/lib/libvirt/images/`. +2. Creates a 128 GB disk for the VM backed by that image. +3. Builds a cloud-init **NoCloud** seed ISO from `cloud-init/user-data` and `cloud-init/meta-data`. +4. Creates and starts the VM with **virt-install** (2 vCPU, 4 GB RAM, 128 GB disk, seed ISO attached). + +## Customization + +- **Pool directory:** `POOL_DIR=/path/to/images ./deploy-rina-vm.sh` +- **Network:** By default the script uses the libvirt network `default`. For a bridge (e.g. `br0`): + `NETWORK=br0 ./deploy-rina-vm.sh` +- **Resources:** `CPU=4 MEMORY_GB=8 DISK_GB=256 ./deploy-rina-vm.sh` + +## Firewall and SSH (same as “Rina PC”) + +There is no firewall/SSH config in this repo. To **apply the same rules as on the Rina PC**: + +1. On the Rina PC, export current rules, for example: + - **UFW:** `sudo ufw status verbose > ufw-rina-pc.txt` + - **iptables:** `sudo iptables-save > iptables-rina-pc.txt` +2. Copy those rules to this VM (e.g. copy the files to `rina@192.168.0.225`) and apply the same way (ufw or iptables). +3. Or document the desired rules and add them to `cloud-init/user-data` under `runcmd` or a one-time script (e.g. enable ufw and allow SSH). + +Example in `user-data` (if using UFW and you want SSH + same as Rina PC): + +```yaml +runcmd: + - apt-get install -y ufw + - ufw allow 22/tcp # SSH + # Add more rules to match Rina PC, then: + - ufw --force enable +``` + +Adjust rules to match your Rina PC before deploying. + +## Interface name (192.168.0.225 not working) + +If the VM gets an IP via DHCP instead of 192.168.0.225, the interface name might not be `enp1s0`. On the VM run `ip a` and note the main interface (e.g. `eth0`). Then in `cloud-init/user-data`, change the `network.ethernets` key from `enp1s0` to that name, rebuild the seed ISO, and redeploy (or fix netplan inside the VM once). + +## Useful commands (on the host) + +```bash +virsh list --all +virsh console rina +virsh shutdown rina +virsh start rina +``` + +## Files + +- `deploy-rina-vm.sh` – Deploy script (download image, create disk, build seed ISO, virt-install). +- `cloud-init/user-data` – User `rina`, password, sudo, SSH, packages, static IP, 4 GB swap. +- `cloud-init/meta-data` – Instance ID and hostname. +- `cloud-init/network-config` – Standalone netplan-style config (reference; main config is in user-data). + +## Reference + +- Debian cloud images: +- Debian 13 netinst (alternative): diff --git a/cloud-init/meta-data b/cloud-init/meta-data new file mode 100644 index 0000000..6e71d24 --- /dev/null +++ b/cloud-init/meta-data @@ -0,0 +1,2 @@ +instance-id: rina-vm-01 +local-hostname: rina diff --git a/cloud-init/network-config b/cloud-init/network-config new file mode 100644 index 0000000..cc86ef4 --- /dev/null +++ b/cloud-init/network-config @@ -0,0 +1,14 @@ +# Static network for Rina VM (VLAN 40 / br1.40) +# Adjust gateway and nameservers to match your VLAN 40 +version: 2 +ethernets: + enp1s0: + addresses: + - 192.168.40.225/24 + routes: + - to: default + via: 192.168.40.1 + nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 diff --git a/cloud-init/user-data b/cloud-init/user-data new file mode 100644 index 0000000..9075e91 --- /dev/null +++ b/cloud-init/user-data @@ -0,0 +1,53 @@ +#cloud-config +# Rina VM - Debian minimal with cloud-init +# User, SSH, swap, static IP, and base setup + +# Static network on VLAN 40 (br1.40). IP 192.168.40.225; adjust gateway if your VLAN 40 differs. +network: + version: 2 + ethernets: + enp1s0: + addresses: [192.168.40.225/24] + routes: + - to: default + via: 192.168.40.1 + nameservers: + addresses: [192.168.40.1] + +users: + - name: rina + lock_passwd: false + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: [] + # Add your SSH public key here for key-based login, e.g.: + # ssh_authorized_keys: + # - ssh-ed25519 AAAA... your-key + - name: tototheo + lock_passwd: false + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: [] + +chpasswd: + list: | + rina:rinapwd + tototheo:xFpbIjpHE38qzUB + expire: false + +package_update: true +package_upgrade: true +packages: + - openssh-server + - cloud-guest-utils + - qemu-guest-agent + +runcmd: + # Create 4 GiB swap file + - fallocate -l 4G /swapfile + - chmod 600 /swapfile + - mkswap /swapfile + - swapon /swapfile + - echo '/swapfile none swap sw 0 0' >> /etc/fstab + +final_message: "Rina VM is ready. Login as rina or tototheo." diff --git a/deploy-rina-vm.sh b/deploy-rina-vm.sh new file mode 100644 index 0000000..b3443ac --- /dev/null +++ b/deploy-rina-vm.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# Deploy Rina VM on a remote host running QEMU/libvirt (virsh). +# Uses Debian cloud image + cloud-init for automated minimal install. +# +# Run on the remote machine (or from your machine with LIBVIRT_DEFAULT_URI). +# Requires: libvirt, virt-install, qemu-img, genisoimage (or cloud-image-utils) + +set -e + +VM_NAME="${VM_NAME:-rina}" +CPU="${CPU:-2}" +MEMORY_GB="${MEMORY_GB:-4}" +DISK_GB="${DISK_GB:-128}" +# Debian 13 (Trixie) generic cloud image with cloud-init +DEBIAN_IMAGE_URL="${DEBIAN_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2}" +IMAGE_NAME=$(basename "$DEBIAN_IMAGE_URL") +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLOUD_INIT_DIR="$SCRIPT_DIR/cloud-init" +POOL_DIR="${POOL_DIR:-/var/lib/libvirt/images}" +BASE_IMAGE="$POOL_DIR/$IMAGE_NAME" +VM_DISK="$POOL_DIR/${VM_NAME}.qcow2" +SEED_ISO="$POOL_DIR/${VM_NAME}-seed.iso" +# Network: libvirt virtual network (e.g. 'default') or host bridge +# VLAN 40 (eth1.40 + br1.40): run systemd-networkd config on the host first, then: +# BRIDGE=br1.40 ./deploy-rina-vm.sh +# Other: BRIDGE=br0 or NETWORK=default +NETWORK="${NETWORK:-default}" +BRIDGE="${BRIDGE:-br1.40}" + +echo "=== Rina VM deployment ===" +echo " VM name: $VM_NAME" +echo " CPU: $CPU Memory: ${MEMORY_GB}G Disk: ${DISK_GB}G" +echo " Base image: $DEBIAN_IMAGE_URL" +echo " Pool dir: $POOL_DIR" +if [[ -n "$BRIDGE" ]]; then + echo " Network: bridge $BRIDGE" +else + echo " Network: $NETWORK" +fi +echo "" + +# 1) Ensure base image exists and resize copy for VM +if [[ ! -f "$BASE_IMAGE" ]]; then + echo "Downloading Debian cloud image..." + sudo mkdir -p "$POOL_DIR" + sudo curl -L -o "$BASE_IMAGE" "$DEBIAN_IMAGE_URL" + echo "Downloaded $BASE_IMAGE" +fi + +if [[ -f "$VM_DISK" ]]; then + echo "VM disk already exists: $VM_DISK" + read -p "Overwrite? (y/N) " -n 1 -r; echo + if [[ ! $REPLY =~ ^[yY]$ ]]; then + echo "Aborted." + exit 1 + fi + sudo rm -f "$VM_DISK" +fi + +echo "Creating VM disk (${DISK_GB}G) from base image..." +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE_IMAGE" "$VM_DISK" +sudo qemu-img resize "$VM_DISK" "${DISK_GB}G" +echo "Done: $VM_DISK" + +# 2) Build cloud-init NoCloud seed ISO +for f in user-data meta-data network-config; do + if [[ ! -f "$CLOUD_INIT_DIR/$f" ]]; then + echo "Missing cloud-init file: $CLOUD_INIT_DIR/$f" + exit 1 + fi +done + +if command -v cloud-localds &>/dev/null; then + echo "Building seed ISO with cloud-localds..." + cloud-localds -v "$SEED_ISO" \ + "$CLOUD_INIT_DIR/user-data" \ + "$CLOUD_INIT_DIR/meta-data" +else + echo "Building seed ISO with genisoimage..." + TMP_CI="$SCRIPT_DIR/.cloud-init-seed" + mkdir -p "$TMP_CI" + cp "$CLOUD_INIT_DIR/user-data" "$CLOUD_INIT_DIR/meta-data" "$TMP_CI/" + sudo genisoimage -output "$SEED_ISO" -volid cidata -joliet -rock "$TMP_CI"/user-data "$TMP_CI"/meta-data + rm -rf "$TMP_CI" +fi +echo "Seed ISO: $SEED_ISO" + +# 3) Create and start VM +if sudo virsh list --all --name | grep -qx "$VM_NAME"; then + echo "VM '$VM_NAME' already defined. Undefine it first: virsh undefine $VM_NAME" + exit 1 +fi + +echo "Creating VM..." +sudo virt-install \ + --name "$VM_NAME" \ + --memory "$(( MEMORY_GB * 1024 ))" \ + --vcpus "$CPU" \ + --import \ + --disk "path=$VM_DISK,format=qcow2,bus=virtio" \ + --disk "path=$SEED_ISO,device=cdrom,bus=sata" \ + --network "$(if [[ -n "$BRIDGE" ]]; then echo "bridge=$BRIDGE,model=virtio"; else echo "network=$NETWORK,model=virtio"; fi)" \ + --os-variant debiantrixie \ + --noautoconsole + +# 4) Dump domain XML to project directory for reference/version control +VM_XML="$SCRIPT_DIR/${VM_NAME}.xml" +sudo virsh dumpxml "$VM_NAME" > "$VM_XML" +echo "VM definition saved to: $VM_XML" + +echo "" +echo "VM '$VM_NAME' is starting. First boot runs cloud-init (user rina, password rinapwd, IP 192.168.40.225 on VLAN 40 / br1.40)." +echo "To open console: virsh console $VM_NAME" +echo "To check: virsh list; ssh rina@192.168.40.225 (after cloud-init finishes, ~1–2 min)." diff --git a/docs/vlan40-bridge-systemd-networkd.md b/docs/vlan40-bridge-systemd-networkd.md new file mode 100644 index 0000000..5b8e80c --- /dev/null +++ b/docs/vlan40-bridge-systemd-networkd.md @@ -0,0 +1,190 @@ +# Bridge for VLAN 40 with systemd-networkd + +Steps to create a persistent bridge **br1.40** for VLAN 40 on **eth1** using **systemd-networkd** (systemd 232). + +--- + +## Your current layout (satbox) + +Existing files in `/etc/systemd/network/`: + +- **10-** — eth0 (link + network) +- **11-, 12-, 13-** — eth0.50, eth0.60, eth0.201 (netdev + network each) +- **20-, 30-, 40-** — eth1, eth2, eth3 (link + network) +- **50-** — br1 (netdev + network) + +New files below use **21-** for VLAN 40 on eth1 (`eth1.40`) and **51-** for bridge `br1.40` (after 50-br1). + +--- + +## 1. Prerequisites + +- **Kernel module**: VLAN support must be loaded. Ensure `8021q` is loaded (often already is if you use VLANs): + + ```bash + sudo modprobe 8021q + ``` + + To load at boot (if not already): + + ```bash + echo 8021q | sudo tee -a /etc/modules-load.d/8021q.conf + ``` + +- **Config directory**: All configs go under `/etc/systemd/network/`. Create it if missing: + + ```bash + sudo mkdir -p /etc/systemd/network + ``` + +--- + +## 2. Define the VLAN interface (.netdev) + +Create a netdev file so systemd-networkd creates the VLAN interface on eth1 at boot. + +**File:** `/etc/systemd/network/21-eth1.40.netdev` + +```ini +[NetDev] +Name=eth1.40 +Kind=vlan + +[Vlan] +Id=40 +``` + +--- + +## 3. Define the bridge (.netdev) + +Create the bridge device. Same pattern as your existing `50-br1.netdev`. + +**File:** `/etc/systemd/network/51-br1.40.netdev` + +```ini +[NetDev] +Name=br1.40 +Kind=bridge +``` + +--- + +## 4. Attach the VLAN to the bridge (.network) + +Use a `.network` file that matches the VLAN interface and adds it to the bridge. + +**File:** `/etc/systemd/network/21-eth1.40.network` + +```ini +[Match] +Name=eth1.40 + +[Network] +Bridge=br1.40 +``` + +--- + +## 5. Configure the bridge (.network) + +Optional: assign an IP to the bridge if the host must be on VLAN 40 (e.g. for management or routing). If the bridge is only for VMs, you can omit the address or use a small subnet. Same pattern as your existing `50-br1.network`. + +**File:** `/etc/systemd/network/51-br1.40.network` + +```ini +[Match] +Name=br1.40 + +[Network] +Address=192.168.40.254/24 +``` + +Adjust the address to match your VLAN 40 subnet. Remove the `Address=` line if you do not want an IP on the host. + +--- + +## 6. Add VLAN to the parent interface (.network) + +Edit **`20-eth1.network`** so that eth1 explicitly carries VLAN 40. Add a `VLAN=eth1.40` line under the `[Network]` section. + +If the file currently looks like: + +```ini +[Match] +Name=eth1 + +[Network] +# ... other options (e.g. no config if eth1 is only used by br1) +``` + +add: + +```ini +[Match] +Name=eth1 + +[Network] +VLAN=eth1.40 +``` + +If there are already other keys under `[Network]`, add `VLAN=eth1.40` on a new line in that same section. This ties the VLAN to eth1 so it is created when eth1 is configured. + +--- + +## 7. Apply and check + +1. Restart systemd-networkd: + + ```bash + sudo systemctl restart systemd-networkd + ``` + +2. Check that the VLAN and bridge exist and the VLAN is in the bridge: + + ```bash + ip -br link show eth1.40 + ip -br link show br1.40 + ip link show master br1.40 + ``` + +3. Optional: check status: + + ```bash + networkctl status br1.40 + networkctl status eth1.40 + ``` + +--- + +## 8. Deploy the VM on br1.40 + +After `br1.40` is up and has `eth1.40` as a port, deploy the Rina VM onto it: + +```bash +BRIDGE=br1.40 ./deploy-rina-vm.sh +``` + +The VM will get an IP in your VLAN 40 subnet via cloud-init (e.g. 192.168.40.225); adjust cloud-init if your subnet or gateway differ. + +--- + +## File summary + +| File | Purpose | +|------|--------| +| **Edit** `20-eth1.network` | Add `VLAN=eth1.40` under `[Network]` so eth1 carries VLAN 40 | +| `21-eth1.40.netdev` | Creates VLAN interface `eth1.40` on eth1 | +| `21-eth1.40.network` | Puts `eth1.40` into bridge `br1.40` | +| `51-br1.40.netdev` | Creates bridge `br1.40` (after 50-br1) | +| `51-br1.40.network` | Optional: IP on `br1.40` (e.g. 192.168.40.254/24) | + +Files are processed in alphanumeric order. Netdevs (21-, 51-) are applied first, then network files; br1.40 exists before `21-eth1.40.network` adds the VLAN to it. + +--- + +## Troubleshooting + +- **VLAN interface not created**: Ensure `8021q` is loaded and eth1 is up. Check logs: `journalctl -u systemd-networkd -n 50`. +- **VLAN not in bridge**: Ensure the `[Match] Name=` in `21-eth1.40.network` matches the VLAN interface name exactly (`eth1.40`). +- **Changes not applied**: Run `sudo systemctl restart systemd-networkd` and re-check `ip link` and `networkctl`. diff --git a/setup-vlan40-bridge.sh b/setup-vlan40-bridge.sh new file mode 100644 index 0000000..fcf3bd9 --- /dev/null +++ b/setup-vlan40-bridge.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Run on the hypervisor (satbox) to create bridge br1.40 and attach your VLAN 40 interface. +# Create the VLAN interface manually first, e.g.: +# ip link add link eth1 name eth1.40 type vlan id 40 +# +# Usage: +# sudo ./setup-vlan40-bridge.sh eth1.40 +# sudo VLAN_IF=eth1.40 ./setup-vlan40-bridge.sh +# +# Make persistent via systemd-networkd (see docs/vlan40-bridge-systemd-networkd.md). + +set -e + +VLAN_IF="${1:-$VLAN_IF}" +BRIDGE="br1.40" + +if [[ -z "$VLAN_IF" ]]; then + echo "Usage: $0 (e.g. eth1.40)" + echo " or: VLAN_IF=eth1.40 $0" + echo "Create the VLAN interface manually first: ip link add link eth1 name eth1.40 type vlan id 40" + exit 1 +fi + +if ! ip link show "$VLAN_IF" &>/dev/null; then + echo "Interface $VLAN_IF not found. Create it first, e.g.:" + echo " ip link add link eth1 name eth1.40 type vlan id 40" + exit 1 +fi + +echo "=== Creating bridge $BRIDGE and attaching $VLAN_IF ===" + +# 1) Create bridge if it doesn't exist +if ! ip link show "$BRIDGE" &>/dev/null; then + echo "Creating bridge $BRIDGE..." + ip link add name "$BRIDGE" type bridge + ip link set "$BRIDGE" up +else + echo "Bridge $BRIDGE already exists." + ip link set "$BRIDGE" up 2>/dev/null || true +fi + +# 2) Attach VLAN interface to bridge +if ! ip link show "$VLAN_IF" | grep -q "master $BRIDGE"; then + ip link set "$VLAN_IF" master "$BRIDGE" + ip link set "$VLAN_IF" up +else + ip link set "$VLAN_IF" up 2>/dev/null || true +fi + +echo "" +echo "Done. $VLAN_IF is on bridge $BRIDGE." +echo "Optionally assign an IP to $BRIDGE if this host must be on VLAN 40, e.g.:" +echo " ip addr add 192.168.40.254/24 dev $BRIDGE" +echo "" +echo "Deploy Rina VM on this bridge with:" +echo " BRIDGE=$BRIDGE ./deploy-rina-vm.sh" +echo "(Or use systemd-networkd for persistent config; see docs/vlan40-bridge-systemd-networkd.md)" +echo ""