Compare commits
4 Commits
7611a8daf3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bae38db78d | |||
| f032c4abbd | |||
| bda71a27da | |||
| af15844ed5 |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Ignore everything in .vm directory
|
||||
.vm/*
|
||||
# But keep .gitkeep
|
||||
!.vm/.gitkeep
|
||||
|
||||
# Ignore result in iso directory
|
||||
iso/result
|
||||
157
.roo/rules/rules.md
Normal file
157
.roo/rules/rules.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# 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
|
||||
12
candle-sensor
Normal file
12
candle-sensor
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
1
example/channel
Normal file
1
example/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-25.05
|
||||
@@ -1,9 +1,8 @@
|
||||
{ ... }: {
|
||||
imports = [
|
||||
./interfaces.nix
|
||||
./dhcp.nix
|
||||
./firewall.nix
|
||||
./unbound.nix
|
||||
./dnsmasq.nix # New dnsmasq configuration
|
||||
./wireguard.nix
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
{ 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
98
modules/cloonar-assistant/networking/dnsmasq.nix
Normal file
98
modules/cloonar-assistant/networking/dnsmasq.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
{ 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 ];
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
{ 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 ];
|
||||
}
|
||||
154
scripts/deploy-to-vm
Executable file
154
scripts/deploy-to-vm
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
||||
PROJECT_ROOT=$(readlink -f "$SCRIPT_DIR/..")
|
||||
TARGET_DIR="/tmp/cloonar-config"
|
||||
SSH_PORT=2222
|
||||
SSH_HOST="localhost"
|
||||
SSH_USER="root"
|
||||
SSH_PASS="linux"
|
||||
SSH_OPTIONS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
ARCHIVE_PATH="/tmp/cloonar-config.tar.gz"
|
||||
|
||||
# Parse command line options
|
||||
START_VM=0
|
||||
KEEP_FILES=0
|
||||
VERBOSE=0
|
||||
NO_REBOOT=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--start-vm)
|
||||
START_VM=1
|
||||
shift
|
||||
;;
|
||||
--keep-files)
|
||||
KEEP_FILES=1
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE=1
|
||||
shift
|
||||
;;
|
||||
--no-reboot)
|
||||
NO_REBOOT=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 [--start-vm] [--keep-files] [--verbose] [--no-reboot]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() {
|
||||
echo "==> $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "ERROR: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
verbose() {
|
||||
if [[ $VERBOSE -eq 1 ]]; then
|
||||
echo "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ $KEEP_FILES -eq 0 ]]; then
|
||||
log "Cleaning up temporary files..."
|
||||
rm -f "$ARCHIVE_PATH"
|
||||
sshpass -p "$SSH_PASS" ssh $SSH_OPTIONS -p $SSH_PORT $SSH_USER@$SSH_HOST "rm -rf $TARGET_DIR" || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# Check if VM is running by testing SSH connection
|
||||
check_vm_running() {
|
||||
sshpass -p "$SSH_PASS" ssh $SSH_OPTIONS -p $SSH_PORT $SSH_USER@$SSH_HOST "echo 2>/dev/null" >/dev/null
|
||||
}
|
||||
|
||||
# Wait for SSH to become available
|
||||
wait_for_ssh() {
|
||||
local retries=60
|
||||
local wait_time=2
|
||||
|
||||
log "Waiting for SSH connection..."
|
||||
while [[ $retries -gt 0 ]]; do
|
||||
if check_vm_running; then
|
||||
log "SSH connection established"
|
||||
return 0
|
||||
fi
|
||||
verbose "Waiting... ($retries attempts remaining)"
|
||||
retries=$((retries - 1))
|
||||
sleep $wait_time
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if sshpass is installed
|
||||
if ! command -v sshpass >/dev/null; then
|
||||
error "sshpass is required but not installed. Please install it first."
|
||||
fi
|
||||
|
||||
# Start VM if requested or not running
|
||||
if [[ $START_VM -eq 1 ]] || ! check_vm_running; then
|
||||
log "Starting VM..."
|
||||
"$SCRIPT_DIR/run-vm" >/dev/null 2>&1 || error "Failed to start VM"
|
||||
log "VM started, waiting 10 seconds for initial boot..."
|
||||
sleep 10
|
||||
wait_for_ssh || error "Failed to establish SSH connection"
|
||||
fi
|
||||
|
||||
# Create archive of project files
|
||||
log "Creating project archive..."
|
||||
cd "$PROJECT_ROOT"
|
||||
tar czf "$ARCHIVE_PATH" \
|
||||
example/ \
|
||||
modules/ \
|
||||
.sops.yaml \
|
||||
2>/dev/null || true
|
||||
|
||||
if [[ ! -f "$ARCHIVE_PATH" ]]; then
|
||||
error "Failed to create archive at $ARCHIVE_PATH"
|
||||
fi
|
||||
|
||||
# Copy files to VM
|
||||
log "Copying files to VM..."
|
||||
sshpass -p "$SSH_PASS" ssh $SSH_OPTIONS -p $SSH_PORT $SSH_USER@$SSH_HOST "rm -rf $TARGET_DIR; mkdir -p $TARGET_DIR" || error "Failed to create target directory"
|
||||
sshpass -p "$SSH_PASS" scp $SSH_OPTIONS -P $SSH_PORT "$ARCHIVE_PATH" "$SSH_USER@$SSH_HOST:$TARGET_DIR/cloonar-config.tar.gz" || error "Failed to copy archive"
|
||||
|
||||
# Extract files and build configuration
|
||||
log "Extracting files and building configuration..."
|
||||
sshpass -p "$SSH_PASS" ssh $SSH_OPTIONS -p $SSH_PORT $SSH_USER@$SSH_HOST "cd $TARGET_DIR && \
|
||||
tar xzf cloonar-config.tar.gz && \
|
||||
nixos-rebuild switch \
|
||||
-I nixpkgs=\$(cat example/channel)/nixexprs.tar.xz \
|
||||
-I nixos-config=$TARGET_DIR/example/configuration.nix" || error "Build failed"
|
||||
|
||||
BUILD_EXIT=$?
|
||||
|
||||
if [[ $BUILD_EXIT -eq 0 ]]; then
|
||||
log "Configuration built and activated successfully!"
|
||||
|
||||
if [[ $NO_REBOOT -eq 0 ]]; then
|
||||
log "Rebooting VM..."
|
||||
sshpass -p "$SSH_PASS" ssh $SSH_OPTIONS -p $SSH_PORT $SSH_USER@$SSH_HOST "systemctl reboot"
|
||||
log "Waiting for VM to reboot..."
|
||||
sleep 30
|
||||
wait_for_ssh || error "Failed to reconnect after reboot"
|
||||
log "VM is back online"
|
||||
fi
|
||||
|
||||
else
|
||||
error "Build failed with exit code $BUILD_EXIT"
|
||||
fi
|
||||
|
||||
if [[ $KEEP_FILES -eq 1 ]]; then
|
||||
log "Files kept in $TARGET_DIR on VM"
|
||||
fi
|
||||
@@ -2,12 +2,11 @@
|
||||
set -Euo pipefail
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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.
|
||||
# This script sets up and launches (or stops) a QEMU virtual machine with OVMF.
|
||||
#
|
||||
# Usage:
|
||||
# ./run-vm.sh [install]
|
||||
# - Pass "install" to attach the ISO as a CD-ROM for installation.
|
||||
# ./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)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Paths to OVMF firmware (pflash)
|
||||
@@ -17,9 +16,33 @@ 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=$(readlink -f "$SCRIPT_DIR/../.vm/OVMF_VARS-myvm.fd")
|
||||
IMG_PATH=$(readlink -f "$SCRIPT_DIR/../.vm/disk.img")
|
||||
OVMF_VARS_PATH="$TARGET_DIR/OVMF_VARS-myvm.fd"
|
||||
IMG_PATH="$TARGET_DIR/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 "============================================================"
|
||||
@@ -92,7 +115,8 @@ 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=""
|
||||
@@ -100,9 +124,9 @@ fi
|
||||
echo
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 6. Launch QEMU
|
||||
# 6. Launch QEMU (in the background)
|
||||
# -----------------------------------------------------------------------------
|
||||
echo "[6/6] Launching QEMU VM now..."
|
||||
echo "[6/6] Launching QEMU VM now (in background)..."
|
||||
echo "------------------------------------------------------------"
|
||||
echo " • Machine: q35, KVM acceleration"
|
||||
echo " • Memory: 4096 MB"
|
||||
@@ -122,7 +146,7 @@ echo
|
||||
# Construct network options
|
||||
NET_OPTS="-netdev user,id=net0,hostfwd=tcp::2222-:22 -device e1000,netdev=net0"
|
||||
|
||||
# Run QEMU using eval to allow variable expansion in CDROM_OPTS
|
||||
# Run QEMU in the background and store its PID
|
||||
eval qemu-system-x86_64 \
|
||||
-machine type=q35,accel=kvm \
|
||||
-m 4096 \
|
||||
@@ -137,9 +161,14 @@ 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 "============================================================"
|
||||
echo " QEMU VM has exited"
|
||||
echo "============================================================"
|
||||
echo "To stop the VM at any time, run:"
|
||||
echo " $0 stop"
|
||||
echo
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user