Add dormant Rocky server profile

This commit is contained in:
Fabio Scotto di Santolo
2026-08-31 21:32:03 +02:00
parent 45340103d0
commit e0a81d6a70
12 changed files with 263 additions and 31 deletions

View File

@@ -1,6 +1,7 @@
# AGENTS.md
Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBSD transition targets, WSL, and an Ubuntu server.
Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBSD transition targets,
WSL, an Ubuntu server, and a dormant Rocky Linux 9 server profile.
## Source Of Truth
- Main orchestration: `ansible/site.yml`
@@ -16,6 +17,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS
- Void desktop profile is also the base for other future/reference hosts via `platform_void + graphical_desktop`
- Workstation: `deadalus` is Windows + Fedora WSL.
- Ubuntu server: `prometheus`
- Dormant Rocky server: `rocky_server` is empty until a migration target is explicitly assigned.
- NAS: `atlas` (Rocky Linux 9, reached through SSH)
- Hosts intentionally belong to multiple groups; trust `ansible/site.yml` over hostname assumptions.
- Inventory axes are independent: `platform_*`, `role_*`, and `desktop_*`. Legacy `void` and `desktop` remain compatibility parents.
@@ -41,6 +43,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS
- Fedora laptop work: `ansible-playbook ansible/site.yml --limit nymph --check --diff`
- WSL workstation dev: `ansible-playbook ansible/site.yml --limit deadalus --check --diff`
- Server: `ansible-playbook ansible/site.yml --limit prometheus --check --diff`
- Rocky server after activation: `ansible-playbook ansible/site.yml --limit <host> --check --diff`
- Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --check --diff`
- Focused checks:
- Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit <host> --tags emacs --check --diff -e emacs_enabled=true`
@@ -78,7 +81,9 @@ The Void desktop package lists in `ansible/inventory/group_vars/void.yml` are ke
- `desktop_common_packages` — GUI infrastructure shared by the minimal desktop mode.
- `desktop_minimal_packages` — applications, integration components, and the `emptty` display manager.
- `desktop_sway_packages` — binaries specific to the Sway session.
`profile_packages` in the same file is cross-distro and is overridden by `group_vars/server.yml` and the workstation group vars; do not move desktop-specific Void entries through it.
`profile_packages` remains the shared package bucket for Void, Fedora, and Ubuntu profiles. Rocky uses
`rocky_profile_packages` so RPM-specific names do not leak back into the other platforms; do not move
desktop-specific Void entries through either bucket.
The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-independent content and `desktop_minimal_dotfiles` carries Thunar, Udiskie, and MIME defaults. `desktop_void_dotfiles` remains reserved for files that need the Void runtime.
## Workstation Notes
@@ -87,6 +92,17 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
- Fedora WSL installs Mise from the official `jdxcode/mise` COPR and uses its pinned Temurin Java 11 JDK; update the declared Mise version deliberately.
- Windows applications are installed manually and are not managed from the WSL profile.
## Rocky Server Notes
- `rocky_server` is an empty child of both `platform_rocky` and `server`; it must stay empty until a
real migration target is ready.
- The active `prometheus` host remains in `ubuntu_server`. Never place the same inventory host in both
`ubuntu_server` and `rocky_server`; use a distinct name while testing a parallel replacement.
- The target must already provide `server_username` with local sudo access before the profile runs.
- The Rocky profile installs Docker CE, uses firewalld, preserves SELinux enforcement, and renders the
same server Compose stack. It does not transfer data, start containers, update DNS, or cut over traffic.
- Atlas-only OpenZFS, NFS, Samba, Cockpit, and native Syncthing packages stay selected through Atlas
host variables and must not leak into `rocky_server`.
## Atlas NAS Notes
- `atlas` is a remote Rocky Linux 9 NAS. Keep its connection, LAN, pool and mountpoint values in
`host_vars/atlas.yml`. Bootstrap it once with `-e atlas_connection_username=<existing-admin>`;

View File

