From 9bef381265d982319d8ce29f8e8cef91fdbad696 Mon Sep 17 00:00:00 2001 From: Sho Ishida Date: Wed, 15 Jul 2026 07:49:29 +0000 Subject: [PATCH] Deploy initial playbook --- ansible/my_inventory/group_vars/all.yml | 8 +- ansible/my_inventory/group_vars/vms/vars.yml | 7 + ansible/my_inventory/group_vars/vms/vault.yml | 30 +++ ansible/my_inventory/hosts | 12 + ansible/playbooks/01_host_prep.yml | 28 +++ ansible/playbooks/02_kvm_setup.yml | 33 +++ ansible/playbooks/03_freeipa_install.yml | 64 ++++++ ansible/playbooks/04_services_deploy.yml | 217 ++++++++++++++++++ .../playbooks/templates/hamster.service.j2 | 18 ++ .../playbooks/templates/minecraft.service.j2 | 20 ++ .../playbooks/templates/navidrome.service.j2 | 23 ++ .../playbooks/templates/palworld.service.j2 | 20 ++ ansible/playbooks/templates/portal.service.j2 | 18 ++ .../templates/rclone-mount.service.j2 | 20 ++ ansible/playbooks/templates/rclone.conf.j2 | 4 + ansible/site.yml | 16 ++ 16 files changed, 534 insertions(+), 4 deletions(-) create mode 100644 ansible/my_inventory/group_vars/vms/vars.yml create mode 100644 ansible/my_inventory/group_vars/vms/vault.yml create mode 100644 ansible/my_inventory/hosts create mode 100644 ansible/playbooks/01_host_prep.yml create mode 100644 ansible/playbooks/02_kvm_setup.yml create mode 100644 ansible/playbooks/03_freeipa_install.yml create mode 100644 ansible/playbooks/04_services_deploy.yml create mode 100644 ansible/playbooks/templates/hamster.service.j2 create mode 100644 ansible/playbooks/templates/minecraft.service.j2 create mode 100644 ansible/playbooks/templates/navidrome.service.j2 create mode 100644 ansible/playbooks/templates/palworld.service.j2 create mode 100644 ansible/playbooks/templates/portal.service.j2 create mode 100644 ansible/playbooks/templates/rclone-mount.service.j2 create mode 100644 ansible/playbooks/templates/rclone.conf.j2 create mode 100644 ansible/site.yml diff --git a/ansible/my_inventory/group_vars/all.yml b/ansible/my_inventory/group_vars/all.yml index 84d8f7f..2fe1d9a 100644 --- a/ansible/my_inventory/group_vars/all.yml +++ b/ansible/my_inventory/group_vars/all.yml @@ -8,10 +8,10 @@ host_ip: "172.30.1.200" gateway_ip: "172.30.1.254" freeipa_ip: "172.30.1.85" -portfolio_ip: "172.30.1.86" -minecraft_ip: "172.30.1.87" -palworld_ip: "172.30.1.88" -navidrome_ip: "172.30.1.89" +portfolio_ip: "172.30.1.93" +minecraft_ip: "172.30.1.91" +palworld_ip: "172.30.1.90" +navidrome_ip: "172.30.1.92" dns_servers: - "172.30.1.85" diff --git a/ansible/my_inventory/group_vars/vms/vars.yml b/ansible/my_inventory/group_vars/vms/vars.yml new file mode 100644 index 0000000..e6e5dc0 --- /dev/null +++ b/ansible/my_inventory/group_vars/vms/vars.yml @@ -0,0 +1,7 @@ +--- +podman_user: "sho" +podman_user_home: "/home/sho" + +rclone_gdrive_token: "{{ vault_rclone_gdrive_token }}" + +minecraft_ram_limit: "4G" diff --git a/ansible/my_inventory/group_vars/vms/vault.yml b/ansible/my_inventory/group_vars/vms/vault.yml new file mode 100644 index 0000000..13b9ee9 --- /dev/null +++ b/ansible/my_inventory/group_vars/vms/vault.yml @@ -0,0 +1,30 @@ +$ANSIBLE_VAULT;1.1;AES256 +39363737363131613035623835336161323864666538303739363633393363623065303461376430 +3963383961353038326233316437373236636138376432340a346563393835396536396539373165 +30623639363336316164393563613766316434383930653034616561383163653563333163323932 +3132363766643831380a643139643334363230343135336561393132613165316632383136363533 +39353434613866386463323136643139323266343139386637623565303764643566663761363235 +32633661343035643330643430643962363136393030653564333530313866643065633938373137 +31666635303932333938363638393366356566613061613561363538306631653331646665326336 +35336465336136313264343666353764663765613736656466633533313739396334313039363536 +31643564326537666537663466383063623332383461393833366361663965336138653163333837 +64356138623236626234323034396132616336383864666563643130346262356532366434336533 +38353732336564353036363262343230366532386633366362333930653230326531323035346534 +62313863326637373933303831376139303236633233356163306439363133346366343137313436 +39323731376136613033313764613864616664353734666565623538623334666334653534666639 +66393766343263336165353564336232386163326565653033623261373738653863343438323535 +61383138366264383834623536346339396331646635643361633630363163353164363430353531 +36356638393966303935326333643633363735626333373632343765643566393766393163336230 +33303533666466306462643561353537643464313431353032613230643937373066363733666638 +61323736363966636439666265353433623435343434316538333435356338366366376136393866 +63356662326632613065353133323632373333613839313136363339646262326562653234666238 +39333739623133646536633130626165363839386366646638633164376263633534386262366531 +32653433393833386130343033313263643934313235646334353939326338376531346535393333 +33353339356433316231663135623537613039326331303937343464303363666631643435366162 +37643561373261366166303332313736643736396237316464383331653637663539626530373666 +64643334663337336434303163386533316335306133336533326338376365386537646231623032 +63383732636538636164666431653831323436373536383566396637616230326437383364653837 +66656461313132383631373430653930346235383162663932383966353164666262663162623732 +65323534663663336232623039643838373863616462643463356266356431333361666163633163 +35363832333562333531363534313332326162393437323732356338336533353134623362383762 +31636563363364653063643665353361613632393761636530306333313431326166 diff --git a/ansible/my_inventory/hosts b/ansible/my_inventory/hosts new file mode 100644 index 0000000..b7ed71d --- /dev/null +++ b/ansible/my_inventory/hosts @@ -0,0 +1,12 @@ +[hypervisor] +homelab-host.lab.local ansible_host=172.30.1.200 ansible_user=sho + +[vms] +freeipa.lab.local ansible_host=172.30.1.85 ansible_user=sho +portfolio.lab.local ansible_host=172.30.1.93 ansible_user=sho +minecraft.lab.local ansible_host=172.30.1.91 ansible_user=sho +palworld.lab.local ansible_host=172.30.1.90 ansible_user=sho +navidrome.lab.local ansible_host=172.30.1.92 ansible_user=sho + +[vms:vars] +ansible_python_interpreter=/usr/bin/python3 diff --git a/ansible/playbooks/01_host_prep.yml b/ansible/playbooks/01_host_prep.yml new file mode 100644 index 0000000..d4dfcde --- /dev/null +++ b/ansible/playbooks/01_host_prep.yml @@ -0,0 +1,28 @@ +--- +- name: Prepare Hypervisor Host + hosts: hypervisor + gather_facts: true + + tasks: + - name: Enable EPEL repository + become: true + ansible.builtin.dnf: + name: epel-release + state: present + + - name: Install utility tools + become: true + ansible.builtin.dnf: + name: + - htop + - tmux + - git + - curl + - rsync + - fuse3 + state: present + + - name: Configure system timezone + become: true + ansible.builtin.command: timedatectl set-timezone {{ timezone }} + changed_when: false diff --git a/ansible/playbooks/02_kvm_setup.yml b/ansible/playbooks/02_kvm_setup.yml new file mode 100644 index 0000000..0628464 --- /dev/null +++ b/ansible/playbooks/02_kvm_setup.yml @@ -0,0 +1,33 @@ +--- +- name: Setup KVM Hypervisor and Cockpit + hosts: hypervisor + gather_facts: true + + tasks: + - name: Install virtualization packages and Cockpit + become: true + ansible.builtin.dnf: + name: + - qemu-kvm + - libvirt + - virt-install + - cockpit + - cockpit-machines + state: present + + - name: Add user to libvirt group + become: true + ansible.builtin.user: + name: "{{ admin_user }}" + groups: libvirt + append: true + + - name: Ensure virtualization and cockpit services are active + become: true + ansible.builtin.systemd_service: + name: "{{ item }}" + state: started + enabled: true + loop: + - libvirtd + - cockpit.socket diff --git a/ansible/playbooks/03_freeipa_install.yml b/ansible/playbooks/03_freeipa_install.yml new file mode 100644 index 0000000..23b798b --- /dev/null +++ b/ansible/playbooks/03_freeipa_install.yml @@ -0,0 +1,64 @@ +--- +- name: Install and Configure FreeIPA Server + hosts: freeipa.lab.local + gather_facts: true + + tasks: + - name: Set hostname to fully-qualified domain name + become: true + ansible.builtin.hostname: + name: "freeipa.{{ domain_name }}" + + - name: Install FreeIPA Server packages and firewalld + become: true + ansible.builtin.dnf: + name: + - freeipa-server + - freeipa-server-dns + - bind + - bind-utils + - firewalld + - python3-firewall + state: present + + - name: Ensure firewalld is started and enabled + become: true + ansible.builtin.systemd_service: + name: firewalld + state: started + enabled: true + + - name: Check if FreeIPA is already installed + ansible.builtin.stat: + path: /etc/ipa/default.conf + register: ipa_installed + + - name: Run FreeIPA unattended installer + become: true + ansible.builtin.command: > + ipa-server-install + --unattended + --realm={{ realm_name }} + --domain={{ domain_name }} + --ds-password={{ ipa_directory_manager_password }} + --admin-password={{ ipa_admin_password }} + --setup-dns + --forwarder=1.1.1.1 + --no-host-dns + when: not ipa_installed.stat.exists + vars: + ansible_command_timeout: 1200 + changed_when: false + + - name: Configure firewalld rules for FreeIPA + become: true + ansible.posix.firewalld: + service: "{{ item }}" + permanent: true + immediate: true + state: enabled + loop: + - freeipa-ldaps + - freeipa-ldap + - dns + - kerberos diff --git a/ansible/playbooks/04_services_deploy.yml b/ansible/playbooks/04_services_deploy.yml new file mode 100644 index 0000000..d4149cf --- /dev/null +++ b/ansible/playbooks/04_services_deploy.yml @@ -0,0 +1,217 @@ +--- +# 1. PORTFOLIO VM DEPLOYMENT +- name: Deploy Portfolio Dashboard on Portfolio VM + hosts: portfolio.lab.local + gather_facts: true + tasks: + - name: Install Podman + become: true + ansible.builtin.apt: + name: [podman, crun] + state: present + update_cache: true + + - name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "/home/{{ admin_user }}/containers/portal" + - "/home/{{ admin_user }}/containers/hamster" + + - name: Copy portal website files + ansible.builtin.copy: + src: "../../portal/" + dest: "/home/{{ admin_user }}/containers/portal/" + mode: "0644" + + - name: Copy hamster website files + ansible.builtin.copy: + src: "{{ item }}" + dest: "/home/{{ admin_user }}/containers/hamster/" + mode: "0644" + with_fileglob: + - "../../hamster/*.html" + - "../../hamster/*.css" + - "../../hamster/*.js" + + - name: Copy hamster images + ansible.builtin.copy: + src: "../../hamster/images" + dest: "/home/{{ admin_user }}/containers/hamster/" + mode: "0644" + + - name: Deploy Portal Systemd Service + become: true + ansible.builtin.template: + src: "templates/portal.service.j2" + dest: "/etc/systemd/system/portal.service" + mode: "0644" + + - name: Deploy Hamster Systemd Service + become: true + ansible.builtin.template: + src: "templates/hamster.service.j2" + dest: "/etc/systemd/system/hamster.service" + mode: "0644" + + - name: Start and enable Portal service + become: true + ansible.builtin.systemd_service: + name: portal.service + state: started + enabled: true + daemon_reload: true + + - name: Start and enable Hamster service + become: true + ansible.builtin.systemd_service: + name: hamster.service + state: started + enabled: true + daemon_reload: true + +# 2. MINECRAFT VM DEPLOYMENT +- name: Deploy Minecraft Server on Minecraft VM + hosts: minecraft.lab.local + gather_facts: true + tasks: + - name: Install Podman + become: true + ansible.builtin.apt: + name: [podman, crun] + state: present + update_cache: true + + - name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "/home/{{ admin_user }}/containers/minecraft" + + - name: Deploy Minecraft Systemd Service + become: true + ansible.builtin.template: + src: "templates/minecraft.service.j2" + dest: "/etc/systemd/system/minecraft.service" + mode: "0644" + + - name: Start and enable Minecraft service + become: true + ansible.builtin.systemd_service: + name: minecraft.service + state: started + enabled: true + daemon_reload: true + +# 3. PALWORLD VM DEPLOYMENT +- name: Deploy Palworld Server on Palworld VM + hosts: palworld.lab.local + gather_facts: true + tasks: + - name: Install Podman + become: true + ansible.builtin.apt: + name: [podman, crun] + state: present + update_cache: true + + - name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "/home/{{ admin_user }}/containers/palworld" + + - name: Deploy Palworld Systemd Service + become: true + ansible.builtin.template: + src: "templates/palworld.service.j2" + dest: "/etc/systemd/system/palworld.service" + mode: "0644" + + - name: Start and enable Palworld service + become: true + ansible.builtin.systemd_service: + name: palworld.service + state: started + enabled: true + daemon_reload: true + +# 4. NAVIDROME VM DEPLOYMENT +- name: Deploy Navidrome and Rclone Mount on Navidrome VM + hosts: navidrome.lab.local + gather_facts: true + tasks: + - name: Install Podman and utilities + become: true + ansible.builtin.apt: + name: [podman, crun, fuse3, rclone] + state: present + update_cache: true + + - name: Enable user_allow_other in fuse.conf + become: true + ansible.builtin.lineinfile: + path: /etc/fuse.conf + regexp: '^#?user_allow_other' + line: 'user_allow_other' + + - name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "/home/{{ admin_user }}/.config/rclone" + - "/home/{{ admin_user }}/containers/navidrome/config" + - "/home/{{ admin_user }}/containers/navidrome/cache" + + - name: Create Google Drive mount point + become: true + ansible.builtin.file: + path: /mnt/gdrive + state: directory + owner: "{{ admin_user }}" + group: "{{ admin_user }}" + mode: "0755" + + - name: Copy rclone configuration + ansible.builtin.template: + src: "templates/rclone.conf.j2" + dest: "/home/{{ admin_user }}/.config/rclone/rclone.conf" + mode: "0600" + + - name: Configure rclone-mount systemd service + become: true + ansible.builtin.template: + src: "templates/rclone-mount.service.j2" + dest: /etc/systemd/system/rclone-mount.service + mode: "0644" + + - name: Start rclone-mount service + become: true + ansible.builtin.systemd_service: + name: rclone-mount + state: started + enabled: true + daemon_reload: true + + - name: Deploy Navidrome Systemd Service + become: true + ansible.builtin.template: + src: "templates/navidrome.service.j2" + dest: "/etc/systemd/system/navidrome.service" + mode: "0644" + + - name: Start and enable Navidrome service + become: true + ansible.builtin.systemd_service: + name: navidrome.service + state: started + enabled: true + daemon_reload: true diff --git a/ansible/playbooks/templates/hamster.service.j2 b/ansible/playbooks/templates/hamster.service.j2 new file mode 100644 index 0000000..a8b5e83 --- /dev/null +++ b/ansible/playbooks/templates/hamster.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Hamster Girlfriend Web Server Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +ExecStartPre=-/usr/bin/podman rm -f hamster +ExecStart=/usr/bin/podman run --name hamster \ + -p 8080:80 \ + -v /home/{{ admin_user }}/containers/hamster:/usr/share/nginx/html:ro \ + docker.io/library/nginx:alpine +ExecStop=/usr/bin/podman stop -t 10 hamster +ExecStopPost=-/usr/bin/podman rm -f hamster + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/minecraft.service.j2 b/ansible/playbooks/templates/minecraft.service.j2 new file mode 100644 index 0000000..a9b643a --- /dev/null +++ b/ansible/playbooks/templates/minecraft.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Minecraft Server Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +ExecStartPre=-/usr/bin/podman rm -f minecraft +ExecStart=/usr/bin/podman run --name minecraft \ + -p 25565:25565 \ + -v /home/{{ admin_user }}/containers/minecraft:/data \ + -e EULA=TRUE \ + -e MEMORY={{ minecraft_ram_limit | default('4G') }} \ + docker.io/itzg/minecraft-server:latest +ExecStop=/usr/bin/podman stop -t 20 minecraft +ExecStopPost=-/usr/bin/podman rm -f minecraft + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/navidrome.service.j2 b/ansible/playbooks/templates/navidrome.service.j2 new file mode 100644 index 0000000..000f7a4 --- /dev/null +++ b/ansible/playbooks/templates/navidrome.service.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=Navidrome Music Streamer Container +After=network-online.target +Wants=network-online.target +Requires=rclone-mount.service +After=rclone-mount.service + +[Service] +Type=simple +Restart=always +ExecStartPre=-/usr/bin/podman rm -f navidrome +ExecStart=/usr/bin/podman run --name navidrome \ + -p 4533:4533 \ + -v /home/{{ admin_user }}/containers/navidrome/config:/data \ + -v /home/{{ admin_user }}/containers/navidrome/cache:/cache \ + -v /mnt/gdrive:/music:ro \ + -e ND_MUSICFOLDER=/music \ + docker.io/deluan/navidrome:latest +ExecStop=/usr/bin/podman stop -t 10 navidrome +ExecStopPost=-/usr/bin/podman rm -f navidrome + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/palworld.service.j2 b/ansible/playbooks/templates/palworld.service.j2 new file mode 100644 index 0000000..1bc3e6b --- /dev/null +++ b/ansible/playbooks/templates/palworld.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Palworld Dedicated Server Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +ExecStartPre=-/usr/bin/podman rm -f palworld +ExecStart=/usr/bin/podman run --name palworld \ + -p 8211:8211/udp \ + -v /home/{{ admin_user }}/containers/palworld:/palworld \ + -e PLAYERS=8 \ + -e PORT=8211 \ + docker.io/thijsvanloef/palworld-server-docker:latest +ExecStop=/usr/bin/podman stop -t 20 palworld +ExecStopPost=-/usr/bin/podman rm -f palworld + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/portal.service.j2 b/ansible/playbooks/templates/portal.service.j2 new file mode 100644 index 0000000..6d1dbe2 --- /dev/null +++ b/ansible/playbooks/templates/portal.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Portal Web Server Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +ExecStartPre=-/usr/bin/podman rm -f portal +ExecStart=/usr/bin/podman run --name portal \ + -p 80:80 \ + -v /home/{{ admin_user }}/containers/portal:/usr/share/nginx/html:ro \ + docker.io/library/nginx:alpine +ExecStop=/usr/bin/podman stop -t 10 portal +ExecStopPost=-/usr/bin/podman rm -f portal + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/rclone-mount.service.j2 b/ansible/playbooks/templates/rclone-mount.service.j2 new file mode 100644 index 0000000..9a2a5ed --- /dev/null +++ b/ansible/playbooks/templates/rclone-mount.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Rclone Google Drive Mount +After=network-online.target +Wants=network-online.target + +[Service] +Type=notify +User={{ admin_user }} +Group={{ admin_user }} +ExecStart=/usr/bin/rclone mount gdrive: /mnt/gdrive \ + --config=/home/{{ admin_user }}/.config/rclone/rclone.conf \ + --vfs-cache-mode full \ + --vfs-cache-max-age 24h \ + --allow-other +ExecStop=/bin/fusermount3 -u /mnt/gdrive +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/templates/rclone.conf.j2 b/ansible/playbooks/templates/rclone.conf.j2 new file mode 100644 index 0000000..9191cea --- /dev/null +++ b/ansible/playbooks/templates/rclone.conf.j2 @@ -0,0 +1,4 @@ +[gdrive] +type = drive +scope = drive +token = {{ rclone_gdrive_token }} diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..4b3289f --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,16 @@ +--- +- name: Host Preparation + import_playbook: ./playbooks/01_host_prep.yml + tags: host_prep + +- name: Virtualization Setup + import_playbook: ./playbooks/02_kvm_setup.yml + tags: kvm_setup + +- name: Identity Management Installation + import_playbook: ./playbooks/03_freeipa_install.yml + tags: freeipa + +- name: Services Deployment + import_playbook: ./playbooks/04_services_deploy.yml + tags: services