This commit is contained in:
Graham McIntire 2025-09-18 16:08:26 -05:00
parent c959b4581c
commit d5e3ce88a3
No known key found for this signature in database
52 changed files with 2560 additions and 65 deletions

View file

@ -31,14 +31,16 @@ make facts
``` ```
### Terraform Operations ### Terraform Operations
**IMPORTANT**: Use OpenTofu (tofu) instead of terraform for all operations.
```bash ```bash
cd terraform cd terraform
terraform init tofu init
terraform plan tofu plan
terraform apply tofu apply
# Format terraform files before committing # Format terraform files before committing
terraform fmt tofu fmt
``` ```
### K3s Cluster Deployment (VNTX) ### K3s Cluster Deployment (VNTX)

53
ansible/MAILCOW_SETUP.md Normal file
View file

@ -0,0 +1,53 @@
# Mailcow Setup Instructions
## Current Status
1. **DNS Records Applied**
- A records: mail.w5isp.com, mail.mcintire.me → 204.110.191.216
- Autodiscover/Autoconfig records for both domains
- MX record for w5isp.com → mail.w5isp.com
- SPF record for w5isp.com: `v=spf1 mx a ip4:204.110.191.216 ~all`
- DMARC record for w5isp.com with quarantine policy
- DKIM placeholder (needs actual key from Mailcow)
2. **Mailcow Deployed**
- Running on sync.w5isp.com with Docker Compose
- All containers running successfully
- Web interface accessible via HTTPS (Mailcow includes nginx)
- No additional reverse proxy needed - Mailcow's nginx handles SSL
## Next Steps
### 1. Access Mailcow Web UI
Access the web interface at: https://mail.w5isp.com
Default admin login: admin / moohoo (change immediately!)
### 3. Configure Domains in Mailcow
1. Log into Mailcow admin panel
2. Go to Configuration → Domains
3. Add domain: w5isp.com
4. Add domain: mcintire.me (for receiving only, MX still points to ProtonMail)
### 4. Get DKIM Key and Update DNS
1. In Mailcow: Configuration → Domains → w5isp.com → DKIM
2. Copy the public key
3. Update the DKIM record in `terraform/dns_w5isp.tf`
4. Apply with `tofu apply`
### 5. Create Mailboxes
1. Go to Mailboxes → Add mailbox
2. Create postmaster@w5isp.com (required for DMARC reports)
3. Create any other mailboxes needed
### 6. Test Mail Delivery
1. Send test email to/from w5isp.com addresses
2. Check mail logs in Mailcow UI
3. Verify DMARC/SPF/DKIM with mail-tester.com
## Important Notes
- mcintire.me remains with ProtonMail (MX records unchanged)
- Only w5isp.com is fully migrated to Mailcow
- Firewall ports 25, 587, 465, 143, 993, 110, 995 are opened by the role
- Mailcow data is stored in /opt/mailcow-dockerized
- The mail server IP is 204.110.191.216 (sync.w5isp.com)

View file

@ -0,0 +1,3 @@
---
# Host-specific variables for apps.w5isp.com
ansible_remote_tmp: /tmp/.ansible-${USER}/tmp

View file

@ -0,0 +1,3 @@
---
# Host-specific variables for caddy.w5isp.com
ansible_python_interpreter: /usr/bin/python3.12

View file

@ -0,0 +1,7 @@
---
# Vault file for sync.w5isp.com - Mailcow secrets
vault_mailcow_api_key: "drVv1AtEHuJw5C0+MxlKGGOMYiLx7C6dlfcAFlh2wZY="
vault_mailcow_dbpass: "om30XAgUbHTcPy3uGNzJAp8L+uGvNyoS"
vault_mailcow_dbroot: "jhIXyrY/DOS8wuRPSmCuRR/DqqXzi2Lq"
vault_mailcow_redispass: "9YZCv2mqofPYl7p4ep5DvL2PVBIBhMK/"

View file

@ -0,0 +1,15 @@
---
# Host-specific variables for sync.w5isp.com
ansible_user: ansible
ansible_python_interpreter: /usr/bin/python3
# Mailcow configuration
mailcow_hostname: mail.w5isp.com
mailcow_http_bind: "127.0.0.1"
mailcow_https_bind: "127.0.0.1"
mailcow_api_key: "{{ vault_mailcow_api_key }}"
mailcow_dbpass: "{{ vault_mailcow_dbpass }}"
mailcow_dbroot: "{{ vault_mailcow_dbroot }}"
# Since we're using Caddy as reverse proxy
mailcow_skip_lets_encrypt: "y"

View file

@ -0,0 +1,25 @@
---
# Host-specific variables for w5isp.w5isp.com
ansible_user: ansible
ansible_python_interpreter: /usr/bin/python3
# K3s configuration for single-node setup
k3s_version: v1.28.5+k3s1
k3s_install_exec: >-
--disable servicelb
--write-kubeconfig-mode 644
--cluster-cidr=10.42.0.0/16
--service-cidr=10.43.0.0/16
--cluster-dns=10.43.0.10
--flannel-backend=vxlan
# Network configuration
k3s_server_ip: 204.110.191.200
# DNS configuration
nameservers:
- 9.9.9.9
- 149.112.112.112
# Tailscale configuration (if needed)
tailscale_enabled: false

View file

@ -11,13 +11,13 @@ ns2.vntx.net
#ntp.vntx.net #ntp.vntx.net
[app_servers] [app_servers]
#aprs.me
dallas.aprs2.net dallas.aprs2.net
apps.w5isp.com apps.w5isp.com
[home_servers] [home_servers]
skippy.w5isp.com skippy.w5isp.com
g.w5isp.com sync.w5isp.com ansible_host=204.110.191.216
w5isp.w5isp.com ansible_host=204.110.191.200
[proxmox] [proxmox]
lab02.w5isp.com lab02.w5isp.com
@ -29,9 +29,24 @@ lab02.w5isp.com
lab03.w5isp.com lab03.w5isp.com
lab04.w5isp.com lab04.w5isp.com
g.w5isp.com g.w5isp.com
sync.w5isp.com
[caddy_servers] [caddy_servers]
g.w5isp.com caddy.w5isp.com
[k3s_servers]
home-k3s-control-1 ansible_host=10.0.19.100
home-k3s-control-2 ansible_host=10.0.19.101
home-k3s-control-3 ansible_host=10.0.19.102
home-k3s-worker-1 ansible_host=10.0.19.110
home-k3s-worker-2 ansible_host=10.0.19.111
home-k3s-worker-3 ansible_host=10.0.19.112
[syncthing_servers]
sync.w5isp.com
[postgresql_servers] [postgresql_servers]
postgres ansible_host=10.0.19.224 #postgres ansible_host=10.0.19.224
[mailcow_servers]
sync.w5isp.com

10
ansible/mailcow.yml Normal file
View file

@ -0,0 +1,10 @@
---
# Deploy Mailcow mail server
- name: Deploy Mailcow
hosts: mailcow_servers
become: yes
vars_files:
- host_vars/sync.w5isp.com.vault.yml
roles:
- mailcow

View file

@ -139,6 +139,59 @@
- raid - raid
- postgresql - postgresql
# Plays from general.yml
- name: Apply general configuration to all nodes
hosts: all
become: yes
become_method: sudo
roles:
- general
tags:
- general
- name: SNMP clients
hosts: snmp_clients
become: yes
become_method: sudo
roles:
- snmp_clients
tags:
- snmp
- name: k3s
hosts: k3s_servers
become: yes
become_method: sudo
tags: k3s
roles:
- k3s_servers
- name: Docker servers
hosts: docker
become: yes
become_method: sudo
tags: docker
roles:
- docker
vars:
gpg_fingerprint: "9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
- name: Syslog servers
hosts: syslog
become: yes
become_method: sudo
tags: syslog
roles:
- syslog_servers
- name: Syncthing servers
hosts: syncthing_servers
become: yes
become_method: sudo
tags: syncthing
roles:
- syncthing
# - import_playbook: baseline/proxmox/main.yml # - import_playbook: baseline/proxmox/main.yml
# - name: Tailscale Home # - name: Tailscale Home
# hosts: tailscale_home # hosts: tailscale_home

View file

@ -14,8 +14,6 @@
- name: Clean DNF cache - name: Clean DNF cache
ansible.builtin.command: dnf clean all ansible.builtin.command: dnf clean all
args:
warn: false
changed_when: false changed_when: false
- name: Upgrade all packages - name: Upgrade all packages

View file

