Compare commits

...

10 commits

Author SHA1 Message Date
da2445f2c7
Fix forgejo runner setup, prevent data wipe on re-run, add SSH key to Forgejo
- forgejo role: tasks no longer wipe data on subsequent runs (check for existing
  data before resetting)
- runner: use data.forgejo.org/forgejo/runner:12, register with --no-interactive,
  run as root for Docker socket access
- runner-config: proper generate-config format
- graham user: password hash in managed_users config
- SSH key added to Forgejo graham user
- direnv in base Debian packages
2026-07-24 13:52:10 -05:00
1e1e423772
Set graham user password hash in managed_users config 2026-07-24 13:44:19 -05:00
437031aaa3
Add direnv to base Debian packages 2026-07-24 13:32:58 -05:00
292758900e
Add hi (74.50.113.232) with Forgejo + Caddy, bootstrapped support.vntx.net
- DNS: hi.mcintire.me A/AAAA, git.mcintire.me A/AAAA updated to new host
- forgejo role: Docker Compose deployment with SQLite, INSTALL_LOCK, admin user creation
- Caddyfile-hi.j2: reverse proxy git.mcintire.me -> localhost:3000
- tailscale role: skip connect when no auth key; dynamic repo suite
- debian role: fix Signed-By for debian.sources on trixie
- curl added to base Debian packages
- support.vntx.net: added to vntx_servers, DNS, host_vars
2026-07-24 13:12:33 -05:00
8018ce0eb1
fix(resolvers): put DNSStubListener=no in resolved.conf.d, not unit dropins
Butane unit dropins write to /etc/systemd/system/<unit>.d/, but
DNSStubListener is a systemd-resolved config option that belongs in
/etc/systemd/resolved.conf.d/. Use storage.files instead so Ignition
places it in the correct location.
2026-07-24 11:21:10 -05:00
28e119cee2
fix(resolvers): disable systemd-resolved stub listener instead of masking
systemd-resolved must run for host DNS (nss/resolv.conf), but its stub
listener on 127.0.0.53:53 conflicts with Unbound's bind to 0.0.0.0:53.

Replace masking with DNSStubListener=no dropin + static resolv.conf
pointing to 1.1.1.1 and 9.9.9.9, so the host can resolve without
occupying port 53.
2026-07-24 11:17:33 -05:00
3b9507e5c4
fix(resolvers): mask systemd-resolved to prevent port 53 conflict with unbound 2026-07-24 11:13:29 -05:00
7ff93a5ac3
update opensearch 2026-07-24 09:18:08 -05:00
Graham McInitre
d644868602 Migrate mcintire.me DNS from self-hosted BIND9 to Cloudflare
- Add mcintire.me zone + all 14 DNS records to tofu/cloudflare.tf (A, MX, CNAME, SRV, TXT)
- Include zone settings, HSTS, bot management, firewall for mcintire.me
- Update Porkbun NS from as393837_ns to cloudflare_ns in tofu/dns.tf
- Remove mcintire.me zone from BIND9 master config (group_vars/bind9_servers/)
- Delete mcintire.me.yml zone file
- Remove mcintire.me secondary zone from git.mcintire.me host_vars
2026-07-24 09:14:36 -05:00
Graham McInitre
a6b99691fb fix(prop): add PrunePropagationPolicy=foreground to ArgoCD sync options 2026-07-21 08:45:15 -05:00
39 changed files with 1201 additions and 55 deletions

View file

@ -29,6 +29,7 @@ managed_users:
shell: /bin/bash
home: /home/graham
admin: true
password: "$6$VwHy94JE9eBN3Tex$nCmD8F7O0cq9hKW8esrwbTVh19QEq7bRlPga9u4ZtcnEPy1g6CKgOxVH9JEGWhuIqIHypEgaLwpicTF.h5wkd1"
authorized_keys:
- type: github
value: gmcintire

View file

@ -25,7 +25,6 @@ bind_allow_transfer:
bind_zones:
- "{{ beanjar_org_zone }}"
- "{{ gridmap_org_zone }}"
- "{{ mcintire_me_zone }}"
- "{{ manero_org_zone }}"
- "{{ ntxarms_com_zone }}"
- "{{ towerops_net_zone }}"

View file

@ -1,42 +0,0 @@
---
# mcintire.me DNS zone configuration
mcintire_me_zone:
name: mcintire.me
type: master
create_reverse_zones: false
name_servers:
- ns1.as393837.net.
- ns2.as393837.net.
- ns-global.kjsl.com.
mail_servers:
- name: mail
preference: 10
- name: mail.nsnw.ca.
preference: 20
hosts:
- name: mail
ip: 107.174.178.20
- name: photos
ip: 204.110.191.8
- name: git
ip: 172.245.56.83
- name: vm1
ip: 172.245.56.83
other_name_servers:
- ha.mcintire.me. IN CNAME q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa.
- _acme-challenge.ha.mcintire.me. IN CNAME _acme-challenge.q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa.
- autodiscover.mcintire.me. IN CNAME mail.w5isp.com.
- autoconfig.mcintire.me. IN CNAME mail.w5isp.com.
- em977211.mcintire.me. IN CNAME return.smtp2go.net.
- s977211._domainkey.mcintire.me. IN CNAME dkim.smtp2go.net.
- link.mcintire.me. IN CNAME track.smtp2go.net.
- _autodiscover._tcp.mcintire.me. IN SRV 0 0 443 mail.w5isp.com.
text:
- name: "@"
text: '"v=spf1 mx a ip4:107.174.178.20 ~all"'
- name: _dmarc
text: '"v=DMARC1; p=quarantine; rua=mailto:postmaster@mcintire.me; ruf=mailto:postmaster@mcintire.me; fo=1"'
- name: dkim._domainkey
text: '"v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0rQie7Hz5AmDbVUX+rKNgp6Hf7crtWyfy6qKbqnrxmmernz1rQ6HyOrHhFAlc8nVLNKr8XP2DOROb1jAnrjndZo9I/ymbrrrCBsi9w6zAht2BheijO/R9k5CDRjeSafm6bg0oMKihtNMIdXvEj9ND8hDpZZKxtOrKFH7zLm4CPmm7jf0gqZH2yK+AA3OYUGSYa1OT0LtdMXadk0IVOI2VYf37Hzb3RUvGOYMVkT0xi+23DiyFk7AyCiyv3LQVBMT+/bwyd4k3yVVn2cr9+Nor+HXVjqASLtqcFLMf1SuX0ezSAjBG7BbqQr5G6Jz+n5YglqEIUKVYzJFE7JdDmqQYQIDAQAB"'
- name: "@"
text: '"yahoo-verification-key=Pt59XUhQSd7dbVc0x6TQD+mBEtOOTMHFRV1ZG78h4Ug="'