@@ -36,8 +36,9 @@ infra/
## Managed machines
The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, an Ubuntu server, and
a Rocky Linux 9 NAS. Configuration is layered instead of being tied to host names:
The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, an Ubuntu server, a
dormant Rocky Linux 9 server profile, and a Rocky Linux 9 NAS. Configuration is layered instead of
being tied to host names:
```text
common user environment
@@ -53,6 +54,7 @@ common user environment
| `nymph` | Fedora | Desktop laptop | GNOME |
| `deadalus` | Fedora WSL | Development workstation | — |
| `prometheus` | Ubuntu | Server | — |
| — (`rocky_server`, dormant) | Rocky 9 | Server | — |
| `atlas` | Rocky 9 | NAS | — |
```text
@@ -99,9 +101,15 @@ That gives it Fedora packages through DNF, Docker from the official repository,
## Server
`prometheus` is the Ubuntu LTS server. It has no graphical environment and gets server-specific dotfiles and templates.
`prometheus` is the Ubuntu LTS server. It has no graphical environment and gets server-specific
dotfiles and templates. `rocky_server` is the empty alternative profile for a future Rocky Linux 9
migration; it does not select any host until one is explicitly added to that inventory group.
The server profile installs Ubuntu packages, Docker from the official repository, declared systemd services, UFW rules, and the server Compose stack. Syncthing ports `22000/tcp`, `22000/udp`, and `21027/udp` are opened; the Syncthing GUI is not directly opened in UFW.
The server profile installs platform-specific packages, Docker CE from the official repository,
declared systemd services, the server Compose stack, and either UFW on Ubuntu or firewalld on Rocky.
Syncthing ports `22000/tcp`, `22000/udp`, and `21027/udp` are opened; the Syncthing GUI is not
directly exposed by the managed firewall rules. Rocky bind mounts use private SELinux relabeling for
application data while host system files remain unchanged.
Server identity comes from `server_username`, `server_user_group`, and `server_user_home` in `ansible/inventory/group_vars/server.yml`. `server_username` defaults to `username`, but it can be overridden, for example:
@@ -112,6 +120,12 @@ ansible-playbook ansible/site.yml --limit prometheus \
-e server_user_home=/srv/myuser
```
The dormant profile provisions configuration only: it does not transfer data, start the Compose
stack, update DNS, or perform a cutover. During migration, add the replacement machine to
`rocky_server` under a distinct inventory name after creating `server_username` with local sudo
access. When reusing `prometheus` at cutover, remove it from `ubuntu_server` before adding it to
`rocky_server`; a host must never belong to both platform groups.
## NAS
`atlas` is a Rocky Linux 9 NAS reached through SSH. Its pool already exists: the profile only
@@ -229,13 +243,14 @@ ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --di
## What `site.yml` runs
```text
all -> dotfiles_common
all except platform_rocky -> dotfiles_common
platform_void -> packages_void + services_runit
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
platform_freebsd -> packages_freebsd + services_freebsd
platform_fedora -> packages_fedora + services_systemd
platform_rocky -> packages_rocky + services_systemd
atlas -> profile_atlas
rocky_server -> dotfiles_common + profile_server (after platform_rocky)
platform_fedora & role_personal_workstation -> profile_personal_workstation
platform_fedora & desktop_gnome -> profile_desktop_gnome
workstation_dev_fedora -> profile_workstation_dev_common
@@ -248,8 +263,9 @@ So, in practice:
- `platform_fedora` configures `ikaros`, `nymph`, and `deadalus`.
- `deadalus` gets the Fedora development layer followed by the WSL layer.
- `ubuntu_server` configures `prometheus`.
- Empty `rocky_server` defines the Rocky 9 server alternative without targeting a machine.
- `atlas` receives the Rocky platform layer and the NAS profile through SSH.
- Empty `platform_void` and `platform_freebsd` groups do nothing until they get a host.
- Empty `platform_void`, `platform_freebsd`, and `rocky_server` groups do nothing until they get a host.
- The playbook never restarts the display manager during a run.
- `secrets/vault.yml` and then `secrets/vault.local.yml` are loaded only when present.

View File