@ -13,14 +13,15 @@
when: ansible_os_family == "Debian" when: ansible_os_family == "Debian"
- name: Add Caddy GPG key (Debian/Ubuntu) - name: Add Caddy GPG key (Debian/Ubuntu)
apt_key: get_url:
url: "https://dl.cloudsmith.io/public/caddy/stable/gpg.key" url: "https://dl.cloudsmith.io/public/caddy/stable/gpg.key"
state: present dest: /etc/apt/trusted.gpg.d/caddy-stable.asc
mode: '0644'
when: ansible_os_family == "Debian" when: ansible_os_family == "Debian"
- name: Add Caddy repository (Debian/Ubuntu) - name: Add Caddy repository (Debian/Ubuntu)
apt_repository: apt_repository:
repo: "deb https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main" repo: "deb [signed-by=/etc/apt/trusted.gpg.d/caddy-stable.asc] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main"
state: present state: present
filename: caddy-stable filename: caddy-stable
when: ansible_os_family == "Debian" when: ansible_os_family == "Debian"

View file

@ -23,3 +23,38 @@ truck.w5isp.com {
photos.w5isp.com { photos.w5isp.com {
reverse_proxy http://10.0.16.1:2283 reverse_proxy http://10.0.16.1:2283
} }
# Mailcow configuration
mail.w5isp.com {
reverse_proxy http://204.110.191.216:8080 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
autodiscover.w5isp.com {
reverse_proxy http://204.110.191.216:8080
}
autoconfig.w5isp.com {
reverse_proxy http://204.110.191.216:8080
}
mail.mcintire.me {
reverse_proxy http://204.110.191.216:8080 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
autodiscover.mcintire.me {
reverse_proxy http://204.110.191.216:8080
}
autoconfig.mcintire.me {
reverse_proxy http://204.110.191.216:8080
}

View file

@ -0,0 +1,48 @@
---
# Default variables for Mailcow
mailcow_hostname: "{{ inventory_hostname }}"
mailcow_timezone: "America/Chicago"
mailcow_base_path: /opt/mailcow-dockerized
# API configuration
mailcow_api_key: "{{ vault_mailcow_api_key | default('') }}"
mailcow_api_allow_ips: "127.0.0.1,::1"
# HTTP/HTTPS bindings
mailcow_http_bind: "127.0.0.1"
mailcow_http_port: 8080
mailcow_https_bind: "127.0.0.1"
mailcow_https_port: 8443
# Mail ports - bind to all interfaces
mailcow_smtp_port: 25
mailcow_submission_port: 587
mailcow_smtps_port: 465
mailcow_imap_port: 143
mailcow_imaps_port: 993
mailcow_pop3_port: 110
mailcow_pop3s_port: 995
# Database configuration
mailcow_dbname: mailcow
mailcow_dbuser: mailcow
mailcow_dbpass: "{{ vault_mailcow_dbpass | default('') }}"
mailcow_dbroot: "{{ vault_mailcow_dbroot | default('') }}"
# Skip various checks
mailcow_skip_lets_encrypt: "y"
mailcow_skip_sogo: "n"
mailcow_skip_clamd: "n"
mailcow_skip_solr: "n"
mailcow_additional_san: ""
# Compose project name
mailcow_compose_project_name: mailcowdockerized
# Git repository
mailcow_git_repo: https://github.com/mailcow/mailcow-dockerized.git
mailcow_git_branch: master
# External mail storage path (outside Docker)
mailcow_vmail_path: /opt/mailcow-mail

View file

@ -0,0 +1,15 @@
---
# Handlers for Mailcow
- name: restart mailcow
systemd:
name: mailcow
state: restarted
daemon_reload: yes
become: yes
- name: reload mailcow
command: docker compose restart
args:
chdir: "{{ mailcow_base_path }}"

View file

@ -0,0 +1,196 @@
---
# Install and configure Mailcow
- name: Install required packages
apt:
name:
- git
- curl
- docker.io
- docker-compose
- python3-pip
- python3-docker
- jq
state: present
update_cache: yes
- name: Ensure docker service is running
systemd:
name: docker
state: started
enabled: yes
- name: Create mailcow user
user:
name: mailcow
groups: docker
shell: /bin/bash
home: /home/mailcow
create_home: yes
state: present
- name: Ensure mailcow base directory exists
file:
path: "{{ mailcow_base_path }}"
state: directory
owner: mailcow
group: mailcow
mode: '0755'
- name: Check if Mailcow repository exists
stat:
path: "{{ mailcow_base_path }}/.git"
register: mailcow_repo
- name: Remove non-git Mailcow directory
file:
path: "{{ mailcow_base_path }}"
state: absent
when:
- mailcow_base_path is defined
- not mailcow_repo.stat.exists
- name: Clone Mailcow repository
git:
repo: "{{ mailcow_git_repo }}"
dest: "{{ mailcow_base_path }}"
version: "{{ mailcow_git_branch }}"
force: yes
when: not mailcow_repo.stat.exists
- name: Add Mailcow directory to git safe directories
command: git config --global --add safe.directory {{ mailcow_base_path }}
when: mailcow_repo.stat.exists
- name: Update Mailcow repository
git:
repo: "{{ mailcow_git_repo }}"
dest: "{{ mailcow_base_path }}"
version: "{{ mailcow_git_branch }}"
update: yes
when: mailcow_repo.stat.exists
ignore_errors: yes
- name: Change ownership of Mailcow directory
file:
path: "{{ mailcow_base_path }}"
owner: mailcow
group: mailcow
recurse: yes
# Let generate_config.sh create the default configuration
- name: Create docker-compose override file
template:
src: docker-compose.override.yml.j2
dest: "{{ mailcow_base_path }}/docker-compose.override.yml"
owner: mailcow
group: mailcow
mode: '0640'
backup: yes
notify: restart mailcow
- name: Make generate_config.sh executable
file:
path: "{{ mailcow_base_path }}/generate_config.sh"
mode: '0755'
owner: mailcow
group: mailcow
- name: Check if mailcow is already configured
stat:
path: "{{ mailcow_base_path }}/mailcow.conf"
register: mailcow_configured
- name: Remove incomplete mailcow.conf
file:
path: "{{ mailcow_base_path }}/mailcow.conf"
state: absent
when:
- mailcow_configured.stat.exists
- mailcow_configured.stat.size < 1000 # If config is too small, it's incomplete
- name: Run generate_config.sh script
shell: |
cd {{ mailcow_base_path }}
./generate_config.sh << EOF
{{ mailcow_hostname }}
{{ mailcow_timezone }}
EOF
args:
creates: "{{ mailcow_base_path }}/mailcow.conf"
- name: Update mailcow.conf with secure passwords
lineinfile:
path: "{{ mailcow_base_path }}/mailcow.conf"
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value }}"
state: present
loop:
- { key: "DBPASS", value: "{{ vault_mailcow_dbpass }}" }
- { key: "DBROOT", value: "{{ vault_mailcow_dbroot }}" }
- { key: "REDISPASS", value: "{{ vault_mailcow_redispass }}" }
- { key: "API_KEY", value: "{{ vault_mailcow_api_key }}" }
no_log: true
- name: Stop any running Mailcow containers
command: docker compose down
args:
chdir: "{{ mailcow_base_path }}"
ignore_errors: yes
- name: Prune unused Docker networks
command: docker network prune -f
changed_when: true
- name: Pull docker images
command: docker compose pull
args:
chdir: "{{ mailcow_base_path }}"
- name: Start Mailcow services
command: docker compose up -d
args:
chdir: "{{ mailcow_base_path }}"
- name: Check if ufw is installed
command: which ufw
register: ufw_check
changed_when: false
failed_when: false
- name: Configure firewall for mail services
ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto }}"
comment: "{{ item.comment }}"
loop:
- { port: "25", proto: "tcp", comment: "SMTP" }
- { port: "587", proto: "tcp", comment: "SMTP Submission" }
- { port: "465", proto: "tcp", comment: "SMTPS" }
# - { port: "143", proto: "tcp", comment: "IMAP" } # Disabled non-TLS
- { port: "993", proto: "tcp", comment: "IMAPS" }
# - { port: "110", proto: "tcp", comment: "POP3" } # Disabled non-TLS
- { port: "995", proto: "tcp", comment: "POP3S" }
- { port: "4190", proto: "tcp", comment: "Sieve" }
when:
- ansible_facts['os_family'] == "Debian"
- ufw_check.rc == 0
- name: Create systemd service for Mailcow
template:
src: mailcow.service.j2
dest: /etc/systemd/system/mailcow.service
owner: root
group: root
mode: '0644'
notify: restart mailcow
- name: Enable Mailcow service
systemd:
name: mailcow
enabled: yes
daemon_reload: yes
# Caddy not needed - Mailcow includes nginx for SSL termination

View file

