Initial commit 🚀 🐒

This commit is contained in:
xbazzi 2025-07-10 21:24:28 -06:00
commit ebab5d44f0
97 changed files with 1484 additions and 0 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"ansible.python.interpreterPath": "/run/current-system/sw/bin/python",
"ansible.validation.lint.path": "",
"ansible.validation.lint.enabled": false
}

150
README.md Normal file
View File

@ -0,0 +1,150 @@
# Ansible Starter Kit
Easy way for my homies to start up an ansible project for their homelab or local machine.
# Prerequisites
The ideal setup for a homelab is to have:
- The same username/password in all vms/hosts so you don't have to type a different password for each host
- SSH key auth on each host so you don't have to type a password at all
# Ansible basics
I'll explain the basic units of an ansible project. For this example we're going to assume you want to mount
an NFS share in all your hosts.
From bottom to top we have:
## Tasks
A task is the lowest unit in an ansible project.
A task could be:
- Creating a directory
- Installing an os package (curl, docker, nfs-common, etc)
- Starting a docker container
### Examples
```yml
- name: Add SSH key for remote user
ansible.posix.authorized_key:
user: javi
state: present
key: "{{ lookup('file', '/home/javi/.ssh/homelab_keypair_ed25519.pub') }}"
```
```yml
- name: Ensure NFS client is installed
ansible.builtin.package:
name: nfs-common
state: present
become: true
```
## Role
A role is a self contained, re-usable unit that will give a meaningful result. Think of it like a class in a program.
Roles have a list of tasks, as well as variables and files associated with those tasks. All contained in a folder.
A role can look like:
```
roles/
portainer/
├── defaults/
│ └── main.yml # Default variables
├── files/
│ └── ... # Static files to be copied (e.g., configs, scripts)
├── handlers/
│ └── main.yml # Handlers (e.g., service restart)
├── meta/
│ └── main.yml # Role metadata (e.g., dependencies)
├── tasks/
│ └── main.yml # Main list of tasks to execute
├── templates/
│ └── ... # Jinja2 templates
├── vars/
│ └── main.yml # Non-overridable variables
└── README.md # Optional: Document what this role does
sshkey/
├── defaults/
│ └── main.yml # Default variables
├── files/
│ └── ... # Static files to be copied (e.g., configs, scripts)
├── handlers/
│ └── main.yml # Handlers (e.g., service restart)
├── meta/
│ └── main.yml # Role metadata (e.g., dependencies)
├── tasks/
│ └── main.yml # Main list of tasks to execute
├── templates/
│ └── ... # Jinja2 templates
├── vars/
│ └── main.yml # Non-overridable variables
└── README.md # Optional: Document what this role does
```
However, only the `defaults` and `tasks` subfolders and subsequent `main.yml` are required. Everything else is optional.
A role can be:
- Mounting a share
- Deploying a docker container with all its requirements like a database and mapped volumes
# Playbook
A playbook combines roles and task to create a final state in a host or group of hosts.
A playbook can be:
- Make sure all your hosts have sshkey auth, portainer, docker and an nfs share mounted.
- Deploy a suite of apps to a host or multiple hosts
## Example
```yml
---
- name: Deploy apps to apps-1 node
hosts: apps
become: true
roles:
- role: apps/kan
vars:
port: 7070
- role: apps/memos
vars:
port: 7071
- role: apps/vaultwarden
vars:
port: 7072
- role: apps/erugo
vars:
port: 7073
- role: apps/tianji
vars:
port: 7074
- role: apps/stirling-pdf o
vars:
port: 7075
- role: apps/dumbware-todo
vars:
port: 7076
pin: 8989
- role: apps/dumbware-drop
vars:
port: 7077
pin: "8989"
```
# Setup
1. Clone this repo `git clone git@gitgud.foo:javif89/ansible-starter-kit.git [your project name]`
2. Run `setup.sh` to set up the vault password and become password
3. Set up your hosts in `hosts.yml`
4. Start making your roles and playbooks

5
ansible.cfg Executable file
View File

@ -0,0 +1,5 @@
[defaults]
remote_user = ansible
inventory = inventory/hosts.yml
roles_path = ./roles
vault_password_file = ~/.ansible-vault-key

View File