@@ -16,17 +16,39 @@ rocky_syncthing_archive_url: >-
~ '.tar.gz'
}}
rocky_manage_openzfs_repo: false
rocky_manage_syncthing_binary: false
rocky_manage_docker_repo: false
rocky_manage_github_cli_repo: false
rocky_docker_repo_url: https://download.docker.com/linux/rhel/docker-ce.repo
rocky_docker_repo_file: /etc/yum.repos.d/docker-ce.repo
rocky_github_cli_repo_url: https://cli.github.com/packages/rpm/gh-cli.repo
rocky_github_cli_repo_file: /etc/yum.repos.d/gh-cli.repo
enabled_services:
- firewalld
rocky_common_packages:
- bash-completion
- bat
- fzf
- git
- jq
- tree
- unzip
- vim-enhanced
- wget
- zip
- zoxide
rocky_packages_base:
- cockpit
- dnf-plugins-core
- firewalld
- nfs-utils
- openssh-server
- policycoreutils-python-utils
- rsync
- samba
- sudo
- zfs
rocky_profile_packages: []
rocky_docker_packages: []
rocky_docker_conflicting_packages: []

View File

@@ -0,0 +1,57 @@
---
rocky_manage_docker_repo: true
rocky_manage_github_cli_repo: true
rocky_profile_packages:
- avahi
- curl
- dmidecode
- dosfstools
- gcc
- gcc-c++
- git-delta
- gh
- gnupg2
- htop
- make
- nmap-ncat
- nodejs
- parted
- pciutils
- pinentry
- ranger
- ripgrep
rocky_docker_packages:
- containerd.io
- docker-buildx-plugin
- docker-ce
- docker-ce-cli
- docker-compose-plugin
rocky_docker_conflicting_packages:
- docker
- docker-client
- docker-client-latest
- docker-common
- docker-engine
- docker-latest
- docker-latest-logrotate
- docker-logrotate
- podman
- runc
enabled_services:
- firewalld
- docker
server_firewall_backend: firewalld
server_firewalld_zone: public
server_firewalld_services:
- ssh
server_firewalld_ports:
- 22000/tcp
- 22000/udp
- 21027/udp
server_sshd_service_name: sshd
server_compose_selinux_mount_option: Z

View File

@@ -31,6 +31,10 @@ enabled_services:
- ufw
- docker
server_firewall_backend: ufw
server_sshd_service_name: ssh
server_compose_selinux_mount_option: ""
ubuntu_dotfiles:
- src: .bashrc.d/
dest: .bashrc.d/

View File

@@ -26,6 +26,16 @@ atlas_zfs_dataset_icloud_photos: icloud_photos
atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT
atlas_manage_storage: false
rocky_manage_openzfs_repo: true
rocky_manage_syncthing_binary: true
host_packages:
- cockpit
- nfs-utils
- policycoreutils-python-utils
- samba
- zfs
atlas_nfs_exports:
- path: "{{ atlas_work_mountpoint }}"
clients: "{{ atlas_lan_subnet }}(rw,sync,no_subtree_check,root_squash)"

View File

@@ -19,6 +19,8 @@ all:
platform_rocky:
hosts:
atlas:
children:
rocky_server:
void:
children:
@@ -76,6 +78,7 @@ all:
server:
children:
rocky_server:
ubuntu_server:
workstation_dev_fedora:
@@ -92,3 +95,6 @@ all:
hosts:
prometheus:
ansible_connection: local
rocky_server:
hosts: {}

View File

