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:
Nik Afiq 2026-03-05 18:55:41 +09:00
parent 891588c202
commit f33fdc4044
10 changed files with 345 additions and 0 deletions

4
ansible.cfg Normal file
View File

@ -0,0 +1,4 @@
[defaults]
inventory = ansible/inventory.yml
roles_path = ansible/roles
host_key_checking = False

17
ansible/inventory.yml Normal file
View 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

View 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

View 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

View 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

View File

@ -0,0 +1,5 @@
---
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted

View 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 }}"

View 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

View 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
View 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