@ -0,0 +1,96 @@
$ANSIBLE_VAULT;1.1;AES256
38333861353432643165366435353534316564346533666439376631373562366530386636623333
6130343936376163336432366437623062643161636466640a383232343564636234376330323138
37393731643030313230613363343639363737393364346231643835613532636530363964383933
3834343936353965390a313439663463626461376461636462316237366430356437346164393034
38633331646465666165343365616366623636613264663062613238656466326537373135393234
38623034306461386132373262666532633562376532303762356663343930623464376661366238
33373638386366643030366632636138653032633436373932613261656331633663643839306633
39613136306130626635393333366136646665393932383563373739323730396633363334643639
36323337336563616165626463306637653865643931613731636233313061616130623662393465
63366131643135623337313735386566616663343263353561316132343138653761303436386433
64653736616439623331373865383439343637343737313466363265333033663836633635623732
30643137633738356665326138363734623766613462323831623931633163373933353661653434
35343466363132663765623739336136656332333932303332363164366630376638353166316636
31653133386461323532666661363865383430653230636233616238356431623462316133633239
34316561333530353166626632653566333966326663383635323165356231386232346263363666
61366430353239353732663437353165353562313438383063393935306534646165336232333861
32633138323036323334343866333963353965303536373930336164323565333862353032336566
39613263303966343337393165633866323233653132626264316238313131663961613037643865
35376138643934383435636534343231303933396138643231613336613536333961333562343963
31376438366438346231656364303535336465623237336263383761363630623632356335326231
61366132613062353833613162623634383134666334366435646238343462396535336534316264
35326461653331636462366536653430646438626562373635613464313031666434333732616239
61383536623762653463363031343332393166646264663031656531363761666364653866326230
33363264663366653930616133363539633463306434653732383435613430626439313935633162
62323366653639343063353662383265626538646361383030396533366635353830383365636435
36636433393635633237316131616630383464313535303137376131646566383366333935303830
34663630613438613836393333626463623466393831303833626666636338356533616436636331
32393665346538353539633634303161616662366433386139343034333963336630353634346232
63333561326532336533326333613961643134306264373730346137653962663930326261333763
32353264616333313263343964663465636235333438343334393963653530663130366133353031
64626537663362316239643436646236636361656365373232663931613634333465643137313964
31323464303432346337616530333135346166623561623531313561633533643161363930323265
32363538323134383434316332383064663437653161316162636339663036316139633636646131
34343939383935623333613835613431346532323530366331613065666566323731663336623137
36653165623235343832653438393836353630663063613337346364393439303738656564626432
64613863376463343433303933656163666532326430383863383636386331643265623963653730
31666430303666373030343831383436376666346236646231346565643564656339326231383337
32386239646634643261393531636666666637386633396136653661373835636237323031333434
31633237633566313131306261613834356139306436393862333533336534383662663837626464
63653831373235373638303864323531623965333662386239396637636562373632393365663062
61333934613865626639393236303562643165316466386461636239373336623965333531303335
63393030326230303537383431663634616133353734353835636565326261386165633730386665
61363736653762326634316634663530306163366165303464373833633363613338383330353062
62313331343830323063663363386566373964356566313638653331336366343236356565656331
61326466653362326337626532353637636535373762383034653464313961353430666132376163
63323636393331376565343037613631323130646466656531663335373461653063353166346336
32613564373634663863343036623038656462643532653539396538646532383161333535313164
66306532393137393736316330383466646265633539326437643039643037393735306136383337
35353465643762313762653938376331356232356131336131616531303162383737623736363130
38336634653564363564323964356564313665396461623836633533633534653961323632653766
39383236663664643666333730623731663438326539346135326564326666396463323661393132
35343862653264323564343730313938663664663035303332333163633137323661323431343138
66336666386635643662323431626636636231666561396438336234366331616138323636336664
36396133323937306463386261363766623366663965623361313264663861626161366166366237
36393962613135326339623261333635326138336466306132333230643536376334386166346364
61643231363737656131363935626433373164363161646365633536383563343936653333333832
33383565346366616365363030646432363633663537316335376333313236616637633066333664
63313039656664663664393633353466376264356161343532353838373366393835653134353438
61396164636566313535656533396332316565336664363034353235643635616564663563633338
32313662666566336437366666376630336466373831343431636662323431393061326139326537
35303362366338383230643734333533323535303534303637623136613634333436656131376635
30623736356634616366323563636535633530623435613634636662636436303337373762393734
63363637323733306532343239303030626135663366333763623933613034333339323636653338
37646665343632363466383762623763363238633265333166396165633461666662303332373164
30356231356132323037396231613939353463656333613735376562323837323430623665373364
63656238356161303462366130636236633238623861373830323237376563353934356239656539
62346638643263383636666437646466393361366465366362336634663636306230373466366266
38353133383937336336633239373739663731666162643037623630323739363464623763353163
36653733663138386432323465383537383137623333333933366232323934623730613139656536
39623564316135373830316238396664323663663137653130326163356566653630613662613638
32626261383533393833633539633330383537323534346134366333346438323232336337623861
35306632356165313063373738303130386436396532616365313633656637373362313639626338
36616665386663303636323264623839303562303064306139333263343839323436333930393136
36663531643363643537636437646266643032616437656239666539653163343935633366646534
36623935356565653831366462653830393465353065386130303065626365663235366530303431
63353635653163303138383163663931356139626264383331346532663961316261393832626430
66303435393739303461363731363733646534363766626462333761623537343734343833393634
31356537653630363563313539356535663032613538303264633864396365613366386366656336
37643666636436626162636234333938303266393162393933393038366437613165366630386438
37313634656632653238383134653039323739643366343631343530386237336139313164393133
64653638363662333461323365333861396266653238306530613064316362663131633461366161
32633835393832646530623033346238343761393036353137626463613139393839616432626263
35663232323734333631613139666366666436653566653064393666356165336439303937326637
36633438623933323964303065313332373762346463343263386439646533306332363136386434
34363236383733323232306264613137383831633534666337356137316433656238363864646138
39303661383963313833323330656666373536303931383464343036663035616630343063383139
63653263323533303933653138666138336530633162653533336466353235366333643835313365
38303462623430356339323731646238636663393838653466323030653866646435323636343337
37316336383234323336383061666235663539616631663936613430313138643061393439383636
31656535366361326566666264656465633337643365393765303732633238653231623735313638
36303537393038306165393365343334373333393933356133313264396236623936343763366330
37373262393230326132393237633335353964346434616137636662343635306632373532663830
66333039613330306231363364323861363964313336666165616635363166623435636366343364
64356364373832366435656539613238646538623035346434346364386434623461653763656135
646161643166613037303031663863666465

View File

@ -0,0 +1,2 @@
iscsi_target_ip: nas.lan.xbazzi.com # TrueNAS IP
iscsi_target_iqn: iqn.2005-10.org.freenas.ctl:pve-iscsi

View File

@ -0,0 +1,11 @@
$ANSIBLE_VAULT;1.1;AES256
61636264373765333930663036663164363332363765353836326361383438303065623938353338
3861383264346132613466666363623562383437643464640a343830356164323732313631666532
61646636633062333539393266366537613037646137376463343638356562383538376534376533
6361373233623565310a343466666233623138316439616239376266343932616366636232633735
32623335633732653637336163666265383066303565386261353539656333656337393530323639
65313233376434343761653264626563653031623236616362396262643463656535613237383435
35343439643330343362333362396338646162313063623334326264316235636333376434626535
34353332653138653765323936346536323038366238323932393335363762623237653962616664
37653963633936653866656537663435333731343937616237353734383537316361633836363666
3064366264653335663331383332656638323335633731353531