@ -0,0 +1,43 @@
{
# Global options
admin off
persist_config off
}
# Mailcow web interface
mail.w5isp.com {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }} {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
# Autodiscover/Autoconfig for w5isp.com
autodiscover.w5isp.com {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }}
}
autoconfig.w5isp.com {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }}
}
# Mailcow web interface for mcintire.me
mail.mcintire.me {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }} {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
# Autodiscover/Autoconfig for mcintire.me
autodiscover.mcintire.me {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }}
}
autoconfig.mcintire.me {
reverse_proxy http://127.0.0.1:{{ mailcow_http_port }}
}

View file

@ -0,0 +1,37 @@
# Mailcow reverse proxy configuration for Caddy
mail.w5isp.com {
reverse_proxy localhost:{{ mailcow_http_port }} {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
# Redirect for autodiscover
autodiscover.w5isp.com {
reverse_proxy localhost:{{ mailcow_http_port }}
}
# Redirect for autoconfig
autoconfig.w5isp.com {
reverse_proxy localhost:{{ mailcow_http_port }}
}
# For mcintire.me domain
mail.mcintire.me {
reverse_proxy localhost:{{ mailcow_http_port }} {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
autodiscover.mcintire.me {
reverse_proxy localhost:{{ mailcow_http_port }}
}
autoconfig.mcintire.me {
reverse_proxy localhost:{{ mailcow_http_port }}
}

View file

@ -0,0 +1,18 @@
services:
nginx-mailcow:
labels:
- "traefik.enable=false"
postfix-mailcow:
ports:
- "0.0.0.0:{{ mailcow_smtp_port }}:25"
- "0.0.0.0:{{ mailcow_submission_port }}:587"
- "0.0.0.0:{{ mailcow_smtps_port }}:465"
dovecot-mailcow:
ports:
# - "0.0.0.0:{{ mailcow_imap_port }}:143" # Disabled non-TLS IMAP
- "0.0.0.0:{{ mailcow_imaps_port }}:993"
# - "0.0.0.0:{{ mailcow_pop3_port }}:110" # Disabled non-TLS POP3
- "0.0.0.0:{{ mailcow_pop3s_port }}:995"
- "0.0.0.0:4190:4190"

View file

@ -0,0 +1,64 @@
# ------------------------------
# Mailcow configuration
# ------------------------------
# Your mail server hostname
MAILCOW_HOSTNAME={{ mailcow_hostname }}
# Your timezone
TZ={{ mailcow_timezone }}
# Fixed project name
COMPOSE_PROJECT_NAME={{ mailcow_compose_project_name }}
# Database configuration
DBNAME={{ mailcow_dbname }}
DBUSER={{ mailcow_dbuser }}
DBPASS={{ mailcow_dbpass }}
DBROOT={{ mailcow_dbroot }}
# Redis configuration
REDISPASS={{ mailcow_redispass | default(vault_mailcow_redispass) }}
# HTTP/HTTPS bindings
HTTP_BIND={{ mailcow_http_bind }}
HTTP_PORT={{ mailcow_http_port }}
HTTPS_BIND={{ mailcow_https_bind }}
HTTPS_PORT={{ mailcow_https_port }}
# Mail port bindings
SMTP_PORT={{ mailcow_smtp_port }}
SMTPS_PORT={{ mailcow_smtps_port }}
SUBMISSION_PORT={{ mailcow_submission_port }}
IMAP_PORT={{ mailcow_imap_port }}
IMAPS_PORT={{ mailcow_imaps_port }}
POP_PORT={{ mailcow_pop3_port }}
POPS_PORT={{ mailcow_pop3s_port }}
SIEVE_PORT=4190
# API configuration
API_KEY={{ mailcow_api_key }}
API_ALLOW_FROM={{ mailcow_api_allow_ips }}
# Skip various components
SKIP_LETS_ENCRYPT={{ mailcow_skip_lets_encrypt }}
SKIP_SOGO={{ mailcow_skip_sogo }}
SKIP_CLAMD={{ mailcow_skip_clamd }}
SKIP_SOLR={{ mailcow_skip_solr }}
# Additional SAN for certificate
ADDITIONAL_SAN={{ mailcow_additional_san }}
# Watchdog configuration
WATCHDOG_NOTIFY_EMAIL=
WATCHDOG_NOTIFY_BAN=n
WATCHDOG_SUBJECT="Watchdog ALERT"
# Other settings
MAILDIR_GC_TIME=7200
ADDITIONAL_SERVER_NAMES=
ACME_CONTACT=
# Docker settings
DOCKER_COMPOSE_VERSION=native

View file

@ -0,0 +1,17 @@
[Unit]
Description=Mailcow dockerized mail server
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ mailcow_base_path }}
ExecStart=/usr/bin/docker-compose up -d
ExecStop=/usr/bin/docker-compose down
ExecReload=/usr/bin/docker-compose restart
User=mailcow
Group=docker
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,6 @@
---
- name: restart syncthing
systemd:
name: syncthing@graham
state: restarted
daemon_reload: yes

View file

@ -0,0 +1,132 @@
---
# Install and configure Syncthing
- name: Install required packages
apt:
name:
- apt-transport-https
- curl
- gnupg2
state: present
update_cache: yes
- name: Add Syncthing GPG key
get_url:
url: "https://syncthing.net/release-key.gpg"
dest: /usr/share/keyrings/syncthing-archive-keyring.gpg
mode: '0644'
- name: Add Syncthing repository
apt_repository:
repo: "deb [signed-by=/usr/share/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable"
state: present
filename: syncthing
- name: Install Syncthing
apt:
name: syncthing
state: present
update_cache: yes
- name: Ensure graham user exists
user:
name: graham
state: present
- name: Create Syncthing configuration directory
file:
path: /home/graham/.config/syncthing
state: directory
owner: graham
group: graham
mode: '0755'
- name: Deploy Syncthing configuration
template:
src: config.xml.j2
dest: /home/graham/.config/syncthing/config.xml
owner: graham
group: graham
mode: '0600'
backup: yes
notify: restart syncthing
- name: Create Syncthing systemd service
copy:
content: |
[Unit]
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=man:syncthing(1)
After=network.target
[Service]
Type=simple
User=graham
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
Restart=on-failure
RestartSec=5
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
# Hardening
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/syncthing@graham.service
owner: root
group: root
mode: '0644'
notify: restart syncthing
- name: Stop old syncthing service if exists
systemd:
name: syncthing@syncthing
state: stopped
enabled: no
failed_when: false
- name: Enable and start Syncthing service
systemd:
name: syncthing@graham
enabled: yes
state: started
daemon_reload: yes
- name: Check if ufw is installed
command: which ufw
register: ufw_check
changed_when: false
failed_when: false
- name: Configure firewall for Syncthing web UI
ufw:
rule: allow
port: '8384'
proto: tcp
comment: 'Syncthing Web UI'
when:
- ansible_facts['os_family'] == "Debian"
- ufw_check.rc == 0
- name: Configure firewall for Syncthing sync protocol
ufw:
rule: allow
port: '22000'
proto: tcp
comment: 'Syncthing Sync Protocol'
when:
- ansible_facts['os_family'] == "Debian"
- ufw_check.rc == 0
- name: Configure firewall for Syncthing discovery
ufw:
rule: allow
port: '21027'
proto: udp
comment: 'Syncthing Local Discovery'
when:
- ansible_facts['os_family'] == "Debian"
- ufw_check.rc == 0

View file

@ -0,0 +1,57 @@
<configuration version="37">
<gui enabled="true" tls="false" debugging="false">
<address>0.0.0.0:8384</address>
<apikey></apikey>
<theme>default</theme>
</gui>
<options>
<listenAddress>default</listenAddress>
<globalAnnounceServer>default</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<localAnnouncePort>21027</localAnnouncePort>
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<reconnectionIntervalS>60</reconnectionIntervalS>
<relaysEnabled>true</relaysEnabled>
<relayReconnectIntervalM>10</relayReconnectIntervalM>
<startBrowser>false</startBrowser>
<natEnabled>true</natEnabled>
<natLeaseMinutes>60</natLeaseMinutes>
<natRenewalMinutes>30</natRenewalMinutes>
<natTimeoutSeconds>10</natTimeoutSeconds>
<urAccepted>-1</urAccepted>
<urSeen>3</urSeen>
<urURL>https://data.syncthing.net/newdata</urURL>
<urPostInsecurely>false</urPostInsecurely>
<urInitialDelayS>1800</urInitialDelayS>
<restartOnWakeup>true</restartOnWakeup>
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
<upgradeToPreReleases>false</upgradeToPreReleases>
<keepTemporariesH>24</keepTemporariesH>
<cacheIgnoredFiles>false</cacheIgnoredFiles>
<progressUpdateIntervalS>5</progressUpdateIntervalS>
<limitBandwidthInLan>false</limitBandwidthInLan>
<minHomeDiskFree unit="%">1</minHomeDiskFree>
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
<tempIndexMinBlocks>10</tempIndexMinBlocks>
<unackedNotificationIDs></unackedNotificationIDs>
<trafficClass>0</trafficClass>
<setLowPriority>true</setLowPriority>
<maxFolderConcurrency>0</maxFolderConcurrency>
<crashReportingURL>https://crash.syncthing.net/newcrash</crashReportingURL>
<crashReportingEnabled>true</crashReportingEnabled>
<stunKeepaliveStartS>180</stunKeepaliveStartS>
<stunKeepaliveMinS>20</stunKeepaliveMinS>
<stunServers>default</stunServers>
<databaseTuning>auto</databaseTuning>
<maxConcurrentIncomingRequestKiB>0</maxConcurrentIncomingRequestKiB>
<announceLANAddresses>true</announceLANAddresses>
<sendFullIndexOnUpgrade>false</sendFullIndexOnUpgrade>
<connectionLimitEnough>0</connectionLimitEnough>
<connectionLimitMax>0</connectionLimitMax>
<insecureAllowOldTLSVersions>false</insecureAllowOldTLSVersions>
</options>
</configuration>

View file

@ -0,0 +1,111 @@
---
- name: Create MariaDB LXC Container
hosts: lab02
become: true
vars:
ct_id: 301
ct_name: mariadb
ct_template: "local:vztmpl/debian-13-standard_13.1-1_amd64.tar.zst"
ct_memory: 2048 # 2GB RAM
ct_disk_size: 50 # 50GB storage
ct_cores: 2
ct_ip: "10.0.19.6/22"
ct_gateway: "10.0.19.254"
ct_storage: "ceph" # Use Ceph RBD storage
mariadb_root_password: "dOtPgrVnkIW1zwfjvW9rSw"
tasks:
- name: Create MariaDB LXC container
command: |
pct create {{ ct_id }} {{ ct_template }} \
--hostname {{ ct_name }} \
--memory {{ ct_memory }} \
--cores {{ ct_cores }} \
--net0 name=eth0,bridge=vmbr0,ip={{ ct_ip }},gw={{ ct_gateway }} \
--storage {{ ct_storage }} \
--rootfs {{ ct_storage }}:{{ ct_disk_size }} \
--unprivileged 1 \
--features nesting=1 \
--onboot 1 \
--startup order=20
- name: Start container
command: pct start {{ ct_id }}
- name: Wait for container to boot
pause:
seconds: 30
- name: Update container packages
shell: pct exec {{ ct_id }} -- apt update && pct exec {{ ct_id }} -- apt upgrade -y
- name: Install MariaDB
shell: |
pct exec {{ ct_id }} -- bash -c "
export DEBIAN_FRONTEND=noninteractive
apt-get install -y mariadb-server mariadb-client
"
- name: Start and enable MariaDB
shell: |
pct exec {{ ct_id }} -- bash -c "
systemctl enable mariadb
systemctl start mariadb
"
- name: Configure MariaDB for network access
shell: |
pct exec {{ ct_id }} -- bash -c "
# Update bind-address
sed -i 's/^bind-address.*=.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
# Set root password and configure access
mysql -u root << 'EOF'
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('{{ mariadb_root_password }}');
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{{ mariadb_root_password }}';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EOF
# Restart MariaDB
systemctl restart mariadb
"
- name: Save credentials to file
copy:
content: |
MariaDB Container Credentials
=============================
Container ID: {{ ct_id }}
Host: lab02
IP Address: 10.0.19.6
Port: 3306
Root Credentials:
Username: root
Password: {{ mariadb_root_password }}
Connection String:
mysql -h 10.0.19.6 -u root -p'{{ mariadb_root_password }}'
Note: This container was created on {{ ansible_date_time.iso8601 }}
dest: "./mariadb_credentials.txt"
mode: '0600'
delegate_to: localhost
- name: Display connection info
debug:
msg: |
MariaDB container created successfully!
Container details:
- Container ID: {{ ct_id }}
- Host: lab02
- IP: 10.0.19.6
- Storage: {{ ct_disk_size }}GB on {{ ct_storage }}
Root credentials:
- Username: root
- Password: {{ mariadb_root_password }}
Credentials saved to: ./mariadb_credentials.txt

View file

@ -0,0 +1,35 @@
---
- name: Fix DNS on K3s Nodes
hosts: k3s_cluster
become: true
tasks:
- name: Update resolv.conf to use Quad9 DNS
copy:
content: |
nameserver 9.9.9.9
nameserver 149.112.112.112
dest: /etc/resolv.conf
backup: yes
- name: Ensure systemd-resolved is not managing resolv.conf
systemd:
name: systemd-resolved
state: stopped
enabled: no
ignore_errors: yes
- name: Test DNS resolution
command: nslookup registry-1.docker.io
register: dns_test
changed_when: false
- name: Show DNS test results
debug:
msg: "{{ dns_test.stdout_lines }}"
- name: Restart containerd to pick up DNS changes
systemd:
name: containerd
state: restarted
when: inventory_hostname in groups['k3s_cluster']

View file

@ -0,0 +1,23 @@
MariaDB Container Credentials
=============================
Created: 2025-09-12
Container Details:
- Container ID: 301
- Host: lab02
- IP Address: 10.0.19.6
- Port: 3306
Root Credentials:
- Username: root
- Password: dOtPgrVnkIW1zwfjvW9rSw
- Connection: mysql -h 10.0.19.6 -u root -p'dOtPgrVnkIW1zwfjvW9rSw'
Wavelog Database:
- Database: wavelog
- Username: wavelog
- Password: m4icNmGTRtOMgrNxsxEdrw
- Connection: mysql -h 10.0.19.6 -u wavelog -p'm4icNmGTRtOMgrNxsxEdrw' wavelog
Kubernetes Secret:
kubectl create secret generic wavelog-db-secret --from-literal=password="m4icNmGTRtOMgrNxsxEdrw" -n wavelog

View file

@ -0,0 +1,31 @@
---
- name: Restart K3s Services After DNS Fix
hosts: k3s_cluster
become: true
tasks:
- name: Test DNS resolution using getent
command: getent hosts registry-1.docker.io
register: dns_test
changed_when: false
ignore_errors: yes
- name: Show DNS test results
debug:
msg: "{{ dns_test.stdout | default('DNS resolution failed') }}"
- name: Restart K3s service on control nodes
systemd:
name: k3s
state: restarted
when: inventory_hostname in groups['k3s_control_plane']
- name: Restart K3s agent on worker nodes
systemd:
name: k3s-agent
state: restarted
when: inventory_hostname in groups['k3s_workers']
- name: Wait for services to stabilize
pause:
seconds: 30

View file

@ -0,0 +1,123 @@
---
- name: Setup MariaDB in Container
hosts: lab02
become: true
vars:
ct_id: 301
mariadb_root_password: "dOtPgrVnkIW1zwfjvW9rSw"
wavelog_password: "m4icNmGTRtOMgrNxsxEdrw"
tasks:
- name: Configure DNS in container
shell: |
pct exec {{ ct_id }} -- bash -c "echo 'nameserver 9.9.9.9' > /etc/resolv.conf"
- name: Update container packages
shell: pct exec {{ ct_id }} -- apt-get update
register: apt_update
retries: 3
delay: 5
until: apt_update.rc == 0
- name: Install MariaDB Server
shell: |
pct exec {{ ct_id }} -- bash -c "
export DEBIAN_FRONTEND=noninteractive
apt-get install -y mariadb-server mariadb-client
"
- name: Start and enable MariaDB
shell: |
pct exec {{ ct_id }} -- systemctl enable mariadb
pct exec {{ ct_id }} -- systemctl start mariadb
- name: Secure MariaDB installation
shell: |
pct exec {{ ct_id }} -- bash -c "mysql -u root << 'EOF'
-- Set root password
ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mariadb_root_password }}';
-- Create root user for remote access
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{{ mariadb_root_password }}';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';
-- Remove test database
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
-- Reload privileges
FLUSH PRIVILEGES;
EOF"
- name: Configure MariaDB for network access
shell: |
pct exec {{ ct_id }} -- sed -i 's/^bind-address.*=.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
pct exec {{ ct_id }} -- systemctl restart mariadb
- name: Create Wavelog database and user
shell: |
pct exec {{ ct_id }} -- mysql -u root -p'{{ mariadb_root_password }}' << 'EOF'
CREATE DATABASE IF NOT EXISTS wavelog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'wavelog'@'%' IDENTIFIED BY '{{ wavelog_password }}';
GRANT ALL PRIVILEGES ON wavelog.* TO 'wavelog'@'%';
FLUSH PRIVILEGES;
EOF
- name: Test database connections
shell: |
pct exec {{ ct_id }} -- mysql -u root -p'{{ mariadb_root_password }}' -e "SELECT VERSION();"
pct exec {{ ct_id }} -- mysql -u wavelog -p'{{ wavelog_password }}' wavelog -e "SELECT DATABASE();"
register: db_test
- name: Save all credentials
copy:
content: |
MariaDB Container Credentials
=============================
Created: {{ ansible_date_time.iso8601 }}
Container Details:
- Container ID: {{ ct_id }}
- Host: lab02
- IP Address: 10.0.19.6
- Port: 3306
Root Credentials:
- Username: root
- Password: {{ mariadb_root_password }}
- Connection: mysql -h 10.0.19.6 -u root -p'{{ mariadb_root_password }}'
Wavelog Database:
- Database: wavelog
- Username: wavelog
- Password: {{ wavelog_password }}
- Connection: mysql -h 10.0.19.6 -u wavelog -p'{{ wavelog_password }}' wavelog
Test Results:
{{ db_test.stdout }}
dest: "./mariadb_all_credentials.txt"
mode: '0600'
delegate_to: localhost
- name: Update Kubernetes secret for Wavelog
shell: |
kubectl create secret generic wavelog-db-secret \
--from-literal=password="{{ wavelog_password }}" \
--dry-run=client -o yaml | kubectl apply -f - -n wavelog
environment:
KUBECONFIG: "{{ playbook_dir }}/kubeconfig"
delegate_to: localhost
- name: Display summary
debug:
msg: |
MariaDB setup completed successfully!
Root password: {{ mariadb_root_password }}
Wavelog password: {{ wavelog_password }}
All credentials saved to: ./mariadb_all_credentials.txt
Kubernetes secret updated: wavelog-db-secret