View file

@ -72,6 +72,8 @@ vntx_net_zone:
ip: 204.110.191.221
- name: net
ip: 104.238.146.79
- name: support
ip: 204.110.191.231
- name: uisp
ip: 204.110.191.224
- name: dns

View file

@ -31,9 +31,6 @@ bind_zones:
- name: aprs.me
type: secondary
primaries: [204.110.191.222]
- name: mcintire.me
type: secondary
primaries: [204.110.191.222]
- name: manero.org
type: secondary
primaries: [204.110.191.222]

17
ansible/host_vars/hi.yml Normal file
View file

@ -0,0 +1,17 @@
---
ansible_python_interpreter: /usr/bin/python3
tailscale_skip_connect: true
firewall_allow_rules:
- port: 22
proto: tcp
comment: SSH
- port: 80
proto: tcp
comment: HTTP
- port: 443
proto: tcp
comment: HTTPS
- port: 2222
proto: tcp
comment: Forgejo SSH

View file

@ -7,6 +7,10 @@ ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
opensearch_heap_min: "4g"
opensearch_heap_max: "4g"
# OpenSearch security — admin credentials
opensearch_admin_password_hash: "$2b$12$gp3Lk2cCejFzjfacq1Ax0OHapD5XOUNqMN2TtzmA7EMpcZ6wcB71a"
opensearch_admin_password: "1QW8gf8DNfqfkC1saSiw"
# Firewall — Caddy needs HTTP/HTTPS open for Let's Encrypt + traffic
firewall_allow_rules:
- port: 80
@ -15,3 +19,30 @@ firewall_allow_rules:
- port: 443
proto: tcp
comment: HTTPS
- port: 514
proto: udp
from_ip: 204.110.188.0/22
comment: Syslog UDP (VNTX)
- port: 514
proto: udp
from_ip: 10.0.0.0/8
comment: Syslog UDP (home LAN)
- port: 514
proto: udp
from_ip: 100.64.0.0/10
comment: Syslog UDP (Tailscale)
- port: 514
proto: tcp
from_ip: 204.110.188.0/22
comment: Syslog TCP (VNTX)
- port: 514
proto: tcp
from_ip: 10.0.0.0/8
comment: Syslog TCP (home LAN)
- port: 514
proto: tcp
from_ip: 100.64.0.0/10
comment: Syslog TCP (Tailscale)
# Fluent Bit — uses same admin credentials as Dashboards
fluentbit_opensearch_password: "{{ opensearch_admin_password }}"

View file

@ -0,0 +1,16 @@
---
ansible_host: 204.110.191.231
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
tailscale_skip_connect: true
firewall_allow_rules:
- port: 22
proto: tcp
comment: SSH
- port: 80
proto: tcp
comment: HTTP
- port: 443
proto: tcp
comment: HTTPS

View file

@ -8,6 +8,7 @@ unimus.vntx.net
monitor.vntx.net
librenms.vntx.net
uisp.vntx.net
support.vntx.net ansible_host=204.110.191.231
[librenms_servers]
librenms.vntx.net
@ -18,6 +19,7 @@ monitor.vntx.net
[home_servers]
skippy.w5isp.com
mail.mcintire.me ansible_host=107.174.178.20
hi ansible_host=74.50.113.232
dokku.w5isp.com
aprs.w5isp.com
prom.w5isp.com ansible_host=10.0.19.31
@ -28,8 +30,15 @@ prom.w5isp.com
[node_exporter_servers:children]
prometheus_servers
[tailscale_servers]
hi
support.vntx.net
[dokku_servers]
[forgejo_servers]
hi
[aprsc_servers]
aprs.w5isp.com
@ -38,6 +47,7 @@ skippy.w5isp.com
netbox.vntx.net
uisp.vntx.net
logs.vntx.net
hi
[netbox_servers]
netbox.vntx.net

View file

@ -15,6 +15,15 @@
community.general.timezone:
name: "{{ timezone }}"
- name: Install and configure Tailscale
hosts: tailscale_servers
become: true
gather_facts: true
tags:
- tailscale
roles:
- tailscale
- name: Apply firewall configuration
hosts: all:!proxmox_servers:!irc_limited
become: true
@ -355,6 +364,15 @@
roles:
- dokku
- name: Install and configure Forgejo
hosts: forgejo_servers
become: true
gather_facts: true
tags:
- forgejo
roles:
- forgejo
- name: Configure node_exporter targets
hosts: node_exporter_servers
become: true
@ -410,3 +428,13 @@
- logs
roles:
- opensearch
- name: Install and configure Fluent Bit syslog collector
hosts: opensearch_servers
become: true
gather_facts: true
tags:
- fluentbit
- logs
roles:
- fluentbit

View file

@ -45,6 +45,8 @@ base_common_packages:
base_packages_by_os_family:
Debian:
- sudo
- curl
- direnv
- jq
- sysstat
- smartmontools

View file