View File

@ -0,0 +1,11 @@
$ANSIBLE_VAULT;1.1;AES256
64366635666462636332636564316263363561326366646531626365633431373934306334373432
6635396564336238383563613231373339616164326630330a333031643734653738666537386337
34366264356237613534356330393130376262383361636638316562636463633239643264343564
6236663764623439360a333165363430373561336334663739353738343364656432363939383234
32343363373164303130376133633265656564363532663336326263636464623339353966366430
33313339343534386664663361616438346136643361346264393563633630333562346338366530
33666261633236363833343931353535366565363733326661626338363030383365383332373837
62623537396666363265346333366661396139363732666261343132333237636335363338303033
61376630333263313166356334383931326665383631363961633066396539393963313433373763
6362323162346164633639623064376265313764353032663434

View File

@ -0,0 +1,11 @@
$ANSIBLE_VAULT;1.1;AES256
34386433623163393561646137313364663363363138336164363561666533323565653464373961
3061643566303432623963636530303530306433343666350a663933663935643635366561336630
37653433666364366431363032633738646436626336323037343730376662363266333038613064
6131363532623766320a656330613439363562653263666138336639663965303236356139336630
33663961393438316333333030663236623934666632346566363739316262346538626434393066
37663563363038393137353336393233316630323734346439663836313065386536326465626632
30636165626132376138326331663965623965353561616536376266313430373839353230653332
30653866323231333335313261636336333161363334663734633534343561623764393531386239
62343136336332666237353863623330336564313130336239323639303766303361616664623331
3638656138333862366138366539666232376164666239323132

36
inventory/hosts.yml Normal file
View File

@ -0,0 +1,36 @@
$ANSIBLE_VAULT;1.1;AES256
64363164666338376439386465623133383736636361353661303464666164616232366431626333
3437666365663839343866613537323366333564646234350a363434303639333535643039313039
61306663306134666139303061316163323033353366386233643039613365386536333336663864
6463316237376364660a313639623233326634366635313962373830393165343130363337353135
39616535373561353064373139356362663739363138326237393630326535353961623733323766
61366665346365626637626531666434336366643061663363323237613065613638353731663834
63386361646161303339653433316232323135323561363161643833373865366162316465383930
66346466346433643264633162326665613731383036616437643537383833616262646331356638
61336331636334343465316366323861326365386136303565363564343438613866326162613930
35643230316237383865616164653038306339306130316534303230383966353934356366633961
64623761646138633931356230633961353361376239656364326338646436663831323631346531
37633261646635633665663037663733313332396666636435383166343262663834383633646335
39383137383436643865383065613533636132326331303731306465636465613136646462643336
63386635346534343961626339393330633638633263326131313065353164353833333833326164
36613462393334333166613765343938393132363165316532396237636137323262616631393930
32323437316430306532653937613937663963613738323231366565313838656434656532623062
36633561376536373036323330623539383763306561383136633434623062376266333361306266
35333431626230316630626663376462653838656330376266396664323238323234306362343633
66333463646665306561313033313464633231316366633031303863636264333363366666376163
31663464643331666461353331663936656539616235353530366238326333663966386639353334
65626330613330303930643835393365383739313831333235633261356331346161333765323335
39393534393166383161376530623739336635303363393633653666633363656334653961643964
65373237333338313162626636366264356663616261633734613330626562666538313165663562
33393336613963353330336238656433613363306535323930623037663463613136643735613337
63396334626334373737663461386663653865626136353761666531623563643465356366666266
32393536643638303862353234366233323566623862316636323866323336366434376463306661
31313133313639313635333335643836373437306535383734373031306539643738326238373366
31393734383738636333643437393238336365376332643861636435303036653065613164363031
63373632343634303236653036636134306237663462633861366630633034333832646362303236
32636166366430343063316333636134616236373866373865316261626239376639366537306461
33636265313262366264306331313039363734633766323932363465353032633764373332326466
31363034326161643062343230333435343435623336653163323365363233643231306538373939
35643338396661356332343431383232376238626434613462306437653537343530643030333735
38393532303533323430373066343938396533656564663731393738363863663864356462663565
656438663662326136393134643136663737

View File

@ -0,0 +1,5 @@
- name: Apply firewalld config
hosts: staging-vm
become: yes
roles:
- role: provision/alma/firewall

6
playbooks/backup-pve.yml Normal file
View File

@ -0,0 +1,6 @@
---
- name: Backup /etc/pve from all Proxmox nodes
hosts: pve-nodes
become: yes
roles:
- role: utility/pve_backup

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
---
- name: Configure networking for a new node
hosts: pve-nodes
become: true
roles:
- role: pve/setup_networking

View File

@ -0,0 +1,15 @@
---
- name: Deploy DBGate container
hosts: prod1
become: true
roles:
- role: apps/dbgate
vars:
directory: "pg-dev"
container_name: "postgres-dev"
port: 7000
- role: services/postgres
vars:
directory: "pg-beta"
container_name: "postgres-beta"
port: 7001

View File

@ -0,0 +1,16 @@
---
- name: Deploy PostgreSQL container
hosts: prod1
become: true
roles:
- role: services/postgres
- role: services/postgres
vars:
directory: "pg-dev"
container_name: "postgres-dev"
port: 7000
- role: services/postgres
vars:
directory: "pg-beta"
container_name: "postgres-beta"
port: 7001

6
playbooks/example.yml Normal file
View File

@ -0,0 +1,6 @@
---
- name: Example playbook
hosts: vms
become: true
roles:
- role: server/sshkey