@@ -32,6 +32,7 @@
ansible.builtin.dnf:
name: "{{ rocky_openzfs_release_rpm }}"
state: present
when: rocky_manage_openzfs_repo | bool
- name: Disable OpenZFS DKMS repository
tags: [packages, storage]
@@ -39,6 +40,7 @@
name:
- zfs
state: disabled
when: rocky_manage_openzfs_repo | bool
- name: Enable OpenZFS kmod repository
tags: [packages, storage]
@@ -46,6 +48,36 @@
name:
- zfs-kmod
state: enabled
when: rocky_manage_openzfs_repo | bool
- name: Configure official Docker RPM repository
tags: [packages, docker]
ansible.builtin.get_url:
url: "{{ rocky_docker_repo_url }}"
dest: "{{ rocky_docker_repo_file }}"
owner: root
group: root
mode: "0644"
when: rocky_manage_docker_repo | bool
- name: Configure official GitHub CLI RPM repository
tags: [packages]
ansible.builtin.get_url:
url: "{{ rocky_github_cli_repo_url }}"
dest: "{{ rocky_github_cli_repo_file }}"
owner: root
group: root
mode: "0644"
when: rocky_manage_github_cli_repo | bool
- name: Remove packages conflicting with Docker CE
tags: [packages, docker]
ansible.builtin.dnf:
name: "{{ rocky_docker_conflicting_packages }}"
state: absent
when:
- rocky_manage_docker_repo | bool
- rocky_docker_conflicting_packages | length > 0
- name: Refresh Rocky package metadata
tags: [packages]
@@ -58,20 +90,30 @@
name: >-
{{
(
(common_packages | default([]))
(rocky_common_packages | default([]))
+ (rocky_packages_base | default([]))
+ (profile_packages | default([]))
+ (rocky_profile_packages | default([]))
+ (rocky_docker_packages | default([]))
+ (host_packages | default([]))
) | unique
}}
state: present
- name: Add Rocky user to docker group
tags: [packages, docker]
ansible.builtin.user:
name: "{{ effective_username }}"
groups: docker
append: true
when: rocky_docker_packages | length > 0
- name: Require supported architecture for Syncthing
tags: [packages, syncthing]
ansible.builtin.assert:
that:
- ansible_facts['architecture'] == 'x86_64'
fail_msg: The pinned Atlas Syncthing binary currently supports x86_64 only.
when: rocky_manage_syncthing_binary | bool
- name: Read installed Syncthing version
tags: [packages, syncthing]
@@ -83,6 +125,7 @@
changed_when: false
failed_when: false
check_mode: false
when: rocky_manage_syncthing_binary | bool
- name: Determine whether Syncthing must be installed
tags: [packages, syncthing]
@@ -92,6 +135,7 @@
('syncthing v' ~ rocky_syncthing_version ~ ' ')
not in (rocky_syncthing_version_check.stdout | default(''))
}}
when: rocky_manage_syncthing_binary | bool
- name: Create temporary Syncthing extraction directory
tags: [packages, syncthing]
@@ -101,7 +145,9 @@
owner: root
group: root
mode: "0755"
when: rocky_syncthing_install_required | bool
when:
- rocky_manage_syncthing_binary | bool
- rocky_syncthing_install_required | bool
- name: Download pinned Syncthing release
tags: [packages, syncthing]
@@ -112,7 +158,9 @@
owner: root
group: root
mode: "0644"
when: rocky_syncthing_install_required | bool
when:
- rocky_manage_syncthing_binary | bool
- rocky_syncthing_install_required | bool
- name: Extract pinned Syncthing release
tags: [packages, syncthing]
@@ -120,7 +168,9 @@
src: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz"
dest: "/tmp/syncthing-{{ rocky_syncthing_version }}"
remote_src: true
when: rocky_syncthing_install_required | bool
when:
- rocky_manage_syncthing_binary | bool
- rocky_syncthing_install_required | bool
- name: Install pinned Syncthing binary
tags: [packages, syncthing]
@@ -136,16 +186,20 @@
owner: root
group: root
mode: "0755"
when: rocky_syncthing_install_required | bool
when:
- rocky_manage_syncthing_binary | bool
- rocky_syncthing_install_required | bool
- name: Remove Syncthing release archive
tags: [packages, syncthing]
ansible.builtin.file:
path: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz"
state: absent
when: rocky_manage_syncthing_binary | bool
- name: Remove Syncthing extraction directory
tags: [packages, syncthing]
ansible.builtin.file:
path: "/tmp/syncthing-{{ rocky_syncthing_version }}"
state: absent
when: rocky_manage_syncthing_binary | bool

View File

@@ -2,5 +2,5 @@
- name: Reload SSH service
tags: [services]
ansible.builtin.service:
name: ssh
name: "{{ server_sshd_service_name }}"
state: reloaded

View File