@ -0,0 +1,17 @@
{
servers {
protocols h1 h2
}
admin off
persist_config off
}
git.mcintire.me {
reverse_proxy http://127.0.0.1:3000
encode gzip
log {
output file /var/log/caddy/git.mcintire.me.log
format json
}
}

View file

@ -1,4 +1,19 @@
{{ opensearch_caddy_domain | default(inventory_hostname) }} {
@os_denied {
path /os/*
not remote_ip 10.0.0.0/8 100.64.0.0/10 204.110.188.0/22
}
respond @os_denied 403
# OpenSearch API — restricted to internal subnets
handle_path /os/* {
reverse_proxy https://127.0.0.1:9200 {
transport http {
tls_insecure_skip_verify
}
}
}
reverse_proxy 127.0.0.1:{{ opensearch_dashboards_port | default('5601') }} {
header_up X-Forwarded-Host {host}
header_up X-Real-IP {remote_host}

View file

@ -28,6 +28,13 @@
path: /etc/apt/sources.list.d/ppa-linuxfactory-or-kr.list
state: absent
- name: "Add Signed-By to debian.sources mirror entries"
ansible.builtin.replace:
path: /etc/apt/sources.list.d/debian.sources
regexp: '(URIs: mirror\+file://[^\n]+)\n(Suites:[^\n]+)\n(Components:[^\n]+)'
replace: '\1\n\2\n\3\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg'
when: ansible_facts['distribution_release'] == 'trixie'
- name: "Install updates"
ansible.builtin.apt:
name: "*"

View file

@ -0,0 +1,30 @@
---
# Fluent Bit — lightweight syslog collector for logs.vntx.net
# Installed via official apt repo, configured for RFC 3164 syslog (MikroTik).
# --- Package ---
fluentbit_package: fluent-bit
# --- Repo ---
fluentbit_apt_key_url: "https://packages.fluentbit.io/fluentbit.key"
fluentbit_apt_keyring: /etc/apt/keyrings/fluentbit.gpg
# --- User ---
fluentbit_user: fluentbit
fluentbit_group: fluentbit
# --- Paths ---
fluentbit_config_dir: /etc/fluent-bit
fluentbit_log_dir: /var/log/fluent-bit
# --- Syslog Input ---
fluentbit_syslog_port: 514
fluentbit_syslog_bind: "0.0.0.0"
# --- OpenSearch Output ---
fluentbit_opensearch_host: "127.0.0.1"
fluentbit_opensearch_port: 9200
fluentbit_opensearch_index: syslog
fluentbit_opensearch_user: "{{ opensearch_admin_username | default('admin') }}"
# Plaintext password for Fluent Bit → OpenSearch — override in host_vars or vault.
fluentbit_opensearch_password: ""

View file

@ -0,0 +1,6 @@
---
- name: Restart fluent-bit
ansible.builtin.systemd:
name: fluent-bit
state: restarted
daemon_reload: true

View file

@ -0,0 +1,139 @@
---
# Installs and configures Fluent Bit to receive syslog (RFC 3164) on UDP/TCP
# port 514 and forward to the local OpenSearch instance.
- name: Ensure /etc/apt/keyrings exists
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
- name: Download Fluent Bit apt signing key
ansible.builtin.get_url:
url: "{{ fluentbit_apt_key_url }}"
dest: /tmp/fluentbit.key
mode: "0644"
- name: Dearmor and install Fluent Bit apt key
ansible.builtin.shell:
cmd: gpg --dearmor < /tmp/fluentbit.key > {{ fluentbit_apt_keyring }}
creates: "{{ fluentbit_apt_keyring }}"
- name: Add Fluent Bit apt repository
ansible.builtin.deb822_repository:
name: fluentbit
types: [deb]
uris: https://packages.fluentbit.io/ubuntu/noble
suites: [noble]
components: [main]
signed_by: "{{ fluentbit_apt_keyring }}"
state: present
- name: Install fluent-bit
ansible.builtin.apt:
name: "{{ fluentbit_package }}"
state: present
update_cache: true
- name: Stop and disable packaged fluent-bit service
ansible.builtin.systemd:
name: fluent-bit
state: stopped
enabled: false
failed_when: false
- name: Create fluentbit group
ansible.builtin.group:
name: "{{ fluentbit_group }}"
system: true
state: present
- name: Create fluentbit user
ansible.builtin.user:
name: "{{ fluentbit_user }}"
group: "{{ fluentbit_group }}"
system: true
shell: /usr/sbin/nologin
home: "{{ fluentbit_log_dir }}"
create_home: false
state: present
- name: Create fluentbit directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ fluentbit_user }}"
group: "{{ fluentbit_group }}"
mode: "0750"
loop:
- "{{ fluentbit_config_dir }}"
- "{{ fluentbit_log_dir }}"
- name: Render parsers.conf
ansible.builtin.template:
src: parsers.conf.j2
dest: "{{ fluentbit_config_dir }}/parsers.conf"
owner: root
group: "{{ fluentbit_group }}"
mode: "0640"
notify: Restart fluent-bit
- name: Render fluent-bit.conf
ansible.builtin.template:
src: fluent-bit.conf.j2
dest: "{{ fluentbit_config_dir }}/fluent-bit.conf"
owner: root
group: "{{ fluentbit_group }}"
mode: "0640"
notify: Restart fluent-bit
- name: Install fluent-bit systemd unit
ansible.builtin.template:
src: fluent-bit.service.j2
dest: /etc/systemd/system/fluent-bit.service
owner: root
group: root
mode: "0644"
notify: Restart fluent-bit
- name: Enable and start fluent-bit
ansible.builtin.systemd:
name: fluent-bit
enabled: true
state: started
daemon_reload: true
- name: Create syslog-* index pattern in Dashboards
ansible.builtin.uri:
url: "http://127.0.0.1:{{ opensearch_dashboards_port | default('5601') }}/api/saved_objects/index-pattern/syslog-*"
method: POST
user: "{{ fluentbit_opensearch_user }}"
password: "{{ fluentbit_opensearch_password }}"
force_basic_auth: true
headers:
osd-xsrf: "true"
Content-Type: "application/json"
body_format: json
body:
attributes:
title: "syslog-*"
timeFieldName: "@timestamp"
status_code: [200, 201, 409]
- name: Set syslog-* as default index in Dashboards
ansible.builtin.uri:
url: "http://127.0.0.1:{{ opensearch_dashboards_port | default('5601') }}/api/saved_objects/config/{{ opensearch_dashboards_version | default('2.19.6') }}"
method: PUT
user: "{{ fluentbit_opensearch_user }}"
password: "{{ fluentbit_opensearch_password }}"
force_basic_auth: true
headers:
osd-xsrf: "true"
Content-Type: "application/json"
body_format: json
body:
attributes:
defaultIndex: "syslog-*"
status_code: [200, 409]

View file

@ -0,0 +1,40 @@
# {{ ansible_managed | default('Managed by Ansible') }}
# Fluent Bit configuration for logs.vntx.net
# Receives RFC 3164 syslog from MikroTik, Ubiquiti, Cambium, etc.
[SERVICE]
Flush 5
Daemon off
Log_Level info
parsers_file {{ fluentbit_config_dir }}/parsers.conf
[INPUT]
Name syslog
Listen {{ fluentbit_syslog_bind }}
Port {{ fluentbit_syslog_port }}
Mode udp
parser syslog-loose
raw_message_key raw
[INPUT]
Name syslog
Listen {{ fluentbit_syslog_bind }}
Port {{ fluentbit_syslog_port }}
Mode tcp
parser syslog-loose
raw_message_key raw
[OUTPUT]
Name opensearch
Match *
Host {{ fluentbit_opensearch_host }}
Port {{ fluentbit_opensearch_port }}
Index {{ fluentbit_opensearch_index }}
HTTP_User ${FLUENTBIT_OPENSEARCH_USER}
HTTP_Passwd ${FLUENTBIT_OPENSEARCH_PASSWORD}
tls On
tls.verify Off
Suppress_Type_Name On
Logstash_Format On
Logstash_Prefix {{ fluentbit_opensearch_index }}
Retry_Limit false

View file

@ -0,0 +1,41 @@
# {{ ansible_managed | default('Managed by Ansible') }}
# Fluent Bit systemd unit — overrides the packaged unit to add
# AmbientCapabilities for privileged-port binding and credential env vars.
[Unit]
Description=Fluent Bit syslog collector
Documentation=https://docs.fluentbit.io/
After=network-online.target opensearch.service
Wants=network-online.target
Requires=opensearch.service
[Service]
Type=simple
User={{ fluentbit_user }}
Group={{ fluentbit_group }}
Environment=FLUENTBIT_OPENSEARCH_USER={{ fluentbit_opensearch_user }}
Environment=FLUENTBIT_OPENSEARCH_PASSWORD={{ fluentbit_opensearch_password }}
ExecStart=/opt/fluent-bit/bin/fluent-bit \
--config={{ fluentbit_config_dir }}/fluent-bit.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
# Bind to privileged port 514 without running as root
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=full
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
PrivateTmp=true
ReadWritePaths={{ fluentbit_log_dir }}
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,11 @@
# {{ ansible_managed | default('Managed by Ansible') }}
# Syslog parser for mixed network gear (MikroTik, Ubiquiti, Cambium, etc.)
# Handles both standard RFC 3164 and bare/partial formats.
[PARSER]
Name syslog-loose
Format regex
Regex ^(\<(?<pri>[0-9]+)\>)?((?<time>[A-Z][a-z]{2}\s+\d+\s+\d{2}:\d{2}:\d{2})\s+(?<host>\S+)\s+)?(?<ident>\S+)\s+(?<message>.*)$
Time_Key time
Time_Format %b %d %H:%M:%S
Time_Keep On

View file

@ -0,0 +1,10 @@
---
forgejo_version: "10"
forgejo_runner_version: "12"
forgejo_http_port: 3000
forgejo_ssh_port: 2222
forgejo_hostname: git.mcintire.me
forgejo_data_dir: /opt/forgejo
forgejo_admin_user: graham
forgejo_admin_email: graham@mcintire.me
forgejo_admin_password: "{{ lookup('env', 'FORGEJO_ADMIN_PASSWORD') | default('changeme123', true) }}"

View file

@ -0,0 +1,5 @@
---
- name: restart forgejo
ansible.builtin.command: docker compose restart
args:
chdir: "{{ forgejo_data_dir }}"

View file

@ -0,0 +1,178 @@
---
- name: Install prerequisites
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
- apt-transport-https
state: present
update_cache: true
- name: Check if Docker is installed
ansible.builtin.command: which docker
register: docker_installed
changed_when: false
failed_when: false
- name: Add Docker GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/debian/gpg
dest: /usr/share/keyrings/docker-archive-keyring.asc
mode: '0644'
when: docker_installed.rc != 0
- name: Add Docker repository
ansible.builtin.deb822_repository:
name: docker
types: deb
uris: https://download.docker.com/linux/debian
suites: "{{ ansible_facts['distribution_release'] }}"
components: stable
architectures: "{{ ansible_facts['architecture'] | replace('x86_64', 'amd64') }}"
signed_by: /usr/share/keyrings/docker-archive-keyring.asc
state: present
when: docker_installed.rc != 0
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
when: docker_installed.rc != 0
- name: Ensure Docker service is started and enabled
ansible.builtin.systemd:
name: docker
state: started
enabled: true
- name: Add ansible user to docker group
ansible.builtin.user:
name: ansible
groups: docker
append: true
- name: Create Forgejo data directory
ansible.builtin.file:
path: "{{ forgejo_data_dir }}"
state: directory
owner: root
group: root
mode: '0755'
- name: Deploy docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ forgejo_data_dir }}/docker-compose.yml"
owner: root
group: root
mode: '0644'
- name: Check if Forgejo is running
ansible.builtin.command:
cmd: docker compose ps --services --filter "status=running"
chdir: "{{ forgejo_data_dir }}"
register: forgejo_running
changed_when: false
failed_when: false
- name: Check if Forgejo data exists
ansible.builtin.stat:
path: "{{ forgejo_data_dir }}/data/gitea/conf/app.ini"
register: forgejo_data_exists
- name: Start Forgejo if not running
ansible.builtin.command:
cmd: docker compose up -d forgejo
chdir: "{{ forgejo_data_dir }}"
when: "'forgejo' not in forgejo_running.stdout_lines | default([])"
- name: Wait for Forgejo web UI
ansible.builtin.uri:
url: http://127.0.0.1:3000/
method: GET
status_code: 200
register: web_ready
retries: 30
delay: 2
until: web_ready.status == 200
changed_when: false
- name: Check if admin user exists
ansible.builtin.uri:
url: http://127.0.0.1:3000/api/v1/users/{{ forgejo_admin_user }}
method: GET
status_code: [200, 404]
register: admin_check
changed_when: false
- name: Create admin user
ansible.builtin.command:
cmd: >
docker exec -u git -w /data/gitea forgejo forgejo admin user create
--config /data/gitea/conf/app.ini
--username {{ forgejo_admin_user }}
--password {{ forgejo_admin_password }}
--email {{ forgejo_admin_email }}
--admin
when:
- forgejo_data_exists.stat.exists
- admin_check.status == 404
no_log: true
- name: Create runner data directory
ansible.builtin.file:
path: "{{ forgejo_data_dir }}/runner"
state: directory
owner: "1000"
group: "1000"
mode: '0755'
- name: Deploy runner config
ansible.builtin.template:
src: runner-config.yaml.j2
dest: "{{ forgejo_data_dir }}/runner/config.yaml"
owner: root
group: root
mode: '0644'
- name: Check if runner is registered
ansible.builtin.stat:
path: "{{ forgejo_data_dir }}/runner/.runner"
register: runner_registered
- name: Generate runner token
ansible.builtin.command:
cmd: >
docker exec -u git -w /data/gitea forgejo forgejo actions generate-runner-token --config /data/gitea/conf/app.ini
register: runner_token
changed_when: false
when: not runner_registered.stat.exists
- name: Register runner
ansible.builtin.command:
cmd: >
docker run --rm
-v {{ forgejo_data_dir }}/runner:/data
-v /var/run/docker.sock:/var/run/docker.sock
--network forgejo_forgejo
data.forgejo.org/forgejo/runner:{{ forgejo_runner_version }}
forgejo-runner register
--no-interactive
--instance http://forgejo:3000
--token {{ runner_token.stdout }}
--name "forgejo-runner-{{ ansible_hostname }}"
--labels "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm,ubuntu-24.04:docker://node:20-bookworm"
when: not runner_registered.stat.exists
- name: Start runner
ansible.builtin.command:
cmd: docker compose up -d runner
chdir: "{{ forgejo_data_dir }}"
register: runner_started
changed_when: "'Starting' in runner_started.stdout or 'Creating' in runner_started.stdout"

View file

@ -0,0 +1,50 @@
services:
forgejo:
image: codeberg.org/forgejo/forgejo:{{ forgejo_version }}
container_name: forgejo
restart: unless-stopped
networks:
- forgejo
volumes:
- {{ forgejo_data_dir }}/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:{{ forgejo_http_port }}:3000"
- "0.0.0.0:{{ forgejo_ssh_port }}:22"
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO__server__ROOT_URL=https://{{ forgejo_hostname }}
- FORGEJO__server__SSH_DOMAIN={{ forgejo_hostname }}
- FORGEJO__server__HTTP_PORT={{ forgejo_http_port }}
- FORGEJO__server__SSH_PORT={{ forgejo_ssh_port }}
- FORGEJO__server__DOMAIN={{ forgejo_hostname }}
- FORGEJO__database__DB_TYPE=sqlite3
- FORGEJO__database__PATH=/data/gitea/gitea.db
- FORGEJO__security__INSTALL_LOCK=true
- FORGEJO__service__DISABLE_REGISTRATION=true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 10s
timeout: 5s
retries: 10
runner:
image: data.forgejo.org/forgejo/runner:{{ forgejo_runner_version }}
container_name: forgejo-runner
restart: unless-stopped
depends_on:
forgejo:
condition: service_healthy
networks:
- forgejo
user: "0:0"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- {{ forgejo_data_dir }}/runner:/data
command: ["forgejo-runner", "daemon", "--config", "/data/config.yaml"]
working_dir: /data
networks:
forgejo:

View file

@ -0,0 +1,18 @@
log:
level: info
runner:
file: .runner
capacity: 2
labels:
- "ubuntu-latest:docker://node:20-bookworm"
- "ubuntu-22.04:docker://node:20-bookworm"
- "ubuntu-24.04:docker://node:20-bookworm"
cache:
enabled: true
container:
network: ""
privileged: false
docker_host: automount

View file

@ -0,0 +1,10 @@
log:
level: info
runner:
file: /data/runner/.runner
capacity: 2
labels:
- "ubuntu-latest:docker://node:20-bookworm"
- "ubuntu-22.04:docker://node:20-bookworm"
- "ubuntu-24.04:docker://node:20-bookworm"

View file

@ -27,6 +27,17 @@ opensearch_discovery_type: single-node
# Plugins to install (empty by default)
opensearch_plugins: []
# --- Security Plugin ---
opensearch_security_enabled: true
opensearch_admin_username: admin
# bcrypt hash of the admin password — generate with:
# python3 -c "import bcrypt; print(bcrypt.hashpw(b'<password>', bcrypt.gensalt()).decode())"
# Override in host_vars or vault.
opensearch_admin_password_hash: ""
# Plaintext admin password (for Dashboards to authenticate to OpenSearch).
# Override in host_vars or vault.
opensearch_admin_password: ""
# Sysctl: vm.max_map_count required by OpenSearch
opensearch_vm_max_map_count: 262144

View file

@ -81,6 +81,55 @@
mode: "0640"
notify: Restart opensearch
- name: Check if demo TLS certs exist
ansible.builtin.stat:
path: "{{ opensearch_config_dir }}/{{ item }}"
loop:
- esnode.pem
- esnode-key.pem
- root-ca.pem
register: opensearch_demo_certs
when: opensearch_security_enabled | default(true)
- name: Generate self-signed TLS certs for OpenSearch
ansible.builtin.shell:
cmd: |
set -e
openssl req -x509 -newkey rsa:4096 \
-keyout {{ opensearch_config_dir }}/esnode-key.pem \
-out {{ opensearch_config_dir }}/esnode.pem \
-days 3650 -nodes \
-subj "/CN={{ opensearch_node_name }}" \
-addext "subjectAltName=DNS:localhost,DNS:{{ inventory_hostname }},IP:127.0.0.1"
cp {{ opensearch_config_dir }}/esnode.pem {{ opensearch_config_dir }}/root-ca.pem
chown {{ opensearch_user }}:{{ opensearch_group }} {{ opensearch_config_dir }}/esnode.pem {{ opensearch_config_dir }}/esnode-key.pem {{ opensearch_config_dir }}/root-ca.pem
chmod 600 {{ opensearch_config_dir }}/esnode-key.pem
creates: "{{ opensearch_config_dir }}/esnode.pem"
when:
- opensearch_security_enabled | default(true)
- (opensearch_demo_certs.results | selectattr('stat.exists', 'equalto', false) | list | length) > 0
notify: Restart opensearch
- name: Render internal_users.yml
ansible.builtin.template:
src: internal_users.yml.j2
dest: "{{ opensearch_config_dir }}/opensearch-security/internal_users.yml"
owner: "{{ opensearch_user }}"
group: "{{ opensearch_group }}"
mode: "0640"
when: opensearch_security_enabled | default(true)
notify: Restart opensearch
- name: Render roles_mapping.yml
ansible.builtin.template:
src: roles_mapping.yml.j2
dest: "{{ opensearch_config_dir }}/opensearch-security/roles_mapping.yml"
owner: "{{ opensearch_user }}"
group: "{{ opensearch_group }}"
mode: "0640"
when: opensearch_security_enabled | default(true)
notify: Restart opensearch
- name: Render jvm.options
ansible.builtin.template:
src: jvm.options.j2
@ -106,6 +155,25 @@
state: started
daemon_reload: true
- name: Create syslog index template
ansible.builtin.uri:
url: "https://127.0.0.1:{{ opensearch_http_port }}/_index_template/syslog"
method: PUT
user: "{{ opensearch_admin_username }}"
password: "{{ opensearch_admin_password }}"
force_basic_auth: true
validate_certs: false
body_format: json
body:
index_patterns: ["syslog-*"]
template:
settings:
number_of_shards: 1
number_of_replicas: 0
priority: 100
status_code: [200, 201]
when: opensearch_security_enabled | default(true)
# --- OpenSearch Dashboards ---
- name: Create opensearch-dashboards directories

View file

@ -0,0 +1,13 @@
# {{ ansible_managed | default('Managed by Ansible') }}
#
# OpenSearch Security — internal user database.
# Applied on security index initialization (plugins.security.allow_default_init_securityindex: true).
---
_meta:
type: "internalusers"
config_version: 2
{{ opensearch_admin_username }}:
hash: "{{ opensearch_admin_password_hash }}"
reserved: true
description: "Admin user — managed by Ansible"

View file

@ -6,6 +6,12 @@ server.host: 127.0.0.1
server.port: {{ opensearch_dashboards_port }}
opensearch.hosts:
- {{ opensearch_dashboards_opensearch_url }}
- https://127.0.0.1:{{ opensearch_http_port }}
{% if opensearch_security_enabled | default(true) %}
opensearch.username: {{ opensearch_admin_username }}
opensearch.password: {{ opensearch_admin_password }}
opensearch.ssl.verificationMode: none
{% endif %}
opensearch.requestTimeout: 30000

View file

@ -17,8 +17,26 @@ transport.port: {{ opensearch_transport_port }}
# Single-node discovery (no cluster formation needed)
discovery.type: {{ opensearch_discovery_type }}
# Security — disabled for internal-only binding on loopback
# Security — TLS with demo certs, internal user database
{% if opensearch_security_enabled | default(true) %}
plugins.security.disabled: false
{% if opensearch_security_allow_default_init | default(true) %}
plugins.security.allow_default_init_securityindex: true
{% endif %}
plugins.security.authcz.admin_dn:
- "CN={{ opensearch_node_name }}"
- "CN={{ inventory_hostname }}"
plugins.security.ssl.transport.pemcert_filepath: {{ opensearch_config_dir }}/esnode.pem
plugins.security.ssl.transport.pemkey_filepath: {{ opensearch_config_dir }}/esnode-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: {{ opensearch_config_dir }}/root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: {{ opensearch_config_dir }}/esnode.pem
plugins.security.ssl.http.pemkey_filepath: {{ opensearch_config_dir }}/esnode-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: {{ opensearch_config_dir }}/root-ca.pem
{% else %}
plugins.security.disabled: true
{% endif %}
# Performance
indices.memory.index_buffer_size: 20%

View file

@ -0,0 +1,48 @@
# {{ ansible_managed | default('Managed by Ansible') }}
#
# OpenSearch Security — role mappings.
# Maps users and backend roles to OpenSearch roles defined in roles.yml.
---
_meta:
type: "rolesmapping"
config_version: 2
all_access:
reserved: false
users:
- "{{ opensearch_admin_username }}"
backend_roles:
- "admin"
description: "Maps admin to all_access"
own_index:
reserved: false
users:
- "*"
description: "Allow full access to an index named like the username"
logstash:
reserved: false
backend_roles:
- "logstash"
kibana_user:
reserved: false
backend_roles:
- "kibanauser"
description: "Maps kibanauser to kibana_user"
readall:
reserved: false
backend_roles:
- "readall"
manage_snapshots:
reserved: false
backend_roles:
- "snapshotrestore"
kibana_server:
reserved: true
users:
- "kibanaserver"

View file

@ -11,7 +11,7 @@
name: tailscale
types: deb
uris: https://pkgs.tailscale.com/stable/debian
suites: bookworm
suites: "{{ ansible_facts['distribution_release'] }}"
components: main
signed_by: /usr/share/keyrings/tailscale-archive-keyring.gpg
state: present
@ -31,7 +31,10 @@
- name: Connect to Tailscale
ansible.builtin.command: "tailscale up --authkey={{ tailscale_authkey }} {{ tailscale_args | default('') }}"
when: tailscale_status.rc != 0 or 'BackendState":"Running"' not in tailscale_status.stdout
when:
- not (tailscale_skip_connect | default(false))
- tailscale_authkey | length > 0
- tailscale_status.rc != 0 or 'BackendState":"Running"' not in tailscale_status.stdout
register: tailscale_up
changed_when: tailscale_up.rc == 0
@ -39,11 +42,14 @@
ansible.builtin.command: tailscale ip -4
register: tailscale_ip_result
changed_when: false
failed_when: false
- name: Set Tailscale IP fact
ansible.builtin.set_fact:
tailscale_ip: "{{ tailscale_ip_result.stdout | trim }}"
when: tailscale_ip_result.rc == 0
- name: Display Tailscale IP
ansible.builtin.debug:
msg: "Tailscale IP: {{ tailscale_ip }}"
when: tailscale_ip_result.rc == 0

View file

@ -27,3 +27,4 @@ spec:
selfHeal: true
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground

View file

@ -1174,3 +1174,314 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "towerops_dev" {
]
}
}
# =============================================================================
# mcintire.me Zone
#
# Migrated from self-hosted BIND9 (ns1.as393837.net) to Cloudflare.
#
# IMPORTANT: The zone is created fresh (no import block). After the first
# `tofu apply`, run `tofu state show cloudflare_zone.mcintire_me` to get the
# assigned name_servers, then update tofu/dns.tf with those values before
# running `tofu apply` a second time to update Porkbun's NS records.
# =============================================================================
resource "cloudflare_zone" "mcintire_me" {
account = {
id = local.cloudflare_account_id
}
name = "mcintire.me"
}
# =============================================================================
# mcintire.me - A records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_a_mail" {
zone_id = cloudflare_zone.mcintire_me.id
type = "A"
name = "mail"
content = "107.174.178.20"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_a_photos" {
zone_id = cloudflare_zone.mcintire_me.id
type = "A"
name = "photos"
content = "204.110.191.8"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_a_git" {
zone_id = cloudflare_zone.mcintire_me.id
type = "A"
name = "git"
content = "74.50.113.232"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_a_hi" {
zone_id = cloudflare_zone.mcintire_me.id
type = "A"
name = "hi"
content = "74.50.113.232"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_a_vm1" {
zone_id = cloudflare_zone.mcintire_me.id
type = "A"
name = "vm1"
content = "172.245.56.83"
proxied = false
ttl = 86400
}
# =============================================================================
# mcintire.me - AAAA records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_aaaa_git" {
zone_id = cloudflare_zone.mcintire_me.id
type = "AAAA"
name = "git"
content = "2604:4500:0009:003d:1c00:bfff:fe00:0464"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_aaaa_hi" {
zone_id = cloudflare_zone.mcintire_me.id
type = "AAAA"
name = "hi"
content = "2604:4500:0009:003d:1c00:bfff:fe00:0464"
proxied = false
ttl = 86400
}
# =============================================================================
# mcintire.me - MX records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_mx_root_primary" {
zone_id = cloudflare_zone.mcintire_me.id
type = "MX"
name = "mcintire.me"
content = "mail.mcintire.me"
priority = 10
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_mx_root_backup" {
zone_id = cloudflare_zone.mcintire_me.id
type = "MX"
name = "mcintire.me"
content = "mail.nsnw.ca"
priority = 20
ttl = 86400
}
# =============================================================================
# mcintire.me - CNAME records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_cname_ha" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "ha"
content = "q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_acme_ha" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "_acme-challenge.ha"
content = "_acme-challenge.q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_autodiscover" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "autodiscover"
content = "mail.w5isp.com"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_autoconfig" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "autoconfig"
content = "mail.w5isp.com"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_em977211" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "em977211"
content = "return.smtp2go.net"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_dkim_s977211" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "s977211._domainkey"
content = "dkim.smtp2go.net"
proxied = false
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_cname_link" {
zone_id = cloudflare_zone.mcintire_me.id
type = "CNAME"
name = "link"
content = "track.smtp2go.net"
proxied = false
ttl = 86400
}
# =============================================================================
# mcintire.me - SRV records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_srv_autodiscover" {
zone_id = cloudflare_zone.mcintire_me.id
type = "SRV"
name = "_autodiscover._tcp"
ttl = 86400
data = {
priority = 0
weight = 0
port = 443
target = "mail.w5isp.com"
}
lifecycle {
ignore_changes = [priority]
}
}
# =============================================================================
# mcintire.me - TXT records
# =============================================================================
resource "cloudflare_dns_record" "mcintire_me_txt_spf" {
zone_id = cloudflare_zone.mcintire_me.id
type = "TXT"
name = "mcintire.me"
content = "v=spf1 mx a ip4:107.174.178.20 ~all"
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_txt_dmarc" {
zone_id = cloudflare_zone.mcintire_me.id
type = "TXT"
name = "_dmarc"
content = "v=DMARC1; p=quarantine; rua=mailto:postmaster@mcintire.me; ruf=mailto:postmaster@mcintire.me; fo=1"
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_txt_dkim" {
zone_id = cloudflare_zone.mcintire_me.id
type = "TXT"
name = "dkim._domainkey"
content = "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0rQie7Hz5AmDbVUX+rKNgp6Hf7crtWyfy6qKbqnrxmmernz1rQ6HyOrHhFAlc8nVLNKr8XP2DOROb1jAnrjndZo9I/ymbrrrCBsi9w6zAht2BheijO/R9k5CDRjeSafm6bg0oMKihtNMIdXvEj9ND8hDpZZKxtOrKFH7zLm4CPmm7jf0gqZH2yK+AA3OYUGSYa1OT0LtdMXadk0IVOI2VYf37Hzb3RUvGOYMVkT0xi+23DiyFk7AyCiyv3LQVBMT+/bwyd4k3yVVn2cr9+Nor+HXVjqASLtqcFLMf1SuX0ezSAjBG7BbqQr5G6Jz+n5YglqEIUKVYzJFE7JdDmqQYQIDAQAB"
ttl = 86400
}
resource "cloudflare_dns_record" "mcintire_me_txt_yahoo_verification" {
zone_id = cloudflare_zone.mcintire_me.id
type = "TXT"
name = "mcintire.me"
content = "yahoo-verification-key=Pt59XUhQSd7dbVc0x6TQD+mBEtOOTMHFRV1ZG78h4Ug="
ttl = 86400
}
# =============================================================================
# mcintire.me - Zone settings
# =============================================================================
resource "cloudflare_zone_setting" "mcintire_me_min_tls_version" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "min_tls_version"
value = "1.2"
}
resource "cloudflare_zone_setting" "mcintire_me_always_use_https" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "always_use_https"
value = "on"
}
resource "cloudflare_zone_setting" "mcintire_me_tls_1_3" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "tls_1_3"
value = "zrt"
}
resource "cloudflare_zone_setting" "mcintire_me_zero_rtt" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "0rtt"
value = "on"
}
resource "cloudflare_zone_setting" "mcintire_me_ssl" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "ssl"
value = "full"
}
resource "cloudflare_zone_setting" "mcintire_me_security_header" {
zone_id = cloudflare_zone.mcintire_me.id
setting_id = "security_header"
value = {
strict_transport_security = {
enabled = true
max_age = 15552000
include_subdomains = true
preload = true
nosniff = true
}
}
}
# =============================================================================
# mcintire.me - Bot management
# =============================================================================
resource "cloudflare_bot_management" "mcintire_me" {
zone_id = cloudflare_zone.mcintire_me.id
is_robots_txt_managed = false
}
# =============================================================================
# mcintire.me - Firewall
# =============================================================================
resource "cloudflare_ruleset" "mcintire_me_firewall" {
zone_id = cloudflare_zone.mcintire_me.id
name = "default"
kind = "zone"
phase = "http_request_firewall_custom"
rules = [{
action = "block"
expression = local.firewall_expression
description = "block abusers"
enabled = true
}]
}

View file

@ -29,7 +29,7 @@ locals {
"gridmap.org" = local.cloudflare_ns
"gunincrements.com" = local.porkbun_ns
"manero.org" = local.porkbun_ns
"mcintire.me" = local.as393837_ns
"mcintire.me" = local.cloudflare_ns
"ntxarms.com" = local.as393837_ns
"qth.social" = local.porkbun_ns
# towerops.net is not opted in to Porkbun API access

View file

@ -18,6 +18,19 @@ storage:
overwrite: true
contents:
inline: flatcar-resolver1
- path: /etc/resolv.conf
mode: 0644
overwrite: true
contents:
inline: |
nameserver 1.1.1.1
nameserver 9.9.9.9
- path: /etc/systemd/resolved.conf.d/no-stub.conf
mode: 0644
contents:
inline: |
[Resolve]
DNSStubListener=no
- path: /etc/systemd/network/00-eth0.network
mode: 0644
contents:
@ -30,8 +43,8 @@ storage:
[Network]
Address=204.110.191.240/26
Gateway=204.110.191.254
DNS=9.9.9.9
DNS=1.1.1.1
DNS=9.9.9.9
[Address]
Address=2606:1c80::240/64

View file

@ -18,6 +18,19 @@ storage:
overwrite: true
contents:
inline: flatcar-resolver2
- path: /etc/resolv.conf
mode: 0644
overwrite: true
contents:
inline: |
nameserver 1.1.1.1
nameserver 9.9.9.9
- path: /etc/systemd/resolved.conf.d/no-stub.conf
mode: 0644
contents:
inline: |
[Resolve]
DNSStubListener=no
- path: /etc/systemd/network/00-eth0.network
mode: 0644
contents:
@ -30,8 +43,8 @@ storage:
[Network]
Address=204.110.191.250/26
Gateway=204.110.191.254
DNS=9.9.9.9
DNS=1.1.1.1
DNS=9.9.9.9
[Address]
Address=2606:1c80::250/64

View file

@ -24,7 +24,7 @@ module "flatcar_resolver1" {
storage_root = "local-lvm"
storage_ignition = "local"
butane_conf = "${path.module}/flatcar-resolver.bu"
butane_snippet_path = "${path.module}"
butane_snippet_path = path.module
network_devices = [
{
@ -57,7 +57,7 @@ module "flatcar_resolver2" {
storage_root = "local-lvm"
storage_ignition = "local"
butane_conf = "${path.module}/flatcar-resolver2.bu"
butane_snippet_path = "${path.module}"
butane_snippet_path = path.module
network_devices = [
{