View File

@ -0,0 +1,5 @@
- name: Prep all Proxmox nodes for clustering
hosts: pve-nodes
become: yes
roles:
- role: utility/cluster_prep

View File

@ -0,0 +1,13 @@
---
- name: Provision AlmaLinux 9 VM
hosts: staging-vm
become: yes
roles:
- role: server/users
- role: server/sshkey
# - role: server/network
# - role: server/firewall
# - role: provision/alma/common
# - role: provision/alma/nfs
# - role: docker/install
# - role: server/reboot

View File

@ -0,0 +1,9 @@
- name: Sysprep Alma Linux machine
hosts: staging-vm
become: yes
roles:
- role: server/users
- role: server/sysprep
- role: server/sshkey
- role: server/network
- role: server/reboot

View File

View File

@ -0,0 +1,20 @@
---
- name: Create app database
ansible.builtin.include_role:
name: postgres/database
vars:
database: "{{ app_name }}"
- name: Create app db user
ansible.builtin.include_role:
name: postgres/user
vars:
user: "{{ app_name }}"
password: "password"
- name: Give app user full priviledges on DB
ansible.builtin.include_role:
name: postgres/priviledges
vars:
database: "{{ app_name }}"
user: "{{ app_name }}"

View File

@ -0,0 +1,4 @@
docker_dir: "/data/docker/dbgate"
port: "6001"
app_port: "3000"
container_name: "postgres"

View File

@ -0,0 +1,19 @@
- name: Create docker folder
ansible.builtin.file:
dest: "{{ docker_dir }}"
state: directory
mode: '0770'
- name: Put up the postgres container
community.docker.docker_container:
name: "{{container_name}}"
image: postgres:17.4
restart_policy: always
state: started
pull: true
ports:
- "{{ port }}:{{ app_port }}"
env:
CONNECTIONS: postgres_con
volumes:
- "dbgate-data:/root/.dbgate"

View File

View File

@ -0,0 +1,40 @@
---
- name: Install plugins-core to manage DNF repos
ansible.builtin.dnf:
name:
- dnf-plugins-core
state: present
# - name: Install plugins-core to manage DNF repos
# ansible.builtin.command: dnf -y install dnf-plugins-core
- name: Add Docker repo
ansible.builtin.command: dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
register: docker_repo
- name: Verify Docker repo added
ansible.builtin.debug:
var: docker_repo.stdout
- name: Install Docker Engine
ansible.builtin.dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
- name: Enable and start Docker Engine
ansible.builtin.systemd_service:
name: docker
state: started
enabled: true
- name: Verify with Hello World
ansible.builtin.command: docker run hello-world
register: docker_hello
- name: Test
ansible.builtin.debug:
var: docker_hello.stdout_lines

View File

@ -0,0 +1,45 @@
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: yes
- name: Install prerequisite packages
ansible.builtin.apt:
name:
- ca-certificates
- curl
state: present
- name: Create apt keyrings directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Download Docker GPG key
ansible.builtin.get_url:
url: "https://download.docker.com/linux/ubuntu/gpg"
dest: /etc/apt/keyrings/docker.asc
mode: '0644'
- name: Add Docker apt repository
ansible.builtin.apt_repository:
repo: "deb [arch={{ docker_arch }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
filename: docker
state: present
vars:
docker_arch: "{{ ansible_architecture | regex_replace('x86_64', 'amd64') }}"
- name: Update apt cache after adding Docker repository
ansible.builtin.apt:
update_cache: true
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present

View File

View File

@ -0,0 +1,22 @@
- name: Pull Portainer Agent image
become: true
community.docker.docker_image:
name: portainer/agent
tag: latest
source: pull
- name: Deploy Portainer Agent container
become: true
community.docker.docker_container:
name: portainer_agent
image: portainer/agent
pull: false # we already pulled above
state: started
restart_policy: always
ports:
- "9001:9001"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
- /:/host
timeout: 120 # wait up to 2m for it to come up

View File

View File

View File

@ -0,0 +1,13 @@
---
- name: Remove old docker stuff
ansible.builtin.dnf:
name:
- docker
- docker-client
- docker-client-latest
- docker-common
- docker-latest
- docker-latest-logrotate
- docker-logrotate
- docker-engine
state: absent

View File

View File

@ -0,0 +1,2 @@
apps: []
stack_name: "willneverexist"

View File

@ -0,0 +1,27 @@
---
- name: Create app mount directories
ansible.builtin.file:
path: "{{ remote_app_mounts }}/{{ item }}"
state: directory
mode: '0777'
loop: "{{ apps }}"
- name: Create stack directory
ansible.builtin.file:
path: "{{ remote_stacks }}/{{ stack_name }}"
state: directory
mode: '0777'
- name: Copy docker-compose.yml to server
ansible.builtin.copy:
src: '{{ docker_stacks }}/{{ stack_name }}/docker-compose.yml'
dest: '{{ remote_stacks }}/{{ stack_name }}/docker-compose.yml'
owner: javi
group: javi
mode: '0777'
- name: Start up the containers
ansible.builtin.command: docker compose up -d
become: true
args:
chdir: "{{ remote_stacks }}/{{ stack_name }}"

View File

View File

@ -0,0 +1,10 @@
---
- name: Create database
delegate_to: localhost
community.postgresql.postgresql_db:
name: "{{ database }}"
state: present
login_host: "{{ pg_host }}"
login_port: "{{ pg_port }}"
login_user: "{{ pg_user }}"
login_password: "{{ pg_password }}"

View File

@ -0,0 +1 @@
priviledges: ALL

View File

