commit 702d7cf17dee42a13d65c95add98e638a72c07e5 Author: Chris Roberts <chris.roberts@learningunix.net> Date: Wed, 12 Aug 2026 12:15:32 -0500 Initial commit Diffstat:
35 files changed, 635 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +.vault_pass +*.retry +roles/stacks/vars/vault.yml diff --git a/group_vars/all.yml b/group_vars/all.yml @@ -0,0 +1 @@ +--- diff --git a/inventory/hosts.yml b/inventory/hosts.yml @@ -0,0 +1,6 @@ +--- +all: + hosts: + docker-host: + ansible_connection: local + ansible_user: cjr diff --git a/readme.md b/readme.md diff --git a/requirements.yml b/requirements.yml @@ -0,0 +1,5 @@ +--- +collections: + - name: ansible.posix + - name: community.general + - name: community.docker diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml @@ -0,0 +1,37 @@ +--- +- 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: "{{ docker_gpg_url }}" + dest: "{{ docker_gpg_dest }}" + mode: '0644' + +- name: Add Docker apt repository + ansible.builtin.apt_repository: + repo: "deb [arch={{ 'amd64' if ansible_architecture == 'x86_64' else ansible_architecture }} signed-by={{ docker_gpg_dest }}] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + filename: docker + +- name: Install Docker packages + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + update_cache: true + +- name: Enable and start Docker service + ansible.builtin.systemd: + name: docker + enabled: true + state: started + +- name: Add users to docker group + ansible.builtin.user: + name: "{{ item }}" + groups: docker + append: true + loop: "{{ docker_users }}" diff --git a/roles/docker/vars/main.yml b/roles/docker/vars/main.yml @@ -0,0 +1,13 @@ +--- +docker_gpg_url: https://download.docker.com/linux/ubuntu/gpg +docker_gpg_dest: /etc/apt/keyrings/docker.asc +docker_packages: + - containerd.io + - docker-buildx-plugin + - docker-ce + - docker-ce-cli + - docker-ce-rootless-extras + - docker-compose-plugin + - docker-model-plugin +docker_users: + - cjr diff --git a/roles/network/files/50-cloud-init.yaml b/roles/network/files/50-cloud-init.yaml @@ -0,0 +1,15 @@ +network: + version: 2 + ethernets: + ens18: + addresses: + - "192.168.0.249/24" + nameservers: + addresses: + - 192.168.0.1 + - 9.9.9.9 + search: + - lan + routes: + - to: "default" + via: "192.168.0.1" diff --git a/roles/network/handlers/main.yml b/roles/network/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: Apply Netplan + ansible.builtin.command: netplan apply diff --git a/roles/network/tasks/main.yml b/roles/network/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: Deploy Netplan configuration + ansible.builtin.copy: + src: 50-cloud-init.yaml + dest: /etc/netplan/50-cloud-init.yaml + owner: root + group: root + mode: '0600' + notify: Apply Netplan diff --git a/roles/nfs/tasks/main.yml b/roles/nfs/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Ensure NFS mount points exist + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + mode: '0755' + loop: "{{ nfs_mounts }}" + +- name: Configure and mount NFS shares + ansible.posix.mount: + src: "{{ item.src }}" + path: "{{ item.path }}" + fstype: nfs + opts: "{{ item.opts }}" + dump: '0' + passno: '0' + state: mounted + loop: "{{ nfs_mounts }}" diff --git a/roles/nfs/vars/main.yml b/roles/nfs/vars/main.yml @@ -0,0 +1,5 @@ +--- +nfs_mounts: + - src: 192.168.0.240:/mnt/immich + path: /mnt/immich + opts: rw,defaults diff --git a/roles/packages/tasks/main.yml b/roles/packages/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Install apt packages + ansible.builtin.apt: + name: "{{ apt_packages }}" + state: present + update_cache: true + +- name: Install snaps + community.general.snap: + name: "{{ item.name }}" + classic: "{{ item.classic | default(false) }}" + loop: "{{ snap_packages }}" diff --git a/roles/packages/vars/main.yml b/roles/packages/vars/main.yml @@ -0,0 +1,10 @@ +--- +apt_packages: + - curl + - htop + - iotop + - nfs-common + +snap_packages: + - name: go + classic: true diff --git a/roles/stacks/files/freshrss/docker-compose.yml b/roles/stacks/files/freshrss/docker-compose.yml @@ -0,0 +1,14 @@ +--- +services: + freshrss: + image: lscr.io/linuxserver/freshrss:latest + container_name: freshrss + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Chicago + volumes: + - /opt/freshrss/config:/config + ports: + - 80:80 + restart: unless-stopped diff --git a/roles/stacks/files/homepage/bookmarks.yaml b/roles/stacks/files/homepage/bookmarks.yaml @@ -0,0 +1,82 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/latest/configs/bookmarks +- Website: + - learningunix.net: + - abbr: LU + href: https://learningunix.net + + - "Local Hugo": + - abbr: LH + href: http://localhost:1313 + - "Git repo": + - abbr: GT + href: https://gitea.learningunix.net + - "Linked In": + - abbr: LI + href: https://www.linkedin.com/in/chris-roberts-5808a63a7 + +- Training: + - Dion: + - abbr: DT + href: https://members.diontraining.com/enrollments + - Udemy: + - abbr: UD + href: https://www.udemy.com + +- News: + - cnn: + - abbr: CNN + href: https://cnn.com/ + - Vermaden: + - abbr: VERM + href: https://vermaden.wordpress.com/posts/ + +- Entertainment: + - YouTube: + - abbr: YT + href: https://youtube.com/ + +- Utilities: + - SecurityBank: + - abbr: SB + href: https://onlinebanking.sbswmo.com/Pages/Default.html + - Claude: + - abbr: AI + href: https://claude.ai + - SketchUp: + - abbr: SU + href: https://app.sketchup.com/app + - Chase: + - abbr: CD + href: https://www.chase.com/ + +- Local: + - Karakeep: + - abbr: KK + href: http://100.76.215.50:3000 + - KVM-Backups: + - abbr: KVM2 + href: http://192.168.0.142 + - KVM-Proxmox: + - abbr: KVM1 + href: http://192.168.0.132 + +- Tailscale2: + - KVM-Proxmox: + - abbr: PM + href: https://100.101.112.120 + - KVM-Backups: + - abbr: BU + href: https://100.125.166.56 + +- Email: + - "Jake Roberts": + - abbr: JR + href: https://mail.google.com/mail/u/1/#inbox + - "Chris Roberts": + - abbr: CR + href: https://mail.google.com/mail/u/0/#inbox + - "Proton Mail": + - abbr: PM + href: https://mail.proton.me/u/3/inbox diff --git a/roles/stacks/files/homepage/custom.css b/roles/stacks/files/homepage/custom.css diff --git a/roles/stacks/files/homepage/custom.js b/roles/stacks/files/homepage/custom.js diff --git a/roles/stacks/files/homepage/docker-compose.yml b/roles/stacks/files/homepage/docker-compose.yml @@ -0,0 +1,34 @@ +services: + dockerproxy: + image: ghcr.io/tecnativa/docker-socket-proxy:latest + container_name: dockerproxy + environment: + - CONTAINERS=1 + - POST=0 + ports: + - 127.0.0.1:2375:2375 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - proxy + restart: unless-stopped + + homepage: + image: ghcr.io/gethomepage/homepage:latest + init: true + container_name: homepage + ports: + - 3000:3000 + volumes: + - /opt/homepage/:/app/config + environment: + PUID: 1000 + PGID: 1000 + HOMEPAGE_ALLOWED_HOSTS: 192.168.0.249:3000,docker.tail4d3a1.ts.net:3000 + networks: + - proxy + restart: unless-stopped + +networks: + proxy: + driver: bridge diff --git a/roles/stacks/files/homepage/docker.yaml b/roles/stacks/files/homepage/docker.yaml @@ -0,0 +1,10 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/latest/configs/docker/ + + my-docker: + host: dockerproxy + port: 2375 + + # my-docker: + socket: /var/run/docker.sock diff --git a/roles/stacks/files/homepage/kubernetes.yaml b/roles/stacks/files/homepage/kubernetes.yaml @@ -0,0 +1,2 @@ +--- +# sample kubernetes config diff --git a/roles/stacks/files/homepage/proxmox.yaml b/roles/stacks/files/homepage/proxmox.yaml @@ -0,0 +1,4 @@ +--- +# url: https://proxmox.host.or.ip:8006 +# token: username@pam!Token ID +# secret: secret diff --git a/roles/stacks/files/homepage/services.yaml b/roles/stacks/files/homepage/services.yaml @@ -0,0 +1,78 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/latest/configs/services + + + - Media: + # - SABNzbd: + # href: http://192.168.0.186:8080 + # description: Media + # icon: sabnzbd.png + # widget: + # type: sabnzbd + # url: http://192.168.0.186:8080 + # key: be46b1e60ff4481eb4d6c26ebc5ba92a + #- Radarr: + # href: http://192.168.0.186:7878 + # description: Movies + # icon: radarr.png + # widget: + # type: radarr + # url: http://192.168.0.186:7878 + # key: 315a5cdb6dd74e07859cd6047e76c1ff + # enableQueue: false + # - Sonarr: + #href: http://192.168.0.242:8989 + #description: TV + #icon: sonarr.png + #widget: + #type: sonarr + #url: http://192.168.0.242:8989 + #key: d1863299765c48f0a4bba046f4e35f8b + #enableQueue: false + - Jellyfin: + href: http://192.168.0.241:8096 + description: watch media + icon: jellyfin.png + - FreshRSS: + href: http://192.168.0.249 + description: Local News Service + icon: freshrss.png + - HDHomerun: + href: http://192.168.0.40 + description: Network tuner + icon: hdhomerun.png + + - Services: + href: https://192.168.0.240:8006 + description: Virtual Machine Host + icon: proxmox.png + - Homelab: + href: https://192.168.0.117:8006 + description: Homelab virtual host + icon: proxmox.png + + - Immich: + href: http://192.168.0.249:2283 + Description: Picture backups + icon: immich.png + - Tailscale: + - Services: + href: https://100.108.222.50:8006 + description: Virtual Machine Host (remote connection) + icon: proxmox.png + - Immich: + href: http://100.76.149.108:2283 + description: Picture backups + icon: immich.png + - FreshRSS: + href: http://100.76.149.108 + description: Local News Service + icon: freshrss.png + + + + + + + diff --git a/roles/stacks/files/homepage/settings.yaml b/roles/stacks/files/homepage/settings.yaml @@ -0,0 +1,23 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/latest/configs/settings +# +layout: + Media: + style: row + columns: 4 + Tailscale: + style: row + columns: 4 + Email: + style: row + columns: 4 + Tailscale2: + style: row + columns: 4 +theme: dark + + +providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey diff --git a/roles/stacks/files/homepage/widgets.yaml b/roles/stacks/files/homepage/widgets.yaml @@ -0,0 +1,21 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/latest/configs/service-widgets + +- greeting: + text_size: xl + text: "Welcome to Jake's Home Lab" + + +- resources: + labe: Docker VM + cpu: true + memory: true + disk: / + network: true + + +- search: + provider: duckduckgo + target: _blank + diff --git a/roles/stacks/files/immich/docker-compose.yml b/roles/stacks/files/immich/docker-compose.yml @@ -0,0 +1,74 @@ +# +# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose +# +# Make sure to use the docker-compose.yml of the current release: +# +# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml +# +# The compose file on main may not be compatible with the latest release. + +name: immich + +services: + immich-server: + container_name: immich_server + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + # extends: + # file: hwaccel.transcoding.yml + # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding + volumes: + # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file + - ${UPLOAD_LOCATION}:/data + - /etc/localtime:/etc/localtime:ro + env_file: + - .env + ports: + - '2283:2283' + depends_on: + - redis + - database + restart: always + healthcheck: + disable: false + + immich-machine-learning: + container_name: immich_machine_learning + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. + # Example tag: ${IMMICH_VERSION:-release}-cuda + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration + # file: hwaccel.ml.yml + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable + volumes: + - model-cache:/cache + env_file: + - .env + restart: always + healthcheck: + disable: false + + redis: + container_name: immich_redis + image: docker.io/valkey/valkey:8-bookworm@sha256:5b8f8c333bef895c925f56629d6ba90aea95a4f7391f62411e625267c600b19c + healthcheck: + test: redis-cli ping || exit 1 + restart: always + + database: + container_name: immich_postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_INITDB_ARGS: '--data-checksums' + # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs + # DB_STORAGE_TYPE: 'HDD' + volumes: + # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file + - ${DB_DATA_LOCATION}:/var/lib/postgresql/data + shm_size: 128mb + restart: always + +volumes: + model-cache: diff --git a/roles/stacks/files/watchtower/docker-compose.yml b/roles/stacks/files/watchtower/docker-compose.yml @@ -0,0 +1,12 @@ +services: + watchtower: + image: nickfedor/watchtower:latest + container_name: watchtower + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - WATCHTOWER_CLEANUP=true # Remove old images after update + - WATCHTOWER_INCLUDE_STOPPED=false # Only watch running containers + - WATCHTOWER_POLL_INTERVAL=86400 # Check every 24 hours (seconds) + - TZ=America/Chicago diff --git a/roles/stacks/tasks/main.yml b/roles/stacks/tasks/main.yml @@ -0,0 +1,56 @@ +--- +- name: Ensure stack directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: + - /opt/freshrss + - /opt/homepage + - /opt/.immich-app + - /opt/watchtower + +- name: Deploy compose files + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: '0644' + loop: + - { src: freshrss/docker-compose.yml, dest: /opt/freshrss/docker-compose.yml } + - { src: homepage/docker-compose.yml, dest: /opt/homepage/docker-compose.yml } + - { src: immich/docker-compose.yml, dest: /opt/.immich-app/docker-compose.yml } + - { src: watchtower/docker-compose.yml, dest: /opt/watchtower/docker-compose.yml } + +- name: Deploy Immich .env + ansible.builtin.template: + src: immich.env.j2 + dest: /opt/.immich-app/.env + owner: root + group: root + mode: '0600' + +- name: Deploy Homepage config files + ansible.builtin.copy: + src: "homepage/{{ item }}" + dest: "/opt/homepage/{{ item }}" + mode: '0644' + loop: + - services.yaml + - settings.yaml + - widgets.yaml + - docker.yaml + - bookmarks.yaml + - custom.css + - custom.js + - proxmox.yaml + - kubernetes.yaml + +- name: Deploy stacks + community.docker.docker_compose_v2: + project_src: "{{ item }}" + state: present + loop: + - /opt/freshrss + - /opt/homepage + - /opt/.immich-app + - /opt/watchtower diff --git a/roles/stacks/templates/immich.env.j2 b/roles/stacks/templates/immich.env.j2 @@ -0,0 +1,7 @@ +UPLOAD_LOCATION={{ immich_upload_location }} +DB_DATA_LOCATION={{ immich_db_data_location }} +TZ={{ immich_tz }} +IMMICH_VERSION={{ immich_version }} +DB_PASSWORD={{ vault_immich_db_password }} +DB_USERNAME={{ immich_db_username }} +DB_DATABASE_NAME={{ immich_db_database_name }} diff --git a/roles/stacks/vars/main.yml b/roles/stacks/vars/main.yml @@ -0,0 +1,7 @@ +--- +immich_upload_location: /mnt/immich/library +immich_db_data_location: /opt/immich +immich_tz: America/Chicago +immich_version: release +immich_db_username: postgres +immich_db_database_name: immich diff --git a/roles/tailscale/tasks/main.yml b/roles/tailscale/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Download Tailscale GPG key + ansible.builtin.get_url: + url: "{{ tailscale_gpg_url }}" + dest: "{{ tailscale_gpg_dest }}" + mode: '0644' + +- name: Add Tailscale apt repository + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ tailscale_gpg_dest }}] https://pkgs.tailscale.com/stable/ubuntu {{ ansible_distribution_release }} main" + state: present + filename: tailscale + +- name: Install Tailscale + ansible.builtin.apt: + name: tailscale + state: present + update_cache: true + +- name: Enable and start Tailscale service + ansible.builtin.systemd: + name: tailscaled + enabled: true + state: started diff --git a/roles/tailscale/vars/main.yml b/roles/tailscale/vars/main.yml @@ -0,0 +1,3 @@ +--- +tailscale_gpg_url: "https://pkgs.tailscale.com/stable/ubuntu/{{ ansible_distribution_release }}.noarmor.gpg" +tailscale_gpg_dest: /usr/share/keyrings/tailscale-archive-keyring.gpg diff --git a/roles/update/tasks/main.yml b/roles/update/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Update apt cache and upgrade packages + ansible.builtin.apt: + update_cache: true + upgrade: full + autoremove: true + autoclean: true + +- name: Check if reboot is required + ansible.builtin.stat: + path: /var/run/reboot-required + register: reboot_required + +- name: Notify if reboot is required + ansible.builtin.debug: + msg: "Reboot required to apply updates." + when: reboot_required.stat.exists diff --git a/roles/users/tasks/main.yml b/roles/users/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: Set authorized keys for cjr + ansible.posix.authorized_key: + user: cjr + state: present +key: | + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ4IEDCWRgzKMyBndNdwGX862WkcsZjzeGq5i7yW7H0+ cjr@SER6 + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKthb78LN6IzepmSi8Al+C35DEJBNIH6/AIHPA0ldu5N cjr@ansible + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBFLbbSDX1Bx+u6aKnIEGoVN/kb8qEdQuJQtQqQBF8qc cjr@cjr-t15 diff --git a/site.yml b/site.yml @@ -0,0 +1,21 @@ +--- +- name: Configure docker host + hosts: all + become: true + roles: + - role: network + tags: network + - role: packages + tags: packages + - role: docker + tags: docker + - role: tailscale + tags: tailscale + - role: nfs + tags: nfs + - role: update + tags: update + - role: users + tags: users + - role: stacks + tags: stacks