commit 3586c571f34df745ec81b5631f3b9778d5bb9f7f Author: boris Date: Wed Jul 8 16:38:55 2026 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2027f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.ansible/ +*.retry diff --git a/README.md b/README.md new file mode 100644 index 0000000..11c7956 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# nbops-ansible-router + +Ansible framework for building a Debian 13 BIRD router VM from an initial Debian 12 install. + +The first stage performs the Debian 12 to Debian 13 dist upgrade. Router configuration then installs WireGuard and BIRD, applies forwarding sysctl values, and renders configs from variables. + +## Layout + +- `site.yml` - main entry point. +- `inventories/production/hosts.yml` - VM inventory placeholder. +- `group_vars/routers.yml` - shared router variables. +- `roles/debian_dist_upgrade` - Debian 12 to Debian 13 upgrade workflow. +- `roles/bird_router` - WireGuard, sysctl forwarding, CZ.NIC BIRD packages, and BIRD configuration. + +## Usage + +Edit `inventories/production/hosts.yml`, then run: + +```sh +ansible-playbook site.yml +``` + +To run only the OS upgrade stage: + +```sh +ansible-playbook site.yml --tags debian_upgrade +``` + +The upgrade role expects the VM to start on Debian 12/bookworm and upgrades it to Debian 13/trixie. + +## Router Secrets + +WireGuard private keys are read from environment variables by default: + +```sh +export WG1_PRIVATEKEY='...' +export WG2_PRIVATEKEY='...' +ansible-playbook site.yml +``` + +For production, prefer Ansible Vault group vars instead of committing secrets. Public keys, allowed IPs, BGP peers, package track, and package version are defined in `group_vars/routers.yml`. + +## BIRD Package Track + +The router role configures the CZ.NIC Labs apt repository for BIRD. Defaults: + +- `bird_track: bird2` +- `bird_package_version: 2.19.1-cznic.1~trixie` + +To test BIRD 3 later, set: + +```yaml +bird_track: bird3 +bird_package_version: "" +``` diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..36396d9 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,12 @@ +[defaults] +inventory = inventories/production/hosts.yml +roles_path = roles +host_key_checking = True +retry_files_enabled = False +interpreter_python = auto_silent +local_tmp = .ansible/tmp + +[privilege_escalation] +become = True +become_method = sudo +become_ask_pass = False diff --git a/demo-flies/etc/bird/bird.conf b/demo-flies/etc/bird/bird.conf new file mode 100644 index 0000000..cbd6d7e --- /dev/null +++ b/demo-flies/etc/bird/bird.conf @@ -0,0 +1,151 @@ +router id 82.215.64.105; + +log syslog all; +log "/var/log/bird/bird.log" { debug, trace, remote, warning, error, auth, fatal, bug }; +#log "/var/log/bird/info-bird.log" { info }; + +protocol device { scan time 5; } +protocol direct { ipv6; } + +protocol kernel { + ipv6 { export none; }; +# ipv6; + scan time 15; +# learn; +} + +protocol kernel { + ipv4; + scan time 15; +# export filter rt_export; +# learn; +} + +protocol static announcev6 { + ipv6; + description "Static v6 Routes"; +# route 2a12:bec0:6b0::/44 blackhole; + route 2a12:bec0:6b1::/48 via 2a12:bec2:b00b::201; +# route 2a12:bec0:6b9::/48 via 2a12:bec2:b00b::1f7; +# route 2a12:bec0:210:4::/64 via 2a12:bec0:210:4::1; #Pete-tunnel +# route 2a12:bec0:218::/48 via 2a12:bec0:210:4::2; #Pete-range + +} + +protocol static announcev4 { + ipv4; + description "Static v4 Routes"; +} + +protocol static { + ipv6; +} + +protocol static { + ipv4; +} + +filter rt_importv4 { + accept; +} + +filter rt_importv6 { + accept; +} + +filter rt_nothanks { + + if (net ~ [ fd00:dead:beef:c0::/80 ]) then + reject; + if (net ~ [ fd4d:6169:6c69:6f77::/64 ]) then + reject; + if (net ~ [ fd7a:115c:a1e0::/48 ]) then + reject; + if (net ~ [ fd8c:c43c:133e::/64 ]) then + reject; + if (net ~ [ fd8c:c43c:133e:1::/64 ]) then + reject; + if (net ~ [ fd8c:c43c:133e:2::/64 ]) then + reject; + if (net ~ [ fd00:1::/64 ]) then + reject; + if (net ~ [ 2a12:bec2:b00b::/64 ]) then + reject; + if (net ~ [ ::10.10.10.1/128 ]) then + reject; + if (net ~ [ 2a12:bec0:210:3::/64 ]) then + reject; + else + accept; +} + +filter sbehop { + gw = 2a12:bec0:210:3::1; + accept; +} + +filter brt_importv6 { + reject; +} + +filter rt_export { + if (proto = "announcev4") then + accept; + if (proto = "announcev6") then + accept; +# reject_bogon_asns(); +# reject_v6_bogon_prefixes(); +# reject_smallv6_prefixes(); +# reject_long_aspaths(); + reject; +# accept; #export Any Accepted Prefixes +} + +#EBGP upstream sessions +protocol bgp tgj4 { +# debug all; + local 172.22.10.2 as 213026; + neighbor 172.22.10.1 as 199519; +# default bgp_local_pref 20; + multihop 255; + ipv6 { + export limit 30; + import filter sbehop; + export filter rt_nothanks; + graceful restart on; + }; +} + +protocol bgp scaleblade6 { +# debug all; + local 2a12:bec2:b00b::201 as 213026; + neighbor 2a12:bec1::2 as 52041; + multihop 255; +# default bgp_local_pref 20; + ipv6 { + export limit 50; + import filter rt_importv6; + export filter rt_nothanks; + graceful restart on; + }; +} + +#IBGP sessions +protocol bgp ntbx { + local 172.18.72.1 as 213026; + neighbor 172.18.72.2 as 213026; + multihop 255; + ipv6 { + import limit 20; +# import none; + import filter rt_importv6; + export filter rt_export; +# export none; + }; +# ipv4 { +# import limit 20; +# import none; +# export filter rt_export; +## graceful restart on; +# }; +} \ No newline at end of file diff --git a/demo-flies/etc/bird/envvars b/demo-flies/etc/bird/envvars new file mode 100644 index 0000000..c7e1c00 --- /dev/null +++ b/demo-flies/etc/bird/envvars @@ -0,0 +1,3 @@ +BIRD_RUN_USER=bird +BIRD_RUN_GROUP=bird +#BIRD_ARGS= \ No newline at end of file diff --git a/demo-flies/etc/sysctl.conf b/demo-flies/etc/sysctl.conf new file mode 100644 index 0000000..977bee6 --- /dev/null +++ b/demo-flies/etc/sysctl.conf @@ -0,0 +1,3 @@ +net.ipv6.conf.all.forwarding = 1 +net.ipv6.conf.default.forwarding = 1 +net.ipv4.ip_forward = 1 \ No newline at end of file diff --git a/demo-flies/etc/wireguard/wg1.conf b/demo-flies/etc/wireguard/wg1.conf new file mode 100644 index 0000000..56707bc --- /dev/null +++ b/demo-flies/etc/wireguard/wg1.conf @@ -0,0 +1,16 @@ +# Wireguard to pfSense + +[Interface] +PrivateKey = ${WG1_PRIVATEKEY} +Address = 2a12:bec0:6b1::1/64,172.18.72.1/30 +ListenPort = 51825 +MTU = 1430 + +PreUp = ip neigh add proxy 82.215.64.187 dev eth0 + +PostDown = ip neigh del proxy 82.215.64.187 dev eth0 + +[Peer] +PublicKey = SsGjCLJBKQFDuxSnVgwua77fvb/ha4KFRiJcCBk7vF0= +AllowedIPs = 2a12:bec0:6b1::2/64,2a12:bec0:6b9::/48,172.18.72.0/30,82.215.64.187/32 +#2a12:bec0:6b9:2::/64,2a12:bec0:6b9:3::/64,2a12:bec0:6b9:10::/64,2a12:bec0:6b9::/48,172.18.72.0/30,82.215.64.187/32 \ No newline at end of file diff --git a/demo-flies/etc/wireguard/wg2.conf b/demo-flies/etc/wireguard/wg2.conf new file mode 100644 index 0000000..a2924c2 --- /dev/null +++ b/demo-flies/etc/wireguard/wg2.conf @@ -0,0 +1,13 @@ +# Wireguard to TGJ + +[Interface] +PrivateKey = ${WG2_PRIVATEKEY} +Address = 2a12:bec0:210:3::2/64,172.22.10.2/30 +ListenPort = 51820 +MTU = 1430 + +[Peer] +PublicKey = 1XaLeqKZimM1cS57dPzTSf/UGW8bThADjf+oBmRF82I= +AllowedIPs = 2a12:bec0:21f::2/64,172.22.10.1/30,2a12:bec0:212::/48,2a12:bec0:210::/48,2a12:bec0:214::/48 +Endpoint = v4.router.colo.tgj.services:51869 +PersistentKeepalive = 25 \ No newline at end of file diff --git a/demo-flies/readme.md b/demo-flies/readme.md new file mode 100644 index 0000000..aa1a5f0 --- /dev/null +++ b/demo-flies/readme.md @@ -0,0 +1,8 @@ +82.215.64.187 = Virtual Home IP used by Bird +82.215.64.105 = BIRD VPS IP +82.215.64.169 = VPS IP +v4.router.colo.tgj.services:51869 = TGJ WG Remote Node + +82.215.64.187 is cloud-init added to the default interface, this should be removed in wireguard PreUp, or ansible config + +Should use ansible variables for allowedIPs, private / public keys, etc. anything that should not be committed to github should be in group vars or envy \ No newline at end of file diff --git a/group_vars/routers.yml b/group_vars/routers.yml new file mode 100644 index 0000000..03a51f1 --- /dev/null +++ b/group_vars/routers.yml @@ -0,0 +1,115 @@ +--- +debian_upgrade_target_release: trixie +debian_upgrade_target_major_version: "13" +debian_upgrade_source_major_version: "12" + +debian_upgrade_reboot_timeout: 1800 + +router_external_interface: eth0 + +router_virtual_home_ip: 82.215.64.187 +router_bird_vps_ip: 82.215.64.105 +router_vps_ip: 82.215.64.169 + +router_sysctl: + net.ipv6.conf.all.forwarding: 1 + net.ipv6.conf.default.forwarding: 1 + net.ipv4.ip_forward: 1 + +bird_enable: true +bird_track: bird2 +bird_package_version: 2.19.1-cznic.1~trixie +bird_router_id: 82.215.64.105 +bird_local_as: 213026 +bird_run_user: bird +bird_run_group: bird +bird_log_file: /var/log/bird/bird.log +bird_sbehop_gateway: 2a12:bec0:210:3::1 + +bird_static_routes_v6: + - prefix: 2a12:bec0:6b1::/48 + via: 2a12:bec2:b00b::201 + +bird_reject_prefixes_v6: + - fd00:dead:beef:c0::/80 + - fd4d:6169:6c69:6f77::/64 + - fd7a:115c:a1e0::/48 + - fd8c:c43c:133e::/64 + - fd8c:c43c:133e:1::/64 + - fd8c:c43c:133e:2::/64 + - fd00:1::/64 + - 2a12:bec2:b00b::/64 + - ::10.10.10.1/128 + - 2a12:bec0:210:3::/64 + +bird_bgp_sessions: + - name: tgj4 + local_address: 172.22.10.2 + local_as: 213026 + neighbor_address: 172.22.10.1 + neighbor_as: 199519 + address_family: ipv6 + export_limit: 30 + import_filter: sbehop + export_filter: rt_nothanks + graceful_restart: true + - name: scaleblade6 + local_address: 2a12:bec2:b00b::201 + local_as: 213026 + neighbor_address: 2a12:bec1::2 + neighbor_as: 52041 + address_family: ipv6 + export_limit: 50 + import_filter: rt_importv6 + export_filter: rt_nothanks + graceful_restart: true + - name: ntbx + local_address: 172.18.72.1 + local_as: 213026 + neighbor_address: 172.18.72.2 + neighbor_as: 213026 + address_family: ipv6 + import_limit: 20 + import_filter: rt_importv6 + export_filter: rt_export + +wireguard_enable: true +wireguard_interfaces: + - name: wg1 + description: WireGuard to pfSense + private_key_env: WG1_PRIVATEKEY + address: + - 2a12:bec0:6b1::1/64 + - 172.18.72.1/30 + listen_port: 51825 + mtu: 1430 + pre_up: + - "ip addr del 82.215.64.187/32 dev eth0 2>/dev/null || true" + - "ip neigh add proxy 82.215.64.187 dev eth0 2>/dev/null || true" + post_down: + - "ip neigh del proxy 82.215.64.187 dev eth0 2>/dev/null || true" + peers: + - public_key: SsGjCLJBKQFDuxSnVgwua77fvb/ha4KFRiJcCBk7vF0= + allowed_ips: + - 2a12:bec0:6b1::2/64 + - 2a12:bec0:6b9::/48 + - 172.18.72.0/30 + - 82.215.64.187/32 + - name: wg2 + description: WireGuard to TGJ + private_key_env: WG2_PRIVATEKEY + address: + - 2a12:bec0:210:3::2/64 + - 172.22.10.2/30 + listen_port: 51820 + mtu: 1430 + peers: + - public_key: 1XaLeqKZimM1cS57dPzTSf/UGW8bThADjf+oBmRF82I= + allowed_ips: + - 2a12:bec0:21f::2/64 + - 172.22.10.1/30 + - 2a12:bec0:212::/48 + - 2a12:bec0:210::/48 + - 2a12:bec0:214::/48 + endpoint: v4.router.colo.tgj.services:51869 + persistent_keepalive: 25 diff --git a/inventories/production/hosts.yml b/inventories/production/hosts.yml new file mode 100644 index 0000000..0c9c115 --- /dev/null +++ b/inventories/production/hosts.yml @@ -0,0 +1,7 @@ +all: + children: + routers: + hosts: + router-vm: + ansible_host: 10.10.10.10 + ansible_user: debian diff --git a/roles/bird_router/defaults/main.yml b/roles/bird_router/defaults/main.yml new file mode 100644 index 0000000..3eaa9f2 --- /dev/null +++ b/roles/bird_router/defaults/main.yml @@ -0,0 +1,34 @@ +--- +router_external_interface: eth0 + +router_sysctl: + net.ipv6.conf.all.forwarding: 1 + net.ipv6.conf.default.forwarding: 1 + net.ipv4.ip_forward: 1 + +wireguard_enable: true +wireguard_package_name: wireguard-tools +wireguard_interfaces: [] + +bird_enable: true +bird_track: bird2 +bird_package_name: "{{ bird_track }}" +bird_package_version: "" +bird_cznic_repo_enabled: true +bird_cznic_repo_base_url: https://pkg.labs.nic.cz +bird_cznic_repo_distribution: "{{ ansible_facts.distribution_release | default('trixie') }}" +bird_cznic_key_url: https://pkg.labs.nic.cz/gpg + +bird_config_path: /etc/bird/bird.conf +bird_envvars_path: /etc/bird/envvars +bird_service_name: bird +bird_router_id: 127.0.0.1 +bird_local_as: 64512 +bird_run_user: bird +bird_run_group: bird +bird_log_file: /var/log/bird/bird.log +bird_sbehop_gateway: "" +bird_static_routes_v6: [] +bird_static_routes_v4: [] +bird_reject_prefixes_v6: [] +bird_bgp_sessions: [] diff --git a/roles/bird_router/handlers/main.yml b/roles/bird_router/handlers/main.yml new file mode 100644 index 0000000..be1a359 --- /dev/null +++ b/roles/bird_router/handlers/main.yml @@ -0,0 +1,20 @@ +--- +- name: Reload sysctl + ansible.builtin.command: sysctl --system + listen: reload_sysctl + changed_when: false + +- name: Restart WireGuard interfaces + ansible.builtin.systemd: + name: "wg-quick@{{ item.name }}" + state: restarted + loop: "{{ wireguard_interfaces }}" + listen: restart_wireguard_interfaces + when: wireguard_enable + +- name: Restart BIRD + ansible.builtin.systemd: + name: "{{ bird_service_name }}" + state: restarted + listen: restart_bird + when: bird_enable diff --git a/roles/bird_router/tasks/main.yml b/roles/bird_router/tasks/main.yml new file mode 100644 index 0000000..da65f22 --- /dev/null +++ b/roles/bird_router/tasks/main.yml @@ -0,0 +1,131 @@ +- name: Validate BIRD package track + ansible.builtin.assert: + that: + - bird_track in ['bird2', 'bird3'] + fail_msg: "bird_track must be either bird2 or bird3." + when: bird_enable + +- name: Install repository prerequisites + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + - python3-debian + state: present + update_cache: true + when: bird_enable and bird_cznic_repo_enabled + +- name: Configure CZ.NIC Labs BIRD apt repository + ansible.builtin.deb822_repository: + name: "cznic-labs-{{ bird_track }}" + types: deb + uris: "{{ bird_cznic_repo_base_url }}/{{ bird_track }}" + suites: "{{ bird_cznic_repo_distribution }}" + components: main + signed_by: "{{ bird_cznic_key_url }}" + state: present + when: bird_enable and bird_cznic_repo_enabled + +- name: Update package cache after configuring CZ.NIC repository + ansible.builtin.apt: + update_cache: true + when: bird_enable and bird_cznic_repo_enabled + +- name: Install WireGuard tools + ansible.builtin.apt: + name: "{{ wireguard_package_name }}" + state: present + when: wireguard_enable + +- name: Install selected BIRD package + ansible.builtin.apt: + name: "{{ bird_package_name if bird_package_version | length == 0 else bird_package_name + '=' + bird_package_version }}" + state: present + when: bird_enable + +- name: Configure router sysctl values + ansible.builtin.template: + src: router-sysctl.conf.j2 + dest: /etc/sysctl.d/99-router.conf + owner: root + group: root + mode: "0644" + notify: reload_sysctl + +- name: Ensure WireGuard configuration directory exists + ansible.builtin.file: + path: /etc/wireguard + state: directory + owner: root + group: root + mode: "0700" + when: wireguard_enable + +- name: Validate WireGuard private keys are provided + ansible.builtin.assert: + that: + - >- + (item.private_key is defined and item.private_key | length > 0) + or + (item.private_key_env is defined and lookup('ansible.builtin.env', item.private_key_env) | length > 0) + fail_msg: "Private key for {{ item.name }} is missing. Set it via Ansible Vault or the matching environment lookup." + loop: "{{ wireguard_interfaces }}" + no_log: true + when: wireguard_enable + +- name: Configure WireGuard interfaces + ansible.builtin.template: + src: wg.conf.j2 + dest: "/etc/wireguard/{{ item.name }}.conf" + owner: root + group: root + mode: "0600" + loop: "{{ wireguard_interfaces }}" + no_log: true + notify: restart_wireguard_interfaces + when: wireguard_enable + +- name: Enable WireGuard interfaces + ansible.builtin.systemd: + name: "wg-quick@{{ item.name }}" + enabled: true + state: started + loop: "{{ wireguard_interfaces }}" + when: wireguard_enable + +- name: Ensure BIRD log directory exists + ansible.builtin.file: + path: "{{ bird_log_file | dirname }}" + state: directory + owner: "{{ bird_run_user }}" + group: "{{ bird_run_group }}" + mode: "0755" + when: bird_enable + +- name: Configure BIRD environment + ansible.builtin.template: + src: bird-envvars.j2 + dest: "{{ bird_envvars_path }}" + owner: root + group: root + mode: "0644" + notify: restart_bird + when: bird_enable + +- name: Configure BIRD + ansible.builtin.template: + src: bird.conf.j2 + dest: "{{ bird_config_path }}" + owner: root + group: "{{ bird_run_group }}" + mode: "0640" + validate: "bird -p -c %s" + notify: restart_bird + when: bird_enable + +- name: Enable BIRD service + ansible.builtin.systemd: + name: "{{ bird_service_name }}" + enabled: true + state: started + when: bird_enable diff --git a/roles/bird_router/templates/bird-envvars.j2 b/roles/bird_router/templates/bird-envvars.j2 new file mode 100644 index 0000000..5ca6c97 --- /dev/null +++ b/roles/bird_router/templates/bird-envvars.j2 @@ -0,0 +1,3 @@ +BIRD_RUN_USER={{ bird_run_user }} +BIRD_RUN_GROUP={{ bird_run_group }} +#BIRD_ARGS= diff --git a/roles/bird_router/templates/bird.conf.j2 b/roles/bird_router/templates/bird.conf.j2 new file mode 100644 index 0000000..c5ac7b4 --- /dev/null +++ b/roles/bird_router/templates/bird.conf.j2 @@ -0,0 +1,109 @@ +router id {{ bird_router_id }}; + +log syslog all; +log "{{ bird_log_file }}" { debug, trace, remote, warning, error, auth, fatal, bug }; + +protocol device { scan time 5; } +protocol direct { ipv6; } + +protocol kernel { + ipv6 { export none; }; + scan time 15; +} + +protocol kernel { + ipv4; + scan time 15; +} + +protocol static announcev6 { + ipv6; + description "Static v6 Routes"; +{% for route in bird_static_routes_v6 %} +{% if route.blackhole | default(false) %} + route {{ route.prefix }} blackhole; +{% else %} + route {{ route.prefix }} via {{ route.via }}; +{% endif %} +{% endfor %} +} + +protocol static announcev4 { + ipv4; + description "Static v4 Routes"; +{% for route in bird_static_routes_v4 %} +{% if route.blackhole | default(false) %} + route {{ route.prefix }} blackhole; +{% else %} + route {{ route.prefix }} via {{ route.via }}; +{% endif %} +{% endfor %} +} + +protocol static { + ipv6; +} + +protocol static { + ipv4; +} + +filter rt_importv4 { + accept; +} + +filter rt_importv6 { + accept; +} + +filter rt_nothanks { +{% for prefix in bird_reject_prefixes_v6 %} + if (net ~ [ {{ prefix }} ]) then reject; +{% endfor %} + accept; +} + +filter sbehop { + gw = {{ bird_sbehop_gateway }}; + accept; +} + +filter brt_importv6 { + reject; +} + +filter rt_export { + if (proto = "announcev4") then accept; + if (proto = "announcev6") then accept; + reject; +} + +{% for session in bird_bgp_sessions %} +protocol bgp {{ session.name }} { + local {{ session.local_address }} as {{ session.local_as }}; + neighbor {{ session.neighbor_address }} as {{ session.neighbor_as }}; + multihop {{ session.multihop | default(255) }}; + {{ session.address_family | default('ipv6') }} { +{% if session.import_limit is defined %} + import limit {{ session.import_limit }}; +{% endif %} +{% if session.export_limit is defined %} + export limit {{ session.export_limit }}; +{% endif %} +{% if session.import_filter is defined %} + import filter {{ session.import_filter }}; +{% else %} + import none; +{% endif %} +{% if session.export_filter is defined %} + export filter {{ session.export_filter }}; +{% else %} + export none; +{% endif %} +{% if session.graceful_restart | default(false) %} + graceful restart on; +{% endif %} + }; +} + +{% endfor %} diff --git a/roles/bird_router/templates/router-sysctl.conf.j2 b/roles/bird_router/templates/router-sysctl.conf.j2 new file mode 100644 index 0000000..1c11af3 --- /dev/null +++ b/roles/bird_router/templates/router-sysctl.conf.j2 @@ -0,0 +1,3 @@ +{% for key, value in router_sysctl | dictsort %} +{{ key }} = {{ value }} +{% endfor %} diff --git a/roles/bird_router/templates/wg.conf.j2 b/roles/bird_router/templates/wg.conf.j2 new file mode 100644 index 0000000..8861651 --- /dev/null +++ b/roles/bird_router/templates/wg.conf.j2 @@ -0,0 +1,39 @@ +# {{ item.description | default(item.name) }} + +[Interface] +{% if item.private_key is defined %} +PrivateKey = {{ item.private_key }} +{% else %} +PrivateKey = {{ lookup('ansible.builtin.env', item.private_key_env) }} +{% endif %} +Address = {{ item.address | join(',') }} +{% if item.listen_port is defined %} +ListenPort = {{ item.listen_port }} +{% endif %} +{% if item.mtu is defined %} +MTU = {{ item.mtu }} +{% endif %} +{% for command in item.pre_up | default([]) %} +PreUp = {{ command }} +{% endfor %} +{% for command in item.post_up | default([]) %} +PostUp = {{ command }} +{% endfor %} +{% for command in item.pre_down | default([]) %} +PreDown = {{ command }} +{% endfor %} +{% for command in item.post_down | default([]) %} +PostDown = {{ command }} +{% endfor %} +{% for peer in item.peers | default([]) %} + +[Peer] +PublicKey = {{ peer.public_key }} +AllowedIPs = {{ peer.allowed_ips | join(',') }} +{% if peer.endpoint is defined %} +Endpoint = {{ peer.endpoint }} +{% endif %} +{% if peer.persistent_keepalive is defined %} +PersistentKeepalive = {{ peer.persistent_keepalive }} +{% endif %} +{% endfor %} diff --git a/roles/debian_dist_upgrade/defaults/main.yml b/roles/debian_dist_upgrade/defaults/main.yml new file mode 100644 index 0000000..e548679 --- /dev/null +++ b/roles/debian_dist_upgrade/defaults/main.yml @@ -0,0 +1,10 @@ +--- +debian_upgrade_source_major_version: "12" +debian_upgrade_target_major_version: "13" +debian_upgrade_target_release: trixie +debian_upgrade_reboot_timeout: 1800 + +debian_upgrade_sources_list_path: /etc/apt/sources.list +debian_upgrade_sources_backup_path: /etc/apt/sources.list.bookworm.bak +debian_upgrade_deb822_sources_path: /etc/apt/sources.list.d/debian.sources +debian_upgrade_deb822_sources_backup_path: /etc/apt/sources.list.d/debian.sources.bookworm.bak diff --git a/roles/debian_dist_upgrade/handlers/main.yml b/roles/debian_dist_upgrade/handlers/main.yml new file mode 100644 index 0000000..9aa8d8a --- /dev/null +++ b/roles/debian_dist_upgrade/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Reboot after Debian dist upgrade + ansible.builtin.reboot: + msg: Rebooting after Debian dist upgrade + reboot_timeout: "{{ debian_upgrade_reboot_timeout }}" + listen: reboot_after_debian_dist_upgrade diff --git a/roles/debian_dist_upgrade/tasks/main.yml b/roles/debian_dist_upgrade/tasks/main.yml new file mode 100644 index 0000000..ff11403 --- /dev/null +++ b/roles/debian_dist_upgrade/tasks/main.yml @@ -0,0 +1,118 @@ +--- +- name: Validate current host is Debian + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Debian" + fail_msg: "This role only supports Debian hosts." + +- name: Validate expected source Debian major version + ansible.builtin.assert: + that: + - ansible_facts.distribution_major_version == debian_upgrade_source_major_version + fail_msg: >- + Expected Debian {{ debian_upgrade_source_major_version }} before upgrade, + found Debian {{ ansible_facts.distribution_major_version }}. + success_msg: "Host is Debian {{ debian_upgrade_source_major_version }} and ready for upgrade." + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Update package cache on current release + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Upgrade installed packages before release change + ansible.builtin.apt: + upgrade: dist + autoremove: true + autoclean: true + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Check for classic apt sources list + ansible.builtin.stat: + path: "{{ debian_upgrade_sources_list_path }}" + register: debian_upgrade_sources_list_stat + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Check for deb822 apt sources + ansible.builtin.stat: + path: "{{ debian_upgrade_deb822_sources_path }}" + register: debian_upgrade_deb822_sources_stat + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Back up current apt sources + ansible.builtin.copy: + src: "{{ debian_upgrade_sources_list_path }}" + dest: "{{ debian_upgrade_sources_backup_path }}" + remote_src: true + mode: "0644" + force: false + when: + - ansible_facts.distribution_major_version != debian_upgrade_target_major_version + - debian_upgrade_sources_list_stat.stat.exists + +- name: Back up current deb822 apt sources + ansible.builtin.copy: + src: "{{ debian_upgrade_deb822_sources_path }}" + dest: "{{ debian_upgrade_deb822_sources_backup_path }}" + remote_src: true + mode: "0644" + force: false + when: + - ansible_facts.distribution_major_version != debian_upgrade_target_major_version + - debian_upgrade_deb822_sources_stat.stat.exists + +- name: Configure Debian target release classic apt sources + ansible.builtin.template: + src: sources.list.j2 + dest: "{{ debian_upgrade_sources_list_path }}" + owner: root + group: root + mode: "0644" + when: + - ansible_facts.distribution_major_version != debian_upgrade_target_major_version + - debian_upgrade_sources_list_stat.stat.exists or not debian_upgrade_deb822_sources_stat.stat.exists + +- name: Configure Debian target release deb822 apt sources + ansible.builtin.template: + src: debian.sources.j2 + dest: "{{ debian_upgrade_deb822_sources_path }}" + owner: root + group: root + mode: "0644" + when: + - ansible_facts.distribution_major_version != debian_upgrade_target_major_version + - debian_upgrade_deb822_sources_stat.stat.exists + +- name: Update package cache for target release + ansible.builtin.apt: + update_cache: true + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Perform Debian dist upgrade + ansible.builtin.apt: + upgrade: dist + autoremove: true + autoclean: true + dpkg_options: force-confdef,force-confold + environment: + DEBIAN_FRONTEND: noninteractive + APT_LISTCHANGES_FRONTEND: none + notify: reboot_after_debian_dist_upgrade + when: ansible_facts.distribution_major_version != debian_upgrade_target_major_version + +- name: Flush reboot handler before verification + ansible.builtin.meta: flush_handlers + +- name: Gather facts after possible upgrade + ansible.builtin.setup: + +- name: Verify Debian target version + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Debian" + - ansible_facts.distribution_major_version == debian_upgrade_target_major_version + fail_msg: >- + Debian upgrade did not finish as expected. Found + {{ ansible_facts.distribution }} {{ ansible_facts.distribution_major_version }}. + success_msg: "Host is running Debian {{ debian_upgrade_target_major_version }}." diff --git a/roles/debian_dist_upgrade/templates/debian.sources.j2 b/roles/debian_dist_upgrade/templates/debian.sources.j2 new file mode 100644 index 0000000..9ab2989 --- /dev/null +++ b/roles/debian_dist_upgrade/templates/debian.sources.j2 @@ -0,0 +1,11 @@ +Types: deb +URIs: http://deb.debian.org/debian +Suites: {{ debian_upgrade_target_release }} {{ debian_upgrade_target_release }}-updates +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb +URIs: http://security.debian.org/debian-security +Suites: {{ debian_upgrade_target_release }}-security +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg diff --git a/roles/debian_dist_upgrade/templates/sources.list.j2 b/roles/debian_dist_upgrade/templates/sources.list.j2 new file mode 100644 index 0000000..5581b45 --- /dev/null +++ b/roles/debian_dist_upgrade/templates/sources.list.j2 @@ -0,0 +1,3 @@ +deb http://deb.debian.org/debian {{ debian_upgrade_target_release }} main contrib non-free non-free-firmware +deb http://deb.debian.org/debian {{ debian_upgrade_target_release }}-updates main contrib non-free non-free-firmware +deb http://security.debian.org/debian-security {{ debian_upgrade_target_release }}-security main contrib non-free non-free-firmware diff --git a/site.yml b/site.yml new file mode 100644 index 0000000..b72223b --- /dev/null +++ b/site.yml @@ -0,0 +1,15 @@ +--- +- name: Prepare Debian router base system + hosts: routers + become: true + gather_facts: true + + roles: + - role: debian_dist_upgrade + tags: + - debian_upgrade + + - role: bird_router + tags: + - bird + - router