@ -0,0 +1,28 @@
---
- name: Grant database-level privileges on "{{ database }}"
delegate_to: localhost
community.postgresql.postgresql_privs:
db: "{{ database }}"
type: database
objs: "{{ database }}"
privs: "CREATE"
role: "{{ user }}"
state: present
login_host: "{{ pg_host }}"
login_port: "{{ pg_port }}"
login_user: "{{ pg_user }}"
login_password: "{{ pg_password }}"
- name: Give user full priviledges on database
delegate_to: localhost
community.postgresql.postgresql_privs:
db: "{{ database }}"
type: schema
objs: public
privs: "{{ priviledges }}"
role: "{{ user }}"
state: present
login_host: "{{ pg_host }}"
login_port: "{{ pg_port }}"
login_user: "{{ pg_user }}"
login_password: "{{ pg_password }}"

View File

@ -0,0 +1 @@
password: "password"

View File

@ -0,0 +1,11 @@
---
- name: Create postgres user
delegate_to: localhost
community.postgresql.postgresql_user:
name: "{{ user }}"
password: "{{ password }}"
state: present
login_host: "{{ pg_host }}"
login_port: "{{ pg_port }}"
login_user: "{{ pg_user }}"
login_password: "{{ pg_password }}"

View File

@ -0,0 +1,56 @@
---
- name: Set system timezone
ansible.builtin.command: timedatectl set-timezone "{{ timezone }}"
register: output
changed_when: output.rc != 0
- name: Set hostname
ansible.builtin.hostname:
name: "{{ provision_hostname }}"
use: systemd
- name: Upgrade all packages
ansible.builtin.dnf:
name: "*"
update_only: true
- name: Add CodeReady Builder repo
ansible.builtin.command: dnf config-manager --set-enabled crb
register: output
changed_when: output.rc != 0
- name: Install QEMU Guest Agent
ansible.builtin.dnf:
name:
- 'qemu-guest-agent'
- name: Enable and start QEMU Guest Agent
ansible.builtin.service:
name: qemu-guest-agent
enabled: yes
state: started
- name: Install EPEL
ansible.builtin.dnf:
name:
- 'epel-release'
update_cache: true
- name: Install Dev Tools
ansible.builtin.dnf:
name:
- '@Development tools'
update_cache: true
- name: Install baseline packages
ansible.builtin.dnf:
name:
- vim
- curl
- git
- bash-completion
- firewalld
- fastfetch
- btop
state: latest
update_cache: true

View File

@ -0,0 +1,18 @@
- name: Add Docker repository
ansible.builtin.get_url:
url: https://download.docker.com/linux/centos/docker-ce.repo
dest: /etc/yum.repos.d/docker-ce.repo
- name: Install Docker packages
ansible.builtin.dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
state: present
- name: Enable and start Docker
ansible.builtin.service:
name: docker
enabled: yes
state: started

View File

@ -0,0 +1,23 @@
---
- name: Install NFS client
ansible.builtin.dnf:
name: nfs-utils
state: present
- name: Create mount points
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: nfsuser
group: nfsuser
mode: '0755'
loop: "{{ nfs_mounts }}"
- name: Mount NFS shares
ansible.posix.mount:
src: "{{ item.src }}"
path: "{{ item.path }}"
fstype: nfs
opts: "{{ item.opts }}"
state: mounted
loop: "{{ nfs_mounts }}"

View File

View File

@ -0,0 +1,107 @@
---
# - name: Set hostname
# hostname:
# name: "{{ inventory_hostname }}"
# - name: Disable Proxmox Enterprise repo
# lineinfile:
# path: /etc/apt/sources.list.d/pve-enterprise.list
# regexp: '^deb'
# line: '# deb ...'
# state: present
# ignore_errors: yes # In case the file doesn't exist
- name: Find all sources.list.d files
find:
paths: /etc/apt/sources.list.d
patterns: "*.list"
file_type: file
register: list_files
- name: Comment out any line with 'enterprise' in each file
lineinfile:
path: "{{ item.path }}"
regexp: '^(?!#).*enterprise'
line: '# \g<0>'
backrefs: yes
state: present
loop: "{{ list_files.files }}"
- name: Overwrite sources.list with Proxmox-recommended repos
copy:
dest: /etc/apt/sources.list
content: |
deb http://ftp.debian.org/debian bookworm main contrib
deb http://ftp.debian.org/debian bookworm-updates main contrib
# Proxmox VE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription
# security updates
deb http://security.debian.org/debian-security bookworm-security main contrib
mode: '0644'
- name: Add Proxmox no-subscription repo to sources.list.d
copy:
dest: /etc/apt/sources.list.d/pve-no-subscription.list
content: |
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
owner: root
group: root
mode: '0644'
- name: Update apt cache
apt:
update_cache: yes
- name: Update /etc/hosts with all PVE nodes
template:
src: hosts.j2
dest: /etc/hosts
mode: "0644"
- name: Ensure search domain and nameserver set properly
template:
src: resolv.j2
dest: /etc/resolv.conf
mode: "0644"
- name: Ensure chrony is installed
apt:
name: chrony
state: present
update_cache: yes
- name: Enable and start chronyd
service:
name: chrony
state: started
enabled: yes
- name: Discover iSCSI targets from TrueNAS
shell: |
iscsiadm -m discovery -t st -p {{ iscsi_target_ip }}
register: iscsi_discovery
changed_when: false
- name: Login to discovered iSCSI target (unauthenticated)
shell: |
iscsiadm -m node -T {{ iscsi_target_iqn }} -p {{ iscsi_target_ip }} --login
register: iscsi_login
changed_when: "'Login to' in iscsi_login.stdout or 'already present' in iscsi_login.stdout"
- name: Make iSCSI login persistent across reboots
shell: |
iscsiadm -m node -T {{ iscsi_target_iqn }} -p {{ iscsi_target_ip }} --op update -n node.startup -v automatic
changed_when: false
# - name: Ensure vg_ha exists
# command: vgs vg_ha
# register: vg_result
# failed_when: vg_result.rc != 0
# changed_when: false
# - name: Debug VG presence
# debug:
# msg: "VG 'vg_ha' found on {{ inventory_hostname }}"

