Add initial Ansible configuration and playbooks for homelab setup
- Created ansible.cfg for configuration settings - Added inventory.yml for host definitions - Implemented bootstrap playbook for Minisforum setup - Developed setup playbook for K3s installation - Defined common role with user and package management tasks - Established K3s server role with configuration and installation tasks - Included Traefik Helm values for ingress management
This commit is contained in:
parent
891588c202
commit
f33fdc4044
4
ansible.cfg
Normal file
4
ansible.cfg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[defaults]
|
||||||
|
inventory = ansible/inventory.yml
|
||||||
|
roles_path = ansible/roles
|
||||||
|
host_key_checking = False
|
||||||
17
ansible/inventory.yml
Normal file
17
ansible/inventory.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
all:
|
||||||
|
vars:
|
||||||
|
ansible_user: nik
|
||||||
|
ansible_ssh_private_key_file: ~/.ssh/id_ed25519-nik-macbookair
|
||||||
|
|
||||||
|
children:
|
||||||
|
k3s_server:
|
||||||
|
hosts:
|
||||||
|
minisforum:
|
||||||
|
ansible_host: 192.168.7.77
|
||||||
|
ansible_port: 430
|
||||||
|
|
||||||
|
k3s_agents:
|
||||||
|
hosts:
|
||||||
|
# debian will be added here in Phase 2
|
||||||
|
# debian:
|
||||||
|
# ansible_host: 192.168.7.X
|
||||||
18
ansible/playbooks/bootstrap-minisforum.yml
Normal file
18
ansible/playbooks/bootstrap-minisforum.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
# Run: ansible-playbook -i ansible/inventory.yml ansible/playbooks/bootstrap-minisforum.yml
|
||||||
|
# Requires: SSH access to 192.168.7.7 as root (or a user with NOPASSWD sudo)
|
||||||
|
#
|
||||||
|
# What this does:
|
||||||
|
# - Creates the 'nik' user with sudo access
|
||||||
|
# - Hardens SSH (no password auth, no root login)
|
||||||
|
# - Installs base packages
|
||||||
|
# - Configures UFW firewall
|
||||||
|
# - Creates /data/* directories for persistent volumes
|
||||||
|
|
||||||
|
- name: Bootstrap Minisforum
|
||||||
|
hosts: minisforum
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- common
|
||||||
27
ansible/playbooks/setup-k3s.yml
Normal file
27
ansible/playbooks/setup-k3s.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
# Run: ansible-playbook -i ansible/inventory.yml ansible/playbooks/setup-k3s.yml
|
||||||
|
#
|
||||||
|
# What this does:
|
||||||
|
# - Installs K3s in server mode (with Traefik disabled)
|
||||||
|
# - Installs Helm
|
||||||
|
# - Fetches kubeconfig to /tmp/k3s-minisforum.yaml on your workstation
|
||||||
|
# - Labels the node as node-role=primary
|
||||||
|
#
|
||||||
|
# After this playbook:
|
||||||
|
# export KUBECONFIG=/tmp/k3s-minisforum.yaml
|
||||||
|
# kubectl get nodes # should show minisforum as Ready
|
||||||
|
#
|
||||||
|
# Then deploy Traefik:
|
||||||
|
# helm repo add traefik https://helm.traefik.io/traefik
|
||||||
|
# helm repo update
|
||||||
|
# helm upgrade --install traefik traefik/traefik \
|
||||||
|
# --namespace traefik --create-namespace \
|
||||||
|
# -f values/traefik.yml
|
||||||
|
|
||||||
|
- name: Install K3s server
|
||||||
|
hosts: minisforum
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- k3s-server
|
||||||
31
ansible/roles/common/defaults/main.yml
Normal file
31
ansible/roles/common/defaults/main.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
username: nik
|
||||||
|
timezone: Asia/Tokyo
|
||||||
|
|
||||||
|
base_packages:
|
||||||
|
- curl
|
||||||
|
- git
|
||||||
|
- htop
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- unzip
|
||||||
|
- ca-certificates
|
||||||
|
- gnupg
|
||||||
|
- lsb-release
|
||||||
|
- nfs-common # needed in Phase 4 for Jellyfin NFS mount from Debian
|
||||||
|
|
||||||
|
ufw_allowed_ports:
|
||||||
|
- { port: 430, proto: tcp, comment: SSH }
|
||||||
|
- { port: 80, proto: tcp, comment: HTTP }
|
||||||
|
- { port: 443, proto: tcp, comment: HTTPS }
|
||||||
|
- { port: 6443, proto: tcp, comment: K3s API server }
|
||||||
|
- { port: 10250, proto: tcp, comment: Kubelet }
|
||||||
|
- { port: 8472, proto: udp, comment: Flannel VXLAN }
|
||||||
|
|
||||||
|
data_dirs:
|
||||||
|
- /data/gitea
|
||||||
|
- /data/jellyfin
|
||||||
|
- /data/pihole
|
||||||
|
- /data/dashy
|
||||||
|
- /data/glances
|
||||||
|
- /data/traefik
|
||||||
5
ansible/roles/common/handlers/main.yml
Normal file
5
ansible/roles/common/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
- name: Restart sshd
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: sshd
|
||||||
|
state: restarted
|
||||||
73
ansible/roles/common/tasks/main.yml
Normal file
73
ansible/roles/common/tasks/main.yml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
- name: Set timezone
|
||||||
|
community.general.timezone:
|
||||||
|
name: "{{ timezone }}"
|
||||||
|
|
||||||
|
- name: Install base packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ base_packages }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: Create primary user
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ username }}"
|
||||||
|
groups: sudo
|
||||||
|
shell: /bin/bash
|
||||||
|
create_home: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Set up authorized SSH key for user
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ username }}"
|
||||||
|
state: present
|
||||||
|
key: "{{ lookup('file', '~/.ssh/id_ed25519-nik-macbookair.pub') }}"
|
||||||
|
|
||||||
|
- name: Harden SSH — disable password auth
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/ssh/sshd_config
|
||||||
|
regexp: "{{ item.regexp }}"
|
||||||
|
line: "{{ item.line }}"
|
||||||
|
state: present
|
||||||
|
loop:
|
||||||
|
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
|
||||||
|
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
|
||||||
|
- { regexp: '^#?PubkeyAuthentication', line: 'PubkeyAuthentication yes' }
|
||||||
|
- { regexp: '^#?Port ', line: 'Port 430' }
|
||||||
|
notify: Restart sshd
|
||||||
|
|
||||||
|
- name: Install UFW
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: ufw
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Set UFW default deny incoming
|
||||||
|
community.general.ufw:
|
||||||
|
default: deny
|
||||||
|
direction: incoming
|
||||||
|
|
||||||
|
- name: Set UFW default allow outgoing
|
||||||
|
community.general.ufw:
|
||||||
|
default: allow
|
||||||
|
direction: outgoing
|
||||||
|
|
||||||
|
- name: Allow required ports
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ item.port }}"
|
||||||
|
proto: "{{ item.proto }}"
|
||||||
|
comment: "{{ item.comment }}"
|
||||||
|
loop: "{{ ufw_allowed_ports }}"
|
||||||
|
|
||||||
|
- name: Enable UFW
|
||||||
|
community.general.ufw:
|
||||||
|
state: enabled
|
||||||
|
|
||||||
|
- name: Create persistent data directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ username }}"
|
||||||
|
group: "{{ username }}"
|
||||||
|
mode: "0755"
|
||||||
|
loop: "{{ data_dirs }}"
|
||||||
14
ansible/roles/k3s-server/defaults/main.yml
Normal file
14
ansible/roles/k3s-server/defaults/main.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
k3s_version: v1.32.2+k3s1 # pin to a specific version; update deliberately
|
||||||
|
k3s_server_ip: 192.168.7.77
|
||||||
|
|
||||||
|
# Written to /etc/rancher/k3s/config.yaml on the server
|
||||||
|
k3s_server_config:
|
||||||
|
disable:
|
||||||
|
- traefik # we deploy Traefik ourselves via Helm
|
||||||
|
flannel-backend: vxlan
|
||||||
|
node-ip: "{{ k3s_server_ip }}"
|
||||||
|
tls-san:
|
||||||
|
- "{{ k3s_server_ip }}"
|
||||||
|
- minisforum
|
||||||
|
- minisforum.local
|
||||||
68
ansible/roles/k3s-server/tasks/main.yml
Normal file
68
ansible/roles/k3s-server/tasks/main.yml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
- name: Create K3s config directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/rancher/k3s
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Write K3s server config
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/rancher/k3s/config.yaml
|
||||||
|
content: "{{ k3s_server_config | to_nice_yaml }}"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Download and install K3s
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: >
|
||||||
|
curl -sfL https://get.k3s.io |
|
||||||
|
INSTALL_K3S_VERSION={{ k3s_version }}
|
||||||
|
sh -
|
||||||
|
creates: /usr/local/bin/k3s # skip if already installed
|
||||||
|
|
||||||
|
- name: Wait for K3s to be ready
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
path: /etc/rancher/k3s/k3s.yaml
|
||||||
|
timeout: 60
|
||||||
|
|
||||||
|
- name: Ensure K3s service is running
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: k3s
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Read node token
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /var/lib/rancher/k3s/server/node-token
|
||||||
|
register: k3s_token_raw
|
||||||
|
|
||||||
|
- name: Save node token as fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
k3s_node_token: "{{ k3s_token_raw['content'] | b64decode | trim }}"
|
||||||
|
|
||||||
|
- name: Print node token (needed for Phase 2 agent join)
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "K3s node token: {{ k3s_node_token }}"
|
||||||
|
|
||||||
|
- name: Fetch kubeconfig to workstation
|
||||||
|
ansible.builtin.fetch:
|
||||||
|
src: /etc/rancher/k3s/k3s.yaml
|
||||||
|
dest: /tmp/k3s-minisforum.yaml
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
- name: Fix kubeconfig server address
|
||||||
|
ansible.builtin.replace:
|
||||||
|
path: /tmp/k3s-minisforum.yaml
|
||||||
|
regexp: 'https://127\.0\.0\.1:6443'
|
||||||
|
replace: "https://{{ k3s_server_ip }}:6443"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
|
creates: /usr/local/bin/helm
|
||||||
|
|
||||||
|
- name: Label server node as primary
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: k3s kubectl label node minisforum node-role=primary --overwrite
|
||||||
|
changed_when: false # label is idempotent but shell module always reports changed
|
||||||
88
values/traefik.yml
Normal file
88
values/traefik.yml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Traefik Helm values — Phase 1
|
||||||
|
# Chart: traefik/traefik
|
||||||
|
# Deploy:
|
||||||
|
# helm repo add traefik https://helm.traefik.io/traefik
|
||||||
|
# helm repo update
|
||||||
|
# helm upgrade --install traefik traefik/traefik \
|
||||||
|
# --namespace traefik --create-namespace \
|
||||||
|
# -f values/traefik.yml
|
||||||
|
|
||||||
|
globalArguments:
|
||||||
|
- "--global.checknewversion=false"
|
||||||
|
- "--global.sendanonymoususage=false"
|
||||||
|
|
||||||
|
additionalArguments:
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.email=nik@nik4nao.xyz"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.storage=/data/traefik/acme.json"
|
||||||
|
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ":80"
|
||||||
|
http:
|
||||||
|
redirections:
|
||||||
|
entryPoint:
|
||||||
|
to: websecure
|
||||||
|
scheme: https
|
||||||
|
websecure:
|
||||||
|
address: ":443"
|
||||||
|
|
||||||
|
ingressClass:
|
||||||
|
enabled: true
|
||||||
|
isDefaultIngressClass: true
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: LoadBalancer
|
||||||
|
# K3s includes ServiceLB (klipper) — it will bind this to the node's IP automatically
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
existingClaim: ""
|
||||||
|
storageClass: ""
|
||||||
|
path: /data/traefik
|
||||||
|
size: 128Mi
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: traefik-data
|
||||||
|
hostPath:
|
||||||
|
path: /data/traefik
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
|
||||||
|
volumeMounts:
|
||||||
|
- name: traefik-data
|
||||||
|
mountPath: /data/traefik
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
replicas: 1
|
||||||
|
# Pin to Minisforum (primary node)
|
||||||
|
# Remove this section in Phase 2 once you have a multi-node cluster
|
||||||
|
# and only want Traefik on the server node
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: node-role
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- primary
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
enabled: true
|
||||||
|
# Accessible internally at http://traefik.192.168.7.7.nip.io or via IngressRoute
|
||||||
|
# Do NOT expose the dashboard externally
|
||||||
|
ingressRoute:
|
||||||
|
dashboard:
|
||||||
|
enabled: true
|
||||||
|
matchRule: Host(`traefik.home.arpa`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
# Add BasicAuth middleware here if you want dashboard password protection
|
||||||
|
|
||||||
|
logs:
|
||||||
|
general:
|
||||||
|
level: INFO
|
||||||
|
access:
|
||||||
|
enabled: true
|
||||||
Loading…
x
Reference in New Issue
Block a user