diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a4d834a..0000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Ignore everything in .vm directory -.vm/* -# But keep .gitkeep -!.vm/.gitkeep - -# Ignore result in iso directory -iso/result \ No newline at end of file diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md deleted file mode 100644 index 7f4c55e..0000000 --- a/.roo/rules/rules.md +++ /dev/null @@ -1,157 +0,0 @@ -# Cloonar Assistant LLM Rules - -This document defines the rules and guidelines for an LLM working with the Cloonar Assistant NixOS module project. - -## 1. Project Understanding - -### 1.1 Core Components -- Network Infrastructure (VLANs, DHCP, DNS, Firewall) -- Security Services (WireGuard VPN, SSL/ACME) -- Home Automation (Home Assistant) -- System Services (Dynamic DNS, Container Management) -- Development Tools (ISO Builder, VM Testing) - -### 1.2 Module Architecture -```mermaid -graph TD - A[Cloonar Assistant] --> B[Network Management] - A --> C[Security] - A --> D[Services] - A --> E[Development Tools] - - B --> B1[VLANs] - B --> B2[DHCP/Kea] - B --> B3[DNS/Unbound] - B --> B4[Firewall/nftables] - - C --> C1[WireGuard VPN] - C --> C2[SSL/ACME] - C --> C3[SOPS Integration] - - D --> D1[Home Assistant] - D --> D2[Dynamic DNS] - D --> D3[Container Services] - - E --> E1[ISO Builder] - E --> E2[VM Testing] -``` - -## 2. NixOS Integration (REQUIRED) - -### 2.1 Package and Option Verification -- ALWAYS use the NixOS MCP server to verify packages and options before suggesting them -- Query format: `use_mcp_tool` with server "nixos" for all NixOS-related lookups -- Verify package availability in the project's current NixOS version -- Validate option compatibility and deprecation status - -### 2.2 Configuration Guidelines -- All NixOS configurations must be validated through MCP before suggestion -- Use proper module imports and option declarations -- Follow NixOS naming conventions and type declarations -- Consider module dependencies and conflicts - -## 3. Development Guidelines - -### 3.1 Code Structure -- Maintain modular organization in `modules/cloonar-assistant/` -- Follow existing patterns for option declarations -- Use descriptive names for options and properties -- Keep related functionality grouped in appropriate submodules - -### 3.2 Testing Protocol -- Utilize the VM testing scripts in `scripts/` -- Test configuration changes with `test-configuration` -- Verify VLAN and network functionality in VM environment -- Ensure proper service container operation - -## 4. Configuration Support - -### 4.1 Network Configuration -- Define appropriate VLANs based on network requirements -- Configure firewall rules using nftables syntax -- Set up proper DHCP and DNS services -- Implement correct routing between VLANs - -### 4.2 Service Configuration -- Configure Home Assistant container with proper isolation -- Set up SSL certificates via ACME -- Manage WireGuard VPN peers and configurations -- Configure dynamic DNS updates - -## 5. Security Best Practices - -### 5.1 General Security -- Never expose sensitive information in configurations -- Use SOPS for secrets management -- Implement proper network segmentation -- Follow principle of least privilege - -### 5.2 Network Security -- Verify firewall rule correctness -- Implement proper VLAN isolation -- Secure VPN configurations -- Validate SSL certificate management - -## 6. Troubleshooting Framework - -### 6.1 Diagnostic Approach -```mermaid -flowchart LR - A[Issue Reported] --> B{Category?} - B --> C[Network] - B --> D[Service] - B --> E[Build] - - C --> C1[Check VLANs] - C --> C2[Verify Firewall] - C --> C3[Test DNS] - - D --> D1[Container Status] - D --> D2[Service Logs] - D --> D3[Dependencies] - - E --> E1[Nix Errors] - E --> E2[Option Issues] - E --> E3[Build Logs] -``` - -### 6.2 Common Issues -- VLAN connectivity problems -- Container networking issues -- SSL certificate renewal failures -- Build and configuration errors - -## 7. Self-Maintenance Rules - -### 7.1 Rule Update Triggers -Monitor and update rules.md when: -- Major NixOS version changes affect module functionality -- New security considerations emerge -- Core module features are added/modified -- Breaking changes in dependencies occur - -### 7.2 Update Protocol -1. Identify breaking changes or important updates -2. Document impact on existing configurations -3. Update relevant rule sections -4. Add new guidelines if needed -5. Update version compatibility information - -### 7.3 Documentation Sync -- Keep rules aligned with current codebase -- Update mermaid diagrams for architectural changes -- Maintain accurate NixOS version compatibility info -- Document new features and deprecations - -### 7.4 Change Validation -Before updating rules: -- Verify changes against current codebase -- Test impact on existing configurations -- Check NixOS MCP for option/package changes -- Validate security implications - -## 8. Version Information - -- Last Updated: 2025-06-06 -- Compatible NixOS Versions: 23.05, 23.11, 24.05 -- Project Version: Current Master \ No newline at end of file diff --git a/candle-sensor b/candle-sensor deleted file mode 100644 index 4f6b364..0000000 --- a/candle-sensor +++ /dev/null @@ -1,12 +0,0 @@ -it does not work if the sensor is underneath - -i could build a device which you can put on the table and looks to the candles -it has 80cm of recognizing a flame. - -it would use the following components -Adafruit ESP32 Feather V2 -ANGEEK KY-026 Flame Sensor Module -2000 - 3000 mah LiPo - -battery life would be 20 Days, cost would be around 35€ just plain devices cost without work time -3d printed housing would be needed diff --git a/modules/cloonar-assistant/networking/default.nix b/modules/cloonar-assistant/networking/default.nix index a317a1c..94eff10 100644 --- a/modules/cloonar-assistant/networking/default.nix +++ b/modules/cloonar-assistant/networking/default.nix @@ -1,8 +1,9 @@ { ... }: { imports = [ ./interfaces.nix + ./dhcp.nix ./firewall.nix - ./dnsmasq.nix # New dnsmasq configuration + ./unbound.nix ./wireguard.nix ]; } diff --git a/modules/cloonar-assistant/networking/dhcp.nix b/modules/cloonar-assistant/networking/dhcp.nix new file mode 100644 index 0000000..164acd0 --- /dev/null +++ b/modules/cloonar-assistant/networking/dhcp.nix @@ -0,0 +1,187 @@ +{ config, lib, ... }: +{ + services.kea.dhcp4 = lib.mkIf config.cloonar-assistant.firewall.enable { + enable = true; + settings = { + interfaces-config = { + interfaces = [ + "lan" + "server" + "infrastructure" + "multimedia" + "smart" + "guest" + ]; + }; + lease-database = { + name = "/var/lib/kea/dhcp4.leases"; + persist = true; + type = "memfile"; + }; + rebind-timer = 2000; + renew-timer = 1000; + subnet4 = [ + { + id = 96; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.96.100 - ${config.cloonar-assistant.networkPrefix}.96.240"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.96.0/24"; + interface = "lan"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.96.1"; + } + { + name = "domain-name"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-search"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-name-servers"; + data = "${config.cloonar-assistant.networkPrefix}.96.1"; + } + ]; + reservations = [ + ]; + } + { + id = 97; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.97.100 - ${config.cloonar-assistant.networkPrefix}.97.240"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.97.0/24"; + interface = "server"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.97.1"; + } + { + name = "domain-name"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-name-servers"; + data = "${config.cloonar-assistant.networkPrefix}.97.1"; + } + ]; + reservations = [ + ]; + } + { + id = 101; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.101.100 - ${config.cloonar-assistant.networkPrefix}.101.240"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.101.0/24"; + interface = "infrastructure"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.101.1"; + } + { + name = "domain-name"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-name-servers"; + data = "${config.cloonar-assistant.networkPrefix}.101.1"; + } + { + name = "capwap-ac-v4"; + code = 138; + data = "${config.cloonar-assistant.networkPrefix}.97.2"; + } + ]; + reservations = [ + ]; + } + { + id = 99; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.99.100 - ${config.cloonar-assistant.networkPrefix}.99.240"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.99.0/24"; + interface = "multimedia"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.99.1"; + } + { + name = "domain-name"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-name-servers"; + data = "${config.cloonar-assistant.networkPrefix}.99.1"; + } + ]; + reservations = [ + ]; + } + { + id = 254; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.254.10 - ${config.cloonar-assistant.networkPrefix}.254.254"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.254.0/24"; + interface = "guest"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.254.1"; + } + { + name = "domain-name-servers"; + data = "9.9.9.9"; + } + ]; + } + { + id = 100; + pools = [ + { + pool = "${config.cloonar-assistant.networkPrefix}.100.100 - ${config.cloonar-assistant.networkPrefix}.100.240"; + } + ]; + subnet = "${config.cloonar-assistant.networkPrefix}.100.0/24"; + interface = "smart"; + option-data = [ + { + name = "routers"; + data = "${config.cloonar-assistant.networkPrefix}.100.1"; + } + { + name = "domain-name"; + data = config.cloonar-assistant.domain; + } + { + name = "domain-name-servers"; + data = "${config.cloonar-assistant.networkPrefix}.100.1"; + } + ]; + reservations = [ + ]; + } + ]; + valid-lifetime = 4000; + }; + }; +} diff --git a/modules/cloonar-assistant/networking/dnsmasq.nix b/modules/cloonar-assistant/networking/dnsmasq.nix deleted file mode 100644 index 9ae354f..0000000 --- a/modules/cloonar-assistant/networking/dnsmasq.nix +++ /dev/null @@ -1,98 +0,0 @@ -{ config, lib, ... }: - -{ - # Disable systemd-resolved (same as current unbound.nix) - services.resolved.enable = false; - - # Main dnsmasq service with preserved conditional enablement - services.dnsmasq = lib.mkIf config.cloonar-assistant.firewall.enable { - enable = true; - resolveLocalQueries = false; # We handle DNS manually - - settings = { - # Interface binding - interface = [ - "lan" - "server" - "infrastructure" - "multimedia" - "smart" - "guest" - ]; - - # DHCP ranges per VLAN - dhcp-range = [ - "${config.cloonar-assistant.networkPrefix}.96.100,${config.cloonar-assistant.networkPrefix}.96.240,24h" - "${config.cloonar-assistant.networkPrefix}.97.100,${config.cloonar-assistant.networkPrefix}.97.240,24h" - "${config.cloonar-assistant.networkPrefix}.101.100,${config.cloonar-assistant.networkPrefix}.101.240,24h" - "${config.cloonar-assistant.networkPrefix}.99.100,${config.cloonar-assistant.networkPrefix}.99.240,24h" - "${config.cloonar-assistant.networkPrefix}.100.100,${config.cloonar-assistant.networkPrefix}.100.240,24h" - "${config.cloonar-assistant.networkPrefix}.254.10,${config.cloonar-assistant.networkPrefix}.254.254,24h" - ]; - - # DHCP options with VLAN tagging - dhcp-option = [ - # LAN VLAN (.96) - "tag:lan,option:router,${config.cloonar-assistant.networkPrefix}.96.1" - "tag:lan,option:dns-server,${config.cloonar-assistant.networkPrefix}.96.1" - "tag:lan,option:domain-name,${config.cloonar-assistant.domain}" - - # Server VLAN (.97) - "tag:server,option:router,${config.cloonar-assistant.networkPrefix}.97.1" - "tag:server,option:dns-server,${config.cloonar-assistant.networkPrefix}.97.1" - "tag:server,option:domain-name,${config.cloonar-assistant.domain}" - - # Infrastructure VLAN (.101) with CAPWAP option - "tag:infrastructure,option:router,${config.cloonar-assistant.networkPrefix}.101.1" - "tag:infrastructure,option:dns-server,${config.cloonar-assistant.networkPrefix}.101.1" - "tag:infrastructure,option:domain-name,${config.cloonar-assistant.domain}" - "tag:infrastructure,138,${config.cloonar-assistant.networkPrefix}.97.2" # CAPWAP - - # Multimedia VLAN (.99) - "tag:multimedia,option:router,${config.cloonar-assistant.networkPrefix}.99.1" - "tag:multimedia,option:dns-server,${config.cloonar-assistant.networkPrefix}.99.1" - "tag:multimedia,option:domain-name,${config.cloonar-assistant.domain}" - - # Smart VLAN (.100) - "tag:smart,option:router,${config.cloonar-assistant.networkPrefix}.100.1" - "tag:smart,option:dns-server,${config.cloonar-assistant.networkPrefix}.100.1" - "tag:smart,option:domain-name,${config.cloonar-assistant.domain}" - - # Guest VLAN (.254) - DNS isolation - "tag:guest,option:router,${config.cloonar-assistant.networkPrefix}.254.1" - "tag:guest,option:dns-server,9.9.9.9" # External DNS only - ]; - - # Static DNS records - address = [ - "/fw.${config.cloonar-assistant.domain}/${config.cloonar-assistant.networkPrefix}.97.1" - "/fw/${config.cloonar-assistant.networkPrefix}.97.1" - "/home-assistant.${config.cloonar-assistant.domain}/${config.cloonar-assistant.networkPrefix}.97.20" - "/mopidy.${config.cloonar-assistant.domain}/${config.cloonar-assistant.networkPrefix}.97.21" - "/snapcast.${config.cloonar-assistant.domain}/${config.cloonar-assistant.networkPrefix}.97.21" - "/localhost/127.0.0.1" - "/localhost.${config.cloonar-assistant.domain}/127.0.0.1" - ]; - - # Domain configuration - domain = "${config.cloonar-assistant.domain}"; - expand-hosts = true; - - # Upstream DNS servers (plain DNS, no DoT support in dnsmasq) - server = [ - "9.9.9.9" - "149.112.112.11" - ]; - - # Performance and security - cache-size = 1000; - neg-ttl = 60; - domain-needed = true; # Don't forward plain names - bogus-priv = true; # Don't forward RFC1918 reverse lookups - bind-interfaces = true; # Only bind to specified interfaces - }; - }; - - # Firewall configuration (preserve existing) - networking.firewall.allowedUDPPorts = [ 53 5353 ]; -} \ No newline at end of file diff --git a/modules/cloonar-assistant/networking/unbound.nix b/modules/cloonar-assistant/networking/unbound.nix new file mode 100644 index 0000000..4745b26 --- /dev/null +++ b/modules/cloonar-assistant/networking/unbound.nix @@ -0,0 +1,138 @@ +{ config, lib, pkgs, ... }: +let + cfg = { + remote-control.control-enable = true; + server = { + interface = [ "0.0.0.0" "::0" ]; + interface-automatic = "yes"; + access-control = [ + "127.0.0.0/8 allow" + "${config.cloonar-assistant.networkPrefix}.96.0/24 allow" + "${config.cloonar-assistant.networkPrefix}.97.0/24 allow" + "${config.cloonar-assistant.networkPrefix}.98.0/24 allow" + "${config.cloonar-assistant.networkPrefix}.99.0/24 allow" + "${config.cloonar-assistant.networkPrefix}.101.0/24 allow" + "0.0.0.0/0 allow" + ]; + tls-cert-bundle = "/etc/ssl/certs/ca-certificates.crt"; + local-zone = "\"${config.cloonar-assistant.domain}\" transparent"; + local-data = [ + "\"localhost A 127.0.0.1\"" + "\"localhost.${config.cloonar-assistant.domain} A 127.0.0.1\"" + "\"localhost AAAA ::1\"" + "\"localhost.${config.cloonar-assistant.domain} AAAA ::1\"" + "\"fw.${config.cloonar-assistant.domain} A ${config.cloonar-assistant.networkPrefix}.97.1\"" + "\"fw A ${config.cloonar-assistant.networkPrefix}.97.1\"" + + "\"mopidy.${config.cloonar-assistant.domain} IN A ${config.cloonar-assistant.networkPrefix}.97.21\"" + "\"snapcast.${config.cloonar-assistant.domain} IN A ${config.cloonar-assistant.networkPrefix}.97.21\"" + "\"home-assistant.${config.cloonar-assistant.domain} IN A ${config.cloonar-assistant.networkPrefix}.97.20\"" + ]; + local-data-ptr = [ + "\"127.0.0.1 localhost\"" + "\"::1 localhost\"" + "\"${config.cloonar-assistant.networkPrefix}.97.1 fw.${config.cloonar-assistant.domain}\"" + "\"${config.cloonar-assistant.networkPrefix}.97.20 home-assistant.${config.cloonar-assistant.domain}\"" + "\"${config.cloonar-assistant.networkPrefix}.97.21 snapcast.${config.cloonar-assistant.domain}\"" + ]; + # ssl-upstream = "yes"; + }; + forward-zone = [ + { + name = "."; + forward-tls-upstream = "yes"; + forward-first = "no"; + forward-addr = [ + "9.9.9.9@853#dns9.quad9.net" + "149.112.112.11@853#dns11.quad9.net" + ]; + } + ]; + }; +in { + users.users.unbound = { + group = "unbound"; + isSystemUser = true; + extraGroups = [ "ssl-users" ]; + }; + users.groups.unbound = { }; + + services.resolved.enable = false; + + services.unbound = { + enable = true; + settings = cfg; + }; + + systemd.services.unbound-sync = lib.mkIf config.cloonar-assistant.firewall.enable { + enable = true; + path = with pkgs; [ unbound inotify-tools ]; + script = '' + function readFile() { + if [[ "''\$2" == "A" ]] ; then + cat "''\$1" | tail -n +2 | while IFS=, read -r address hwaddr client_id valid_lifetime expire subnet_id fqdn_fwd fqdn_rev hostname state user_context + do + echo "''\${address},''\${hostname}" + done + else + cat "''\$1" | tail -n +2 | while IFS=, read -r address duid valid_lifetime expire subnet_id pref_lifetime lease_type iaid prefix_len fqdn_fwd fqdn_rev hostname hwaddr state user_context hwtype hwaddr_source + do + echo "''\${address},''\${hostname}" + done + fi + } + + function readFileUnique() { + readFile "''\$1" ''\$2 | uniq | while IFS=, read -r address hostname + do + if echo "''\${1}" | grep -Eq '.*\.(${config.cloonar-assistant.domain})'; then + echo ''\${hostname} ''\$2 ''\${address} + unbound-control local_data ''\${hostname} ''\$2 ''\${address} > /dev/null 2>&1 + if [[ "''\$2" == "A" ]] ; then + echo ''\${address} | while IFS=. read -r ip0 ip1 ip2 ip3 + do + unbound-control local_data ''\${ip3}.''\${ip2}.''\${ip1}.''\${ip0}.ip4.arpa. PTR ''\${hostname} > /dev/null 2>&1 + unbound-control local_data ''\${ip3}.''\${ip2}.''\${ip1}.''\${ip0}.in-addr.arpa. PTR ''\${hostname} > /dev/null 2>&1 + done + fi + else + if [[ "''\$2" == "A" ]] ; then + echo ''\${address} | while IFS=. read -r ip0 ip1 ip2 ip3 + do + if [[ "''\${hostname}" != "" ]]; then + domain=${config.cloonar-assistant.domain} + if [[ "''\${hostname}" != *. ]]; then + unbound-control local_data ''\${hostname}.''\${domain} ''\$2 ''\${address} > /dev/null 2>&1 + else + unbound-control local_data ''\${hostname}''\${domain} ''\$2 ''\${address} > /dev/null 2>&1 + fi + + fi + unbound-control local_data ''\${ip3}.''\${ip2}.''\${ip1}.''\${ip0}.ip4.arpa. PTR ''\${hostname} > /dev/null 2>&1 + unbound-control local_data ''\${ip3}.''\${ip2}.''\${ip1}.''\${ip0}.in-addr.arpa. PTR ''\${hostname} > /dev/null 2>&1 + done + fi + fi + done + } + + function syncFile() { + # readFileUnique "''\$1" "''\$2" + while true; do + readFileUnique "''\$1" "''\$2" + sleep 10 + done + } + + syncFile "/var/lib/kea/dhcp4.leases" A & + # syncFile "/var/lib/kea/dhcp6.leases" AAAA & + wait + ''; + wants = [ "network-online.target" "unbound.service" ]; + after = [ "network-online.target" "unbound.service" ]; + partOf = [ "unbound.service" ]; + wantedBy = [ "multi-user.target" ]; + }; + + networking.firewall.allowedUDPPorts = [ 53 5353 ]; +} diff --git a/scripts/run-vm b/scripts/run-vm index 35a7c26..f3d1850 100755 --- a/scripts/run-vm +++ b/scripts/run-vm @@ -2,11 +2,12 @@ set -Euo pipefail # ----------------------------------------------------------------------------- -# This script sets up and launches (or stops) a QEMU virtual machine with OVMF. -# +# This script sets up and launches a QEMU virtual machine with OVMF (UEFI). +# It checks for the necessary files, creates directories/images as needed, +# and provides clear, user-friendly output along the way. # Usage: -# ./run-vm.sh [install] # starts (and backgrounds) the VM; use "install" to attach the ISO -# ./run-vm.sh stop # kills the running QEMU VM (reads PID from .vm/qemu.pid) +# ./run-vm.sh [install] +# - Pass "install" to attach the ISO as a CD-ROM for installation. # ----------------------------------------------------------------------------- # Paths to OVMF firmware (pflash) @@ -16,33 +17,9 @@ OVMF_VARS_DEFAULT="/run/libvirt/nix-ovmf/OVMF_VARS.fd" # Determine where this script lives and compute related paths SCRIPT_DIR=$(dirname "$(readlink -f "$0")") TARGET_DIR=$(readlink -f "$SCRIPT_DIR/../.vm") -OVMF_VARS_PATH="$TARGET_DIR/OVMF_VARS-myvm.fd" -IMG_PATH="$TARGET_DIR/disk.img" +OVMF_VARS_PATH=$(readlink -f "$SCRIPT_DIR/../.vm/OVMF_VARS-myvm.fd") +IMG_PATH=$(readlink -f "$SCRIPT_DIR/../.vm/disk.img") ISO_DIR=$(readlink -f "$SCRIPT_DIR/../iso/result/iso") -PID_FILE="$TARGET_DIR/qemu.pid" - -# If first argument is "stop", then kill the running VM and exit: -if [ "${1-}" = "stop" ]; then - if [ -f "$PID_FILE" ]; then - VM_PID=$(<"$PID_FILE") - if kill -0 "$VM_PID" 2>/dev/null; then - echo "Killing QEMU (PID $VM_PID)..." - kill "$VM_PID" - # Optionally wait for it to die: - wait "$VM_PID" 2>/dev/null || true - echo "✅ VM stopped." - rm -f "$PID_FILE" - exit 0 - else - echo "⚠️ No running QEMU process with PID $VM_PID. Removing stale PID file." - rm -f "$PID_FILE" - exit 1 - fi - else - echo "⚠️ No PID file found at $PID_FILE. Is the VM running?" - exit 1 - fi -fi echo echo "============================================================" @@ -115,8 +92,7 @@ fi if [ "$INSTALL_MODE" -eq 1 ]; then echo "[5/6] Install mode enabled: CD-ROM will be attached" - CDROM_OPTS="-drive file=\"$ISO_FILE\",format=raw,if=none,media=cdrom,id=cd1,readonly=on \ --device ahci,id=ahci0 -device ide-cd,bus=ahci0.0,drive=cd1,bootindex=1" + CDROM_OPTS="-drive file=\"$ISO_FILE\",format=raw,if=none,media=cdrom,id=cd1,readonly=on -device ahci,id=ahci0 -device ide-cd,bus=ahci0.0,drive=cd1,bootindex=1" else echo "[5/6] Normal boot mode: No CD-ROM attached" CDROM_OPTS="" @@ -124,9 +100,9 @@ fi echo # ----------------------------------------------------------------------------- -# 6. Launch QEMU (in the background) +# 6. Launch QEMU # ----------------------------------------------------------------------------- -echo "[6/6] Launching QEMU VM now (in background)..." +echo "[6/6] Launching QEMU VM now..." echo "------------------------------------------------------------" echo " • Machine: q35, KVM acceleration" echo " • Memory: 4096 MB" @@ -146,7 +122,7 @@ echo # Construct network options NET_OPTS="-netdev user,id=net0,hostfwd=tcp::2222-:22 -device e1000,netdev=net0" -# Run QEMU in the background and store its PID +# Run QEMU using eval to allow variable expansion in CDROM_OPTS eval qemu-system-x86_64 \ -machine type=q35,accel=kvm \ -m 4096 \ @@ -161,14 +137,9 @@ eval qemu-system-x86_64 \ $CDROM_OPTS \ \ $NET_OPTS \ - -vga virtio \ - & + -vga virtio -VM_PID=$! -echo "$VM_PID" > "$PID_FILE" -echo "✅ QEMU started with PID $VM_PID. PID file: $PID_FILE" echo -echo "To stop the VM at any time, run:" -echo " $0 stop" -echo -exit 0 +echo "============================================================" +echo " QEMU VM has exited" +echo "============================================================"