updates
This commit is contained in:
parent
763d7a938c
commit
216bd8e668
7 changed files with 445 additions and 1 deletions
48
ansible/files/build-nixos-template.sh
Normal file
48
ansible/files/build-nixos-template.sh
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build a NixOS qcow2 image for Proxmox template and upload to Proxmox host.
|
||||
# Run on a Linux host with nix installed.
|
||||
set -euo pipefail
|
||||
|
||||
PROXMOX_HOST="10.0.0.2"
|
||||
TEMPLATE_VMID="9010"
|
||||
TEMPLATE_NAME="nixos-cloud-init"
|
||||
STORAGE="local-lvm"
|
||||
NIXOS_CONFIG="./nixos-template-config.nix"
|
||||
|
||||
echo "==> Building NixOS qcow2 image..."
|
||||
NIX_PATH="nixpkgs=https://flakehub.com/f/DeterminateSystems/nixpkgs-weekly/*.tar.gz"
|
||||
IMAGE=$(nix-build '<nixpkgs/nixos>' \
|
||||
--argstr system x86_64-linux \
|
||||
-A config.system.build.qcow2 \
|
||||
--arg configuration "$(readlink -f "$NIXOS_CONFIG")" \
|
||||
2>&1 | tail -1)
|
||||
|
||||
echo "==> Image built: $IMAGE"
|
||||
|
||||
QCOW2_PATH="$IMAGE/nixos.qcow2"
|
||||
if [ ! -f "$QCOW2_PATH" ]; then
|
||||
echo "ERROR: qcow2 not found at $QCOW2_PATH"
|
||||
ls -la "$IMAGE/" 2>/dev/null || echo "No files in image dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Uploading to Proxmox..."
|
||||
scp "$QCOW2_PATH" "root@${PROXMOX_HOST}:/tmp/nixos-template.qcow2"
|
||||
|
||||
echo "==> Importing as VM disk..."
|
||||
ssh "root@${PROXMOX_HOST}" bash -s << 'EOF'
|
||||
set -e
|
||||
qm destroy 9010 --purge 2>/dev/null || true
|
||||
qm create 9010 --name nixos-cloud-init --memory 2048 --cores 2 \
|
||||
--net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci \
|
||||
--ide2 local-lvm:cloudinit --boot c --bootdisk scsi0 \
|
||||
--serial0 socket --vga serial0
|
||||
qm importdisk 9010 /tmp/nixos-template.qcow2 local-lvm
|
||||
qm set 9010 --scsi0 "local-lvm:vm-9010-disk-0"
|
||||
qm template 9010
|
||||
rm /tmp/nixos-template.qcow2
|
||||
echo "Template VM 9010 created successfully."
|
||||
qm config 9010
|
||||
EOF
|
||||
|
||||
echo "==> Done. Template VMID 9010 is ready."
|
||||
32
ansible/files/nixos-template-config.nix
Normal file
32
ansible/files/nixos-template-config.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# NixOS template configuration for Proxmox cloud-init VMs.
|
||||
# Built into a qcow2 image and imported as a Proxmox template.
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
# Boot
|
||||
boot.loader.grub.device = "/dev/sda";
|
||||
boot.loader.timeout = 0;
|
||||
|
||||
# Network — cloud-init will configure (override proxmox-image default)
|
||||
networking.useDHCP = lib.mkForce true;
|
||||
networking.hostName = "nixos";
|
||||
|
||||
# SSH for Ansible
|
||||
services.openssh.enable = true;
|
||||
services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password";
|
||||
|
||||
# QEMU guest agent for Proxmox integration
|
||||
services.qemuGuest.enable = true;
|
||||
|
||||
# cloud-init — consumes Proxmox config drive (hostname, SSH keys, network)
|
||||
services.cloud-init.enable = true;
|
||||
services.cloud-init.network.enable = true;
|
||||
|
||||
# Python for Ansible management
|
||||
environment.systemPackages = [ pkgs.python3 ];
|
||||
|
||||
# Minimal system
|
||||
documentation.enable = false;
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
4
ansible/host_vars/nixos-resolver.yml
Normal file
4
ansible/host_vars/nixos-resolver.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
ansible_user: root
|
||||
ansible_python_interpreter: /run/current-system/sw/bin/python3
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
|
@ -63,3 +63,7 @@ db.w5isp.com
|
|||
[irc_servers]
|
||||
us.manero.org ansible_host=manero-us
|
||||
ca.manero.org ansible_host=manero-ca
|
||||
|
||||
[nixos_resolvers]
|
||||
nixos-resolver ansible_host=nixos-resolver
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,248 @@
|
|||
- role: ns
|
||||
tags: ns
|
||||
|
||||
- name: Build and upload NixOS template to Proxmox
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tags:
|
||||
- template
|
||||
- never
|
||||
vars:
|
||||
proxmox_node: "{{ lookup('env', 'PROXMOX_NODE') | default('vm2-380', true) }}"
|
||||
proxmox_api_host: 10.0.0.2
|
||||
proxmox_api_user: "{{ lookup('env', 'PROXMOX_API_USER') | default('root@pam', true) }}"
|
||||
proxmox_api_password: "{{ lookup('env', 'PROXMOX_API_PASSWORD') }}"
|
||||
nixos_template_vmid: 9010
|
||||
build_host: ci
|
||||
tasks:
|
||||
- name: Upload NixOS config to build host
|
||||
ansible.builtin.copy:
|
||||
src: files/nixos-template-config.nix
|
||||
dest: /tmp/nixos-template-config.nix
|
||||
delegate_to: "{{ build_host }}"
|
||||
|
||||
- name: Build NixOS proxmox image
|
||||
ansible.builtin.shell: >
|
||||
nixos-generate -f proxmox
|
||||
-o /tmp/nixos-template.vma.zst
|
||||
-c /tmp/nixos-template-config.nix
|
||||
args:
|
||||
chdir: /tmp
|
||||
environment:
|
||||
PATH: "/run/current-system/sw/bin:{{ ansible_env.PATH }}"
|
||||
delegate_to: "{{ build_host }}"
|
||||
register: _nixos_build
|
||||
|
||||
- name: Find built VMA file in nix store
|
||||
ansible.builtin.shell: >
|
||||
nix-shell -p nixos-generators --run
|
||||
"nixos-generate -f proxmox -o /tmp/nixos-template.vma.zst -c /tmp/nixos-template-config.nix" 2>&1
|
||||
| grep '^/' | tail -1
|
||||
args:
|
||||
chdir: /tmp
|
||||
delegate_to: "{{ build_host }}"
|
||||
register: _vma_path
|
||||
changed_when: false
|
||||
|
||||
- name: Copy VMA from build host to Proxmox
|
||||
ansible.builtin.shell: >
|
||||
scp {{ build_host }}:'{{ _vma_path.stdout }}' /tmp/nixos-template.vma.zst &&
|
||||
scp /tmp/nixos-template.vma.zst root@{{ proxmox_api_host }}:/tmp/nixos-template.vma.zst
|
||||
|
||||
- name: Destroy existing template VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ nixos_template_vmid }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
state: absent
|
||||
force: true
|
||||
|
||||
- name: Create VM and import disk on Proxmox
|
||||
ansible.builtin.shell: >
|
||||
set -e;
|
||||
vma extract /tmp/nixos-template.vma.zst /tmp/nixos-vma;
|
||||
qm create {{ nixos_template_vmid }} --name nixos-cloud-init
|
||||
--memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
|
||||
--scsihw virtio-scsi-pci --ide2 local-lvm:cloudinit
|
||||
--boot c --bootdisk scsi0 --serial0 socket --vga serial0;
|
||||
qm importdisk {{ nixos_template_vmid }}
|
||||
/tmp/nixos-vma/disk-drive-virtio0.raw local-lvm;
|
||||
qm set {{ nixos_template_vmid }}
|
||||
--scsi0 "local-lvm:vm-{{ nixos_template_vmid }}-disk-0";
|
||||
qm template {{ nixos_template_vmid }};
|
||||
rm -rf /tmp/nixos-vma /tmp/nixos-template.vma.zst
|
||||
delegate_to: "{{ proxmox_node }}"
|
||||
|
||||
- name: Provision NixOS VM on Proxmox
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tags:
|
||||
- nixos
|
||||
- provision
|
||||
- never
|
||||
vars:
|
||||
proxmox_node: "{{ lookup('env', 'PROXMOX_NODE') | default('vm2-380', true) }}"
|
||||
proxmox_api_host: 10.0.0.2
|
||||
proxmox_api_user: "{{ lookup('env', 'PROXMOX_API_USER') | default('root@pam', true) }}"
|
||||
proxmox_api_password: "{{ lookup('env', 'PROXMOX_API_PASSWORD') }}"
|
||||
proxmox_storage: local-lvm
|
||||
proxmox_template_vmid: 9010
|
||||
proxmox_template_name: nixos-cloud-init
|
||||
vm_name: nixos-resolver
|
||||
vm_vmid: 110
|
||||
vm_cores: 2
|
||||
vm_memory: 4096
|
||||
vm_disk_size: 64
|
||||
vm_ssh_keys: |
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLyVSEEAEbGZZqwPwEup0XrlTpu3x3iF9ExvvQPA4xN
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOzrDMNn3nINGdIogzRxY05fkn05LSYi98BGW1Bz5yXD
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIER/8suIKcrT3a/NZHjvOpRosPC5uX4kcznIJHt/98qj
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEEKtgXwoW8HZGqC+KJok9iNpI/lK4glvoryL4Ng/AL+
|
||||
tasks:
|
||||
- name: Clone NixOS template
|
||||
community.proxmox.proxmox_kvm:
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ proxmox_template_vmid }}"
|
||||
clone: "{{ proxmox_template_name }}"
|
||||
newid: "{{ vm_vmid }}"
|
||||
name: "{{ vm_name }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
storage: "{{ proxmox_storage }}"
|
||||
timeout: 90
|
||||
full: true
|
||||
agent: true
|
||||
|
||||
- name: Configure VM resources
|
||||
community.proxmox.proxmox_kvm:
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ vm_vmid }}"
|
||||
name: "{{ vm_name }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
cores: "{{ vm_cores }}"
|
||||
memory: "{{ vm_memory }}"
|
||||
update: true
|
||||
|
||||
- name: Resize disk
|
||||
community.proxmox.proxmox_disk:
|
||||
vmid: "{{ vm_vmid }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
disk: scsi0
|
||||
size: "{{ vm_disk_size }}G"
|
||||
state: resized
|
||||
register: _disk_resize
|
||||
failed_when:
|
||||
- _disk_resize.failed
|
||||
- '"shrinking disks is not supported" not in (_disk_resize.msg | default(""))'
|
||||
|
||||
- name: Configure cloud-init
|
||||
community.proxmox.proxmox_kvm:
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ vm_vmid }}"
|
||||
name: "{{ vm_name }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
ciuser: root
|
||||
sshkeys: "{{ vm_ssh_keys }}"
|
||||
ipconfig:
|
||||
ipconfig0: "ip=dhcp"
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
onboot: true
|
||||
update: true
|
||||
|
||||
- name: Start VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ vm_vmid }}"
|
||||
name: "{{ vm_name }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_password: "{{ proxmox_api_password }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
validate_certs: false
|
||||
state: started
|
||||
|
||||
- name: Wait for VM IP via QEMU agent
|
||||
ansible.builtin.shell: >
|
||||
python3 -c "
|
||||
import urllib3, json, time
|
||||
urllib3.disable_warnings()
|
||||
from proxmoxer import ProxmoxAPI
|
||||
proxmox = ProxmoxAPI('{{ proxmox_api_host }}',
|
||||
user='{{ proxmox_api_user }}', password='{{ proxmox_api_password }}',
|
||||
verify_ssl=False)
|
||||
for _ in range(48):
|
||||
try:
|
||||
nets = proxmox.nodes('{{ proxmox_node }}').qemu('{{ vm_vmid }}').agent('network-get-interfaces').get()
|
||||
for iface in nets.get('result', []):
|
||||
for addr in iface.get('ip-addresses', []):
|
||||
if addr.get('ip-address-type') == 'ipv4' and addr['ip-address'] != '127.0.0.1':
|
||||
print(addr['ip-address'])
|
||||
exit(0)
|
||||
time.sleep(5)
|
||||
except Exception:
|
||||
time.sleep(5)
|
||||
exit(1)
|
||||
"
|
||||
register: _agent_ip
|
||||
changed_when: false
|
||||
retries: 1
|
||||
delay: 0
|
||||
|
||||
- name: Add VM to in-memory inventory
|
||||
ansible.builtin.add_host:
|
||||
name: "{{ vm_name }}"
|
||||
ansible_host: "{{ _agent_ip.stdout | trim }}"
|
||||
groups: nixos_resolvers
|
||||
|
||||
- name: Configure NixOS Unbound resolver
|
||||
hosts: nixos_resolvers
|
||||
become: true
|
||||
gather_facts: true
|
||||
tags:
|
||||
- nixos
|
||||
- unbound
|
||||
- never
|
||||
tasks:
|
||||
- name: Load resolver group variables
|
||||
ansible.builtin.include_vars:
|
||||
file: group_vars/resolvers/main.yml
|
||||
|
||||
- name: Deploy NixOS configuration with Unbound
|
||||
ansible.builtin.template:
|
||||
src: roles/resolvers/templates/nixos-unbound.nix.j2
|
||||
dest: /etc/nixos/unbound.nix
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
register: _nixos_config
|
||||
|
||||
- name: Import unbound module in configuration.nix
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/nixos/configuration.nix
|
||||
line: "./unbound.nix"
|
||||
regexp: '^./unbound\.nix'
|
||||
insertafter: '^\{ config,'
|
||||
state: present
|
||||
register: _nixos_import
|
||||
|
||||
- name: Rebuild NixOS
|
||||
ansible.builtin.command: nixos-rebuild switch
|
||||
when: _nixos_config.changed or _nixos_import.changed
|
||||
|
||||
- name: Apply general configuration to all nodes
|
||||
hosts: all
|
||||
become: true
|
||||
|
|
|
|||
114
ansible/roles/resolvers/templates/nixos-unbound.nix.j2
Normal file
114
ansible/roles/resolvers/templates/nixos-unbound.nix.j2
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Managed by Ansible (roles/resolvers/templates/nixos-unbound.nix.j2)
|
||||
# Settings derived from unbound.conf.j2 — same configuration as resolver1.
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
accessControl = [
|
||||
"0.0.0.0/0 refuse"
|
||||
"::0/0 refuse"
|
||||
{%- for netblock in resolver_allowed_netblocks %}
|
||||
"{{ netblock }} allow"
|
||||
{%- endfor %}
|
||||
];
|
||||
|
||||
blocklistZone = pkgs.writeText "blocklist.rpz" ''
|
||||
$TTL 3600
|
||||
$ORIGIN blocklist.rpz.
|
||||
@ IN SOA localhost. root.localhost. 1 3600 600 86400 3600
|
||||
@ IN NS localhost.
|
||||
{%- for domain in resolver_blocked_domains | default([]) %}
|
||||
{{ domain }} CNAME .
|
||||
*.{{ domain }} CNAME .
|
||||
{%- endfor %}
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
interface = [ "0.0.0.0" "::0" ];
|
||||
port = 53;
|
||||
|
||||
# DoH endpoint via loopback (Caddy terminates TLS upstream)
|
||||
interface-auto = false;
|
||||
https-port = 8080;
|
||||
http-endpoint = "/dns-query";
|
||||
http-notls-downstream = true;
|
||||
|
||||
access-control = accessControl;
|
||||
|
||||
# Performance tuning
|
||||
num-threads = 4;
|
||||
msg-cache-size = "512m";
|
||||
rrset-cache-size = "512m";
|
||||
key-cache-size = "512m";
|
||||
msg-cache-slabs = 8;
|
||||
rrset-cache-slabs = 8;
|
||||
infra-cache-slabs = 8;
|
||||
key-cache-slabs = 8;
|
||||
num-queries-per-thread = 1024;
|
||||
|
||||
outgoing-port-permit = "32768-60999";
|
||||
outgoing-num-tcp = 100;
|
||||
incoming-num-tcp = 600;
|
||||
|
||||
# Prefetch and serve-expired
|
||||
prefetch = true;
|
||||
prefetch-key = true;
|
||||
serve-expired = true;
|
||||
serve-expired-ttl = 86400;
|
||||
serve-expired-client-timeout = 1800;
|
||||
|
||||
# Socket tuning
|
||||
so-reuseport = true;
|
||||
so-rcvbuf = "4m";
|
||||
ip-transparent = true;
|
||||
edns-buffer-size = 1232;
|
||||
edns-tcp-keepalive = true;
|
||||
|
||||
# Hardening
|
||||
harden-short-bufsize = true;
|
||||
harden-large-queries = true;
|
||||
harden-glue = true;
|
||||
harden-dnssec-stripped = true;
|
||||
harden-below-nxdomain = true;
|
||||
aggressive-nsec = true;
|
||||
qname-minimisation = true;
|
||||
|
||||
# Logging
|
||||
log-time-ascii = true;
|
||||
|
||||
# Statistics
|
||||
extended-statistics = true;
|
||||
shm-enable = false;
|
||||
};
|
||||
|
||||
remote-control = {
|
||||
control-enable = true;
|
||||
control-interface = "127.0.0.1";
|
||||
control-port = 8953;
|
||||
control-use-cert = false;
|
||||
};
|
||||
|
||||
rpz = {
|
||||
name = "blocklist.rpz";
|
||||
zonefile = "${blocklistZone}";
|
||||
rpz-action-override = "nxdomain";
|
||||
rpz-log = true;
|
||||
rpz-log-name = "blocklist";
|
||||
};
|
||||
};
|
||||
|
||||
# DNSSEC root trust anchor
|
||||
enableRootTrustAnchor = true;
|
||||
};
|
||||
|
||||
# Python for Ansible management (appended, doesn't clobber other packages)
|
||||
environment.systemPackages = lib.mkAfter [ pkgs.python3 ];
|
||||
|
||||
# Open DNS ports
|
||||
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
}
|
||||
|
|
@ -1363,7 +1363,7 @@ remote-control:
|
|||
# from group_vars/resolvers/main.yml. Requires respip in module-config (above).
|
||||
rpz:
|
||||
name: "blocklist.rpz"
|
||||
zonefile: "/etc/unbound/blocklist.rpz"
|
||||
zonefile: "blocklist.rpz"
|
||||
rpz-action-override: nxdomain
|
||||
rpz-log: yes
|
||||
rpz-log-name: "blocklist"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue