This commit is contained in:
Graham McIntire 2026-02-05 12:46:51 -06:00
parent 7d8ef39291
commit 36265b9da7
No known key found for this signature in database
18 changed files with 497 additions and 3 deletions

View file

@ -18,7 +18,7 @@ mcintire_me_zone:
ip: 107.174.178.20
- name: photos
ip: 204.110.191.8
- name: "pangolin"
- name: git
ip: 172.245.56.83
other_name_servers:
- autodiscover.mcintire.me. IN CNAME mail.w5isp.com.

View file

@ -26,8 +26,6 @@ w5isp_com_zone:
ip: 204.110.191.218
- name: "vm1"
ip: 172.245.56.83
- name: "pangolin"
ip: 172.245.56.83
- name: "*"
ip: 172.245.56.83
other_name_servers:

View file

@ -6,6 +6,10 @@ ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
public_ip: 172.245.56.83
dns_backend: bind9
# Forgejo runner
forgejo_runner_token: "I7rqgwumFl3BjuXnaEfroto7RHxGhsIUzM0lgMmM"
forgejo_runner_name: "vm1-runner"
# This server is a secondary/slave nameserver
bind_zone_role: secondary

View file

@ -28,4 +28,7 @@ vm1.w5isp.com ansible_host=172.245.56.83
[dns_servers:children]
bind9_servers
[forgejo_servers]
vm1.w5isp.com
[postgresql_servers]

View file

@ -287,3 +287,12 @@
# tags: syncthing
# roles:
# - syncthing
- name: Configure Forgejo servers
hosts: forgejo_servers
become: yes
gather_facts: true
tags:
- forgejo
roles:
- forgejo

View file

@ -0,0 +1,89 @@
---
# One-time playbook to regenerate SSH host keys on existing hosts
# Usage: ansible-playbook -l "vm1,pangolin,camper,birdnet" regenerate-ssh-keys.yml
#
# After running this playbook, you'll need to update your local known_hosts:
# ssh-keygen -R hostname
# Or clear all affected entries and re-accept on next connection.
- name: Regenerate SSH host keys on existing hosts
hosts: all
become: true
gather_facts: true
tasks:
- name: Remove existing SSH host keys
file:
path: "{{ item }}"
state: absent
loop:
- /etc/ssh/ssh_host_rsa_key
- /etc/ssh/ssh_host_rsa_key.pub
- /etc/ssh/ssh_host_ecdsa_key
- /etc/ssh/ssh_host_ecdsa_key.pub
- /etc/ssh/ssh_host_ed25519_key
- /etc/ssh/ssh_host_ed25519_key.pub
- /etc/ssh/ssh_host_dsa_key
- /etc/ssh/ssh_host_dsa_key.pub
- name: Regenerate SSH host keys (Debian/Ubuntu)
command: dpkg-reconfigure openssh-server
when: ansible_os_family == "Debian"
- name: Regenerate SSH host keys (RHEL/CentOS)
shell: |
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
when: ansible_os_family == "RedHat"
- name: Regenerate SSH host keys (Alpine)
shell: |
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
when: ansible_os_family == "Alpine"
- name: Set correct permissions on private keys
file:
path: "{{ item }}"
mode: '0600'
loop:
- /etc/ssh/ssh_host_rsa_key
- /etc/ssh/ssh_host_ecdsa_key
- /etc/ssh/ssh_host_ed25519_key
ignore_errors: true
- name: Set correct permissions on public keys
file:
path: "{{ item }}"
mode: '0644'
loop:
- /etc/ssh/ssh_host_rsa_key.pub
- /etc/ssh/ssh_host_ecdsa_key.pub
- /etc/ssh/ssh_host_ed25519_key.pub
ignore_errors: true
- name: Restart SSH service
service:
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
state: restarted
- name: Create marker file to indicate keys were regenerated
file:
path: /etc/ssh/.host_keys_regenerated
state: touch
mode: '0644'
- name: Display new SSH host key fingerprints
shell: |
echo "=== New SSH Host Key Fingerprints for {{ inventory_hostname }} ==="
for key in /etc/ssh/ssh_host_*_key.pub; do
ssh-keygen -lf "$key" 2>/dev/null || true
done
register: fingerprints
changed_when: false
- name: Show fingerprints
debug:
msg: "{{ fingerprints.stdout_lines }}"

View file

@ -1,6 +1,63 @@
---
# System configuration tasks
# SSH host key regeneration for cloned VMs
# Only runs once per host (marker file prevents re-running)
- name: Check if SSH host keys have been regenerated
stat:
path: /etc/ssh/.host_keys_regenerated
register: ssh_keys_marker
- name: Regenerate SSH host keys (first boot after clone)
when: not ssh_keys_marker.stat.exists
block:
- name: Remove existing SSH host keys
file:
path: "{{ item }}"
state: absent
loop:
- /etc/ssh/ssh_host_rsa_key
- /etc/ssh/ssh_host_rsa_key.pub
- /etc/ssh/ssh_host_ecdsa_key
- /etc/ssh/ssh_host_ecdsa_key.pub
- /etc/ssh/ssh_host_ed25519_key
- /etc/ssh/ssh_host_ed25519_key.pub
- /etc/ssh/ssh_host_dsa_key
- /etc/ssh/ssh_host_dsa_key.pub
- name: Regenerate SSH host keys
command: dpkg-reconfigure openssh-server
args:
creates: /etc/ssh/ssh_host_ed25519_key
when: ansible_os_family == "Debian"
notify: restart ssh
- name: Regenerate SSH host keys (RHEL)
shell: |
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
args:
creates: /etc/ssh/ssh_host_ed25519_key
when: ansible_os_family == "RedHat"
notify: restart ssh
- name: Regenerate SSH host keys (Alpine)
shell: |
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
args:
creates: /etc/ssh/ssh_host_ed25519_key
when: ansible_os_family == "Alpine"
notify: restart ssh
- name: Create marker file to prevent re-running
file:
path: /etc/ssh/.host_keys_regenerated
state: touch
mode: '0644'
- name: Install SSH banner
template:
src: issue.net.j2

View file

@ -0,0 +1,16 @@
---
forgejo_home: /opt/forgejo
forgejo_version: "10"
forgejo_http_port: 3000
forgejo_ssh_port: 2222
forgejo_domain: git.mcintire.me
forgejo_root_url: "https://{{ forgejo_domain }}"
# App settings
forgejo_app_name: "Forgejo"
# Runner settings
forgejo_runner_version: "6.2.2"
forgejo_runner_token: ""
forgejo_runner_name: "default-runner"
forgejo_runner_labels: "docker:docker://node:20-bookworm,ubuntu-latest:docker://ubuntu:latest,ubuntu-22.04:docker://ubuntu:22.04"

View file

@ -0,0 +1,11 @@
---
- name: restart forgejo
systemd:
name: forgejo
state: restarted
daemon_reload: true
- name: restart caddy
systemd:
name: caddy
state: reloaded

View file

@ -0,0 +1,152 @@
---
# Install Docker
- name: Install prerequisites for Docker
apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
- git
state: present
update_cache: true
- name: Add Docker's official GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/debian/gpg
dest: /usr/share/keyrings/docker-archive-keyring.asc
mode: '0644'
- name: Add Docker repository
ansible.builtin.deb822_repository:
name: docker
types: deb
uris: https://download.docker.com/linux/debian
suites: "{{ ansible_distribution_release }}"
components: stable
architectures: "{{ ansible_architecture | replace('x86_64', 'amd64') }}"
signed_by: /usr/share/keyrings/docker-archive-keyring.asc
state: present
- name: Install Docker packages
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
- name: Ensure Docker service is started and enabled
systemd:
name: docker
state: started
enabled: true
# Create Forgejo directories
- name: Create forgejo directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ forgejo_home }}"
- "{{ forgejo_home }}/data"
- name: Create runner directory with correct permissions
file:
path: "{{ forgejo_home }}/runner"
state: directory
mode: '0777'
- name: Deploy docker-compose file
template:
src: docker-compose.yml.j2
dest: "{{ forgejo_home }}/docker-compose.yml"
mode: '0644'
notify: restart forgejo
- name: Pull container images
command: docker compose pull
args:
chdir: "{{ forgejo_home }}"
register: pull_result
changed_when: "'Pulling' in pull_result.stdout or 'Downloaded' in pull_result.stderr"
- name: Deploy systemd service for Forgejo
template:
src: forgejo.service.j2
dest: /etc/systemd/system/forgejo.service
owner: root
group: root
mode: '0644'
notify: restart forgejo
- name: Reload systemd daemon
systemd:
daemon_reload: true
- name: Start and enable Forgejo service
systemd:
name: forgejo
state: started
enabled: true
# Install and configure Caddy
- name: Check if Caddy GPG key exists
stat:
path: /usr/share/keyrings/caddy-stable-archive-keyring.asc
register: caddy_gpg_key
- name: Add Caddy GPG key
ansible.builtin.get_url:
url: "https://dl.cloudsmith.io/public/caddy/stable/gpg.key"
dest: /usr/share/keyrings/caddy-stable-archive-keyring.asc
mode: '0644'
when: not caddy_gpg_key.stat.exists
- name: Add Caddy repository
ansible.builtin.deb822_repository:
name: caddy-stable
types: deb
uris: https://dl.cloudsmith.io/public/caddy/stable/deb/debian
suites: any-version
components: main
signed_by: /usr/share/keyrings/caddy-stable-archive-keyring.asc
state: present
- name: Install Caddy
apt:
name: caddy
state: present
update_cache: true
- name: Configure Caddy for Forgejo
template:
src: Caddyfile.j2
dest: /etc/caddy/Caddyfile
owner: root
group: root
mode: '0644'
notify: restart caddy
- name: Ensure Caddy is started and enabled
systemd:
name: caddy
state: started
enabled: true
- name: Wait for Forgejo to be ready
uri:
url: "http://localhost:{{ forgejo_http_port }}/"
method: GET
status_code:
- 200
- 302
validate_certs: false
timeout: 30
register: result
until: result.status in [200, 302]
retries: 12
delay: 5

View file

@ -0,0 +1,3 @@
{{ forgejo_domain }} {
reverse_proxy localhost:{{ forgejo_http_port }}
}

View file

@ -0,0 +1,37 @@
---
services:
forgejo:
image: codeberg.org/forgejo/forgejo:{{ forgejo_version }}
container_name: forgejo
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO__server__DOMAIN={{ forgejo_domain }}
- FORGEJO__server__SSH_DOMAIN={{ forgejo_domain }}
- FORGEJO__server__ROOT_URL={{ forgejo_root_url }}
- FORGEJO__server__SSH_PORT={{ forgejo_ssh_port }}
- FORGEJO__server__SSH_LISTEN_PORT=22
volumes:
- {{ forgejo_home }}/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:{{ forgejo_http_port }}:3000"
- "{{ forgejo_ssh_port }}:22"
runner:
image: code.forgejo.org/forgejo/runner:{{ forgejo_runner_version }}
container_name: forgejo-runner
restart: unless-stopped
user: root
depends_on:
- forgejo
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- {{ forgejo_home }}/runner:/data
- /var/run/docker.sock:/var/run/docker.sock
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
command: ["sh", "-c", "if [ ! -f /data/.runner ]; then forgejo-runner register --no-interactive --instance {{ forgejo_root_url }} --token {{ forgejo_runner_token }} --name {{ forgejo_runner_name }} --labels '{{ forgejo_runner_labels }}'; fi; forgejo-runner daemon"]

View file

@ -0,0 +1,15 @@
[Unit]
Description=Forgejo Git Service
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ forgejo_home }}
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

15
cluster/cloudflared/deploy.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
echo "Deploying cloudflared to Talos cluster..."
kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
echo "Waiting for rollout..."
kubectl rollout status deployment/cloudflared -n cloudflared --timeout=120s
echo ""
echo "Cloudflared deployed successfully!"
kubectl get pods -n cloudflared

View file

@ -0,0 +1,50 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: cloudflared
spec:
replicas: 2
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
imagePullPolicy: IfNotPresent
args:
- tunnel
- --no-autoupdate
- --metrics
- 0.0.0.0:2000
- run
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: tunnel-token
key: token
ports:
- name: metrics
containerPort: 2000
protocol: TCP
livenessProbe:
httpGet:
path: /ready
port: 2000
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
restartPolicy: Always

View file

@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: cloudflared
labels:
pod-security.kubernetes.io/enforce: privileged

View file