View file

@ -0,0 +1,47 @@
---
- name: Setup Wavelog Database in MariaDB
hosts: lab02
become: true
vars:
mysql_container_id: 301
wavelog_password: "m4icNmGTRtOMgrNxsxEdrw"
tasks:
- name: Create Wavelog database and user
shell: |
pct exec {{ mysql_container_id }} -- bash -c "mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS wavelog;
CREATE USER IF NOT EXISTS 'wavelog'@'%' IDENTIFIED BY '{{ wavelog_password }}';
GRANT ALL PRIVILEGES ON wavelog.* TO 'wavelog'@'%';
FLUSH PRIVILEGES;
EOF"
register: db_creation
ignore_errors: yes
- name: If root needs password, try with socket authentication
shell: |
pct exec {{ mysql_container_id }} -- bash -c "mysql <<EOF
CREATE DATABASE IF NOT EXISTS wavelog;
CREATE USER IF NOT EXISTS 'wavelog'@'%' IDENTIFIED BY '{{ wavelog_password }}';
GRANT ALL PRIVILEGES ON wavelog.* TO 'wavelog'@'%';
FLUSH PRIVILEGES;
EOF"
when: db_creation.rc != 0
- name: Test Wavelog database connection
shell: |
pct exec {{ mysql_container_id }} -- mysql -u wavelog -p'{{ wavelog_password }}' -e "SELECT DATABASE();" wavelog
register: test_result
ignore_errors: yes
- name: Display result
debug:
msg: |
Database setup completed!
Host: 10.0.19.6
Port: 3306
Database: wavelog
Username: wavelog
Password: {{ wavelog_password }}
Test result: {{ test_result.stdout | default('Failed to connect') }}