@@ -1,5 +1,13 @@
---
- name: Require supported server firewall backend
tags: [services, packages]
ansible.builtin.assert:
that:
- server_firewall_backend in ['ufw', 'firewalld']
fail_msg: >-
server_firewall_backend must be either ufw or firewalld for the server profile.
- name: Copy server dotfiles
tags: [dotfiles, dotfiles:server]
ansible.builtin.copy:
@@ -72,6 +80,7 @@
tags: [services, packages]
ansible.builtin.set_fact:
server_ufw_rules_effective: "{{ server_ufw_rules | default([]) }}"
when: server_firewall_backend == 'ufw'
- name: Apply server UFW rules
tags: [services, packages]
@@ -89,12 +98,41 @@
interface_out: "{{ item.interface_out | default(omit) }}"
route: "{{ item.route | default(omit) }}"
comment: "{{ item.comment | default(omit) }}"
loop: "{{ server_ufw_rules_effective }}"
loop: "{{ server_ufw_rules_effective | default([]) }}"
loop_control:
label: "{{ item.name | default(item.port) }}"
when: server_firewall_backend == 'ufw'
- name: Enable UFW firewall on server
tags: [services, packages]
community.general.ufw:
state: enabled
when: (server_ufw_rules_effective | default([])) | length > 0
when:
- server_firewall_backend == 'ufw'
- (server_ufw_rules_effective | default([])) | length > 0
- name: Allow server services through firewalld
tags: [services, packages]
ansible.posix.firewalld:
service: "{{ item }}"
zone: "{{ server_firewalld_zone }}"
state: enabled
permanent: true
immediate: true
loop: "{{ server_firewalld_services | default([]) }}"
loop_control:
label: "{{ item }}"
when: server_firewall_backend == 'firewalld'
- name: Allow server ports through firewalld
tags: [services, packages]
ansible.posix.firewalld:
port: "{{ item }}"
zone: "{{ server_firewalld_zone }}"
state: enabled
permanent: true
immediate: true
loop: "{{ server_firewalld_ports | default([]) }}"
loop_control:
label: "{{ item }}"
when: server_firewall_backend == 'firewalld'

View File

@@ -84,6 +84,14 @@
roles:
- profile_atlas
- name: Configure Rocky Linux server
hosts: rocky_server
become: true
roles:
- dotfiles_common
- profile_server
- name: Configure personal workstation role on Fedora
hosts: platform_fedora:&role_personal_workstation
become: true

View File

@@ -1,4 +1,5 @@
---
{%- set selinux_volume_option = server_compose_selinux_mount_option | default('') %}
version: "3.8"
services:
@@ -14,8 +15,8 @@ services:
ND_ENABLETRANSCODING: "true"
volumes:
- "/opt/navidrome/data:/data"
- "/opt/music:/music:ro"
- "/opt/navidrome/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
- "/opt/music:/music:ro{{ ',' ~ selinux_volume_option if selinux_volume_option else '' }}"
user: "1000:1000"
networks:
- web
@@ -30,8 +31,8 @@ services:
- "443:443"
- "81:81"
volumes:
- "/opt/npm/data:/data"
- "/opt/npm/letsencrypt:/etc/letsencrypt"
- "/opt/npm/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
- "/opt/npm/letsencrypt:/etc/letsencrypt{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
networks:
- web
- gitea
@@ -66,7 +67,7 @@ services:
POSTGRES_PASSWORD: "{{ vault_postgres_root_password }}"
volumes:
- "/opt/postgres/data:/var/lib/postgresql/data"
- "/opt/postgres/data:/var/lib/postgresql/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
networks:
- web
gitea:
@@ -79,10 +80,10 @@ services:
networks:
- gitea
volumes:
- /opt/gitea/data:/data
- /opt/gitea/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /home/git/.ssh:/data/git/.ssh
- /home/git/.ssh:/data/git/.ssh{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}
ports:
- "3000:3000"
- "127.0.0.1:222:22"
@@ -95,8 +96,8 @@ services:
expose:
- "8384"
volumes:
- "/opt/syncthing/config:/var/syncthing"
- "/srv/syncthing/data:/data"
- "/opt/syncthing/config:/var/syncthing{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
- "/srv/syncthing/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
ports:
- "22000:22000/tcp"
- "22000:22000/udp"