@ -0,0 +1,9 @@
---
apiVersion: v1
kind: Secret
metadata:
name: tunnel-token
namespace: cloudflared
type: Opaque
stringData:
token: "eyJhIjoiNGQxODE0OGE4MmIwYTkxZjI4OWFmYmJkMjk0NGE2MDMiLCJ0IjoiZDNkODE5YmEtOTI1Yy00ZDg0LTgwNTItNjhlZDMyYjY4NmJiIiwicyI6IlkyVTBaRGd3WlRVdFpUSXdNQzAwTkdNMkxXSmtOMk10TVdKaFpXSTBNR1l6TlRVNCJ9"

19
talos/kubeconfig Normal file
View file

@ -0,0 +1,19 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpekNDQVRDZ0F3SUJBZ0lSQU5pSitIN3JtY0VEa3AxZ1BZMEpSVWt3Q2dZSUtvWkl6ajBFQXdJd0ZURVQKTUJFR0ExVUVDaE1LYTNWaVpYSnVaWFJsY3pBZUZ3MHlOakF4TVRReE56VXlNakphRncwek5qQXhNVEl4TnpVeQpNakphTUJVeEV6QVJCZ05WQkFvVENtdDFZbVZ5Ym1WMFpYTXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CCkJ3TkNBQVRRMUpQeWl2bjkzc0FHSUhTUzJBN0VnQTI2N3ZJM0pWcC9rMWZQWnFNRnpmNHlmRHBwTUQ5cG42SEQKWUx2SjkyazNGRkRoYXh2UDZIUTRRQVV3UW5Mcm8yRXdYekFPQmdOVkhROEJBZjhFQkFNQ0FvUXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWURWUjBPCkJCWUVGTXhodGtYOHZUbEtGMkJ4THBqUXhJZGRuNzJuTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDSVFDeGNaTTQKZ09RTHplU1BrSy9HTnZaV3pueTBoSXM0ZmUrYVJOMENhN1A2UndJaEFJSHc0Ni9EaDJtTWZLQm9GS3hmTnJWTQpiaVNoekF1QzQ2THdVZXJyNmNBcQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://10.0.15.1:6443
name: home-cluster
contexts:
- context:
cluster: home-cluster
namespace: default
user: admin@home-cluster
name: admin@home-cluster
current-context: admin@home-cluster
kind: Config
users:
- name: admin@home-cluster
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJoVENDQVNxZ0F3SUJBZ0lRTGJBMXhGT0NPa2lkbmhnaCtkb3o5VEFLQmdncWhrak9QUVFEQWpBVk1STXcKRVFZRFZRUUtFd3ByZFdKbGNtNWxkR1Z6TUI0WERUSTJNREV6TVRJeE1Ua3hPRm9YRFRJM01ERXpNVEl4TVRreQpPRm93S1RFWE1CVUdBMVVFQ2hNT2MzbHpkR1Z0T20xaGMzUmxjbk14RGpBTUJnTlZCQU1UQldGa2JXbHVNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa2pCaTRiMXl0aUd4cFpJcDVSQVhtYXlCZDU5SlhqWGsKWllRbWI4b0svL2dDeE4yaHFvUCt4dmM1dC9DVXdtTVZKQkFod2FuRnhHalA3TkplMGNYZ3BLTklNRVl3RGdZRApWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWRKUVFNTUFvR0NDc0dBUVVGQndNQ01COEdBMVVkSXdRWU1CYUFGTXhoCnRrWDh2VGxLRjJCeExwalF4SWRkbjcybk1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ1NJdVZ0VTYzaWowRG0KSU5ML01QZEdjY2t6WTlDMURLUGw4bytObStjVmJRSWhBT2orQnB6NUo1ZThub2pNMXI2aHFnRWlpYjF6Y1ZpSwpuaXVPZjYrNFBPU0cKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUVMeHprQzVIMHBLVkxHVU93RmpoaDBPeHdZWFN2R2lIRktIbmF1L09nZllvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFa2pCaTRiMXl0aUd4cFpJcDVSQVhtYXlCZDU5SlhqWGtaWVFtYjhvSy8vZ0N4TjJocW9QKwp4dmM1dC9DVXdtTVZKQkFod2FuRnhHalA3TkplMGNYZ3BBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=