View file

@ -0,0 +1,68 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
containers:
- name: wavelog
image: ghcr.io/wavelog/wavelog:latest
env:
- name: CI_ENV
value: "docker"
- name: WAVELOG_DB_HOST
value: "10.0.19.6"
- name: WAVELOG_DB_PORT
value: "3306"
- name: WAVELOG_DB_NAME
value: "wavelog"
- name: WAVELOG_DB_USER
value: "wavelog"
- name: WAVELOG_DB_PASS
valueFrom:
secretKeyRef:
name: wavelog-db-secret
key: password
ports:
- containerPort: 80
name: http
volumeMounts:
- name: config
mountPath: /var/www/html/application/config/docker
- name: uploads
mountPath: /var/www/html/uploads
- name: userdata
mountPath: /var/www/html/userdata
- name: backup
mountPath: /var/www/html/backup
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
imagePullSecrets:
- name: ghcr-pull-secret
volumes:
- name: config
persistentVolumeClaim:
claimName: wavelog-config
- name: uploads
persistentVolumeClaim:
claimName: wavelog-uploads
- name: userdata
persistentVolumeClaim:
claimName: wavelog-userdata
- name: backup
emptyDir: {}

View file

@ -0,0 +1,89 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
containers:
- name: wavelog
image: php:8.2-apache
env:
- name: DATABASE_HOST
value: "10.0.19.6"
- name: DATABASE_PORT
value: "3306"
- name: DATABASE_NAME
value: "wavelog"
- name: DATABASE_USER
value: "wavelog"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: wavelog-db-secret
key: password
ports:
- containerPort: 80
name: http
volumeMounts:
- name: data
mountPath: /var/www/html
lifecycle:
postStart:
exec:
command:
- /bin/bash
- -c
- |
# Install dependencies if not already installed
if [ ! -f /var/www/html/.deps_installed ]; then
apt-get update && apt-get install -y \
git \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev \
mariadb-client \
curl \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd mysqli pdo pdo_mysql zip intl \
&& a2enmod rewrite \
&& touch /var/www/html/.deps_installed
fi
# Clone Wavelog if not exists
if [ ! -f /var/www/html/index.php ]; then
cd /tmp
git clone https://github.com/wavelog/wavelog.git
cp -R wavelog/* /var/www/html/
cp -R wavelog/.* /var/www/html/ 2>/dev/null || true
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
# Create uploads directory
mkdir -p /var/www/html/uploads
chown -R www-data:www-data /var/www/html/uploads
chmod -R 777 /var/www/html/uploads
fi
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: wavelog-data

View file

@ -0,0 +1,133 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wavelog-uploads
namespace: wavelog
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wavelog-userdata
namespace: wavelog
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wavelog-config
namespace: wavelog
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
containers:
- name: wavelog
image: ghcr.io/wavelog/wavelog:latest
env:
- name: CI_ENV
value: "docker"
- name: DB_HOST
value: "10.0.19.6"
- name: DB_NAME
value: "wavelog"
- name: DB_USER
value: "wavelog"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: wavelog-db-secret
key: password
ports:
- containerPort: 80
name: http
volumeMounts:
- name: config
mountPath: /var/www/html/application/config/docker
- name: uploads
mountPath: /var/www/html/uploads
- name: userdata
mountPath: /var/www/html/userdata
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
imagePullSecrets:
- name: ghcr-pull-secret
volumes:
- name: config
persistentVolumeClaim:
claimName: wavelog-config
- name: uploads
persistentVolumeClaim:
claimName: wavelog-uploads
- name: userdata
persistentVolumeClaim:
claimName: wavelog-userdata
---
apiVersion: v1
kind: Service
metadata:
name: wavelog
namespace: wavelog
spec:
selector:
app: wavelog
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http
---
apiVersion: v1
kind: Service
metadata:
name: wavelog-tailscale
namespace: wavelog
annotations:
tailscale.com/expose: "true"
tailscale.com/hostname: "wavelog"
tailscale.com/funnel: "false"
spec:
selector:
app: wavelog
type: LoadBalancer
loadBalancerClass: tailscale
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http

View file

@ -0,0 +1,68 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
containers:
- name: wavelog
image: ghcr.io/wavelog/wavelog:latest
env:
- name: CI_ENV
value: "docker"
- name: WAVELOG_DB_HOST
value: "10.0.19.6"
- name: WAVELOG_DB_PORT
value: "3306"
- name: WAVELOG_DB_NAME
value: "wavelog"
- name: WAVELOG_DB_USER
value: "wavelog"
- name: WAVELOG_DB_PASS
valueFrom:
secretKeyRef:
name: wavelog-db-secret
key: password
ports:
- containerPort: 80
name: http
volumeMounts:
- name: config
mountPath: /var/www/html/application/config/docker
- name: uploads
mountPath: /var/www/html/uploads
- name: userdata
mountPath: /var/www/html/userdata
- name: backup
mountPath: /var/www/html/backup
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
imagePullSecrets:
- name: ghcr-pull-secret
volumes:
- name: config
persistentVolumeClaim:
claimName: wavelog-config
- name: uploads
persistentVolumeClaim:
claimName: wavelog-uploads
- name: userdata
persistentVolumeClaim:
claimName: wavelog-userdata
- name: backup
emptyDir: {}

View file

@ -0,0 +1,108 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
initContainers:
- name: setup
image: php:8.2-apache
command:
- /bin/bash
- -c
- |
echo "Installing dependencies..."
apt-get update && apt-get install -y \
git unzip libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev libicu-dev mariadb-client curl \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd mysqli pdo pdo_mysql zip intl
echo "Cloning Wavelog..."
cd /tmp
git clone https://github.com/wavelog/wavelog.git
cp -R wavelog/* /var/www/html/
cp -R wavelog/.* /var/www/html/ 2>/dev/null || true
# Create config directory
mkdir -p /var/www/html/application/config/docker
# Create database.php config
cat > /var/www/html/application/config/database.php << 'EOF'
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
'dsn' => '',
'hostname' => '10.0.19.6',
'username' => 'wavelog',
'password' => 'm4icNmGTRtOMgrNxsxEdrw',
'database' => 'wavelog',
'dbdriver' => 'mysqli',
'dbprefix' => '',
'pconnect' => FALSE,
'db_debug' => FALSE,
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8mb4',
'dbcollat' => 'utf8mb4_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
EOF
# Set permissions
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
chmod -R 777 /var/www/html/uploads
chmod -R 777 /var/www/html/application/cache
chmod -R 777 /var/www/html/application/logs
chmod -R 777 /var/www/html/userdata
# Enable Apache rewrite module
a2enmod rewrite
echo "Setup complete!"
volumeMounts:
- name: data
mountPath: /var/www/html
containers:
- name: wavelog
image: php:8.2-apache
ports:
- containerPort: 80
name: http
volumeMounts:
- name: data
mountPath: /var/www/html
env:
- name: APACHE_DOCUMENT_ROOT
value: /var/www/html
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: wavelog-data

View file

@ -0,0 +1,109 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wavelog
namespace: wavelog
spec:
replicas: 1
selector:
matchLabels:
app: wavelog
template:
metadata:
labels:
app: wavelog
spec:
containers:
- name: wavelog
image: php:8.2-apache
env:
- name: CI_ENV
value: "production"
- name: DB_HOST
value: "10.0.19.6"
- name: DB_NAME
value: "wavelog"
- name: DB_USER
value: "wavelog"
- name: DB_PASS
valueFrom:
secretKeyRef:
name: wavelog-db-secret
key: password
ports:
- containerPort: 80
name: http
volumeMounts:
- name: data
mountPath: /var/www/html
command:
- /bin/bash
- -c
- |
# Install dependencies
apt-get update && apt-get install -y \
git \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev \
mariadb-client \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd mysqli pdo pdo_mysql zip intl \
&& a2enmod rewrite
# Clone Wavelog if not exists
if [ ! -d /var/www/html/index.php ]; then
cd /tmp
git clone https://github.com/wavelog/wavelog.git
cp -R wavelog/* /var/www/html/
chown -R www-data:www-data /var/www/html
# Create database config
cat > /var/www/html/application/config/database.php << EOF
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
\$active_group = 'default';
\$query_builder = TRUE;
\$db['default'] = array(
'dsn' => '',
'hostname' => '${DB_HOST}',
'username' => '${DB_USER}',
'password' => '${DB_PASS}',
'database' => '${DB_NAME}',
'dbdriver' => 'mysqli',
'dbprefix' => '',
'pconnect' => FALSE,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8mb4',
'dbcollat' => 'utf8mb4_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
EOF
fi
# Start Apache
apache2-foreground
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: wavelog-data

View file

@ -0,0 +1,15 @@
Wavelog Database Credentials
============================
MariaDB Container: 301 on lab02
Host: 10.0.19.6
Port: 3306
Database: wavelog
Username: wavelog
Password: m4icNmGTRtOMgrNxsxEdrw
Note: Database needs to be created manually in MariaDB container 301.
The root password for MariaDB may be required.
Connection String:
mysql://wavelog:m4icNmGTRtOMgrNxsxEdrw@10.0.19.6:3306/wavelog

View file

@ -0,0 +1,82 @@
# Cluster-380 (w5isp.w5isp.com)
Single-node K3s cluster with Traefik ingress and automatic SSL.
## Architecture
- **Server**: w5isp.w5isp.com (204.110.191.200)
- **OS**: Debian 13
- **K3s**: Single-node cluster with Traefik enabled
- **Ingress**: Traefik with Let's Encrypt automatic SSL
- **Storage**: Longhorn distributed storage
## Setup
1. **Deploy the server** and install K3s using Ansible:
```bash
cd ansible
ansible-playbook -l w5isp.w5isp.com general.yml
```
2. **Deploy infrastructure components**:
```bash
cd home/cluster-380/infrastructure
./deploy-with-traefik.sh
```
3. **Deploy applications**:
```bash
kubectl apply -f ../apps/forgejo-traefik.yaml
```
## Services
### Traefik (Ingress Controller)
- Handles all HTTP/HTTPS traffic on port 80/443
- Automatic SSL certificates via Let's Encrypt
- Can handle TCP routing (e.g., SSH for Forgejo on port 2222)
### Example Application: Forgejo
- URL: https://git.w5isp.com
- SSH: git.w5isp.com:2222
- Automatic SSL certificate
## Adding New Services
Create an IngressRoute for automatic SSL:
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: myapp
namespace: myapp
spec:
entryPoints:
- websecure
routes:
- match: Host(`myapp.w5isp.com`)
kind: Rule
services:
- name: myapp
port: 80
tls:
certResolver: letsencrypt
```
For TCP services:
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: myapp-tcp
namespace: myapp
spec:
entryPoints:
- myapp-tcp # Define in traefik-config.yaml
routes:
- match: HostSNI(`*`)
services:
- name: myapp-tcp
port: 1234
```

View file

@ -0,0 +1,148 @@
# Forgejo deployment with Traefik ingress
---
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: forgejo
---
# PVC for Forgejo data using Longhorn
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: forgejo-data
namespace: forgejo
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 50Gi
---
# Main Forgejo Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo
namespace: forgejo
spec:
replicas: 1
selector:
matchLabels:
app: forgejo
template:
metadata:
labels:
app: forgejo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: forgejo
image: codeberg.org/forgejo/forgejo:1.21-rootless
ports:
- containerPort: 3000
name: http
- containerPort: 2222
name: ssh
env:
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"
- name: FORGEJO__database__DB_TYPE
value: sqlite3
- name: FORGEJO__server__ROOT_URL
value: https://git.w5isp.com/
- name: FORGEJO__server__DOMAIN
value: git.w5isp.com
- name: FORGEJO__server__SSH_DOMAIN
value: git.w5isp.com
- name: FORGEJO__server__SSH_PORT
value: "2222"
- name: FORGEJO__DEFAULT__APP_NAME
value: "W5ISP Git"
- name: FORGEJO__server__OFFLINE_MODE
value: "false"
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: data
persistentVolumeClaim:
claimName: forgejo-data
---
# Service for HTTP/HTTPS
apiVersion: v1
kind: Service
metadata:
name: forgejo-http
namespace: forgejo
spec:
type: ClusterIP
selector:
app: forgejo
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
---
# Service for SSH (direct TCP routing)
apiVersion: v1
kind: Service
metadata:
name: forgejo-ssh
namespace: forgejo
spec:
type: ClusterIP
selector:
app: forgejo
ports:
- name: ssh
port: 2222
targetPort: 2222
protocol: TCP
---
# IngressRoute for HTTPS with automatic SSL
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: forgejo
namespace: forgejo
spec:
entryPoints:
- websecure
routes:
- match: Host(`git.w5isp.com`)
kind: Rule
services:
- name: forgejo-http
port: 80
tls:
certResolver: letsencrypt
---
# IngressRouteTCP for SSH
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: forgejo-ssh
namespace: forgejo
spec:
entryPoints:
- ssh-forgejo
routes:
- match: HostSNI(`*`)
services:
- name: forgejo-ssh
port: 2222

View file

@ -0,0 +1,127 @@
# Complete Forgejo deployment for cluster-380
---
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: forgejo
---
# PVC for Forgejo data using Longhorn
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: forgejo-data
namespace: forgejo
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 50Gi
---
# Main Forgejo Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo
namespace: forgejo
spec:
replicas: 1
selector:
matchLabels:
app: forgejo
template:
metadata:
labels:
app: forgejo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: forgejo
image: codeberg.org/forgejo/forgejo:1.21-rootless
ports:
- containerPort: 3000
name: http
- containerPort: 2222
name: ssh
env:
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"
- name: FORGEJO__database__DB_TYPE
value: sqlite3
- name: FORGEJO__server__ROOT_URL
value: https://git.w5isp.com/
- name: FORGEJO__server__DOMAIN
value: git.w5isp.com
- name: FORGEJO__server__SSH_DOMAIN
value: git.w5isp.com
- name: FORGEJO__DEFAULT__APP_NAME
value: "W5ISP Git"
- name: FORGEJO__server__OFFLINE_MODE
value: "false"
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: data
persistentVolumeClaim:
claimName: forgejo-data
---
# Service for LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: forgejo
namespace: forgejo
spec:
type: LoadBalancer
selector:
app: forgejo
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
- name: ssh
port: 22
targetPort: 2222
protocol: TCP
---
# Ingress for HTTPS access
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: forgejo
namespace: forgejo
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- git.w5isp.com
secretName: forgejo-tls
rules:
- host: git.w5isp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: forgejo
port:
number: 80

View file

@ -0,0 +1,24 @@
#!/bin/bash
# Deploy script for cluster-380 with Traefik ingress
set -e
echo "Deploying system namespaces..."
kubectl apply -f ../system/namespaces.yaml
echo "Configuring Traefik..."
kubectl apply -f ../system/traefik-config.yaml
echo "Waiting for Traefik to be ready..."
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=traefik --timeout=90s
echo "Installing Longhorn v1.5.3..."
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml
echo "Waiting for Longhorn to be ready..."
kubectl -n longhorn-system wait --for=condition=ready pod -l app=longhorn-manager --timeout=180s
echo "Infrastructure components deployed successfully!"
echo ""
echo "You can now deploy applications using Traefik IngressRoutes for automatic SSL"
echo "Example: kubectl apply -f ../apps/forgejo-traefik.yaml"

View file

@ -0,0 +1,27 @@
#!/bin/bash
# Deploy script for cluster-380 infrastructure components
set -e
echo "Deploying system namespaces..."
kubectl apply -f ../system/namespaces.yaml
echo "Installing MetalLB v0.13.12..."
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
echo "Waiting for MetalLB to be ready..."
kubectl -n metallb-system wait --for=condition=ready pod -l app=metallb --timeout=90s
echo "Applying MetalLB configuration..."
kubectl apply -f ../system/metallb-config.yaml
echo "Installing Longhorn v1.5.3..."
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml
echo "Installing cert-manager v1.13.3..."
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
echo "Waiting for cert-manager to be ready..."
kubectl -n cert-manager wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager --timeout=90s
echo "Infrastructure components deployed successfully!"

View file

@ -0,0 +1,18 @@
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 204.110.191.200/32 # Using the single server IP for LoadBalancer services
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- default-pool

View file

@ -0,0 +1,33 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: metallb-system
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/warn: privileged
pod-security.kubernetes.io/audit: privileged
---
apiVersion: v1
kind: Namespace
metadata:
name: longhorn-system
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/warn: privileged
pod-security.kubernetes.io/audit: privileged
---
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
---
apiVersion: v1
kind: Namespace
metadata:
name: ingress-system
---
apiVersion: v1
kind: Namespace
metadata:
name: monitoring

View file

@ -0,0 +1,95 @@
---
# Traefik configuration for automatic SSL
apiVersion: v1
kind: ConfigMap
metadata:
name: traefik-config
namespace: kube-system
data:
traefik.yaml: |
global:
checkNewVersion: false
sendAnonymousUsage: false
api:
dashboard: true
debug: false
entryPoints:
web:
address: ":80"
http:
redirections:
entrypoint:
to: websecure
scheme: https
websecure:
address: ":443"
ssh-forgejo:
address: ":2222/tcp"
providers:
kubernetesIngress:
allowCrossNamespace: true
publishedService:
enabled: true
name: "kube-system/traefik"
kubernetesCRD:
allowCrossNamespace: true
certificatesResolvers:
letsencrypt:
acme:
email: postmaster@w5isp.com
storage: /data/acme.json
httpChallenge:
entryPoint: web
log:
level: INFO
---
# HelmChartConfig to apply Traefik configuration
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
deployment:
kind: DaemonSet
service:
type: LoadBalancer
spec:
loadBalancerIP: 204.110.191.200
ports:
web:
port: 80
expose: true
exposedPort: 80
protocol: TCP
websecure:
port: 443
expose: true
exposedPort: 443
protocol: TCP
ssh-forgejo:
port: 2222
expose: true
exposedPort: 2222
protocol: TCP
persistence:
enabled: true
storageClass: longhorn
size: 1Gi
additionalArguments:
- "--configfile=/config/traefik.yaml"
volumes:
- name: traefik-config
mountPath: /config
type: configMap

View file

@ -1,36 +1,59 @@
resource "dnsimple_zone_record" "mcintire_mx_10" { # Commented out ProtonMail MX records - now using Mailcow
# resource "dnsimple_zone_record" "mcintire_mx_10" {
# zone_name = "mcintire.me"
# name = "@"
# value = "mail.protonmail.ch."
# type = "MX"
# ttl = 3600
# priority = 10
# }
# resource "dnsimple_zone_record" "mcintire_mx_20" {
# zone_name = "mcintire.me"
# name = "@"
# value = "mailsec.protonmail.ch."
# type = "MX"
# ttl = 3600
# priority = 20
# }
# Mailcow MX record
resource "dnsimple_zone_record" "mcintire_mx_mailcow" {
zone_name = "mcintire.me" zone_name = "mcintire.me"
name = "@" name = ""
value = "mail.protonmail.ch."
type = "MX"
ttl = 3600
priority = 10 priority = 10
} value = "mail.w5isp.com"
resource "dnsimple_zone_record" "mcintire_mx_20" {
zone_name = "mcintire.me"
name = "@"
value = "mailsec.protonmail.ch."
type = "MX" type = "MX"
ttl = 3600 ttl = 3600
priority = 20
} }
resource "dnsimple_zone_record" "mcintire_txt_spf" { resource "dnsimple_zone_record" "mcintire_mx_backup" {
zone_name = "mcintire.me" zone_name = "mcintire.me"
name = "@" name = ""
value = "v=spf1 include:_spf.protonmail.ch ~all" priority = 20
type = "TXT" value = "mail.nsnw.ca"
type = "MX"
ttl = 3600 ttl = 3600
} }
resource "dnsimple_zone_record" "mcintire_txt_dmarc" { # Commented out ProtonMail SPF record - replaced with Mailcow SPF
zone_name = "mcintire.me" # resource "dnsimple_zone_record" "mcintire_txt_spf" {
name = "_dmarc" # zone_name = "mcintire.me"
value = "v=DMARC1; p=quarantine" # name = "@"
type = "TXT" # value = "v=spf1 include:_spf.protonmail.ch ~all"
} # type = "TXT"
# ttl = 3600
# }
# Commented out basic DMARC record - replaced with Mailcow DMARC
# resource "dnsimple_zone_record" "mcintire_txt_dmarc" {
# zone_name = "mcintire.me"
# name = "_dmarc"
# value = "v=DMARC1; p=quarantine"
# type = "TXT"
# }
# Keep ProtonMail verification record for now
resource "dnsimple_zone_record" "mcintire_txt_verification" { resource "dnsimple_zone_record" "mcintire_txt_verification" {
zone_name = "mcintire.me" zone_name = "mcintire.me"
name = "@" name = "@"
@ -77,3 +100,67 @@ resource "dnsimple_zone_record" "mcintire_cname_test" {
type = "CNAME" type = "CNAME"
ttl = 14400 ttl = 14400
} }
# Mail server
resource "dnsimple_zone_record" "mcintire_mail" {
zone_name = "mcintire.me"
name = "mail"
value = "204.110.191.216" # sync.w5isp.com - Mailcow server
type = "A"
ttl = 3600
}
# Autodiscover for mail clients (CNAME to mail server)
resource "dnsimple_zone_record" "mcintire_autodiscover" {
zone_name = "mcintire.me"
name = "autodiscover"
value = "mail.w5isp.com"
type = "CNAME"
ttl = 3600
}
# Autodiscover SRV record
resource "dnsimple_zone_record" "mcintire_autodiscover_srv" {
zone_name = "mcintire.me"
name = "_autodiscover._tcp"
value = "0 443 mail.w5isp.com"
type = "SRV"
priority = 0
ttl = 3600
}
# Autoconfig for mail clients
resource "dnsimple_zone_record" "mcintire_autoconfig" {
zone_name = "mcintire.me"
name = "autoconfig"
value = "mail.w5isp.com"
type = "CNAME"
ttl = 3600
}
# SPF record for Mailcow
resource "dnsimple_zone_record" "mcintire_spf_mailcow" {
zone_name = "mcintire.me"
name = ""
value = "v=spf1 mx a ip4:204.110.191.216 ~all"
type = "TXT"
ttl = 3600
}
# DMARC record for Mailcow
resource "dnsimple_zone_record" "mcintire_dmarc_mailcow" {
zone_name = "mcintire.me"
name = "_dmarc"
value = "v=DMARC1; p=quarantine; rua=mailto:postmaster@mcintire.me; ruf=mailto:postmaster@mcintire.me; fo=1"
type = "TXT"
ttl = 3600
}
# DKIM record from Mailcow
resource "dnsimple_zone_record" "mcintire_dkim_mailcow" {
zone_name = "mcintire.me"
name = "dkim._domainkey"
value = "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0rQie7Hz5AmDbVUX+rKNgp6Hf7crtWyfy6qKbqnrxmmernz1rQ6HyOrHhFAlc8nVLNKr8XP2DOROb1jAnrjndZo9I/ymbrrrCBsi9w6zAht2BheijO/R9k5CDRjeSafm6bg0oMKihtNMIdXvEj9ND8hDpZZKxtOrKFH7zLm4CPmm7jf0gqZH2yK+AA3OYUGSYa1OT0LtdMXadk0IVOI2VYf37Hzb3RUvGOYMVkT0xi+23DiyFk7AyCiyv3LQVBMT+/bwyd4k3yVVn2cr9+Nor+HXVjqASLtqcFLMf1SuX0ezSAjBG7BbqQr5G6Jz+n5YglqEIUKVYzJFE7JdDmqQYQIDAQAB"
type = "TXT"
ttl = 3600
}

View file

@ -75,7 +75,7 @@ resource "powerdns_record" "ptr_records4" {
for_each = { for_each = {
for i in range(256) : tostring(i) => { for i in range(256) : tostring(i) => {
name = "${i}.191.110.204.in-addr.arpa." name = "${i}.191.110.204.in-addr.arpa."
record = "${i}.191.client.vntx.net." record = tostring(i) == "216" ? "mail.w5isp.com." : "${i}.191.client.vntx.net."
} }
} }

View file

@ -1,7 +1,7 @@
resource "dnsimple_zone_record" "w5isp_root" { resource "dnsimple_zone_record" "w5isp_root" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "" name = ""
value = "204.110.191.212" # Points to Caddy proxy server value = "204.110.191.212" # Points to Caddy proxy server
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
@ -34,7 +34,7 @@ resource "dnsimple_zone_record" "w5isp_home" {
resource "dnsimple_zone_record" "w5isp_photos" { resource "dnsimple_zone_record" "w5isp_photos" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "photos" name = "photos"
value = "204.110.191.212" # Points to Caddy proxy server value = "204.110.191.8"
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
@ -58,7 +58,7 @@ resource "dnsimple_zone_record" "w5isp_ntfy" {
resource "dnsimple_zone_record" "w5isp_jellyfin" { resource "dnsimple_zone_record" "w5isp_jellyfin" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "jellyfin" name = "jellyfin"
value = "204.110.191.212" # Points to Caddy proxy server value = "204.110.191.212" # Points to Caddy proxy server
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
@ -66,7 +66,7 @@ resource "dnsimple_zone_record" "w5isp_jellyfin" {
resource "dnsimple_zone_record" "w5isp_wildcard" { resource "dnsimple_zone_record" "w5isp_wildcard" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "*" name = "*"
value = "204.110.191.212" # New Caddy proxy server value = "204.110.191.212" # New Caddy proxy server
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
@ -83,34 +83,26 @@ resource "dnsimple_zone_record" "w5isp_mx1" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "" name = ""
priority = 10 priority = 10
value = "mail.protonmail.ch" value = "mail.w5isp.com"
type = "MX" type = "MX"
ttl = 36000 ttl = 3600
}
resource "dnsimple_zone_record" "w5isp_mx2" {
zone_name = "w5isp.com"
name = ""
priority = 20
value = "mailsec.protonmail.ch"
type = "MX"
ttl = 36000
} }
resource "dnsimple_zone_record" "w5isp_txt" { resource "dnsimple_zone_record" "w5isp_txt" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "" name = ""
value = "v=spf1 include:_spf.protonmail.ch ~all" value = "v=spf1 mx a ip4:204.110.191.216 ~all"
type = "TXT" type = "TXT"
ttl = 36000 ttl = 3600
} }
resource "dnsimple_zone_record" "w5isp_txt2" { # DKIM record
resource "dnsimple_zone_record" "w5isp_dkim" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "" name = "dkim._domainkey"
value = "protonmail-verification=be3f91ded714b341c2276128270127cb59aa6760" value = "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxti2t8TvzIqKF9BpKn3rQExKg3sS1SyQuwtQgL0r/ELEJ0D9cKU/iM+eB1ROJctKHoMqKDJoRCMP6eibFcgq2kLKqf+aceEOIBx2OK2WCXML+CNqQZA6yO+A8/Jq7iFZPq8D5FmOoxbwRZkso7CkSennSz/+F7nBPI/OfyEiiI4xJzWH3t8SaAkkcy46O+1K0iCkTpsthon7E2PHa3SPkrjbep/5NImTJLK5LuffiLJtLsiK+73mvsAYCDmrNxPTaDjXkj0TWdKl/d/TnlVJl+YloqWIDt/7LtZQM0C7GZ9flIr7z9hHpSWERXAA5Gj5NQ0/hcY3nF4dPoqWs3VeeQIDAQAB"
type = "TXT" type = "TXT"
ttl = 36000 ttl = 3600
} }
resource "dnsimple_zone_record" "w5isp_g" { resource "dnsimple_zone_record" "w5isp_g" {
@ -153,19 +145,19 @@ resource "dnsimple_zone_record" "w5isp_sendgrid3" {
ttl = 3600 ttl = 3600
} }
resource "dnsimple_zone_record" "w5isp_sendgrid4" { resource "dnsimple_zone_record" "w5isp_dmarc" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "_dmarc" name = "_dmarc"
value = "v=DMARC1; p=none;" value = "v=DMARC1; p=quarantine; rua=mailto:postmaster@w5isp.com; ruf=mailto:postmaster@w5isp.com; fo=1"
type = "TXT" type = "TXT"
ttl = 36000 ttl = 3600
} }
# Caddy proxy server # Caddy proxy server
resource "dnsimple_zone_record" "w5isp_caddy" { resource "dnsimple_zone_record" "w5isp_caddy" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "caddy" name = "caddy"
value = "204.110.191.212" # New Caddy proxy server value = "204.110.191.212" # New Caddy proxy server
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
@ -174,8 +166,57 @@ resource "dnsimple_zone_record" "w5isp_caddy" {
resource "dnsimple_zone_record" "w5isp_git" { resource "dnsimple_zone_record" "w5isp_git" {
zone_name = "w5isp.com" zone_name = "w5isp.com"
name = "git" name = "git"
value = "204.110.191.212" # Points to Caddy proxy server value = "204.110.191.212" # Points to Caddy proxy server
type = "A" type = "A"
ttl = 3600 ttl = 3600
} }
# Mail server
resource "dnsimple_zone_record" "w5isp_mail" {
zone_name = "w5isp.com"
name = "mail"
value = "204.110.191.216" # sync.w5isp.com - Mailcow server
type = "A"
ttl = 3600
}
# W5ISP server - single-node K3s
resource "dnsimple_zone_record" "w5isp_server" {
zone_name = "w5isp.com"
name = "w5isp"
value = "204.110.191.200"
type = "A"
ttl = 3600
}
# Autodiscover for mail clients
resource "dnsimple_zone_record" "w5isp_autodiscover" {
zone_name = "w5isp.com"
name = "autodiscover"
value = "mail.w5isp.com"
type = "CNAME"
ttl = 3600
}
# Autodiscover SRV record
resource "dnsimple_zone_record" "w5isp_autodiscover_srv" {
zone_name = "w5isp.com"
name = "_autodiscover._tcp"
value = "0 443 mail.w5isp.com"
type = "SRV"
priority = 0
ttl = 3600
}
# Autoconfig for mail clients
resource "dnsimple_zone_record" "w5isp_autoconfig" {
zone_name = "w5isp.com"
name = "autoconfig"
value = "mail.w5isp.com"
type = "CNAME"
ttl = 3600
}
# TLSA record for SMTP on port 25 - DNSimple doesn't support TLSA records
# You'll need to add this manually through a different DNS provider if needed:
# _25._tcp.mail.w5isp.com TLSA 3 1 1 885717a52f0e26cdde481d2d44110c3374ff3b7977626afe2f952b655358308a

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
token_secret = "1cdd961f-5ee7-4110-8806-e72357a2226f" token_secret = "1cdd961f-5ee7-4110-8806-e72357a2226f"
token_id = "terraform@pve!terraform-token" token_id = "terraform@pve!terraform-token"