View File

@ -0,0 +1,6 @@
127.0.0.1 localhost
{% for host in groups['pve-nodes'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ host }} {{ host }}.lan.xbazzi.com
{{ hostvars[host]['cluster_ip'] }} {{ host }}-cluster
{% endfor %}

View File

@ -0,0 +1,2 @@
search lan.xbazzi.com
nameserver 10.133.7.1

View File

View File

@ -0,0 +1,26 @@
---
- name: Copy /etc/ backup script to PVE node
template:
src: backup_pve_config.sh.j2
dest: /home/xbazzi/backup_pve.sh
mode: '0755'
- name: Run backup script
shell: /home/xbazzi/backup_pve.sh
- name: Find most recent backup directory
shell: "ls -td /home/xbazzi/pve_backup_* | head -1"
register: latest_backup_dir
changed_when: false
- name: Archive backup folder
archive:
path: "{{ latest_backup_dir.stdout }}"
dest: "{{ latest_backup_dir.stdout }}.tar.gz"
format: gz
- name: Fetch backup archive to control machine
fetch:
src: "{{ latest_backup_dir.stdout }}.tar.gz"
dest: "backups/{{ inventory_hostname }}.tar.gz"
flat: yes

View File

@ -0,0 +1,23 @@
#!/bin/bash
set -e
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_DIR="/home/xbazzi/pve_backup_${TIMESTAMP}"
NODE_NAME=$(hostname)
echo "🔒 Creating backup directory at $BACKUP_DIR..."
mkdir -p "$BACKUP_DIR"
echo "📁 Backing up /etc/pve..."
cp -a /etc/pve "$BACKUP_DIR/etc_pve"
echo "📄 Saving VM and container config files..."
mkdir -p "$BACKUP_DIR/qemu-server" "$BACKUP_DIR/lxc"
cp -a /etc/pve/qemu-server/*.conf "$BACKUP_DIR/qemu-server/" 2>/dev/null || true
cp -a /etc/pve/lxc/*.conf "$BACKUP_DIR/lxc/" 2>/dev/null || true
echo "💽 Saving storage.cfg..."
cp -a /etc/pve/storage.cfg "$BACKUP_DIR/" 2>/dev/null || true
echo "📦 Backup complete on $NODE_NAME."
echo "🗃️ Location: $BACKUP_DIR"

View File

@ -0,0 +1,5 @@
# - name: Restart networking
# ansible.builtin.systemd:
# name: networking
# state: restarted

View File

@ -0,0 +1,46 @@
---
- name: Set up network interfaces for new PVE node
template:
src: interfaces-xbazzi.j2
# dest: /etc/network/interfaces.d/interfaces-xbazzi
dest: /etc/network/interfaces
owner: root
group: root
mode: "0644"
- name: Apply correct permissions to interfaces.d
file:
path: "/etc/network/interfaces.d"
owner: root
group: root
mode: '0644'
- name: Find all files in the directory
ansible.builtin.find:
paths: /etc/network/interfaces.d/
file_type: file
register: files_to_delete
- name: Delete all files
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ files_to_delete.files }}"
- name: Update /etc/hosts with all PVE nodes
template:
src: hosts.j2
dest: /etc/hosts
mode: "0644"
- name: Ensure search domain and nameserver set properly
template:
src: resolv.j2
dest: /etc/resolv.conf
mode: "0644"
- name: Restart networking
ansible.builtin.systemd:
name: networking
state: restarted

View File

@ -0,0 +1,6 @@
127.0.0.1 localhost
{% for host in groups['pve-nodes'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ host }} {{ host }}.lan.xbazzi.com
{{ hostvars[host]['cluster_ip'] }} {{ host }}-cluster
{% endfor %}

View File

@ -0,0 +1,48 @@
auto eno1
iface eno1 inet manual
mtu 1500
auto enp1s0f0
iface enp1s0f0 inet manual
mtu 9000
iface enp1s0f1 inet manual
mtu 9000
# Mgmt interface
auto vmbr0
iface vmbr0 inet static
address {{ vmbr0_ip }}/22
bridge-ports eno1
bridge-stp off
bridge-fd 0
mtu 1500
auto vmbr1
iface vmbr1 inet manual
bridge-ports enp1s0f0
bridge-stp off
bridge-fd 0
bridge-vlan-aware yes
bridge-vids 2-4094
mtu 9000
# Prod interface
auto vmbr1.1337
iface vmbr1.1337 inet static
address {{ vmbr1_1337_ip }}/22
gateway 10.133.7.1
mtu 9000
# DMZ interface
auto vmbr1.666
iface vmbr1.666 inet static
address {{ vmbr1_666_ip }}/22
mtu 1500
# Cluster network
auto {{ cluster_iface }}
iface {{ cluster_iface }} inet static
address {{ cluster_ip }}/28
mtu 1500

View File

@ -0,0 +1,2 @@
search lan.xbazzi.com
nameserver 10.133.7.1

View File

View File

View File

@ -0,0 +1,43 @@
---
- name: Enable and start firewalld
ansible.builtin.systemd:
name: firewalld
enabled: yes
state: started
- name: Assign interface ens18 to core zone
ansible.posix.firewalld:
interface: ens18
zone: core
state: enabled
permanent: true
- name: Assign interface ens19 to mgmt zone
ansible.posix.firewalld:
interface: ens19
zone: mgmt
state: enabled
permanent: true
- name: Assign interface ens20 to dmz zone
ansible.posix.firewalld:
interface: ens20
zone: dmz
state: enabled
permanent: true
- name: Set core to default
ansible.builtin.command: firewall-cmd --set-default-zone=core
# - name: Remove ens18 from public
# ansible.builtin.command: firewall-cmd --zone=public --remove-interface=ens18
# - name: Assign interface ens18 to "internal" zone
# ansible.posix.firewalld:
# interface: ens18
# zone: internal
# state: enabled
# permanent: true
- name: Reload firewalld to apply changes
ansible.builtin.command: firewall-cmd --reload

View File

View File

23
roles/server/ftp/tasks/main.yml Executable file
View File

@ -0,0 +1,23 @@
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
- name: Install proftpd package
ansible.builtin.apt:
name: proftpd
state: present
- name: Ensure proftpd is enabled and started
ansible.builtin.service:
name: proftpd
state: started
enabled: true
become: true
- name: Allow FTP through UFW firewall (if UFW is enabled)
community.general.ufw:
rule: allow
port: 21
proto: tcp
ignore_errors: false

View File

View File

View File

@ -0,0 +1,136 @@
---
- name: Enable and start firewalld
ansible.builtin.systemd:
name: firewalld
enabled: yes
state: started
- name: Enable and start NetworkManager
ansible.builtin.systemd:
name: NetworkManager
enabled: yes
state: started
- name: Check existing zones
ansible.builtin.command: firewall-cmd --get-zones
register: firewalld_zones
- name: Debug output
ansible.builtin.debug:
var: firewalld_zones.stdout
# - name: Create zone "core"
# ansible.builtin.command: firewall-cmd --permanent --new-zone="{{ item }}"
# loop: ["core", "mgmt"]
# # loop: "{{ firewalld_zones.stdout | split }}"
# when: item in firewalld_zones.stdout.split()
# (item != "core" and
# item != "dmz")
- name: Create firewalld core zone
ansible.posix.firewalld:
zone: core
state: present
permanent: true
- name: Create firewalld mgmt zone
ansible.posix.firewalld:
zone: mgmt
state: present
permanent: true
- name: Create firewalld dmz zone
ansible.posix.firewalld:
zone: dmz
state: present
permanent: true
- name: Reload firewalld to apply changes
ansible.builtin.command: firewall-cmd --reload
- name: Enable ssh rule in core for initial ansible config
ansible.posix.firewalld:
zone: core
service: ssh
state: enabled
permanent: true
# - name: Ensure all other zones are disabled
# ansible.posix.firewalld:
# zone: "{{ item }}"
# state: disabled
# permanent: true
# when: item not in zones
# loop: "{{ firewalld_zones.stdout | split }}"
- name: Set up CORE interface manually
nmcli:
conn_name: CORE
zone: core
type: ethernet
ip4: "{{ provision_core_ip4 }}"
gw4: "{{ core_gw4 }}"
dns4: "{{ core_gw4 }}"
method4: "manual"
ifname: ens18
dns4_search: lan.xbazzi.com
state: present
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Set up mgmt interface manually
nmcli:
conn_name: MGMT
zone: mgmt
type: ethernet
ip4: "{{ provision_mgmt_ip4 }}"
routes4: "0.0.0.0/0 {{ mgmt_gw4 }}"
routing_rules4:
- "priority 2 from {{ mgmt_net }} table 200"
route_metric4: 102
dns4: "{{ mgmt_gw4 }}"
method4: "manual"
ifname: "ens19"
dns4_search: "lan.xbazzi.com"
state: present
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Set up dmz interface manually
nmcli:
conn_name: DMZ
zone: dmz
type: ethernet
ip4: "{{ provision_dmz_ip4 }}"
routes4: "0.0.0.0/0 {{ dmz_gw4 }}"
routing_rules4:
- "priority 3 from {{ dmz_net }} table 300"
route_metric4: 103
dns4: "{{ dmz_gw4 }}"
method4: "manual"
ifname: "ens20"
dns4_search: "lan.xbazzi.com"
state: present
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Remove ens18 default connection
nmcli:
conn_name: ens18
state: absent
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Remove ens19 default connection
nmcli:
conn_name: ens19
state: absent
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Remove ens20 default connection
nmcli:
conn_name: ens20
state: absent
# delegate_to: "{{ provision_core_ip4_no_subnet }}"
- name: Remove "Wired connection 1"
nmcli:
conn_name: Wired connection 1
state: absent
# delegate_to: "{{ provision_core_ip4_no_subnet }}"

View File

@ -0,0 +1,6 @@
DEVICE={{ network_config.interface }}
BOOTPROTO=none
ONBOOT=yes
IPADDR={{ network_config.address }}
NETMASK={{ network_config.netmask }}
GATEWAY={{ network_config.gateway }}

View File

View File

View File

View File

@ -0,0 +1,5 @@
---
- name: Reboot machine and send a message
ansible.builtin.shell: "reboot"
async: 1
poll: 0

View File

View File

View File

@ -0,0 +1,12 @@
---
- name: Add ansible user SSH public key
ansible.posix.authorized_key:
user: ansible
key: "{{ lookup('file', '/home/xbazzi/.ssh/ansible_ed25519.pub') }}"
state: present
- name: Add xbazzi user SSH public key
ansible.posix.authorized_key:
user: xbazzi
key: "{{ lookup('file', '/home/xbazzi/.ssh/lan_id_ed25519.pub') }}"
state: present

View File

View File

View File

@ -0,0 +1,115 @@
---
- name: Set hostname to generic localhost
ansible.builtin.hostname:
name: localhost.localdomain
# use: systemd
- name: Ensure IPv4 localhost entry exists in /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4"
state: present
create: yes
regexp: '^127\.0\.0\.1\s+localhost'
- name: Remove IPv6 localhost entry (::1) from /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^::1\s+localhost'
state: absent
# - name: Remove xbazzi user
# ansible.builtin.user:
# name: xbazzi
# state: absent
# remove: true
# - name: Truncate machine-id
# ansible.builtin.command: truncate -s 0 /etc/machine-id
- name: Remove DBus machine-id if exists
ansible.builtin.file:
path: /var/lib/dbus/machine-id
state: absent
- name: Remove root SSH folder
ansible.builtin.file:
path: /root/.ssh
state: absent
- name: Remove anaconda kickstart config
ansible.builtin.file:
path: /root/anaconda-ks.cfg
state: absent
- name: Clear logs
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /var/log/boot.log
- /var/log/cron
- /var/log/dmesg
- /var/log/grubby
- /var/log/lastlog
- /var/log/maillog
- /var/log/messages
- /var/log/secure
- /var/log/spooler
- /var/log/tallylog
- /var/log/wtmp
- /var/log/yum.log
- /var/log/audit/audit.log
- /var/log/tuned/tuned.log
- /var/log/wpa_supplicant.log
- /var/log/ovirt-guest-agent/ovirt-guest-agent.log
- name: Rotate and vacuum journal logs
ansible.builtin.shell: |
journalctl --rotate
journalctl --vacuum-time=1s
when: ansible_facts['distribution_major_version'] is version('8', '>=')
- name: Clear shell history
ansible.builtin.copy:
content: ""
dest: /root/.bash_history
force: true
- name: Find all SSH keys
ansible.builtin.find:
paths:
- /etc/ssh
- /home/
patterns:
- "ssh_host*"
- "id_*"
- "authorized_keys"
- "known_hosts"
- "config"
use_regex: false
recurse: true
file_type: file
register: ssh_files
- name: Debug found SSH keys
debug:
msg: "{{ item.path }}"
loop: "{{ ssh_files.files }}"
- name: Remove SSH keys
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ ssh_files.files }}"
# loop: "{{ ssh_keys.results | map(attribute='files') | flatten }}"
- name: Sync changes to disk
ansible.builtin.command: sync
- name: Remove old local SSH known_hosts entry (necessary to avoid fingerprint warning)
become_user: xbazzi
local_action:
module: command
args:
cmd: ssh-keygen -R "{{ hostvars['staging-vm'].ansible_host }}"

View File

View File

View File

View File

@ -0,0 +1,45 @@
---
- name: Add xbazzi group
ansible.builtin.group:
name: xbazzi
state: present
gid: 1337
- name: Add xbazzi user
ansible.builtin.user:
name: xbazzi
create_home: true
shell: /bin/bash
groups: wheel,xbazzi
uid: 1337
state: present
# - name: Add ansible group
# ansible.builtin.group:
# name: ansible
# state: present
# gid: 1001
# - name: Add ansible user
# ansible.builtin.user:
# name: ansible
# create_home: true
# shell: /bin/bash
# groups: wheel,ansible
# state: present
# uid: 1001
- name: Add nfsuser group
ansible.builtin.group:
name: nfsuser
state: present
gid: 3005
- name: Add nfsuser user
ansible.builtin.user:
name: nfsuser
create_home: true
shell: /bin/bash
groups: wheel
state: present
uid: 3005

View File

View File

@ -0,0 +1,5 @@
directory: "postgres"
default_user: "postgres"
default_password: "password"
port: "5432"
container_name: "postgres"

View File

@ -0,0 +1,23 @@
- name: Create data folder
ansible.builtin.file:
dest: "{{ docker_dir }}/{{ directory }}"
state: directory
owner: root
group: docker
mode: '0770'
recurse: yes
- name: Put up the postgres container
community.docker.docker_container:
name: "{{container_name}}"
image: postgres:17.4
restart_policy: always
state: started
pull: true
ports:
- "{{ port }}:5432"
env:
POSTGRES_USER: "{{ default_user }}"
POSTGRES_PASSWORD: "{{ default_password }}"
volumes:
- "{{ docker_dir }}/{{ directory }}/data:/var/lib/postgresql/data/"

View File

@ -0,0 +1,3 @@
mount_host: "{{ hostvars['nas'].ansible_host }}"
share: "/mnt/ALEXANDRIA/"
mount_path: "/mnt/unspecifiedshare"

View File

@ -0,0 +1,21 @@
---
- name: Ensure NFS client is installed
ansible.builtin.package:
name: nfs-common
state: present
become: true
- name: Create mount point directory
ansible.builtin.file:
path: "{{ mount_path }}"
state: directory
mode: '0777'
become: true
- name: Mount share
ansible.posix.mount:
src: "{{ mount_host }}:{{ share }}"
path: "{{ mount_path }}"
fstype: nfs
state: mounted
become: true

28
setup.sh Executable file
View File

@ -0,0 +1,28 @@
echo "**Deleting .git so you can start your own repo"
rm -rf .git
echo "***Ansible vault password***"
echo "This is used to encrypt/descrypt secrets in your vault"
echo "We'll save it to a file in ~/.[file name] so it doesn't have to be typed every time"
echo
read -p "File name (no . prefix): " ansible_vault_pass_filename
read -s -p "Password: " ansible_vault_pass
echo $ansible_vault_pass > ~/.$ansible_vault_pass_filename
echo "vault_password_file = ~/.$ansible_vault_pass_filename" >>ansible.cfg
echo
echo
echo "***Ansible become password***"
echo "A lot of actions need sudo. This password will be stored in group_vars/all.yml encrypted"
read -s -p "Password: " ansible_become_pass
echo "# Sudo password for your servers" >>./group_vars/all.yml
ansible-vault encrypt_string "$ansible_become_pass" --name 'ansible_become_pass' >>./group_vars/all.yml
echo
echo
echo "Setup complete"
echo "You can delete setup.sh since running it again would cause issues"
echo "Config for vault password was output to ./ansible.cfg"
echo "Config for sudo (become) password was output to ./group_vars/all.yml"