mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
Compare commits
53 Commits
51ba397c6a
...
feature/at
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76f3e10ee4 | ||
|
|
c899bb7192 | ||
|
|
2ab5ba6818 | ||
|
|
a347880d4c | ||
|
|
b05a2b4e74 | ||
|
|
75d9081a11 | ||
|
|
b4b3bd10b8 | ||
|
|
db10d1296e | ||
|
|
8c35ef63c9 | ||
|
|
64aebe8c34 | ||
|
|
569e6ef24e | ||
|
|
ab8c51a57d | ||
|
|
7203ac6e2d | ||
|
|
fc8df74050 | ||
|
|
350fc7ac95 | ||
|
|
ab0bbf30ab | ||
|
|
24268938bd | ||
|
|
54e2917062 | ||
|
|
eab66b6d3d | ||
|
|
ae1ca44aa7 | ||
|
|
295f1a62ad | ||
|
|
6da430b9e4 | ||
|
|
e92ff1c729 | ||
|
|
8d3f67885c | ||
|
|
bc9b381525 | ||
|
|
2004b48cf3 | ||
|
|
54e1e88a4e | ||
|
|
cb88e95385 | ||
|
|
764d329a8f | ||
|
|
1517c92866 | ||
|
|
6296925f2d | ||
|
|
45340103d0 | ||
|
|
67b576d9b3 | ||
|
|
5914ae1557 | ||
|
|
671581d717 | ||
|
|
7f6a45f614 | ||
|
|
aadebbebd0 | ||
|
|
bb907febf8 | ||
|
|
2c1f58ce8e | ||
|
|
10bf0ee685 | ||
|
|
17ffa58e19 | ||
|
|
097374f2c5 | ||
|
|
8ed439317a | ||
|
|
bc5a7572a7 | ||
|
|
a6a58245ff | ||
|
|
3f0654a4bd | ||
|
|
8771fe79c8 | ||
|
|
3badf14c60 | ||
|
|
31bc8d07de | ||
|
|
304479a3f9 | ||
|
|
6ba0040192 | ||
|
|
49105a6f91 | ||
|
|
d59913e837 |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(xargs ls -la)",
|
||||
"Bash(command -v pacman)",
|
||||
"Bash(command -v paclist)",
|
||||
"Bash(command -v pacinfo)",
|
||||
"Bash(git -C /home/fscotto/AnsiblePlaybook add ansible/inventory/group_vars/arch.yml ansible/roles/services_systemd/tasks/main.yml)",
|
||||
"Bash(git -C /home/fscotto/AnsiblePlaybook commit -m ' *)",
|
||||
"Bash(git -C /home/fscotto/AnsiblePlaybook diff --stat)",
|
||||
"Bash(git -C /home/fscotto/AnsiblePlaybook status --short)",
|
||||
"Bash(git -C /home/fscotto/AnsiblePlaybook log --oneline --all -- ansible/inventory/host_vars/nymph.yml ansible/roles/profile_desktop_host/tasks/nymph.yml 'dotfiles/nymph/*')",
|
||||
"Bash(git *)",
|
||||
"Bash(wlogout --help)",
|
||||
"Bash(xbps-query *)",
|
||||
"Bash(xbps-install --dry-run gtklock)"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# DuckDNS runtime files contain a rendered Vault token and must stay on the host.
|
||||
/dotfiles/server/duckdns/
|
||||
156
AGENTS.md
156
AGENTS.md
@@ -1,6 +1,6 @@
|
||||
# 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, Fedora IoT, WSL, a Rocky Linux 9 server, and an Atlas NAS.
|
||||
|
||||
## Source Of Truth
|
||||
- Main orchestration: `ansible/site.yml`
|
||||
@@ -14,8 +14,10 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS
|
||||
- Current personal desktop: `ikaros = platform_fedora + role_personal_workstation + graphical_desktop + desktop_gnome`
|
||||
- Current laptop: `nymph = platform_fedora + graphical_desktop + desktop_gnome`
|
||||
- Void desktop profile is also the base for other future/reference hosts via `platform_void + graphical_desktop`
|
||||
- Workstation: `deadalus` is Windows + WSL; Ansible target is `deadalus-wsl`
|
||||
- Ubuntu server: `prometheus`
|
||||
- Workstation: `deadalus` is Windows + Fedora WSL.
|
||||
- Rocky server: `prometheus` belongs to `rocky_server`.
|
||||
- NAS: `atlas` (Rocky Linux 9, reached through SSH)
|
||||
- Always-on LAN node: `aegis` (Fedora IoT on Raspberry Pi 4, 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.
|
||||
|
||||
@@ -23,7 +25,10 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS
|
||||
- Preserve layering `all -> platform -> role -> desktop -> host`.
|
||||
- Keep `ansible/site.yml` small; orchestration belongs there, implementation belongs in roles.
|
||||
- Prefer minimal, targeted edits. Preserve idempotency and existing ordering.
|
||||
- All hosts use `ansible_connection: local`.
|
||||
- Use Git Flow branch prefixes: `feature/` for new functionality, `bugfix/` for non-urgent fixes,
|
||||
`hotfix/` for urgent production fixes, `release/` for release preparation, and `support/` for
|
||||
maintained release lines. Do not use abbreviated prefixes such as `feat/`.
|
||||
- Desktop and WSL hosts use `ansible_connection: local`; remote infrastructure hosts use SSH.
|
||||
- Treat `secrets/` as sensitive. Never print secret values.
|
||||
- Tmux plugins are bootstrapped by TPM on the host; the repo only keeps tmux config and custom helper scripts.
|
||||
- Read the relevant role tasks, templates, vars, and deployed dotfiles before editing.
|
||||
@@ -38,12 +43,25 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS
|
||||
- Host-focused dry runs:
|
||||
- Fedora desktop work: `ansible-playbook ansible/site.yml --limit ikaros --check --diff`
|
||||
- Fedora laptop work: `ansible-playbook ansible/site.yml --limit nymph --check --diff`
|
||||
- WSL dev: `ansible-playbook ansible/site.yml --limit deadalus-wsl --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`
|
||||
- Aegis IoT: `ansible-playbook ansible/site.yml --limit aegis --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`
|
||||
- AI coding agents: `ansible-playbook ansible/site.yml --limit <host> --tags ai_agents --check --diff`
|
||||
- Mail bootstrap: `sh -n scripts/bootstrap_mail.sh` and `shellcheck scripts/bootstrap_mail.sh`
|
||||
- Server compose render: `docker compose -f /opt/docker/server/docker-compose.yml config`
|
||||
- Server compose render: `podman-compose -f /opt/docker/server/docker-compose.yml config` and `systemctl status podman-compose-server`
|
||||
- Atlas media stack:
|
||||
`ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff`
|
||||
- Atlas network/share hardening:
|
||||
`ansible-playbook ansible/site.yml --limit atlas --tags hardening,sharing --check --diff`
|
||||
- Atlas phase-one rootless services:
|
||||
`ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff`
|
||||
- Prometheus/Atlas WireGuard overlay:
|
||||
`ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard --check --diff`
|
||||
- DuckDNS config only: `ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff`
|
||||
|
||||
## Conventions
|
||||
- Use FQCN Ansible modules.
|
||||
@@ -75,18 +93,116 @@ 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 and Fedora 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
|
||||
- `deadalus` is modeled as Windows + WSL; keep Linux dev automation on `deadalus-wsl`.
|
||||
- Native Linux workstation groups remain available for future hosts but have no current host in the main inventory.
|
||||
- `deadalus` is modeled as Windows + Fedora WSL and is the sole workstation target.
|
||||
- Fedora WSL belongs to `platform_fedora`, `workstation_dev_fedora`, and the shared WSL layer. It must not receive Flatpak or Snap runtimes.
|
||||
- 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
|
||||
- DuckDNS is rendered by `profile_server` from host-local `server_duckdns_domain` and
|
||||
`vault_duckdns_token`. Keep the rotated token in encrypted Vault or untracked local vars, never in
|
||||
dotfiles. The private `~/duckdns/duck.sh` keeps the existing entrypoint; rendering uses `no_log`
|
||||
and disables diffs. Provisioning does not execute the updater or change its external schedule.
|
||||
- `rocky_server` is a child of both `platform_rocky` and `server`; `prometheus` is its active target.
|
||||
- The target must already provide `server_username` with local sudo access before the profile runs.
|
||||
- The Rocky profile installs Podman and podman-compose, uses firewalld, preserves SELinux enforcement, and renders the
|
||||
existing Nginx Proxy Manager/Gitea Compose stack with a `podman-compose-server` systemd unit. PostgreSQL and
|
||||
Navidrome are no longer part of the desired Prometheus configuration. The role does not stop or remove legacy
|
||||
containers, delete `/opt/postgres/data`, start the Compose stack, update DNS, or cut over traffic.
|
||||
- Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes `80/tcp` and
|
||||
`443/tcp`; bind its administration interface only to `127.0.0.1:81` and use `npm-tunnel` from Ikaros or Nymph.
|
||||
Nextcloud remains disabled; do not provision `/srv/nextcloud` directories.
|
||||
- `scripts/migrate_prometheus_data.sh` is the separate, source-host-run NPM/Gitea migration path. It dry-runs by
|
||||
default and requires explicit source-stack quiescing before copying persistent Docker data with rsync.
|
||||
- Atlas-only OpenZFS, NFS, Samba, and Syncthing stay selected through Atlas host variables and must not
|
||||
leak into `rocky_server`. Cockpit plus its Navigator and Podman extensions are selected explicitly for
|
||||
Prometheus through its host variables.
|
||||
|
||||
## 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>`;
|
||||
subsequent runs use the dedicated Atlas account.
|
||||
- The pool is normally pre-existing. A one-time bootstrap may create it only when `atlas_create_pool=true`
|
||||
is explicitly supplied and `atlas_zpool_disks` contains exactly four real `/dev/disk/by-id/...` paths.
|
||||
Never partition, force, destroy, roll back, or modify the vdev layout of an existing pool.
|
||||
- `atlas_manage_storage`, `atlas_manage_sharing`, and `atlas_manage_firewall` are enabled in Atlas host vars as
|
||||
the declared steady state; set one false only for a deliberate suspension. `atlas_manage_media_stack` remains false
|
||||
until the future rootful Immich stack has its required Vault inputs and target validation.
|
||||
- Atlas requires `vault_atlas_admin_password_hash` for Cockpit and, while sharing is enabled,
|
||||
`vault_atlas_samba_password`. The future rootful media stack also requires
|
||||
`vault_atlas_immich_db_password`. Never print these values.
|
||||
- Atlas creates the complete declared hierarchy only under the verified existing or explicitly bootstrapped pool: `work`, `archive`,
|
||||
`archive/app_data`, `archive/app_data/navidrome`, `archive/app_data/syncthing`, `media`, `media/music`,
|
||||
`media/photobook`, `backups`, `backups/services`, and `backup_prometheus`. `backups/services` has a `500G`
|
||||
refreservation. There is no separate legacy `zpool/syncthing` dataset.
|
||||
- The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and receives only
|
||||
the `video` and `render` supplementary groups. Immich's rootful Quadlets run as `1100:1100`; Server and ML receive
|
||||
`/dev/dri`, while the Photobook external library is read-only at `/external/photobook`.
|
||||
- Atlas applies persistent kernel network hardening: redirects and source routes are rejected, martians logged, reverse-path filtering remains loose for WireGuard, and IPv4 forwarding is disabled. SSH permits only the declared administrator using public-key authentication; root login, passwords, agent and remote forwarding
|
||||
are disabled, while local forwarding remains available for private administrative tunnels. Photobook is exported only to the configured Aegis IP with all access squashed to UID/GID
|
||||
`1100`. Targeted SELinux is enforced persistently; a required reboot is reported but never initiated automatically. The primary LAN interface is assigned explicitly to the managed firewalld zone, and firewall rules are applied before NFS or SMB are started; their service state and TCP listeners are then verified. SMB3 exposes `Archive` to Vault-backed authorized accounts on mandatory encrypted, signed SMB3 over TCP/445 only and admits the configured LAN without host-specific exclusions.
|
||||
- Atlas NPM and Immich share a rootful Podman network. NPM publishes HTTP/HTTPS, but its administration port remains
|
||||
bound to `127.0.0.1:81`; do not expose it directly to the LAN or Internet.
|
||||
- `profile_backend_phase1` is limited to rootless Navidrome and Syncthing user Quadlets on Atlas. Official Navidrome
|
||||
`0.63.2` uses SQLite below `/data` and does not support `ND_DATABASE_URL` or an external PostgreSQL backend; do not
|
||||
recreate the obsolete Prometheus `navidromedb` service. The role requires the storage role's `zpool/media/music`,
|
||||
`zpool/archive/app_data`, `zpool/archive/app_data/navidrome`, and `zpool/archive/app_data/syncthing` datasets at
|
||||
their exact paths. It never creates the pool.
|
||||
- Keep `backend_phase1_start_services` false until the stopped Prometheus Navidrome data directory has been copied to
|
||||
Atlas and its SQLite database verified. The playbook renders the target but never migrates or deletes application
|
||||
data; after cutover, set the flag true to enable and start Navidrome and Syncthing.
|
||||
- Phase 1 must not change Prometheus' existing NPM deployment. NPM continues to be managed exactly by `profile_server`;
|
||||
use `10.0.0.2:4533` for Navidrome and `10.0.0.2:8384` for the Syncthing GUI. Syncthing does not use host networking:
|
||||
its GUI, transfer, QUIC and discovery ports are explicitly published only on `10.0.0.2`; native transfer/discovery does not use the HTTP proxy.
|
||||
- `wireguard_overlay` manages the required `wg0` path between Prometheus and Atlas, persists private keys only on their
|
||||
respective hosts, exchanges only derived public keys, and verifies a real peer handshake. The initial run must
|
||||
include both hosts. Prometheus
|
||||
opens `51820/udp`; after creating the WireGuard firewalld zone, restore Prometheus' rootful Podman networking with
|
||||
`podman network reload --all` so the existing proxy stack retains container DNS. The Atlas backend role admits
|
||||
service ports only in the WireGuard firewalld zone.
|
||||
|
||||
## Atlas NAS TODO
|
||||
- Provide the required Vault variables and validate the first remote bootstrap on the real Rocky Linux 9 host.
|
||||
Before the first apply, confirm the pool, mountpoints, LAN subnet and firewalld zone. Keep
|
||||
`atlas_manage_media_stack` disabled until `/dev/dri`, the container paths and the Immich database secret are validated.
|
||||
- Validate the complete baseline on the target: OpenZFS kmod loading, existing pool import, dataset
|
||||
mounts, SSH reconnect, Cockpit and all selected 45Drives plugins, NFSv4, SMB and Syncthing.
|
||||
- Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files
|
||||
through both NFS and SMB before considering multiprotocol access complete.
|
||||
- Add Ansible-managed ZFS snapshot retention and scrub timers. Use Cockpit Scheduler for visibility
|
||||
or manual operations, not as the only source of configuration, and never automate snapshot rollback.
|
||||
- Manage the Syncthing star topology, device IDs, folders, folder modes, ignore rules and protected GUI
|
||||
or API access for the selected clients.
|
||||
- Validate the managed WireGuard path and its LAN/VPN-only firewalld rules before enabling remote services;
|
||||
never expose SSH, Cockpit, NFS, SMB or Syncthing through public port forwarding.
|
||||
- Add the Atlas-initiated least-privilege Prometheus backup pull: Prometheus exposes only prepared
|
||||
read-only dumps through a dedicated account and Atlas retains the private SSH key, pinned host key,
|
||||
atomic pull, verification, retention and systemd service/timer.
|
||||
- Add the encrypted offsite backup with Borg to a Hetzner Storage Box: use a dedicated SSH identity,
|
||||
pin the host key, keep Borg repository credentials and encryption material in Vault, use
|
||||
snapshot-consistent sources, and manage retries, logging, pruning, repository checks and restores.
|
||||
- Add the UUID-bound offline USB backup with versioned rsync, locking, capacity checks, verification,
|
||||
safe unmounting and a tested restore procedure; never trigger it for an arbitrary USB disk.
|
||||
- Add monitoring and alerting for pool health, scrub/resilver, SMART data, temperatures, free space and
|
||||
failed backup timers, plus a controlled Rocky kernel/OpenZFS update and reboot procedure.
|
||||
- Document and test disaster recovery: rebuild Atlas with Ansible, import the existing pool, restore
|
||||
from snapshot/USB/Hetzner, preserve Vault and Borg recovery material offline, and define RPO/RTO.
|
||||
- Optionally design iCloud photo ingestion as a separate workflow after the storage and backup layers
|
||||
are validated; do not make it a dependency of the Atlas baseline.
|
||||
|
||||
## Coding Agent Notes
|
||||
- Shared agent packages live in `ai_agents_npm_packages` in `ansible/inventory/group_vars/all.yml`.
|
||||
- Shared agent definitions and lifecycle flags live in `ai_agents` in `ansible/inventory/group_vars/all.yml`.
|
||||
- Shared agent dotfiles live in `ai_agents_dotfiles`; rendered configs live in `ai_agents_templates`.
|
||||
- Desktop, native workstation, and WSL profiles consume the shared agent package list; do not duplicate package entries in profile-specific vars.
|
||||
- `dotfiles_common` copies common dotfiles plus `ai_agents_dotfiles`, then renders `ai_agents_templates`.
|
||||
- Every `ai_agents.<agent>` entry has independent `install_enabled`, `deploy_enabled`, and `uninstall_enabled` flags. Installation and removal must not both be true for the same agent; the common pre-task fails before changes when they conflict.
|
||||
- Fedora, Void desktop, and WSL workstation profiles consume the shared agent definitions; do not duplicate package entries in profile-specific vars. IBM Bob on the workstation follows its own flags.
|
||||
- `dotfiles_common` deploys `ai_agents_dotfiles` and renders `ai_agents_templates` only when deployment is enabled.
|
||||
- Removal is limited to the managed npm packages and `/usr/local/bin/bob`; never remove agent dotfiles, instructions, credentials, or user data.
|
||||
- Keep `.config/ai/` as the common instruction source; update agent-specific entrypoints to reference it rather than duplicating instruction text.
|
||||
|
||||
## Tooling Notes
|
||||
@@ -100,3 +216,19 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
|
||||
- Keep `README.md` and `AGENTS.md` aligned when workflows materially change.
|
||||
- If you add a new operational area, also add the narrowest validation command for it.
|
||||
- Call out checks you could not run and any follow-up verification needed.
|
||||
|
||||
## Aegis Fedora IoT Notes
|
||||
- `aegis` is a remote Fedora IoT Raspberry Pi 4 node. Bootstrap it once with
|
||||
`ansible/bootstrap/aegis.bu`; the remaining configuration is applied by `profile_aegis` over SSH.
|
||||
- Fedora IoT is immutable. Do not add it to mutable Fedora package or shared dotfile roles.
|
||||
- `profile_aegis` owns rootful Podman Quadlets, persistent container state under `/var/lib`, the
|
||||
Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep
|
||||
`aegis_lan_subnet` and `aegis_adguard_web_port` host-specific; SSH permits only the declared
|
||||
key-authenticated users, never root or password authentication. Keep Apple IDs and other
|
||||
credentials in Vault and use `no_log` for their rendering.
|
||||
- `aegis_adguard_web_port` defaults to `80`. The initial AdGuard Home wizard port `3000` is intentionally unmanaged: open and close it manually only while
|
||||
completing initial setup. Disable the local systemd-resolved stub through `profile_aegis` before
|
||||
AdGuard binds port 53; keep
|
||||
`/etc/resolv.conf` linked to `/run/systemd/resolve/resolv.conf` so Aegis retains router-provided DNS.
|
||||
- iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is
|
||||
persisted in `/var/lib/icloudpd/config`.
|
||||
|
||||
70
CLAUDE.md
70
CLAUDE.md
@@ -1,70 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this repo is
|
||||
|
||||
Ansible-driven personal infrastructure as code for Fedora desktops (`ikaros`, `nymph`), WSL development (`deadalus-wsl`), and an Ubuntu server (`prometheus`). See `AGENTS.md` for topology and `README.md` for full profile descriptions.
|
||||
|
||||
## Validation commands
|
||||
|
||||
```bash
|
||||
# Minimum before any change
|
||||
ansible-playbook ansible/site.yml --syntax-check
|
||||
|
||||
# Dry-run per host
|
||||
ansible-playbook ansible/site.yml --limit ikaros --check --diff
|
||||
ansible-playbook ansible/site.yml --limit nymph --check --diff
|
||||
ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --check --diff
|
||||
|
||||
# Linting
|
||||
ansible-lint ansible/site.yml
|
||||
ansible-lint ansible/roles
|
||||
yamllint ansible/
|
||||
|
||||
# Tag-scoped dry-run
|
||||
ansible-playbook ansible/site.yml --limit ikaros --tags <tag> --check --diff
|
||||
|
||||
# Shell scripts
|
||||
sh -n scripts/bootstrap_mail.sh && shellcheck scripts/bootstrap_mail.sh
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Configuration is composed in layers: `all → OS → profile → host`. All hosts use `ansible_connection: local`.
|
||||
|
||||
`ansible/site.yml` is the sole orchestration entry point — keep it thin. Implementation belongs in roles under `ansible/roles/`.
|
||||
|
||||
Variable layering:
|
||||
- `ansible/inventory/group_vars/` — OS and profile defaults
|
||||
- `ansible/inventory/host_vars/` — host-specific overrides (never put host overrides in group_vars)
|
||||
|
||||
Dotfiles under `dotfiles/` mirror the same layer split: `common/`, `desktop/`, `server/`, `workstation/`, then per-host directories (`ikaros/`, `nymph/`).
|
||||
|
||||
## Key conventions
|
||||
|
||||
- FQCN for all Ansible modules.
|
||||
- Prefer declarative modules over `command`/`shell`; when shell is unavoidable, make idempotency explicit.
|
||||
- YAML: start with `---`, 2-space indent, file modes quoted as `"0644"`, booleans as booleans.
|
||||
- `no_log: true` on any task that handles secrets.
|
||||
- Do not restart `emptty` in playbook tasks on active desktop hosts — do it manually via SSH/TTY.
|
||||
|
||||
## Void desktop package buckets (keep disjoint)
|
||||
|
||||
- `void_packages_base` — system runtime (init, kernel, audio core, networking, firewall, hw daemons)
|
||||
- `desktop_common_packages` — WM-agnostic GUI infra (GTK theme, polkit, keyring, portals, flatpak, printing)
|
||||
- `desktop_sway_packages` — Sway-specific binaries
|
||||
- `profile_packages` — cross-distro; do not move desktop-only Void entries into it
|
||||
|
||||
Dotfile var split follows the same logic: `desktop_common_dotfiles` (WM-agnostic), `desktop_void_dotfiles` (Void runtime — turnstile services, bash fragments).
|
||||
|
||||
## Secrets
|
||||
|
||||
- `secrets/vault.yml` and `secrets/vault.local.yml` are loaded conditionally if present.
|
||||
- `secrets/.vault_pass.gpg` is used automatically if present; falls back to `secrets/.vault_pass`.
|
||||
- Never print vault values; always use `no_log: true` where secrets flow through tasks.
|
||||
|
||||
## AI agent dotfiles
|
||||
|
||||
Shared agent instructions live in `dotfiles/common/.config/ai/`. Agent-specific entrypoints reference that path rather than duplicating content. Shared npm packages are in `ai_agents_npm_packages` in `ansible/inventory/group_vars/all.yml`.
|
||||
667
README.it.md
Normal file
667
README.it.md
Normal file
@@ -0,0 +1,667 @@
|
||||
# Infra — Personal Infrastructure as Code
|
||||
|
||||
> **English version:** [README.md](README.md)
|
||||
|
||||
Questo repository contiene la configurazione **Infrastructure as Code (IaC)** utilizzata per gestire e mantenere allineate diverse macchine personali tramite **Ansible**.
|
||||
|
||||
L'obiettivo è avere **una singola fonte di verità** per:
|
||||
|
||||
- configurazione delle macchine
|
||||
- pacchetti installati
|
||||
- servizi di sistema
|
||||
- configurazioni utente (dotfiles)
|
||||
|
||||
Il repository consente di gestire più sistemi operativi e profili macchina mantenendo una struttura modulare, riproducibile e idempotente.
|
||||
|
||||
---
|
||||
|
||||
# Architettura del progetto
|
||||
|
||||
```text
|
||||
infra/
|
||||
├── ansible/
|
||||
│ ├── ansible.cfg
|
||||
│ ├── site.yml
|
||||
│ ├── inventory/
|
||||
│ │ ├── hosts.yml
|
||||
│ │ ├── group_vars/
|
||||
│ │ └── host_vars/
|
||||
│ ├── templates/
|
||||
│ └── roles/
|
||||
│
|
||||
├── dotfiles/
|
||||
│ ├── common/
|
||||
│ ├── desktop/
|
||||
│ ├── fedora/
|
||||
│ ├── server/
|
||||
│ ├── workstation/
|
||||
│ ├── workstation_dev_wsl/
|
||||
│ └── nymph/
|
||||
│
|
||||
├── scripts/
|
||||
├── secrets/
|
||||
├── README.md
|
||||
└── README.it.md
|
||||
```
|
||||
|
||||
Il repository è diviso in due componenti principali:
|
||||
|
||||
| Componente | Scopo |
|
||||
| ---------- | -------------------------------------- |
|
||||
| ansible | provisioning e configurazione macchine |
|
||||
| dotfiles | configurazioni utente versionate |
|
||||
|
||||
---
|
||||
|
||||
# Macchine gestite
|
||||
|
||||
Il repository modella attualmente host Fedora/GNOME, una workstation Fedora WSL, un server Rocky
|
||||
Linux 9 e un NAS Rocky Linux 9. La composizione resta separata in assi indipendenti:
|
||||
|
||||
```text
|
||||
common user environment
|
||||
+ host-specific platform
|
||||
+ role-specific software
|
||||
+ independently selectable desktop
|
||||
+ host hardware overrides
|
||||
```
|
||||
|
||||
Matrice target:
|
||||
|
||||
| Host | Platform | Role | Desktop |
|
||||
| ------------ | -------- | -------------------- | ------- |
|
||||
| ikaros | Fedora | Personal workstation | GNOME |
|
||||
| nymph | Fedora | Desktop laptop | GNOME |
|
||||
| deadalus | Fedora WSL | Workstation dev | — |
|
||||
| prometheus | Rocky Linux | Server | — |
|
||||
| atlas | Rocky Linux | NAS | — |
|
||||
|
||||
Regola operativa:
|
||||
|
||||
```text
|
||||
ikaros must be boring
|
||||
nymph is allowed to break
|
||||
```
|
||||
|
||||
`ikaros` usa Fedora Workstation/GNOME come desktop personale stabile; `nymph` usa lo stesso
|
||||
target Fedora Workstation/GNOME come laptop. I gruppi legacy `void` e `desktop` restano alias di
|
||||
compatibilita per eventuali host Void futuri mentre i nuovi assi sono
|
||||
`platform_*`, `role_*` e `desktop_*`.
|
||||
|
||||
Nota sullo stato attuale del playbook principale:
|
||||
|
||||
- `ansible/site.yml` applica oggi in automatico Fedora/GNOME su `ikaros` e `nymph`
|
||||
- `ansible/site.yml` applica il profilo Fedora WSL alla workstation `deadalus`
|
||||
- `ansible/site.yml` applica il profilo server Rocky a `prometheus` con DNF, systemd, dotfiles server e firewalld
|
||||
- `ansible/site.yml` applica il profilo NAS Rocky su `atlas` tramite SSH remoto
|
||||
|
||||
## Desktop
|
||||
|
||||
Target operativi:
|
||||
|
||||
- `ikaros`: Fedora Workstation + GNOME, desktop personale stabile/floating.
|
||||
- `nymph`: Fedora Workstation + GNOME, laptop desktop con dotfiles desktop condivisi e GNOME lasciato al default Fedora.
|
||||
|
||||
Il profilo Void desktop resta disponibile come modello riutilizzabile per host
|
||||
futuri e usa esclusivamente `desktop_environment: minimal`: Sway e il default,
|
||||
mentre Niri si seleziona con il gruppo `desktop_niri`. GNOME e disponibile solo
|
||||
sui target Fedora tramite `desktop_gnome`.
|
||||
|
||||
Lo stato attuale del profilo desktop include, tra le altre cose:
|
||||
|
||||
- dotfiles comuni e desktop
|
||||
- sessioni Sway e Niri per eventuali host Void in modalita `minimal`
|
||||
- `emptty` con default host-specific in modalita `minimal` e session file Wayland per `sway`
|
||||
- pacchetti Void Linux e servizi runit; le liste pacchetti Void desktop sono separate per criterio:
|
||||
- `void_packages_base` per il runtime sistema (init, kernel, audio core, networking, firewall, hw daemons)
|
||||
- `desktop_common_packages` per l'infrastruttura condivisa
|
||||
- `desktop_minimal_packages` per applicazioni GTK e `emptty`
|
||||
- `desktop_sway_packages` per i binari specifici della sessione Sway
|
||||
- `turnstile` per i servizi utente Void, incluso `ssh-agent`
|
||||
- `ssh-agent` con socket stabile condiviso tra shell e SSH in `~/.local/state/ssh-agent/socket`
|
||||
- Emacs usa una sola configurazione orientata a Org e authoring, condivisa da desktop Fedora/GNOME e workstation; Vim resta l'editor di sviluppo
|
||||
- `tmux` con plugin gestiti da TPM al bootstrap del profilo desktop
|
||||
- Flatpak con remoto Flathub
|
||||
- GNOME Keyring e `udiskie` nella modalita minimale
|
||||
- multi-monitor Void: sotto Sway è gestito da `kanshi`
|
||||
|
||||
---
|
||||
|
||||
## Workstation
|
||||
|
||||
La workstation `deadalus` usa Fedora in WSL sulla macchina Windows omonima, senza runtime Flatpak o Snap.
|
||||
Il profilo è pensato per sviluppo e lavoro.
|
||||
|
||||
Nel modello Ansible usato qui, un singolo inventory host puo appartenere intenzionalmente a piu gruppi e quindi ricevere piu play nello stesso run: l'associazione non e `1 host = 1 play`, ma `host + gruppi = layering finale`.
|
||||
|
||||
Il profilo workstation e agganciato al playbook principale tramite:
|
||||
|
||||
- layer dev Fedora
|
||||
- layer WSL dedicato per sviluppo con `systemd`
|
||||
|
||||
Lo stato attuale del profilo workstation include:
|
||||
|
||||
- installazione pacchetti base Fedora via dnf
|
||||
- installazione e configurazione di Docker dal repository ufficiale
|
||||
- installazione di Mise dal COPR ufficiale con JDK Eclipse Temurin Java 11 fissato
|
||||
- gestione dei dotfiles workstation e rendering dei template dev condivisi
|
||||
- preparazione di Fedora WSL con `systemd` per il toolchain di sviluppo
|
||||
- attivazione del firewall `firewalld` sui target Fedora che dichiarano regole host-specifiche
|
||||
|
||||
Workflow WSL previsto:
|
||||
|
||||
1. avviare Fedora WSL almeno una volta e completare la creazione dell'utente Linux
|
||||
2. installare Ansible dentro la distribuzione WSL
|
||||
3. lanciare il playbook dalla distribuzione su `deadalus` per configurare l'ambiente dev locale
|
||||
4. usare VS Code con le estensioni Remote (`WSL`, `SSH`, `Dev Containers`) dal lato Windows
|
||||
|
||||
Le applicazioni Windows sono installate e gestite manualmente; il profilo WSL non installa componenti di remoting Python per esse.
|
||||
|
||||
---
|
||||
|
||||
## Server
|
||||
|
||||
Sistema operativo:
|
||||
|
||||
- Rocky Linux 9
|
||||
|
||||
Configurazione:
|
||||
|
||||
- nessun ambiente grafico
|
||||
|
||||
Macchina:
|
||||
|
||||
- `prometheus`
|
||||
|
||||
Profilo orientato a servizi server e gestione di dotfiles dedicati.
|
||||
|
||||
Lo stato attuale del profilo server include:
|
||||
|
||||
- installazione pacchetti Rocky via DNF, EPEL e CRB
|
||||
- installazione di Podman e podman-compose
|
||||
- abilitazione dei servizi systemd dichiarati in inventory/group vars
|
||||
- copia dei dotfiles server e rendering del `docker-compose.yml` per Nginx Proxy Manager e Gitea,
|
||||
piu l'unita `podman-compose-server` (attivazione manuale)
|
||||
- attivazione di firewalld con SSH, Cockpit (`9090/tcp`), HTTP e HTTPS abilitati
|
||||
- Syncthing escluso dal profilo server Rocky
|
||||
|
||||
Il Compose desiderato su Prometheus non include piu Navidrome ne il database PostgreSQL obsoleto.
|
||||
Navidrome e Syncthing appartengono ad Atlas; Navidrome ufficiale usa invece SQLite. Il profilo non
|
||||
arresta o rimuove automaticamente eventuali container legacy e non elimina `/opt/postgres/data`.
|
||||
|
||||
Nginx Proxy Manager pubblica solo `80/tcp` e `443/tcp`; la sua interfaccia di amministrazione e
|
||||
associata a `127.0.0.1:81` ed e raggiungibile da Ikaros o Nymph con l'alias Bash `npm-tunnel`.
|
||||
Nextcloud resta disabilitato e il profilo non crea directory `/srv/nextcloud`.
|
||||
|
||||
La fase 1 su Atlas non modifica questo deployment NPM ne i suoi dati persistenti. Dopo aver attivato
|
||||
WireGuard e i servizi Atlas, configurare i proxy host NPM correnti con upstream Navidrome
|
||||
`http://10.0.0.2:4533` e upstream per la GUI Syncthing `http://10.0.0.2:8384`. Solo la GUI web di
|
||||
Syncthing usa NPM; il traffico di sincronizzazione resta sulle porte native pubblicate esplicitamente solo
|
||||
sull'indirizzo WireGuard di Atlas. Configurare l'autenticazione Syncthing e una policy di accesso NPM adeguata prima di pubblicare la GUI.
|
||||
|
||||
### DuckDNS
|
||||
|
||||
`profile_server` genera `~/duckdns/duck.sh` con permessi `0700`, mantenendo il percorso dello
|
||||
script e `duck.log`. Definire `server_duckdns_domain` negli host vars del server e salvare il
|
||||
**nuovo token rigenerato** in `vault_duckdns_token`, nel Vault cifrato `secrets/vault.yml`
|
||||
(`ansible-vault edit secrets/vault.yml`) oppure negli override non versionati `secrets/vault.local.yml`.
|
||||
Non committare lo script generato e non passare il token sulla riga di comando. Il rendering
|
||||
nasconde output e diff sensibili; lo script verifica TLS e passa il token a curl tramite stdin.
|
||||
Il playbook non esegue lo script e non modifica la sua schedulazione esterna.
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags duckdns
|
||||
```
|
||||
|
||||
La cancellazione dalla cronologia non revoca il token: rigenerarlo sul pannello DuckDNS.
|
||||
Dopo la bonifica, riclonare gli altri checkout senza unire nuovamente la vecchia storia;
|
||||
salvare separatamente eventuali modifiche non committate senza copiare segreti.
|
||||
|
||||
### Migrazione dati
|
||||
|
||||
Dopo il provisioning Rocky, eseguire `scripts/migrate_prometheus_data.sh` **sul server Ubuntu
|
||||
sorgente**. Lo script usa rsync, e in dry-run di default; richiede `--quiesce-source --execute` per
|
||||
fermare lo stack sorgente e copiare in modo consistente soltanto i dati di Nginx Proxy Manager e
|
||||
Gitea. Non sposta Navidrome o Syncthing, non avvia container, non cancella dati e non esegue il
|
||||
cutover.
|
||||
|
||||
Utente del profilo server:
|
||||
|
||||
- il profilo usa `server_username`, `server_user_group` e `server_user_home` definiti in `ansible/inventory/group_vars/server.yml`
|
||||
- per default `server_username` eredita `username`, ma puo essere sovrascritto per tutti gli host server via inventory oppure a runtime con extra vars
|
||||
- esempio override da CLI:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser
|
||||
```
|
||||
|
||||
- se necessario puoi passare anche:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser -e server_user_group=mygroup -e server_user_home=/srv/myuser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NAS
|
||||
|
||||
`atlas` e un NAS Rocky Linux 9 raggiunto tramite SSH. Normalmente il pool ZFS esiste gia e il profilo
|
||||
gestisce solo i dataset figli. Un bootstrap RAIDZ2 una tantum e disponibile solo con conferma esplicita
|
||||
(`atlas_create_pool=true`) e quattro percorsi reali e verificati `/dev/disk/by-id/...` in
|
||||
`atlas_zpool_disks`. Non partiziona, forza, distrugge, esegue rollback o modifica il layout vdev di un
|
||||
pool esistente. I client Linux usano NFSv4, quelli Windows/WSL SMB; entrambi restano limitati alla LAN
|
||||
configurata.
|
||||
|
||||
Per il primo avvio fornire `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
|
||||
`vault_atlas_samba_password` e `vault_atlas_immich_db_password`. Eseguire il bootstrap tramite
|
||||
l'amministratore esistente:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit atlas \
|
||||
-e atlas_connection_username=<existing-admin>
|
||||
```
|
||||
|
||||
`vault_atlas_admin_password_hash` deve essere un hash compatibile con `/etc/shadow`, non una
|
||||
password Cockpit in chiaro. Le esecuzioni successive usano `atlas_admin_username`. Atlas dichiara
|
||||
abilitati storage, condivisioni e regole firewall LAN. Prima della prima applicazione verificare pool e
|
||||
mountpoint esistenti, subnet LAN e zona firewalld attiva. `atlas_manage_media_stack` resta disabilitato
|
||||
finche non saranno validati `/dev/dri`, i percorsi dei container e il segreto del database Immich.
|
||||
|
||||
Con la gestione storage attiva, Atlas crea l'intera gerarchia sotto il pool `zpool` esistente o creato esplicitamente:
|
||||
`work`, `archive`, `archive/app_data`, i dataset applicativi separati
|
||||
`archive/app_data/navidrome` e `archive/app_data/syncthing`, `media`, `media/music`,
|
||||
`media/photobook`, `backups`, `backups/services` e `backup_prometheus`. I dataset applicativi e
|
||||
di archivio usano `zstd`; media, Syncthing e backup dei servizi usano `lz4`;
|
||||
`backups/services` mantiene inoltre una `refreservation` di `500G`.
|
||||
Atlas impone SELinux targeted in modo persistente e segnala, senza avviarlo, l’eventuale reboot necessario per attivarlo. Assegna esplicitamente l’interfaccia LAN primaria alla zona firewalld gestita e applica hardening persistente del kernel di rete: rifiuta redirect e source-route, registra i martian, usa reverse-path filtering loose per WireGuard e disabilita il forwarding IPv4. SSH consente solo l’amministratore dichiarato tramite chiave pubblica; root, password, agent e forwarding
|
||||
remoto sono disabilitati, mentre il forwarding locale resta disponibile per tunnel amministrativi privati. SMB3 pubblica `Archive` solo agli account Samba configurati con password in Vault e
|
||||
ammette la LAN configurata su SMB3 cifrato e firmato, esclusivamente su TCP/445. NFSv4 esporta soltanto
|
||||
`media/photobook` all'IP configurato di Aegis su TCP/2049, con `all_squash` verso UID/GID anonimi `1100`.
|
||||
|
||||
L'account di sistema `immich` usa UID/GID `1100`, shell senza login, nessuna appartenenza a `wheel` e
|
||||
i gruppi supplementari `video` e `render`. I Quadlet rootful di Immich Server, ML, cache compatibile
|
||||
Redis, PostgreSQL e NPM condividono una rete Podman. Immich viene eseguito come `1100:1100`; Server e
|
||||
ML ricevono `/dev/dri` e Photobook e montato in sola lettura su `/external/photobook`. NPM pubblica `80` e
|
||||
`443`, mentre l'amministrazione resta vincolata a `127.0.0.1:81` per l'accesso tramite tunnel SSH.
|
||||
|
||||
La fase 1 e limitata ai Quadlet utente rootless di Navidrome e Syncthing su Atlas. E abilitata nella
|
||||
configurazione host di Atlas e puo essere impostata a `false` solo per una sospensione intenzionale. Navidrome ufficiale `0.63.2` usa il database SQLite sotto `/data` e
|
||||
non supporta `ND_DATABASE_URL` ne un backend PostgreSQL esterno. Il servizio obsoleto `navidromedb`
|
||||
e quindi rimosso da Prometheus invece di essere replicato su Atlas. Il ruolo deriva i percorsi dal
|
||||
pool `zpool`, montato in `/zpool`: musica in sola lettura da `/zpool/media/music`, stato
|
||||
applicativo Navidrome e `navidrome.db` in `/zpool/archive/app_data/navidrome` e dati Syncthing in
|
||||
`/zpool/archive/app_data/syncthing`. `profile_atlas` crea questi dataset quando
|
||||
`atlas_manage_storage` e attivo; il ruolo backend verifica i mountpoint esatti prima di avviare i
|
||||
container. Il ruolo backend non crea mai il pool. Il ruolo separato `wireguard_overlay`
|
||||
gestisce `wg0` tra Prometheus (`10.0.0.1`) e Atlas (`10.0.0.2`), genera una sola volta le chiavi
|
||||
private sui rispettivi host e scambia tramite Ansible soltanto quelle pubbliche. Solo Prometheus apre
|
||||
pubblicamente `51820/udp`. Le porte backend sono ammesse esclusivamente nella zona firewalld WireGuard.
|
||||
|
||||
`backend_phase1_start_services` resta falso durante il trasferimento dello stato applicativo, quindi
|
||||
la prima esecuzione reale del backend genera i Quadlet senza creare un database Atlas vuoto. Dopo aver
|
||||
arrestato Navidrome su Prometheus, copiare l'intera directory `/opt/navidrome/data/` in
|
||||
`/zpool/archive/app_data/navidrome/`, preservando `navidrome.db` e gli eventuali file SQLite laterali.
|
||||
Impostare quindi questa variabile a vero e rieseguire il ruolo per abilitare e avviare Navidrome e
|
||||
Syncthing. Il playbook non copia e non elimina mai i dati applicativi.
|
||||
|
||||
Validare e generare i servizi Atlas con:
|
||||
|
||||
```bash
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags storage
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
|
||||
```
|
||||
|
||||
Per il cutover, arrestare il vecchio Navidrome prima di copiare la sua directory dati, verificare
|
||||
l'ownership dell'account `admin` su Atlas e confermare la presenza del database SQLite copiato prima
|
||||
di impostare `backend_phase1_start_services: true` in `host_vars/atlas.yml`. Conservare i dati sorgente
|
||||
e il container legacy `navidromedb` fermo finche Navidrome su Atlas e una prova di restore non sono
|
||||
stati validati.
|
||||
|
||||
Restano da completare retention delle snapshot, topologia Syncthing, validazione WireGuard/firewall,
|
||||
pull di backup da Prometheus, backup cifrati con Borg su una Hetzner Storage Box, backup USB,
|
||||
monitoraggio e test di disaster recovery. Il backlog operativo dettagliato e in `AGENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
# Composizione della configurazione
|
||||
|
||||
Emacs è abilitato sui profili Fedora/GNOME e workstation; la configurazione canonica è distribuita da `dotfiles_common`, con Org in `~/Org/`, template versionati e export PDF/HTML/Markdown/DOCX/ODT. Per abilitarlo temporaneamente su un altro profilo:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit <host> --tags emacs -e emacs_enabled=true
|
||||
```
|
||||
|
||||
La configurazione finale di una macchina è ottenuta combinando più livelli.
|
||||
|
||||
```text
|
||||
common configuration
|
||||
+ platform configuration
|
||||
+ role configuration
|
||||
+ desktop configuration
|
||||
+ host overrides
|
||||
```
|
||||
|
||||
Esempi correnti:
|
||||
|
||||
```text
|
||||
ikaros -> common + platform_fedora + role_personal_workstation + graphical_desktop + desktop_gnome + ikaros
|
||||
nymph -> common + platform_fedora + graphical_desktop + desktop_gnome + nymph
|
||||
deadalus -> common + platform_fedora + workstation_dev_fedora + workstation_dev_wsl + deadalus
|
||||
```
|
||||
|
||||
Questo approccio consente di:
|
||||
|
||||
- mantenere configurazioni condivise
|
||||
- applicare override specifici per host
|
||||
- evitare duplicazioni
|
||||
- riutilizzare il profilo Void corrente su un host futuro assegnandolo a
|
||||
`platform_void + graphical_desktop + desktop_sway`
|
||||
|
||||
---
|
||||
|
||||
# Ruoli Ansible
|
||||
|
||||
I principali ruoli attualmente presenti sono:
|
||||
|
||||
| Role | Descrizione |
|
||||
| ------------------------- | ----------------------------------- |
|
||||
| base | configurazione base comune |
|
||||
| packages_void | installazione pacchetti su Void |
|
||||
| packages_fedora | installazione pacchetti su Fedora |
|
||||
| packages_rocky | installazione pacchetti su Rocky Linux 9 |
|
||||
| services_runit | gestione servizi runit |
|
||||
| services_systemd | gestione servizi systemd |
|
||||
| profile_desktop_common | bootstrap desktop Void condiviso |
|
||||
| profile_desktop_gnome | dotfiles desktop condivisi per Fedora/GNOME |
|
||||
| profile_desktop_sway | sessione desktop sway / SwayFX (Wayland) |
|
||||
| profile_desktop_niri | sessione desktop Niri su Void (Wayland) |
|
||||
| profile_desktop_host | override desktop specifici per host |
|
||||
| profile_personal_workstation | layer stabile per workstation personale |
|
||||
| profile_workstation_dev_common | configurazione dev workstation condivisa |
|
||||
| profile_workstation_dev_wsl | configurazione WSL condivisa per sviluppo |
|
||||
| profile_server | configurazione server |
|
||||
| profile_atlas | configurazione NAS Rocky Linux 9 |
|
||||
| profile_backend_phase1 | Navidrome e Syncthing rootless su Atlas |
|
||||
| wireguard_overlay | overlay WireGuard Prometheus/Atlas |
|
||||
| dotfiles_common | distribuzione dotfiles comuni |
|
||||
| dotfiles | distribuzione configurazioni utente |
|
||||
|
||||
---
|
||||
|
||||
# Stato attuale del playbook principale
|
||||
|
||||
Il playbook `ansible/site.yml` e attualmente composto da blocchi per asse:
|
||||
|
||||
```text
|
||||
all -> 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_fedora -> packages_fedora + services_systemd
|
||||
platform_rocky -> packages_rocky + services_systemd
|
||||
wireguard_overlay -> wireguard_overlay (dopo platform_rocky)
|
||||
atlas -> profile_atlas
|
||||
role_backend_phase1 -> profile_backend_phase1 (dopo atlas)
|
||||
platform_fedora & role_personal_workstation -> profile_personal_workstation
|
||||
platform_fedora & desktop_gnome -> profile_desktop_gnome
|
||||
workstation_dev_fedora -> profile_workstation_dev_common
|
||||
workstation_dev_wsl -> profile_workstation_dev_wsl (dopo platform_fedora + workstation_dev_fedora)
|
||||
rocky_server -> dotfiles_common + profile_server (dopo platform_rocky)
|
||||
```
|
||||
|
||||
Questo significa che, allo stato attuale:
|
||||
|
||||
- `ikaros` riceve Fedora Workstation/GNOME come target desktop personale stabile
|
||||
- `nymph` riceve Fedora Workstation/GNOME come target laptop
|
||||
- il profilo Void resta selezionabile tramite `platform_void + graphical_desktop` per host futuri
|
||||
- `deadalus` riceve il profilo Fedora WSL tramite play dev dedicati
|
||||
- il server Rocky (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewalld
|
||||
- il NAS Rocky (`atlas`) usa un pool ZFS gia esistente, condivisioni NFSv4/SMB limitate alla LAN e Cockpit/45Drives
|
||||
- lo stack Compose server include soltanto `gitea` e `nginx-proxy-manager`; Navidrome e Syncthing
|
||||
della fase 1 sono Quadlet rootless su Atlas
|
||||
|
||||
# Dotfiles
|
||||
|
||||
La directory `dotfiles/` contiene le configurazioni utente versionate.
|
||||
|
||||
```text
|
||||
dotfiles/
|
||||
├── common
|
||||
├── desktop
|
||||
├── server
|
||||
├── fedora
|
||||
├── workstation
|
||||
├── workstation_dev_wsl
|
||||
└── nymph
|
||||
```
|
||||
|
||||
Le configurazioni sono applicate tramite Ansible e organizzate per livelli:
|
||||
|
||||
| Livello | Scopo |
|
||||
| ------- | -------------------------------- |
|
||||
| common | configurazioni condivise |
|
||||
| profile | configurazioni per tipo macchina |
|
||||
| host | override specifici |
|
||||
|
||||
---
|
||||
|
||||
# Requisiti
|
||||
|
||||
Per utilizzare il repository sono necessari:
|
||||
|
||||
- Python 3
|
||||
- Ansible
|
||||
- `ansible-lint`
|
||||
- `yamllint`
|
||||
- `shellcheck`
|
||||
- collection definite in `ansible/collections/requirements.yml`
|
||||
- accesso locale o SSH alle macchine target, in base a come e definito l'inventory
|
||||
|
||||
Installazione base:
|
||||
|
||||
```bash
|
||||
python3 -m pip install ansible ansible-lint yamllint shellcheck-py
|
||||
ansible-galaxy collection install -r ansible/collections/requirements.yml
|
||||
```
|
||||
|
||||
Gestione segreti:
|
||||
|
||||
- il repository supporta il caricamento opzionale di `secrets/vault.yml`
|
||||
- il repository supporta anche `secrets/vault.local.yml` per override locali non versionati
|
||||
- `secrets/vault.yml.example` funge da template/esempio
|
||||
- se `secrets/vault.yml` non e presente, il playbook continua comunque senza caricare variabili locali opzionali
|
||||
- se `secrets/.vault_pass.gpg` esiste viene usato automaticamente per sbloccare i vault tramite `gpg`; in alternativa resta supportato `secrets/.vault_pass` come fallback legacy locale; se nessuno dei due file esiste Ansible richiede la password in modo interattivo
|
||||
|
||||
---
|
||||
|
||||
# Utilizzo
|
||||
|
||||
Eseguire il playbook principale:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml
|
||||
```
|
||||
|
||||
Allo stato attuale questo comando:
|
||||
|
||||
- distribuisce i dotfiles comuni a tutti gli host
|
||||
- per `platform_void` applica pacchetti Void e servizi runit
|
||||
- per `platform_void + graphical_desktop` applica bootstrap desktop condiviso, sessioni Sway/Niri e override specifici per host
|
||||
- per `platform_fedora` applica pacchetti Fedora e servizi systemd a `ikaros`, `nymph` e `deadalus`
|
||||
- per `platform_fedora & role_personal_workstation` applica il layer personale a `ikaros`
|
||||
- per `platform_fedora & desktop_gnome` applica il profilo GNOME a `ikaros` e `nymph`
|
||||
- per `workstation_dev_wsl` applica i tweak WSL dopo il layer Fedora a `deadalus`, escludendo Flatpak e Snap
|
||||
- per `platform_rocky` applica pacchetti Rocky e servizi systemd ad `atlas` e `prometheus`; quindi applica il profilo NAS ad `atlas` e il profilo server a `prometheus`
|
||||
- non riavvia automaticamente il display manager
|
||||
- carica `secrets/vault.yml` solo se presente
|
||||
- carica `secrets/vault.local.yml` solo se presente, dopo `vault.yml`, cosi gli override locali hanno precedenza
|
||||
|
||||
Per validare prima di applicare:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --syntax-check
|
||||
ansible-playbook ansible/site.yml --limit ikaros,nymph --check --diff
|
||||
ansible-playbook ansible/site.yml --limit ikaros --check --diff
|
||||
ansible-playbook ansible/site.yml --limit nymph --check --diff
|
||||
ansible-playbook ansible/site.yml --limit deadalus --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --check --diff
|
||||
ansible-playbook ansible/site.yml --limit atlas --check --diff
|
||||
ansible-lint ansible/site.yml
|
||||
ansible-lint ansible/roles
|
||||
yamllint ansible/
|
||||
```
|
||||
|
||||
Per testare un override dell'utente server senza modificare l'inventory:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus --check --diff -e server_username=myuser
|
||||
```
|
||||
|
||||
Per validazioni piu mirate:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit <host> --tags <tag1>,<tag2> --check --diff
|
||||
ansible-playbook ansible/site.yml --limit <host> --start-at-task "<task name>" --check --diff
|
||||
ansible-lint ansible/roles/<role>
|
||||
yamllint ansible/path/to/file.yml
|
||||
podman-compose -f /opt/docker/server/docker-compose.yml config
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||
```
|
||||
|
||||
## Tag supportati dal playbook
|
||||
|
||||
Per vedere l'elenco reale aggiornato dei tag disponibili:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --list-tags
|
||||
```
|
||||
|
||||
Allo stato attuale `ansible/site.yml` espone questi tag:
|
||||
|
||||
| Tag | Scopo | Ambito principale |
|
||||
| --- | --- | --- |
|
||||
| `always` | pre-task sempre eseguiti, inclusi caricamento vault e validazioni preliminari | common |
|
||||
| `ai_agents` | installazione agenti AI condivisi | Fedora, WSL |
|
||||
| `atlas` | account, storage, condivisioni e container Atlas | NAS Atlas |
|
||||
| `backend_phase1` | Quadlet rootless Navidrome e Syncthing | NAS Atlas |
|
||||
| `containers` | Quadlet rootful Atlas | NAS Atlas |
|
||||
| `dotfiles` | distribuzione/configurazione dotfiles | tutti i profili |
|
||||
| `dotfiles:common` | dotfiles comuni condivisi | common, workstation, server |
|
||||
| `dotfiles:desktop` | dotfiles desktop | desktop Void, Fedora/GNOME |
|
||||
| `dotfiles:host` | override host-specifici desktop | desktop Void |
|
||||
| `dotfiles:server` | dotfiles dedicati al profilo server | server |
|
||||
| `dotfiles:workstation` | dotfiles dedicati alle workstation | personal workstation, WSL |
|
||||
| `emptty` | gestione display manager `emptty` | desktop Void |
|
||||
| `display-manager` | gestione del display manager `emptty` | desktop Void |
|
||||
| `emacs` | configurazione Emacs condivisa e dipendenze di authoring | desktop Fedora/GNOME e workstation |
|
||||
| `fonts` | installazione font | Fedora |
|
||||
| `fzf` | configurazione FZF | dotfiles comuni |
|
||||
| `git` | configurazione Git e GPG desktop | Fedora/GNOME, desktop Void |
|
||||
| `gnome` | configurazione host GNOME | Fedora/GNOME desktop |
|
||||
| `immich` | account e Quadlet Immich | NAS Atlas |
|
||||
| `sway` | sessione/configurazione sway / SwayFX (Wayland) | desktop Void |
|
||||
| `niri` | sessione/configurazione Niri (Wayland) | desktop Void |
|
||||
| `npm` | installazione pacchetti npm globali | Fedora/GNOME, desktop Void, WSL |
|
||||
| `nvidia` | componenti NVIDIA desktop | desktop Void |
|
||||
| `packages` | installazione e aggiornamento pacchetti | tutti i profili |
|
||||
| `podman` | integrazione Podman Compose e Quadlet rootless | server |
|
||||
| `portal` | configurazione xdg-desktop-portal | desktop Void |
|
||||
| `services` | gestione servizi runit/systemd | tutti i profili |
|
||||
| `sharing` | condivisioni NFSv4 e SMB3 | NAS Atlas |
|
||||
| `storage` | dataset ZFS figli | NAS Atlas |
|
||||
| `theme` | configurazione del tema GTK/Qt | desktop Void |
|
||||
| `tmux` | configurazione e plugin tmux | desktop Fedora/Void, WSL |
|
||||
| `vim` | configurazione Vim | dotfiles comuni |
|
||||
| `wireguard` | overlay WireGuard Prometheus/Atlas | Prometheus, NAS Atlas |
|
||||
| `wsl` | bootstrap e configurazione WSL | WSL |
|
||||
|
||||
Esempi pratici:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit nymph --tags dotfiles:desktop,gnome --check --diff
|
||||
ansible-playbook ansible/site.yml --limit ikaros --tags gnome --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags services,dotfiles:server --check --diff
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Bootstrap di una nuova macchina
|
||||
|
||||
Una nuova macchina può essere inizializzata con i seguenti passaggi:
|
||||
|
||||
```bash
|
||||
git clone <repo>
|
||||
cd <repo-dir>
|
||||
ansible-galaxy collection install -r ansible/collections/requirements.yml
|
||||
ansible-playbook ansible/site.yml
|
||||
```
|
||||
|
||||
Dopo l'esecuzione del playbook la macchina verra configurata secondo il profilo definito e i ruoli attualmente orchestrati.
|
||||
|
||||
Per aggiungere un nuovo host Void che riusa il profilo desktop preservato:
|
||||
|
||||
1. aggiungere l'host a `platform_void`;
|
||||
2. aggiungerlo a `graphical_desktop`;
|
||||
3. usare Sway, oppure aggiungerlo a `desktop_niri` per selezionare Niri;
|
||||
4. lasciare eventuali dettagli hardware in `host_vars/<host>.yml`.
|
||||
|
||||
I gruppi legacy `void` e `desktop` sono parent di compatibilita, quindi un host
|
||||
in `platform_void` e `graphical_desktop` continua a ricevere anche le variabili
|
||||
Void e desktop esistenti.
|
||||
|
||||
Per il flusso mail desktop esiste inoltre uno script dedicato:
|
||||
|
||||
```bash
|
||||
scripts/bootstrap_mail.sh
|
||||
```
|
||||
|
||||
Lo script si occupa del bootstrap dei secret nel keyring, del primo sync con `mbsync` e dell'inizializzazione di `mu` usando la configurazione mail generata dai template.
|
||||
|
||||
Se modifichi questo script, valida almeno con:
|
||||
|
||||
```bash
|
||||
sh -n scripts/bootstrap_mail.sh
|
||||
shellcheck scripts/bootstrap_mail.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Filosofia del progetto
|
||||
|
||||
Il repository segue alcuni principi chiave:
|
||||
|
||||
- Infrastructure as Code
|
||||
- configurazione dichiarativa
|
||||
- idempotenza
|
||||
- ambienti riproducibili
|
||||
- separazione tra configurazione sistema e configurazione utente
|
||||
|
||||
Questo consente di ricreare qualsiasi macchina partendo esclusivamente dal repository.
|
||||
|
||||
---
|
||||
|
||||
# Roadmap
|
||||
|
||||
Possibili evoluzioni future:
|
||||
|
||||
- hardening sicurezza server
|
||||
- configurazione backup
|
||||
- testing automatico playbook
|
||||
- integrazione CI
|
||||
- supporto ad altre distribuzioni Linux
|
||||
|
||||
---
|
||||
|
||||
# Licenza
|
||||
|
||||
Questo progetto è distribuito sotto licenza **LGPL-3.0**.
|
||||
745
README.md
745
README.md
@@ -1,24 +1,14 @@
|
||||
# Infra — Personal Infrastructure as Code
|
||||
|
||||
Questo repository contiene la configurazione **Infrastructure as Code (IaC)** utilizzata per gestire e mantenere allineate diverse macchine personali tramite **Ansible**.
|
||||
> **Italian version:** [README.it.md](README.it.md)
|
||||
|
||||
L'obiettivo è avere **una singola fonte di verità** per:
|
||||
This is my Ansible repo for keeping my personal machines and dotfiles in sync. It is the source of truth for packages, services, and user configuration. The setup is meant to stay modular, reproducible, and idempotent without getting too clever.
|
||||
|
||||
- configurazione delle macchine
|
||||
- pacchetti installati
|
||||
- servizi di sistema
|
||||
- configurazioni utente (dotfiles)
|
||||
|
||||
Il repository consente di gestire più sistemi operativi e profili macchina mantenendo una struttura modulare, riproducibile e idempotente.
|
||||
|
||||
---
|
||||
|
||||
# Architettura del progetto
|
||||
## Layout
|
||||
|
||||
```text
|
||||
infra/
|
||||
├── ansible/
|
||||
│ ├── ansible.cfg
|
||||
│ ├── site.yml
|
||||
│ ├── inventory/
|
||||
│ │ ├── hosts.yml
|
||||
@@ -26,204 +16,296 @@ infra/
|
||||
│ │ └── host_vars/
|
||||
│ ├── templates/
|
||||
│ └── roles/
|
||||
│
|
||||
├── dotfiles/
|
||||
│ ├── common/
|
||||
│ ├── desktop/
|
||||
│ ├── fedora/
|
||||
│ ├── ubuntu/
|
||||
│ ├── server/
|
||||
│ ├── workstation/
|
||||
│ ├── workstation_host_linux/
|
||||
│ ├── workstation_dev_wsl/
|
||||
│ └── nymph/
|
||||
│
|
||||
├── scripts/
|
||||
├── secrets/
|
||||
└── README.md
|
||||
├── README.md
|
||||
└── README.it.md
|
||||
```
|
||||
|
||||
Il repository è diviso in due componenti principali:
|
||||
- `ansible/` holds provisioning and host configuration.
|
||||
- `dotfiles/` holds versioned user configuration.
|
||||
|
||||
| Componente | Scopo |
|
||||
| ---------- | -------------------------------------- |
|
||||
| ansible | provisioning e configurazione macchine |
|
||||
| dotfiles | configurazioni utente versionate |
|
||||
## Managed machines
|
||||
|
||||
---
|
||||
|
||||
# Macchine gestite
|
||||
|
||||
Il repository modella attualmente host Fedora/GNOME, un ambiente WSL di sviluppo e un server Ubuntu.
|
||||
La composizione resta separata in assi indipendenti:
|
||||
The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, a Fedora IoT LAN
|
||||
node, a Rocky Linux 9 server, and a Rocky Linux 9 NAS. Configuration is layered instead of being tied
|
||||
to host names:
|
||||
|
||||
```text
|
||||
common user environment
|
||||
+ host-specific platform
|
||||
+ platform-specific setup
|
||||
+ role-specific software
|
||||
+ independently selectable desktop
|
||||
+ host hardware overrides
|
||||
+ independently selected desktop
|
||||
+ host overrides
|
||||
```
|
||||
|
||||
Matrice target:
|
||||
|
||||
| Host | Platform | Role | Desktop |
|
||||
| ------------ | -------- | -------------------- | ------- |
|
||||
| ikaros | Fedora | Personal workstation | GNOME |
|
||||
| nymph | Fedora | Desktop laptop | GNOME |
|
||||
| deadalus-wsl | Ubuntu | Workstation dev | — |
|
||||
| prometheus | Ubuntu | Server | — |
|
||||
|
||||
Regola operativa:
|
||||
| Host | Platform | Role | Desktop |
|
||||
| --- | --- | --- | --- |
|
||||
| `ikaros` | Fedora | Personal workstation | GNOME |
|
||||
| `nymph` | Fedora | Desktop laptop | GNOME |
|
||||
| `deadalus` | Fedora WSL | Development workstation | — |
|
||||
| `aegis` | Fedora IoT | Always-on LAN node | — |
|
||||
| `prometheus` | Rocky Linux | Server | — |
|
||||
| `atlas` | Rocky Linux | NAS | — |
|
||||
|
||||
```text
|
||||
ikaros must be boring
|
||||
nymph is allowed to break
|
||||
```
|
||||
|
||||
`ikaros` usa Fedora Workstation/GNOME come desktop personale stabile; `nymph` usa lo stesso
|
||||
target Fedora Workstation/GNOME come laptop. I gruppi legacy `void` e `desktop` restano alias di
|
||||
compatibilita per eventuali host Void futuri mentre i nuovi assi sono
|
||||
`platform_*`, `role_*` e `desktop_*`.
|
||||
`ikaros` is the stable personal Fedora/GNOME desktop. `nymph` is the laptop and gets the same shared desktop dotfiles while GNOME itself stays close to the Fedora defaults. The legacy `void` and `desktop` groups are compatibility parents; the main axes are `platform_*`, `role_*`, and `desktop_*`.
|
||||
|
||||
Nota sullo stato attuale del playbook principale:
|
||||
## Desktop profiles
|
||||
|
||||
- `ansible/site.yml` applica oggi in automatico Fedora/GNOME su `ikaros` e `nymph`
|
||||
- `ansible/site.yml` applica anche il ramo `workstation_dev_wsl` per il modello dev in WSL
|
||||
- `ansible/site.yml` applica anche il profilo `ubuntu_server` con baseline apt, systemd, dotfiles server e firewall UFW
|
||||
- `ikaros`: stable Fedora Workstation + GNOME desktop.
|
||||
- `nymph`: Fedora Workstation + GNOME laptop.
|
||||
- Void desktops stay available as reusable future profiles through `platform_void + graphical_desktop`.
|
||||
|
||||
## Desktop
|
||||
Void uses `desktop_environment: minimal`. Sway is the normal session; add a host to `desktop_niri` to select Niri. GNOME is only handled on Fedora through `desktop_gnome`.
|
||||
|
||||
Target operativi:
|
||||
The desktop setup includes shared desktop dotfiles, Sway/Niri support for future Void hosts, `emptty`, `turnstile` user services, a stable ssh-agent socket at `~/.local/state/ssh-agent/socket`, Emacs authoring config, tmux bootstrapped through TPM, Flatpak, GNOME Keyring, Udiskie, and `kanshi` for Sway multi-monitor setups.
|
||||
|
||||
- `ikaros`: Fedora Workstation + GNOME, desktop personale stabile/floating.
|
||||
- `nymph`: Fedora Workstation + GNOME, laptop desktop con dotfiles desktop condivisi e GNOME lasciato al default Fedora.
|
||||
Void package buckets stay separate on purpose:
|
||||
|
||||
Il profilo Void desktop resta disponibile come modello riutilizzabile per host
|
||||
futuri e usa esclusivamente `desktop_environment: minimal`: Sway e il default,
|
||||
mentre Niri si seleziona con il gruppo `desktop_niri`. GNOME e disponibile solo
|
||||
sui target Fedora tramite `desktop_gnome`.
|
||||
|
||||
Lo stato attuale del profilo desktop include, tra le altre cose:
|
||||
|
||||
- dotfiles comuni e desktop
|
||||
- sessioni Sway e Niri per eventuali host Void in modalita `minimal`
|
||||
- `emptty` con default host-specific in modalita `minimal` e session file Wayland per `sway`
|
||||
- pacchetti Void Linux e servizi runit; le liste pacchetti Void desktop sono separate per criterio:
|
||||
- `void_packages_base` per il runtime sistema (init, kernel, audio core, networking, firewall, hw daemons)
|
||||
- `desktop_common_packages` per l'infrastruttura condivisa
|
||||
- `desktop_minimal_packages` per applicazioni GTK e `emptty`
|
||||
- `desktop_sway_packages` per i binari specifici della sessione Sway
|
||||
- `turnstile` per i servizi utente Void, incluso `ssh-agent`
|
||||
- `ssh-agent` con socket stabile condiviso tra shell e SSH in `~/.local/state/ssh-agent/socket`
|
||||
- Emacs usa una sola configurazione orientata a Org e authoring, condivisa da desktop Fedora/GNOME e workstation; Vim resta l'editor di sviluppo
|
||||
- `tmux` con plugin gestiti da TPM al bootstrap del profilo desktop
|
||||
- Flatpak con remoto Flathub
|
||||
- GNOME Keyring e `udiskie` nella modalita minimale
|
||||
- multi-monitor Void: sotto Sway è gestito da `kanshi`
|
||||
|
||||
---
|
||||
- `void_packages_base`: system runtime and services.
|
||||
- `desktop_common_packages`: shared GUI infrastructure.
|
||||
- `desktop_minimal_packages`: GTK applications and `emptty`.
|
||||
- `desktop_sway_packages`: Sway-only binaries.
|
||||
|
||||
## Workstation
|
||||
|
||||
Sistemi operativi supportati:
|
||||
`deadalus` is the only workstation target. It is Fedora running in WSL on the Windows machine with the same name. Flatpak and Snap are explicitly kept out of this profile.
|
||||
|
||||
- Ubuntu WSL, attualmente usato da `deadalus-wsl`
|
||||
- Fedora Workstation nativa, disponibile per host futuri tramite gruppi dedicati
|
||||
The workstation receives two layers:
|
||||
|
||||
Desktop environment host Linux, per eventuali workstation native:
|
||||
- Fedora development setup through `workstation_dev_fedora`.
|
||||
- WSL setup with `systemd` through `workstation_dev_wsl`.
|
||||
|
||||
- GNOME
|
||||
That gives it Fedora packages through DNF, Docker from the official repository, Mise from its official COPR repository with a pinned Eclipse Temurin Java 11 JDK, shared workstation dotfiles and templates, tmux helpers, and WSL systemd configuration. Windows applications are installed manually; the WSL profile does not manage Python remoting components for them.
|
||||
|
||||
Macchine attuali:
|
||||
### WSL workflow
|
||||
|
||||
- `deadalus-wsl` come ambiente dev Ubuntu in WSL sulla workstation Windows `deadalus`
|
||||
|
||||
Questo profilo è pensato per sviluppo e lavoro, con separazione tra layer host e layer dev.
|
||||
|
||||
Nel modello Ansible usato qui, un singolo inventory host puo appartenere intenzionalmente a piu gruppi e quindi ricevere piu play nello stesso run: l'associazione non e `1 host = 1 play`, ma `host + gruppi = layering finale`.
|
||||
|
||||
Il profilo workstation e agganciato al playbook principale e ora distingue:
|
||||
|
||||
- layer dev condiviso tra WSL e workstation Fedora future
|
||||
- layer dev Fedora nativo disponibile per host futuri
|
||||
- layer host Linux GNOME disponibile per host futuri
|
||||
- layer WSL dedicato per sviluppo con `systemd`
|
||||
|
||||
Per esempio, lo stesso host Linux puo stare in `workstation_host_linux` e in `workstation_dev_fedora`, a seconda del layering che vuoi comporre.
|
||||
|
||||
Lo stato attuale del profilo workstation include:
|
||||
|
||||
- installazione pacchetti base Ubuntu via apt
|
||||
- installazione pacchetti base Fedora via dnf per eventuali workstation native
|
||||
- installazione e configurazione di Docker dal repository ufficiale
|
||||
- gestione dei dotfiles workstation e rendering dei template dev condivisi
|
||||
- installazione opzionale di Google Chrome, VS Code, IntelliJ IDEA Ultimate e applicazioni Flatpak per eventuali workstation Fedora native
|
||||
- estensioni GNOME per eventuali host Linux nativi
|
||||
- preparazione del ramo WSL Ubuntu con `systemd` per il toolchain di sviluppo
|
||||
- attivazione del firewall `firewalld` sui target Fedora che dichiarano regole host-specifiche
|
||||
|
||||
Workflow WSL previsto:
|
||||
|
||||
1. avviare Ubuntu WSL almeno una volta e completare la creazione dell'utente Linux
|
||||
2. installare Ansible dentro WSL Ubuntu
|
||||
3. lanciare il playbook da WSL su `deadalus-wsl` per configurare l'ambiente dev locale
|
||||
4. usare VS Code con le estensioni Remote (`WSL`, `SSH`, `Dev Containers`) dal lato Windows
|
||||
|
||||
---
|
||||
1. Start Fedora WSL once and finish creating the Linux user.
|
||||
2. Install Ansible inside Fedora WSL.
|
||||
3. Run the playbook from that distribution with `--limit deadalus`.
|
||||
4. Use Windows-side VS Code with Remote WSL, Remote SSH, and Dev Containers if wanted.
|
||||
|
||||
## Server
|
||||
|
||||
Sistema operativo:
|
||||
`prometheus` is the Rocky Linux 9 server. It has no graphical environment and gets server-specific
|
||||
dotfiles and templates. The profile provisions configuration only: it does not transfer data, start
|
||||
the Compose stack, update DNS, or perform a cutover.
|
||||
|
||||
- Ubuntu LTS
|
||||
The server profile installs platform-specific packages, Podman and podman-compose, declared systemd
|
||||
services, and firewalld. The manually activated `podman-compose-server` unit contains the existing
|
||||
Nginx Proxy Manager and Gitea services. The desired Compose file no longer includes Navidrome,
|
||||
Syncthing, or the obsolete Navidrome PostgreSQL database. Navidrome and Syncthing belong to Atlas;
|
||||
official Navidrome uses SQLite instead. Applying the profile does not stop or remove legacy
|
||||
containers and does not delete `/opt/postgres/data`.
|
||||
|
||||
Configurazione:
|
||||
Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes only
|
||||
`80/tcp` and `443/tcp`; its administration interface is bound to `127.0.0.1:81` and can be reached
|
||||
from Ikaros or Nymph with the `npm-tunnel` Bash alias. Nextcloud remains disabled and the profile
|
||||
does not provision any `/srv/nextcloud` directories.
|
||||
|
||||
- nessun ambiente grafico
|
||||
The Atlas phase-one work does not change this NPM deployment or its persistent data. Once WireGuard
|
||||
and the Atlas services are active, configure the current NPM proxy hosts with Navidrome upstream
|
||||
`http://10.0.0.2:4533` and Syncthing GUI upstream `http://10.0.0.2:8384`. Only the Syncthing web GUI
|
||||
uses NPM; synchronization traffic remains on explicitly published native ports bound only to the Atlas
|
||||
WireGuard address. Configure both Syncthing authentication and an appropriate NPM access policy before
|
||||
publishing its GUI.
|
||||
|
||||
Macchina:
|
||||
|
||||
- `prometheus`
|
||||
|
||||
Profilo orientato a servizi server e gestione di dotfiles dedicati.
|
||||
|
||||
Lo stato attuale del profilo server include:
|
||||
|
||||
- installazione pacchetti base Ubuntu via apt
|
||||
- installazione e configurazione di Docker dal repository ufficiale
|
||||
- abilitazione dei servizi systemd dichiarati in inventory/group vars
|
||||
- copia dei dotfiles server e rendering dei template server, incluso il `docker-compose.yml` dello stack servizi
|
||||
- attivazione del firewall UFW con regola SSH esplicita
|
||||
- apertura delle porte Syncthing `22000/tcp`, `22000/udp` e `21027/udp`, lasciando la GUI non esposta direttamente su UFW
|
||||
|
||||
Utente del profilo server:
|
||||
|
||||
- il profilo usa `server_username`, `server_user_group` e `server_user_home` definiti in `ansible/inventory/group_vars/server.yml`
|
||||
- per default `server_username` eredita `username`, ma puo essere sovrascritto per tutti gli host server via inventory oppure a runtime con extra vars
|
||||
- esempio override da CLI:
|
||||
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:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser
|
||||
ansible-playbook ansible/site.yml --limit prometheus \
|
||||
-e server_username=myuser -e server_user_group=mygroup \
|
||||
-e server_user_home=/srv/myuser
|
||||
```
|
||||
|
||||
- se necessario puoi passare anche:
|
||||
The target must already provide `server_username` with local sudo access.
|
||||
Prometheus authorizes its declared SSH public keys through separate files below
|
||||
`~/.ssh/authorized_keys.d/`, while `sshd` is configured to read those files directly.
|
||||
|
||||
### DuckDNS
|
||||
|
||||
`profile_server` renders `~/duckdns/duck.sh` with mode `0700`, keeping the existing updater path
|
||||
and `duck.log`. Set `server_duckdns_domain` in the server's host vars and store the **rotated**
|
||||
`vault_duckdns_token` in encrypted `secrets/vault.yml` (using `ansible-vault edit secrets/vault.yml`)
|
||||
or untracked `secrets/vault.local.yml`. Never commit the rendered script or put the token on a
|
||||
command line. Rendering hides secret output/diffs; the updater verifies TLS and passes the token
|
||||
to curl through stdin. The playbook neither runs the updater nor changes its external schedule.
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser -e server_user_group=mygroup -e server_user_home=/srv/myuser
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags duckdns
|
||||
```
|
||||
|
||||
---
|
||||
An exposed token must be revoked/regenerated on DuckDNS: deleting it from Git history does not
|
||||
revoke it. After a history cleanup, re-clone other checkouts rather than merging the old history
|
||||
back in; preserve any uncommitted work separately without copying secrets.
|
||||
|
||||
# Composizione della configurazione
|
||||
### Data migration
|
||||
|
||||
Emacs è abilitato sui profili Fedora/GNOME e workstation; la configurazione canonica è distribuita da `dotfiles_common`, con Org in `~/Org/`, template versionati e export PDF/HTML/Markdown/DOCX/ODT. Per abilitarlo temporaneamente su un altro profilo:
|
||||
Provision Rocky first, then run the migration script **on the retired Ubuntu source host**. It is
|
||||
dry-run by default and requires an explicit source-stack stop before it can copy application data:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit <host> --tags emacs -e emacs_enabled=true
|
||||
sudo ./scripts/migrate_prometheus_data.sh \
|
||||
--destination rocky@179.237.102.172 \
|
||||
--identity /root/.ssh/id_ed25519
|
||||
|
||||
sudo ./scripts/migrate_prometheus_data.sh \
|
||||
--destination rocky@179.237.102.172 \
|
||||
--identity /root/.ssh/id_ed25519 \
|
||||
--quiesce-source --execute
|
||||
```
|
||||
|
||||
La configurazione finale di una macchina è ottenuta combinando più livelli.
|
||||
The script copies only Nginx Proxy Manager and Gitea data. It does not delete data, move
|
||||
Navidrome/Syncthing, copy `/home/git/.ssh`, start containers, update DNS, or perform a cutover. The
|
||||
destination SSH host key must already be trusted and the destination account needs passwordless sudo
|
||||
for `rsync`. It preserves ACLs but not extended attributes, so source SELinux labels are not
|
||||
transferred; the Rocky Compose bind mounts apply their own `:Z` labels when containers start.
|
||||
|
||||
## DNS Filter
|
||||
|
||||
`aegis` is a Raspberry Pi 4 running Fedora IoT. Generate Ignition from
|
||||
`ansible/bootstrap/aegis.bu` with the included Podman/Butane helper, then write the SD card with
|
||||
`arm-image-installer`:
|
||||
|
||||
```bash
|
||||
ansible/bootstrap/generate-aegis-ign.sh --write IMAGE DEVICE
|
||||
```
|
||||
|
||||
The controller manages it remotely as `pi@aegis`; unlike local desktop profiles, Aegis is
|
||||
intentionally an SSH inventory target. `profile_aegis` manages rootful Podman Quadlets for AdGuard
|
||||
Home and iCloudPD, persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted
|
||||
firewalld rules, SSH key-only access for `pi`, and `wake-ikaros`. Set the host-local
|
||||
`aegis_lan_subnet` and `aegis_adguard_web_port` values before applying it. The playbook permits
|
||||
AdGuard Home HTTP on port `80`; the initial wizard port `3000` is intentionally unmanaged and must be
|
||||
opened and closed manually during initial setup. The profile disables the local systemd-resolved DNS
|
||||
stub and points `/etc/resolv.conf` to its full resolver data, freeing port 53
|
||||
for AdGuard while retaining DNS learned from the router. Define
|
||||
`vault_aegis_icloudpd_apple_id` in Vault before applying it. iCloudPD still requires interactive MFA
|
||||
initialization after its first deployment.
|
||||
|
||||
New Aegis images create the `admin` account in Butane. Before configuring a newly imaged node, run its
|
||||
first playbook execution with `-e ansible_user=admin`; the SSH hardening role then permits that same
|
||||
account. Keep the inventory on `pi` until the existing node has been replaced.
|
||||
|
||||
Validate the profile before deployment:
|
||||
|
||||
```bash
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit aegis --check --diff --ask-become-pass
|
||||
```
|
||||
|
||||
## NAS
|
||||
|
||||
`atlas` is a Rocky Linux 9 NAS reached through SSH. Normally its pool already exists and the profile
|
||||
only manages child datasets. A one-time RAIDZ2 bootstrap is available only with explicit confirmation
|
||||
(`atlas_create_pool=true`) and exactly four verified `/dev/disk/by-id/...` paths in `atlas_zpool_disks`.
|
||||
It never partitions, forces, destroys, rolls back, or changes the vdev layout of an existing pool. Linux
|
||||
clients use NFSv4 and Windows/WSL clients use SMB; both are restricted to the configured LAN.
|
||||
|
||||
For the first run, provide `vault_atlas_admin_password_hash`, `vault_atlas_samba_password`, and
|
||||
`vault_atlas_immich_db_password`. Bootstrap the host through its
|
||||
existing administrator. Open `51820/udp` towards Prometheus in the provider firewall first, then
|
||||
include both WireGuard peers in the same idempotent playbook run:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus,atlas \
|
||||
-e atlas_connection_username=<existing-admin> \
|
||||
-e atlas_create_pool=true
|
||||
```
|
||||
|
||||
The explicit pool gate is safe to repeat: the role creates the RAIDZ2 pool only when it is absent.
|
||||
WireGuard waits for a real peer handshake before the play continues.
|
||||
|
||||
`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text
|
||||
Cockpit password. Subsequent runs use `atlas_admin_username`. Atlas declares storage, sharing, and its
|
||||
LAN firewall rules enabled. Before the first apply, check the existing pool and mountpoints, LAN subnet,
|
||||
and active firewalld zone. `atlas_manage_media_stack` remains disabled until `/dev/dri`, the container
|
||||
paths, and the Immich database secret are validated. Atlas reads its declared SSH public keys from
|
||||
separate files below `~/.ssh/authorized_keys.d/`.
|
||||
|
||||
With storage management enabled, Atlas creates the complete dataset hierarchy below the existing or
|
||||
explicitly bootstrapped `zpool`: `work`, `archive`, `archive/app_data`, the separate `archive/app_data/navidrome` and
|
||||
`archive/app_data/syncthing` application datasets, `media`, `media/music`, `media/photobook`,
|
||||
`backups`, `backups/services`, and `backup_prometheus`. Application/archive datasets use `zstd`,
|
||||
while media, Syncthing and service-backup datasets use `lz4`; `backups/services` also has a `500G`
|
||||
refreservation. Atlas enforces targeted SELinux persistently and reports, without initiating, any reboot required to activate it. It assigns its primary LAN interface explicitly to the managed firewalld zone and applies persistent kernel network hardening: redirects and source routes are rejected, martians logged, reverse-path filtering remains loose for WireGuard, and IPv4 forwarding is disabled. SSH permits only the declared administrator using public-key authentication; root login, passwords,
|
||||
agent and remote forwarding are disabled, while local forwarding remains available for private administrative tunnels. SMB3 exposes `Archive` only to the configured Vault-backed
|
||||
Samba accounts on encrypted, signed SMB3 over TCP/445 only and admits the configured LAN without host-specific
|
||||
exclusions. NFSv4 exports only `media/photobook` to the configured Aegis IP over TCP/2049, using
|
||||
`all_squash` with anonymous UID/GID `1100`.
|
||||
|
||||
The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and
|
||||
receives `video` and `render` access. The rootful Immich Server, ML, Redis-compatible cache, PostgreSQL,
|
||||
and NPM Quadlets share one Podman network. Immich runs as `1100:1100`; Server and ML receive `/dev/dri`,
|
||||
and Photobook is mounted read-only at `/external/photobook`. NPM publishes ports `80` and `443`; its
|
||||
administration interface remains restricted to `127.0.0.1:81` for SSH-tunnel access.
|
||||
|
||||
Phase 1 is limited to rootless Navidrome and Syncthing user Quadlets on Atlas. It is enabled in the
|
||||
Atlas host configuration and can be set to `false` only for a deliberate suspension. Official Navidrome `0.63.2` uses its SQLite database below `/data`; it does
|
||||
not support `ND_DATABASE_URL` or an external PostgreSQL backend. The obsolete `navidromedb` service
|
||||
was therefore removed from Prometheus instead of being reproduced on Atlas. The role derives all
|
||||
storage paths from the `zpool` mounted at `/zpool`: music is read-only at
|
||||
`/zpool/media/music`, Navidrome application state and `navidrome.db` are stored at
|
||||
`/zpool/archive/app_data/navidrome`, and Syncthing persists at
|
||||
`/zpool/archive/app_data/syncthing`. `profile_atlas` creates these datasets when
|
||||
`atlas_manage_storage` is enabled; the backend role verifies their exact mountpoints before starting
|
||||
containers. The backend role never creates the pool. The separate `wireguard_overlay` role manages `wg0`
|
||||
between Prometheus (`10.0.0.1`) and Atlas (`10.0.0.2`), generating private keys once
|
||||
on their respective hosts and exchanging only public keys through Ansible. Prometheus alone opens
|
||||
`51820/udp` publicly. When the WireGuard zone is created, Ansible reloads firewalld and immediately
|
||||
reloads Prometheus' rootful Podman networks so the existing proxy stack retains container DNS and
|
||||
connectivity. Backend ports are admitted only in the WireGuard firewalld zone.
|
||||
|
||||
`backend_phase1_start_services` stays false during the application-state transfer, so the first real
|
||||
backend run renders the Quadlets without creating an empty Atlas database. After stopping Navidrome
|
||||
on Prometheus, copy the complete `/opt/navidrome/data/` directory into
|
||||
`/zpool/archive/app_data/navidrome/`, preserving `navidrome.db` and any SQLite sidecar files. Then set
|
||||
this variable to true and rerun the role to enable and start Navidrome and Syncthing. The playbook
|
||||
never copies or deletes application data.
|
||||
|
||||
Validate and render the Atlas services with:
|
||||
|
||||
```bash
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags storage
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
|
||||
```
|
||||
|
||||
For the cutover, stop the old Navidrome writer before copying its data directory, verify ownership by
|
||||
the Atlas `admin` account and confirm that the copied SQLite database is present before changing
|
||||
`backend_phase1_start_services: true` in `host_vars/atlas.yml`. Keep the source data and the stopped
|
||||
legacy `navidromedb` container until Navidrome on Atlas and a restore test have been validated.
|
||||
|
||||
Snapshot retention, Syncthing topology, WireGuard/firewall validation, Prometheus backup pulls,
|
||||
encrypted Borg backups to a Hetzner Storage Box, USB backup, monitoring, and disaster-recovery tests
|
||||
remain follow-up work. The detailed operational backlog is kept in `AGENTS.md`.
|
||||
|
||||
## How layering works
|
||||
|
||||
A host can intentionally belong to more than one inventory group. The final configuration is the combination of the host and its groups, not a one-host/one-play mapping.
|
||||
|
||||
```text
|
||||
common configuration
|
||||
@@ -233,241 +315,201 @@ common configuration
|
||||
+ host overrides
|
||||
```
|
||||
|
||||
Esempi correnti:
|
||||
Current examples:
|
||||
|
||||
```text
|
||||
ikaros -> common + platform_fedora + role_personal_workstation + graphical_desktop + desktop_gnome + ikaros
|
||||
nymph -> common + platform_fedora + graphical_desktop + desktop_gnome + nymph
|
||||
ikaros -> common + platform_fedora + role_personal_workstation + graphical_desktop + desktop_gnome + ikaros
|
||||
nymph -> common + platform_fedora + graphical_desktop + desktop_gnome + nymph
|
||||
deadalus -> common + platform_fedora + workstation_dev_fedora + workstation_dev_wsl + deadalus
|
||||
```
|
||||
|
||||
Questo approccio consente di:
|
||||
This keeps shared configuration reusable, lets host overrides stay small, and leaves the Void desktop profile ready for a future host using `platform_void + graphical_desktop + desktop_sway`.
|
||||
|
||||
- mantenere configurazioni condivise
|
||||
- applicare override specifici per host
|
||||
- evitare duplicazioni
|
||||
- riutilizzare il profilo Void corrente su un host futuro assegnandolo a
|
||||
`platform_void + graphical_desktop + desktop_sway`
|
||||
Emacs is enabled on Fedora/GNOME and workstation profiles. `dotfiles_common` deploys the canonical authoring setup, including `~/Org/`, versioned templates, and PDF/HTML/Markdown/DOCX/ODT export support. To turn it on temporarily elsewhere:
|
||||
|
||||
---
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit <host> --tags emacs -e emacs_enabled=true
|
||||
```
|
||||
|
||||
# Ruoli Ansible
|
||||
## AI coding agents
|
||||
|
||||
I principali ruoli attualmente presenti sono:
|
||||
The shared npm-managed agents are OpenCode, Claude Code, Codex, Gemini CLI, and
|
||||
GitHub Copilot; IBM Bob is also managed on `deadalus`. Each agent has its own
|
||||
lifecycle flags in `ansible/inventory/group_vars/all.yml`, so one agent can be
|
||||
installed, configured, or removed without affecting the others:
|
||||
|
||||
| Role | Descrizione |
|
||||
| ------------------------- | ----------------------------------- |
|
||||
| base | configurazione base comune |
|
||||
| packages_void | installazione pacchetti su Void |
|
||||
| packages_freebsd | installazione pacchetti su FreeBSD via pkg |
|
||||
| packages_ubuntu | installazione pacchetti su Ubuntu |
|
||||
| packages_fedora | installazione pacchetti su Fedora |
|
||||
| services_runit | gestione servizi runit |
|
||||
| services_systemd | gestione servizi systemd |
|
||||
| services_freebsd | gestione servizi FreeBSD dichiarati per host |
|
||||
| profile_desktop_common | bootstrap desktop Void condiviso |
|
||||
| profile_desktop_gnome | dotfiles desktop condivisi per Fedora/GNOME |
|
||||
| profile_desktop_sway | sessione desktop sway / SwayFX (Wayland) |
|
||||
| profile_desktop_niri | sessione desktop Niri su Void (Wayland) |
|
||||
| profile_desktop_host | override desktop specifici per host |
|
||||
| profile_personal_workstation | layer stabile per workstation personale |
|
||||
| profile_workstation_dev_common | configurazione dev workstation condivisa |
|
||||
| profile_workstation_gnome | configurazione host workstation GNOME |
|
||||
| profile_workstation_dev_wsl | configurazione WSL Ubuntu per sviluppo |
|
||||
| profile_server | configurazione server |
|
||||
| dotfiles_common | distribuzione dotfiles comuni |
|
||||
| dotfiles | distribuzione configurazioni utente |
|
||||
```yaml
|
||||
ai_agents:
|
||||
opencode:
|
||||
npm_package: opencode-ai
|
||||
install_enabled: true
|
||||
deploy_enabled: true
|
||||
uninstall_enabled: false
|
||||
```
|
||||
|
||||
---
|
||||
Installation uses the npm `latest` state; deployment copies/renders only the
|
||||
configuration belonging to each enabled agent. Removal deletes only the selected
|
||||
managed npm package or, for IBM Bob, `/usr/local/bin/bob`; it preserves dotfiles,
|
||||
instructions, credentials, and user data. Installation and removal are mutually
|
||||
exclusive per agent: the playbook fails before making changes when both flags are
|
||||
true for the same agent. Servers set `ai_agents: {}` and therefore manage none.
|
||||
|
||||
# Stato attuale del playbook principale
|
||||
Run a focused dry run with:
|
||||
|
||||
Il playbook `ansible/site.yml` e attualmente composto da blocchi per asse:
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit ikaros --tags ai_agents --check --diff
|
||||
ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --diff
|
||||
```
|
||||
|
||||
To preview removal, set `install_enabled: false` and `uninstall_enabled: true`
|
||||
only in the entry for the agent being removed, then run:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --diff
|
||||
```
|
||||
|
||||
## Main roles
|
||||
|
||||
| Role | What it does |
|
||||
| --- | --- |
|
||||
| `packages_void` | Installs packages on Void. |
|
||||
| `packages_fedora` | Installs packages on Fedora. |
|
||||
| `packages_rocky` | Installs packages on Rocky Linux 9. |
|
||||
| `services_runit` | Manages runit services. |
|
||||
| `services_systemd` | Manages systemd services. |
|
||||
| `profile_desktop_common` | Shared Void desktop bootstrap. |
|
||||
| `profile_desktop_gnome` | Shared Fedora/GNOME desktop dotfiles. |
|
||||
| `profile_desktop_sway` | Sway / SwayFX Wayland session. |
|
||||
| `profile_desktop_niri` | Niri Wayland session on Void. |
|
||||
| `profile_desktop_host` | Host-specific desktop overrides. |
|
||||
| `profile_personal_workstation` | Stable personal-workstation layer. |
|
||||
| `profile_workstation_dev_common` | Shared workstation development setup. |
|
||||
| `profile_workstation_dev_wsl` | WSL development setup. |
|
||||
| `profile_server` | Server setup. |
|
||||
| `profile_atlas` | Rocky Linux 9 NAS setup. |
|
||||
| `profile_backend_phase1` | Rootless Navidrome and Syncthing on Atlas. |
|
||||
| `wireguard_overlay` | Prometheus/Atlas WireGuard overlay. |
|
||||
| `profile_aegis` | Fedora IoT always-on LAN node. |
|
||||
| `dotfiles_common` | Shared user dotfiles. |
|
||||
|
||||
## 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
|
||||
wireguard_overlay -> wireguard_overlay (after platform_rocky)
|
||||
role_aegis -> profile_aegis
|
||||
atlas -> profile_atlas
|
||||
role_backend_phase1 -> profile_backend_phase1 (after 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
|
||||
workstation_host_linux -> profile_workstation_gnome
|
||||
workstation_dev_wsl -> packages_ubuntu + services_systemd + profile_workstation_dev_common + profile_workstation_dev_wsl
|
||||
ubuntu_server -> packages_ubuntu + services_systemd + profile_server
|
||||
workstation_dev_wsl -> profile_workstation_dev_wsl (after platform_fedora + workstation_dev_fedora)
|
||||
```
|
||||
|
||||
Questo significa che, allo stato attuale:
|
||||
So, in practice:
|
||||
|
||||
- `ikaros` riceve Fedora Workstation/GNOME come target desktop personale stabile
|
||||
- `nymph` riceve Fedora Workstation/GNOME come target laptop
|
||||
- il profilo Void resta selezionabile tramite `platform_void + graphical_desktop` per host futuri
|
||||
- il ramo WSL (`deadalus-wsl`) e predisposto con play dev dedicato
|
||||
- il server Ubuntu (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewall
|
||||
- lo stack container server include `navidrome`, `postgres`, `gitea`, `nginx-proxy-manager` e `syncthing`, con GUI Syncthing raggiungibile tramite la rete Docker `web`
|
||||
- `platform_fedora` configures `ikaros`, `nymph`, and `deadalus`.
|
||||
- `deadalus` gets the Fedora development layer followed by the WSL layer.
|
||||
- `rocky_server` configures the Rocky 9 server, `prometheus`.
|
||||
- `atlas` receives the Rocky platform layer and the NAS profile through SSH.
|
||||
- `aegis` receives only the immutable Fedora IoT profile through SSH; it does not receive
|
||||
mutable Fedora package or common dotfile roles.
|
||||
- Empty `platform_void` 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.
|
||||
|
||||
# Dotfiles
|
||||
## Requirements
|
||||
|
||||
La directory `dotfiles/` contiene le configurazioni utente versionate.
|
||||
|
||||
```text
|
||||
dotfiles/
|
||||
├── common
|
||||
├── desktop
|
||||
├── server
|
||||
├── fedora
|
||||
├── ubuntu
|
||||
├── workstation
|
||||
├── workstation_host_linux
|
||||
├── workstation_dev_wsl
|
||||
└── nymph
|
||||
```
|
||||
|
||||
Le configurazioni sono applicate tramite Ansible e organizzate per livelli:
|
||||
|
||||
| Livello | Scopo |
|
||||
| ------- | -------------------------------- |
|
||||
| common | configurazioni condivise |
|
||||
| profile | configurazioni per tipo macchina |
|
||||
| host | override specifici |
|
||||
|
||||
---
|
||||
|
||||
# Requisiti
|
||||
|
||||
Per utilizzare il repository sono necessari:
|
||||
|
||||
- Python 3
|
||||
- Ansible
|
||||
- `ansible-lint`
|
||||
- `yamllint`
|
||||
- `shellcheck`
|
||||
- collection definite in `ansible/collections/requirements.yml`
|
||||
- accesso locale o SSH alle macchine target, in base a come e definito l'inventory
|
||||
|
||||
Installazione base:
|
||||
You will need Python 3, Ansible, `ansible-lint`, `yamllint`, `shellcheck`, and the collections in `ansible/collections/requirements.yml`.
|
||||
|
||||
```bash
|
||||
python3 -m pip install ansible ansible-lint yamllint shellcheck-py
|
||||
ansible-galaxy collection install -r ansible/collections/requirements.yml
|
||||
```
|
||||
|
||||
Gestione segreti:
|
||||
Secrets are optional:
|
||||
|
||||
- il repository supporta il caricamento opzionale di `secrets/vault.yml`
|
||||
- il repository supporta anche `secrets/vault.local.yml` per override locali non versionati
|
||||
- `secrets/vault.yml.example` funge da template/esempio
|
||||
- se `secrets/vault.yml` non e presente, il playbook continua comunque senza caricare variabili locali opzionali
|
||||
- se `secrets/.vault_pass.gpg` esiste viene usato automaticamente per sbloccare i vault tramite `gpg`; in alternativa resta supportato `secrets/.vault_pass` come fallback legacy locale; se nessuno dei due file esiste Ansible richiede la password in modo interattivo
|
||||
- `secrets/vault.yml` can hold shared local vault values.
|
||||
- `secrets/vault.local.yml` can hold untracked local overrides.
|
||||
- `secrets/vault.yml.example` is the example template.
|
||||
- If no vault file exists, the playbook still runs without those optional values.
|
||||
- `secrets/.vault_pass.gpg` is used when available; `secrets/.vault_pass` is a legacy local fallback. Without either one, Ansible asks for the password interactively.
|
||||
|
||||
---
|
||||
## Running it
|
||||
|
||||
# Utilizzo
|
||||
|
||||
Eseguire il playbook principale:
|
||||
Run the whole playbook:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml
|
||||
```
|
||||
|
||||
Allo stato attuale questo comando:
|
||||
|
||||
- distribuisce i dotfiles comuni a tutti gli host
|
||||
- per `platform_void` applica pacchetti Void e servizi runit
|
||||
- per `platform_void + graphical_desktop` applica bootstrap desktop condiviso, sessioni Sway/Niri e override specifici per host
|
||||
- per `platform_freebsd`, `workstation_dev_fedora` e `workstation_host_linux` non applica nulla finche quei gruppi restano senza host
|
||||
- per `platform_fedora` applica pacchetti Fedora e servizi systemd a `ikaros` e `nymph`
|
||||
- per `platform_fedora & role_personal_workstation` applica il layer personale a `ikaros`
|
||||
- per `platform_fedora & desktop_gnome` applica il profilo GNOME a `ikaros` e `nymph`
|
||||
- per `workstation_dev_wsl` applica pacchetti Ubuntu, servizi systemd, profilo dev comune e tweak WSL dedicati a `deadalus-wsl`
|
||||
- per gli host `ubuntu_server` applica pacchetti Ubuntu, servizi systemd, profilo server, UFW, dotfiles e template dedicati
|
||||
- non riavvia automaticamente il display manager
|
||||
- carica `secrets/vault.yml` solo se presente
|
||||
- carica `secrets/vault.local.yml` solo se presente, dopo `vault.yml`, cosi gli override locali hanno precedenza
|
||||
|
||||
Per validare prima di applicare:
|
||||
Useful checks before applying changes:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --syntax-check
|
||||
ansible-playbook ansible/site.yml --limit ikaros,nymph --check --diff
|
||||
ansible-playbook ansible/site.yml --limit ikaros --check --diff
|
||||
ansible-playbook ansible/site.yml --limit nymph --check --diff
|
||||
ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff
|
||||
ansible-playbook ansible/site.yml --limit deadalus --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --check --diff
|
||||
ansible-playbook ansible/site.yml --limit atlas --check --diff
|
||||
ansible-playbook ansible/site.yml --limit aegis --check --diff
|
||||
ansible-lint ansible/site.yml
|
||||
ansible-lint ansible/roles
|
||||
yamllint ansible/
|
||||
```
|
||||
|
||||
Per testare un override dell'utente server senza modificare l'inventory:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus --check --diff -e server_username=myuser
|
||||
```
|
||||
|
||||
Per validazioni piu mirate:
|
||||
For focused checks:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit <host> --tags <tag1>,<tag2> --check --diff
|
||||
ansible-playbook ansible/site.yml --limit <host> --start-at-task "<task name>" --check --diff
|
||||
ansible-lint ansible/roles/<role>
|
||||
yamllint ansible/path/to/file.yml
|
||||
docker compose -f /opt/docker/server/docker-compose.yml config
|
||||
podman-compose -f /opt/docker/server/docker-compose.yml config
|
||||
ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff
|
||||
```
|
||||
|
||||
## Tag supportati dal playbook
|
||||
## Tags
|
||||
|
||||
Per vedere l'elenco reale aggiornato dei tag disponibili:
|
||||
Use Ansible as the source of truth for the current tag list:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --list-tags
|
||||
```
|
||||
|
||||
Allo stato attuale `ansible/site.yml` espone questi tag:
|
||||
| Tag | Main scope |
|
||||
| --- | --- |
|
||||
| `always` | Common pre-tasks, including optional vault loading. |
|
||||
| `ai_agents` | AI coding-agent install, configuration deployment, and managed-binary removal. |
|
||||
| `atlas` | Atlas NAS account, storage, sharing, and container configuration. |
|
||||
| `backend_phase1` | Rootless Atlas Navidrome and Syncthing Quadlets. |
|
||||
| `containers` | Rootful Atlas Quadlets. |
|
||||
| `dotfiles` | User configuration across all profiles. |
|
||||
| `dotfiles:common` | Shared dotfiles. |
|
||||
| `dotfiles:desktop` | Void and Fedora/GNOME desktop dotfiles. |
|
||||
| `dotfiles:host` | Host-specific Void desktop overrides. |
|
||||
| `dotfiles:server` | Server dotfiles. |
|
||||
| `dotfiles:workstation` | Personal workstation and WSL dotfiles. |
|
||||
| `emacs` | Shared Emacs setup and authoring dependencies. |
|
||||
| `gnome` | Fedora/GNOME desktop configuration. |
|
||||
| `immich` | Atlas Immich account and Quadlets. |
|
||||
| `npm` | Global npm packages. |
|
||||
| `packages` | Package installation and updates. |
|
||||
| `podman` | Podman Compose and rootless Quadlet integration. |
|
||||
| `services` | runit and systemd services. |
|
||||
| `sharing` | Atlas NFSv4 and SMB3 configuration. |
|
||||
| `storage` | Atlas child ZFS datasets. |
|
||||
| `tmux` | tmux configuration and plugins. |
|
||||
| `wireguard` | Prometheus/Atlas WireGuard overlay. |
|
||||
| `wsl` | WSL bootstrap and configuration. |
|
||||
|
||||
| Tag | Scopo | Ambito principale |
|
||||
| --- | --- | --- |
|
||||
| `always` | pre-task sempre eseguiti, inclusi caricamento vault e validazioni preliminari | common |
|
||||
| `ai_agents` | installazione agenti AI condivisi | Fedora, WSL |
|
||||
| `dotfiles` | distribuzione/configurazione dotfiles | tutti i profili |
|
||||
| `dotfiles:common` | dotfiles comuni condivisi | common, workstation, server |
|
||||
| `dotfiles:desktop` | dotfiles desktop | desktop Void, Fedora/GNOME |
|
||||
| `dotfiles:host` | override host-specifici desktop | desktop Void |
|
||||
| `dotfiles:server` | dotfiles dedicati al profilo server | server |
|
||||
| `dotfiles:workstation` | dotfiles dedicati alle workstation | personal workstation, WSL, workstation Linux future |
|
||||
| `emptty` | gestione display manager `emptty` | desktop Void |
|
||||
| `display-manager` | gestione del display manager `emptty` | desktop Void |
|
||||
| `emacs` | configurazione Emacs condivisa e dipendenze di authoring | desktop Fedora/GNOME e workstation |
|
||||
| `fonts` | installazione font | Fedora |
|
||||
| `fzf` | configurazione FZF | dotfiles comuni |
|
||||
| `git` | configurazione Git e GPG desktop | Fedora/GNOME, desktop Void |
|
||||
| `gnome` | configurazione host GNOME | Fedora/GNOME desktop, workstation host Linux future |
|
||||
| `sway` | sessione/configurazione sway / SwayFX (Wayland) | desktop Void |
|
||||
| `niri` | sessione/configurazione Niri (Wayland) | desktop Void |
|
||||
| `npm` | installazione pacchetti npm globali | Fedora/GNOME, desktop Void, workstation Linux, WSL |
|
||||
| `nvidia` | componenti NVIDIA desktop | desktop Void |
|
||||
| `packages` | installazione e aggiornamento pacchetti | tutti i profili |
|
||||
| `portal` | configurazione xdg-desktop-portal | desktop Void |
|
||||
| `services` | gestione servizi runit/systemd | tutti i profili |
|
||||
| `theme` | configurazione del tema GTK/Qt | desktop Void |
|
||||
| `tmux` | configurazione e plugin tmux | desktop Fedora/Void, WSL |
|
||||
| `vim` | configurazione Vim | dotfiles comuni |
|
||||
| `vscode` | installazione/configurazione VS Code | workstation Fedora future, host Linux |
|
||||
| `wsl` | bootstrap e configurazione WSL | WSL |
|
||||
|
||||
Esempi pratici:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit nymph --tags dotfiles:desktop,gnome --check --diff
|
||||
ansible-playbook ansible/site.yml --limit ikaros --tags gnome --check --diff
|
||||
ansible-playbook ansible/site.yml --limit prometheus --tags services,dotfiles:server --check --diff
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Bootstrap di una nuova macchina
|
||||
|
||||
Una nuova macchina può essere inizializzata con i seguenti passaggi:
|
||||
## Bootstrapping a new machine
|
||||
|
||||
```bash
|
||||
git clone <repo>
|
||||
@@ -476,66 +518,11 @@ ansible-galaxy collection install -r ansible/collections/requirements.yml
|
||||
ansible-playbook ansible/site.yml
|
||||
```
|
||||
|
||||
Dopo l'esecuzione del playbook la macchina verra configurata secondo il profilo definito e i ruoli attualmente orchestrati.
|
||||
For a future Void desktop host:
|
||||
|
||||
Per aggiungere un nuovo host Void che riusa il profilo desktop preservato:
|
||||
1. Add it to `platform_void`.
|
||||
2. Add it to `graphical_desktop`.
|
||||
3. Use Sway, or add it to `desktop_niri` for Niri.
|
||||
4. Put hardware-specific details in `host_vars/<host>.yml`.
|
||||
|
||||
1. aggiungere l'host a `platform_void`;
|
||||
2. aggiungerlo a `graphical_desktop`;
|
||||
3. usare Sway, oppure aggiungerlo a `desktop_niri` per selezionare Niri;
|
||||
4. lasciare eventuali dettagli hardware in `host_vars/<host>.yml`.
|
||||
|
||||
I gruppi legacy `void` e `desktop` sono parent di compatibilita, quindi un host
|
||||
in `platform_void` e `graphical_desktop` continua a ricevere anche le variabili
|
||||
Void e desktop esistenti.
|
||||
|
||||
Per prove in VM sono disponibili gruppi di esempio in
|
||||
`ansible/inventory/examples/platform-test-hosts.yml`, da passare esplicitamente
|
||||
con `-i` insieme all'inventory principale.
|
||||
|
||||
Per il flusso mail desktop esiste inoltre uno script dedicato:
|
||||
|
||||
```bash
|
||||
scripts/bootstrap_mail.sh
|
||||
```
|
||||
|
||||
Lo script si occupa del bootstrap dei secret nel keyring, del primo sync con `mbsync` e dell'inizializzazione di `mu` usando la configurazione mail generata dai template.
|
||||
|
||||
Se modifichi questo script, valida almeno con:
|
||||
|
||||
```bash
|
||||
sh -n scripts/bootstrap_mail.sh
|
||||
shellcheck scripts/bootstrap_mail.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Filosofia del progetto
|
||||
|
||||
Il repository segue alcuni principi chiave:
|
||||
|
||||
- Infrastructure as Code
|
||||
- configurazione dichiarativa
|
||||
- idempotenza
|
||||
- ambienti riproducibili
|
||||
- separazione tra configurazione sistema e configurazione utente
|
||||
|
||||
Questo consente di ricreare qualsiasi macchina partendo esclusivamente dal repository.
|
||||
|
||||
---
|
||||
|
||||
# Roadmap
|
||||
|
||||
Possibili evoluzioni future:
|
||||
|
||||
- hardening sicurezza server
|
||||
- configurazione backup
|
||||
- testing automatico playbook
|
||||
- integrazione CI
|
||||
- supporto ad altre distribuzioni Linux
|
||||
|
||||
---
|
||||
|
||||
# Licenza
|
||||
|
||||
Questo progetto è distribuito sotto licenza **LGPL-3.0**.
|
||||
The legacy `void` and `desktop` groups remain compatibility parents, so hosts in `platform_void` and `graphical_desktop` still receive the existing Void and desktop variables.
|
||||
|
||||
57
ansible/bootstrap/aegis.bu
Normal file
57
ansible/bootstrap/aegis.bu
Normal file
@@ -0,0 +1,57 @@
|
||||
# One-time bootstrap for Fedora IoT on Aegis (Raspberry Pi 4).
|
||||
# Generate only: ./generate-aegis-ign.sh
|
||||
# Generate and write an SD card: ./generate-aegis-ign.sh --write IMAGE DEVICE
|
||||
# The write mode uses arm-image-installer with the RPi4 target and embeds
|
||||
# config.ign; it prompts for Wi-Fi credentials unless supplied through its
|
||||
# WIFI_SSID and WIFI_PASS environment variables.
|
||||
# For WiFi, the UEFI 'System Table Selection' must be DeviceTree (Esc at boot).
|
||||
variant: fiot
|
||||
version: 1.0.0
|
||||
passwd:
|
||||
users:
|
||||
- name: admin
|
||||
password_hash: "$6$bNDsU0XC5BxNJS5U$ENDGpdOUPJM3wcXBgNESCQkOqqQ9gN72/xEQoJsAI6QdsaY6mD4G5DBVIv7nHwHQOP35IH1oGRl.Uy3R2iUYQ1"
|
||||
groups:
|
||||
- wheel
|
||||
ssh_authorized_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||
systemd:
|
||||
units:
|
||||
- name: sshd.service
|
||||
enabled: true
|
||||
- name: rpm-ostree-install-wifi.service
|
||||
enabled: true
|
||||
contents: |
|
||||
[Unit]
|
||||
Description=Layer WiFi packages (driver, firmware, NetworkManager-wifi)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
Before=zincati.service
|
||||
ConditionPathExists=!/var/lib/%N.stamp
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/bin/rpm-ostree install -y --allow-inactive NetworkManager-wifi NetworkManager-wwan wpa_supplicant wireless-regdb iw brcmfmac-firmware
|
||||
ExecStart=/bin/touch /var/lib/%N.stamp
|
||||
ExecStart=/bin/systemctl --no-block reboot
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
storage:
|
||||
files:
|
||||
- path: /etc/hostname
|
||||
mode: 0644
|
||||
contents:
|
||||
inline: |
|
||||
aegis
|
||||
- path: /etc/sysctl.d/99-ip-forward.conf
|
||||
mode: 0644
|
||||
contents:
|
||||
inline: |
|
||||
net.ipv4.ip_forward = 1
|
||||
links:
|
||||
- path: /etc/localtime
|
||||
target: ../usr/share/zoneinfo/Europe/Rome
|
||||
147
ansible/bootstrap/generate-aegis-ign.sh
Executable file
147
ansible/bootstrap/generate-aegis-ign.sh
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
BUTANE_IMAGE=${BUTANE_IMAGE:-quay.io/coreos/butane:release}
|
||||
BUTANE_SOURCE=${BUTANE_SOURCE:-"$SCRIPT_DIR/aegis.bu"}
|
||||
IGNITION_OUTPUT=${IGNITION_OUTPUT:-"$SCRIPT_DIR/config.ign"}
|
||||
SSH_PUBLIC_KEY=${SSH_PUBLIC_KEY:-"$HOME/.ssh/id_ed25519.pub"}
|
||||
WIFI_SECURITY=${WIFI_SECURITY:-wpa-psk}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
generate-aegis-ign.sh
|
||||
generate-aegis-ign.sh --write IMAGE DEVICE
|
||||
|
||||
Environment overrides:
|
||||
BUTANE_IMAGE Butane container image (default: quay.io/coreos/butane:release)
|
||||
BUTANE_SOURCE Butane source path (default: aegis.bu beside this script)
|
||||
IGNITION_OUTPUT Ignition output path (default: config.ign beside this script)
|
||||
SSH_PUBLIC_KEY SSH public key passed to arm-image-installer
|
||||
WIFI_SSID Wi-Fi SSID; prompted if unset in --write mode
|
||||
WIFI_PASS Wi-Fi password; prompted if unset in --write mode
|
||||
WIFI_SECURITY Wi-Fi security type (default: wpa-psk)
|
||||
USAGE
|
||||
}
|
||||
|
||||
require_command() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
printf 'Error: required command not found: %s\n' "$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
read_required() {
|
||||
prompt=$1
|
||||
value=$2
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
printf '%s' "$prompt" >&2
|
||||
IFS= read -r value
|
||||
fi
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
printf '%s\n' 'Error: a value is required.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
read_secret() {
|
||||
value=$1
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
printf '%s' 'Wi-Fi password: ' >&2
|
||||
stty -echo
|
||||
IFS= read -r value
|
||||
stty echo
|
||||
printf '\n' >&2
|
||||
fi
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
printf '%s\n' 'Error: a value is required.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
write_image=false
|
||||
case $# in
|
||||
0)
|
||||
;;
|
||||
3)
|
||||
if [ "$1" != '--write' ]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
write_image=true
|
||||
IMAGE=$2
|
||||
DEVICE=$3
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
require_command podman
|
||||
|
||||
if [ ! -f "$BUTANE_SOURCE" ]; then
|
||||
printf 'Error: Butane source not found: %s\n' "$BUTANE_SOURCE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_DIR=$(dirname "$IGNITION_OUTPUT")
|
||||
if [ ! -d "$OUTPUT_DIR" ]; then
|
||||
printf 'Error: output directory not found: %s\n' "$OUTPUT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
umask 077
|
||||
TEMP_OUTPUT=$(mktemp "$OUTPUT_DIR/.config.ign.XXXXXX")
|
||||
trap 'rm -f "$TEMP_OUTPUT"' EXIT HUP INT TERM
|
||||
|
||||
podman run --rm -i "$BUTANE_IMAGE" --strict < "$BUTANE_SOURCE" > "$TEMP_OUTPUT"
|
||||
mv "$TEMP_OUTPUT" "$IGNITION_OUTPUT"
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
printf 'Generated Ignition config: %s\n' "$IGNITION_OUTPUT"
|
||||
|
||||
if [ "$write_image" = false ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$IMAGE" ]; then
|
||||
printf 'Error: image not found: %s\n' "$IMAGE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -b "$DEVICE" ]; then
|
||||
printf 'Error: target is not a block device: %s\n' "$DEVICE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$SSH_PUBLIC_KEY" ]; then
|
||||
printf 'Error: SSH public key not found: %s\n' "$SSH_PUBLIC_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_command arm-image-installer
|
||||
WIFI_SSID=$(read_required 'Wi-Fi SSID: ' "${WIFI_SSID:-}")
|
||||
WIFI_PASS=$(read_secret "${WIFI_PASS:-}")
|
||||
|
||||
printf 'Writing %s to %s.\n' "$IMAGE" "$DEVICE" >&2
|
||||
sudo arm-image-installer \
|
||||
--image="$IMAGE" \
|
||||
--target=rpi4 \
|
||||
--media="$DEVICE" \
|
||||
--ignition="$IGNITION_OUTPUT" \
|
||||
--addkey="$SSH_PUBLIC_KEY" \
|
||||
--resizefs \
|
||||
--wifi-ssid="$WIFI_SSID" \
|
||||
--wifi-pass="$WIFI_PASS" \
|
||||
--wifi-security="$WIFI_SECURITY"
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
# Optional inventory overlay for VM tests. It is not loaded by ansible.cfg.
|
||||
all:
|
||||
children:
|
||||
platform_freebsd:
|
||||
hosts:
|
||||
freebsdtest:
|
||||
ansible_connection: local
|
||||
@@ -90,38 +90,75 @@ common_dotfiles:
|
||||
dest: .config/bat/
|
||||
mode: preserve
|
||||
|
||||
ai_agents_npm_packages:
|
||||
- name: "opencode-ai"
|
||||
state: latest
|
||||
- name: "@anthropic-ai/claude-code"
|
||||
state: latest
|
||||
- name: "@openai/codex"
|
||||
state: latest
|
||||
- name: "@google/gemini-cli"
|
||||
state: latest
|
||||
# AI coding-agent lifecycle controls are independent for every agent.
|
||||
# Installation and removal are mutually exclusive per agent; the common
|
||||
# pre-task enforces this before any changes are made.
|
||||
ai_agents:
|
||||
opencode:
|
||||
npm_package: opencode-ai
|
||||
install_enabled: true
|
||||
deploy_enabled: true
|
||||
uninstall_enabled: false
|
||||
claude_code:
|
||||
npm_package: "@anthropic-ai/claude-code"
|
||||
install_enabled: false
|
||||
deploy_enabled: false
|
||||
uninstall_enabled: true
|
||||
codex:
|
||||
npm_package: "@openai/codex"
|
||||
install_enabled: true
|
||||
deploy_enabled: true
|
||||
uninstall_enabled: false
|
||||
gemini_cli:
|
||||
npm_package: "@google/gemini-cli"
|
||||
install_enabled: true
|
||||
deploy_enabled: true
|
||||
uninstall_enabled: false
|
||||
github_copilot:
|
||||
npm_package: "@github/copilot"
|
||||
install_enabled: false
|
||||
deploy_enabled: false
|
||||
uninstall_enabled: true
|
||||
ibm_bob:
|
||||
install_enabled: true
|
||||
deploy_enabled: false
|
||||
uninstall_enabled: false
|
||||
|
||||
ai_agents_enabled: true
|
||||
ai_agents_deploy_enabled_names: >-
|
||||
{{ ai_agents | dict2items | selectattr('value.deploy_enabled') | map(attribute='key') | list }}
|
||||
|
||||
ai_agents_dotfiles:
|
||||
- name: AI common config
|
||||
src: .config/ai/
|
||||
dest: .config/ai/
|
||||
mode: preserve
|
||||
agents:
|
||||
- opencode
|
||||
- codex
|
||||
- gemini_cli
|
||||
- name: Gemini CLI config
|
||||
src: .gemini/
|
||||
dest: .gemini/
|
||||
mode: preserve
|
||||
agents:
|
||||
- gemini_cli
|
||||
- name: OpenCode config
|
||||
src: .config/opencode/
|
||||
dest: .config/opencode/
|
||||
mode: preserve
|
||||
agents:
|
||||
- opencode
|
||||
- name: Claude Code memory
|
||||
src: .claude/
|
||||
dest: .claude/
|
||||
mode: preserve
|
||||
agents:
|
||||
- claude_code
|
||||
|
||||
ai_agents_templates:
|
||||
- name: Codex config
|
||||
src: .codex/config.toml.j2
|
||||
dest: .codex/config.toml
|
||||
mode: "0644"
|
||||
agents:
|
||||
- codex
|
||||
|
||||
@@ -31,8 +31,7 @@ desktop_binary_tools: []
|
||||
|
||||
desktop_npm_packages: >-
|
||||
{{
|
||||
ai_agents_npm_packages
|
||||
+ [
|
||||
[
|
||||
{'name': '@mermaid-js/mermaid-cli', 'state': 'latest'},
|
||||
{'name': 'vscode-langservers-extracted', 'state': 'latest'}
|
||||
]
|
||||
@@ -225,8 +224,8 @@ desktop_flatpak_packages:
|
||||
- com.github.tchx84.Flatseal
|
||||
- com.spotify.Client
|
||||
- io.github.flattool.Warehouse
|
||||
- org.onlyoffice.desktopeditors
|
||||
- org.telegram.desktop
|
||||
- org.mozilla.Thunderbird
|
||||
|
||||
desktop_flatpak_extensions:
|
||||
- org.gtk.Gtk3theme.Yaru-Blue-dark//stable
|
||||
|
||||
@@ -11,6 +11,7 @@ fedora_desktop_packages:
|
||||
# Fedora equivalents of the development/tooling packages previously pulled by
|
||||
# the Void desktop profile.
|
||||
- 7zip
|
||||
- arm-image-installer
|
||||
- nodejs-bash-language-server
|
||||
- bluez
|
||||
- bridge-utils
|
||||
@@ -21,6 +22,7 @@ fedora_desktop_packages:
|
||||
- croc
|
||||
- delve
|
||||
- dnsmasq
|
||||
- evolution
|
||||
- fastfetch
|
||||
- poppler-glib
|
||||
- poppler-utils
|
||||
@@ -30,9 +32,9 @@ fedora_desktop_packages:
|
||||
- golang
|
||||
- golangci-lint
|
||||
- gopls
|
||||
- gnome-shell-extension-gsconnect
|
||||
- hunspell
|
||||
- libvirt
|
||||
- libreoffice
|
||||
- liberation-fonts
|
||||
- libasan
|
||||
- libubsan
|
||||
@@ -50,10 +52,12 @@ fedora_desktop_packages:
|
||||
- qemu-tools
|
||||
- rclone
|
||||
- remmina
|
||||
- rpi-imager
|
||||
- ruff
|
||||
- rustup
|
||||
- rsync
|
||||
- shfmt
|
||||
- seahorse
|
||||
- syncthing
|
||||
- tealdeer
|
||||
- terminus-fonts
|
||||
@@ -80,7 +84,5 @@ fedora_flatpak_packages:
|
||||
- be.alexandervanhee.gradia
|
||||
- ch.protonmail.protonmail-bridge
|
||||
- com.spotify.Client
|
||||
- org.mozilla.thunderbird
|
||||
- org.onlyoffice.desktopeditors
|
||||
- org.telegram.desktop
|
||||
|
||||
fedora_npm_packages: "{{ ai_agents_npm_packages }}"
|
||||
|
||||
@@ -12,12 +12,6 @@ fedora_packages_base:
|
||||
- ripgrep
|
||||
|
||||
fedora_manage_docker_repo: true
|
||||
fedora_manage_google_chrome_repo: true
|
||||
fedora_manage_intellij_repo: true
|
||||
fedora_manage_vscode_repo: true
|
||||
fedora_intellij_copr_owner: coder966
|
||||
fedora_intellij_copr_project: intellij-idea-ultimate
|
||||
fedora_intellij_package_name: intellij-idea-ultimate
|
||||
|
||||
fedora_emacs_packages:
|
||||
- emacs-pgtk
|
||||
@@ -53,7 +47,7 @@ fedora_dotfiles:
|
||||
|
||||
workstation_firewall_backend: firewalld
|
||||
|
||||
fedora_workstation_dev_packages:
|
||||
fedora_dev_packages:
|
||||
- croc
|
||||
- distrobox
|
||||
- gnupg
|
||||
@@ -62,35 +56,5 @@ fedora_workstation_dev_packages:
|
||||
- openssh-clients
|
||||
- shellcheck
|
||||
|
||||
workstation_host_linux_packages_fedora:
|
||||
- code
|
||||
- croc
|
||||
- firewall-config
|
||||
- gnome-extensions-app
|
||||
- gnome-shell-extension-appindicator
|
||||
- gnome-shell-extension-just-perfection
|
||||
- gnome-shell-extension-no-overview
|
||||
- gnome-tweaks
|
||||
- libreoffice
|
||||
- meld
|
||||
- NetworkManager-openconnect-gnome
|
||||
- pcsc-lite
|
||||
- pinentry-gnome3
|
||||
- podman
|
||||
- podman-compose
|
||||
- rclone
|
||||
- yubikey-manager
|
||||
|
||||
fedora_flatpak_remote_name: flathub
|
||||
fedora_flatpak_remote_url: https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
workstation_flatpak_remote_name: "{{ fedora_flatpak_remote_name }}"
|
||||
workstation_flatpak_remote_url: "{{ fedora_flatpak_remote_url }}"
|
||||
workstation_flatpak_packages:
|
||||
- com.getpostman.Postman
|
||||
- com.spotify.Client
|
||||
- com.github.jeromerobert.pdfarranger
|
||||
- com.github.xournalpp.xournalpp
|
||||
- io.dbeaver.DBeaverCommunity
|
||||
- org.mozilla.Thunderbird
|
||||
- org.telegram.desktop
|
||||
|
||||
2
ansible/inventory/group_vars/platform_fedora_iot.yml
Normal file
2
ansible/inventory/group_vars/platform_fedora_iot.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
# Fedora IoT is immutable: do not attach the mutable Fedora package roles.
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
platform_profile: freebsd
|
||||
platform_family: freebsd
|
||||
platform_package_manager: pkg
|
||||
platform_service_manager: rc
|
||||
ansible_python_interpreter: /usr/local/bin/python3
|
||||
|
||||
vim_plugin_distro_packages:
|
||||
- fzf
|
||||
vim_plugin_source_plugins:
|
||||
- name: fzf
|
||||
repo: https://github.com/junegunn/fzf.git
|
||||
version: v0.72.0
|
||||
- name: fzf.vim
|
||||
repo: https://github.com/junegunn/fzf.vim.git
|
||||
version: 34a564c81f36047f50e593c1656f4580ff75ccca
|
||||
- name: vim-fugitive
|
||||
repo: https://github.com/tpope/vim-fugitive.git
|
||||
version: 3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0
|
||||
|
||||
effective_user_group: "{{ user_group }}"
|
||||
effective_user_home: "/home/{{ effective_username }}"
|
||||
user_home: "/home/{{ username }}"
|
||||
|
||||
freebsd_packages_base:
|
||||
- bash
|
||||
- bash-completion
|
||||
- bat
|
||||
- curl
|
||||
- fzf
|
||||
- git
|
||||
- jq
|
||||
- python3
|
||||
- ripgrep
|
||||
- rsync
|
||||
- tmux
|
||||
- tree
|
||||
- unzip
|
||||
- vim
|
||||
- wget
|
||||
- zip
|
||||
- zoxide
|
||||
|
||||
freebsd_development_packages:
|
||||
- clang
|
||||
- cmake
|
||||
- gdb
|
||||
- go
|
||||
- gmake
|
||||
- node
|
||||
- npm
|
||||
- pkgconf
|
||||
- py311-pip
|
||||
- rust
|
||||
- shellcheck
|
||||
55
ansible/inventory/group_vars/platform_rocky.yml
Normal file
55
ansible/inventory/group_vars/platform_rocky.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
platform_profile: rocky
|
||||
platform_family: redhat
|
||||
platform_package_manager: dnf
|
||||
platform_service_manager: systemd
|
||||
|
||||
rocky_openzfs_release_rpm: https://zfsonlinux.org/epel/zfs-release-3-0.el9.noarch.rpm
|
||||
rocky_openzfs_gpg_key_url: >-
|
||||
https://raw.githubusercontent.com/zfsonlinux/zfsonlinux.github.com/master/zfs-release/RPM-GPG-KEY-openzfs-key2
|
||||
rocky_openzfs_gpg_key_fingerprint: 7DC7 299D CF7C 7FD9 CD87 701B A599 FD5E 9DB8 4141
|
||||
rocky_syncthing_version: 2.1.3
|
||||
rocky_syncthing_archive_checksum: sha256:f929eb8e5b72a85543eeeefb2c38f34a68e0c530e70758a2905b78840c76602c
|
||||
rocky_syncthing_archive_url: >-
|
||||
{{
|
||||
'https://github.com/syncthing/syncthing/releases/download/v'
|
||||
~ rocky_syncthing_version
|
||||
~ '/syncthing-linux-amd64-v'
|
||||
~ rocky_syncthing_version
|
||||
~ '.tar.gz'
|
||||
}}
|
||||
|
||||
rocky_manage_openzfs_repo: false
|
||||
rocky_manage_syncthing_binary: false
|
||||
rocky_manage_podman: false
|
||||
rocky_manage_github_cli_repo: false
|
||||
|
||||
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:
|
||||
- dnf-plugins-core
|
||||
- firewalld
|
||||
- openssh-server
|
||||
- rsync
|
||||
- selinux-policy-targeted
|
||||
- sudo
|
||||
|
||||
rocky_profile_packages: []
|
||||
rocky_podman_packages: []
|
||||
43
ansible/inventory/group_vars/rocky_server.yml
Normal file
43
ansible/inventory/group_vars/rocky_server.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
rocky_manage_podman: 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_podman_packages:
|
||||
- podman
|
||||
- podman-compose
|
||||
|
||||
enabled_services:
|
||||
- firewalld
|
||||
- podman.socket
|
||||
|
||||
server_firewall_backend: firewalld
|
||||
server_firewalld_zone: public
|
||||
server_firewalld_services:
|
||||
- ssh
|
||||
- cockpit
|
||||
- http
|
||||
- https
|
||||
server_firewalld_ports: []
|
||||
server_sshd_service_name: sshd
|
||||
server_compose_selinux_mount_option: Z
|
||||
server_syncthing_enabled: false
|
||||
@@ -13,12 +13,4 @@ personal_workstation_directories:
|
||||
- path: "{{ user_home }}/Remotes"
|
||||
mode: "0755"
|
||||
|
||||
personal_workstation_dotfiles:
|
||||
- src: .gitignore_global
|
||||
dest: .gitignore_global
|
||||
mode: "0644"
|
||||
- src: .themes.gitignore
|
||||
dest: .themes.gitignore
|
||||
mode: "0644"
|
||||
|
||||
personal_workstation_flatpak_packages: []
|
||||
|
||||
@@ -6,7 +6,7 @@ effective_username: "{{ server_username }}"
|
||||
effective_user_group: "{{ server_user_group }}"
|
||||
effective_user_home: "{{ server_user_home }}"
|
||||
server_container_stack_dir: /opt/docker/server
|
||||
ai_agents_enabled: false
|
||||
ai_agents: {}
|
||||
vim_plugins_enabled: false
|
||||
|
||||
profile_packages:
|
||||
@@ -25,6 +25,9 @@ profile_packages:
|
||||
- rsync
|
||||
|
||||
server_dotfiles:
|
||||
- src: .bashrc.d/20-editor-server.sh
|
||||
dest: .bashrc.d/20-editor-server.sh
|
||||
mode: "0644"
|
||||
- src: .gnupg/gpg-agent.conf
|
||||
dest: .gnupg/gpg-agent.conf
|
||||
mode: "0600"
|
||||
@@ -34,9 +37,6 @@ server_dotfiles:
|
||||
- src: .themes.gitignore
|
||||
dest: .themes.gitignore
|
||||
mode: "0644"
|
||||
- src: duckdns/
|
||||
dest: duckdns/
|
||||
mode: preserve
|
||||
|
||||
server_templates:
|
||||
- src: server/.gitconfig.j2
|
||||
@@ -50,6 +50,10 @@ server_templates:
|
||||
no_log: true
|
||||
|
||||
server_directories:
|
||||
- path: "{{ server_user_home }}/.bashrc.d"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0755"
|
||||
- path: "{{ server_user_home }}/.gnupg"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
@@ -58,14 +62,6 @@ server_directories:
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /opt/navidrome/data
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0755"
|
||||
- path: /opt/music
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0755"
|
||||
- path: /opt/npm/data
|
||||
owner: root
|
||||
group: root
|
||||
@@ -74,14 +70,13 @@ server_directories:
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /opt/postgres/data
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /opt/gitea/data
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
server_syncthing_enabled: true
|
||||
server_syncthing_directories:
|
||||
- path: /opt/syncthing/config
|
||||
owner: root
|
||||
group: root
|
||||
@@ -94,33 +89,11 @@ server_directories:
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0755"
|
||||
- path: /srv/nextcloud
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /srv/nextcloud/data
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
server_ufw_rules:
|
||||
- rule: allow
|
||||
name: OpenSSH
|
||||
- rule: allow
|
||||
port: "22000"
|
||||
proto: tcp
|
||||
comment: Syncthing sync traffic
|
||||
- rule: allow
|
||||
port: "22000"
|
||||
proto: udp
|
||||
comment: Syncthing QUIC sync traffic
|
||||
- rule: allow
|
||||
port: "21027"
|
||||
proto: udp
|
||||
comment: Syncthing local discovery
|
||||
|
||||
server_sshd_settings:
|
||||
PermitRootLogin: "no"
|
||||
|
||||
server_sshd_allow_users:
|
||||
- "{{ server_username }}"
|
||||
server_ssh_authorized_keys: []
|
||||
server_ssh_authorized_key_directory: "{{ server_user_home }}/.ssh/authorized_keys.d"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
ubuntu_packages_base:
|
||||
- curl
|
||||
- ufw
|
||||
- htop
|
||||
- build-essential
|
||||
- nodejs
|
||||
- git-delta
|
||||
- jq
|
||||
- ripgrep
|
||||
|
||||
ubuntu_manage_docker_repo: true
|
||||
|
||||
ubuntu_emacs_packages:
|
||||
- emacs
|
||||
- pandoc
|
||||
- latexmk
|
||||
- texlive-luatex
|
||||
- texlive-latex-base
|
||||
- texlive-latex-recommended
|
||||
- texlive-fonts-recommended
|
||||
|
||||
ubuntu_docker_packages:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
|
||||
enabled_services:
|
||||
- ufw
|
||||
- docker
|
||||
|
||||
ubuntu_dotfiles:
|
||||
- src: .bashrc.d/
|
||||
dest: .bashrc.d/
|
||||
mode: preserve
|
||||
@@ -183,6 +183,7 @@ profile_packages:
|
||||
- deluge-gtk
|
||||
- delve
|
||||
- dnsmasq
|
||||
- evolution
|
||||
- poppler-glib
|
||||
- poppler-utils
|
||||
- fontconfig-devel
|
||||
@@ -193,7 +194,6 @@ profile_packages:
|
||||
- hunspell
|
||||
- libvirt
|
||||
- libspa-bluetooth
|
||||
- libreoffice
|
||||
- liberation-fonts-ttf
|
||||
- libX11-devel
|
||||
- libXft-devel
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
---
|
||||
workstation_manage_opencode: true
|
||||
workstation_manage_ibm_bob: true
|
||||
workstation_ibm_bob_install_url: "https://bob.ibm.com/download/bobshell.sh"
|
||||
workstation_npm_packages: "{{ ai_agents_npm_packages + [] }}"
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
---
|
||||
emacs_enabled: true
|
||||
|
||||
workstation_dev_packages:
|
||||
- distrobox
|
||||
- gnupg
|
||||
- gpg-agent
|
||||
- libfido2-1
|
||||
- openssh-client
|
||||
- shellcheck
|
||||
|
||||
workstation_user_directories:
|
||||
- path: "{{ user_home }}/.config"
|
||||
mode: "0755"
|
||||
|
||||
@@ -3,27 +3,25 @@ enabled_services:
|
||||
- docker
|
||||
|
||||
workstation_dev_wsl_packages:
|
||||
- lazygit
|
||||
- mise
|
||||
- pinentry-curses
|
||||
- python3-pip
|
||||
- tmux
|
||||
workstation_dev_wsl_excluded_packages:
|
||||
- alacritty
|
||||
- pcscd
|
||||
- pinentry-gnome3
|
||||
- podman
|
||||
- podman-compose
|
||||
- ufw
|
||||
- yubikey-manager
|
||||
|
||||
# Java 11 and Maven are managed by Mise on this Fedora WSL profile. Keep their
|
||||
# versions pinned; update them deliberately.
|
||||
workstation_mise_java_version: temurin-11.0.31+11
|
||||
workstation_mise_maven_version: 3.9.16
|
||||
|
||||
workstation_is_wsl: true
|
||||
workstation_manage_google_chrome: false
|
||||
workstation_manage_croc: true
|
||||
workstation_croc_version: v11.0.3
|
||||
workstation_removed_snap_packages: []
|
||||
workstation_snap_packages: []
|
||||
workstation_dev_wsl_python_packages: []
|
||||
workstation_wsl_systemd_enabled: true
|
||||
workstation_dev_wsl_dotfiles:
|
||||
- src: .bashrc.d/30-mise.sh
|
||||
dest: .bashrc.d/30-mise.sh
|
||||
mode: "0644"
|
||||
- src: .config/mise/
|
||||
dest: .config/mise/
|
||||
mode: preserve
|
||||
- src: .gnupg/gpg-agent.conf
|
||||
dest: .gnupg/gpg-agent.conf
|
||||
mode: "0600"
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
enabled_services:
|
||||
- docker
|
||||
|
||||
workstation_dev_wsl_packages:
|
||||
- lazygit
|
||||
- pinentry-curses
|
||||
- python3-pip
|
||||
workstation_dev_wsl_excluded_packages:
|
||||
- alacritty
|
||||
- pcscd
|
||||
- pinentry-gnome3
|
||||
- podman
|
||||
- podman-compose
|
||||
- ufw
|
||||
- yubikey-manager
|
||||
workstation_is_wsl: true
|
||||
workstation_manage_google_chrome: false
|
||||
<<<<<<< HEAD
|
||||
workstation_removed_snap_packages: []
|
||||
workstation_snap_packages:
|
||||
- name: glab
|
||||
classic: true
|
||||
workstation_dev_wsl_python_packages: []
|
||||
||||||| parent of cb83ed1 (Update gitconfig on WSL)
|
||||
workstation_removed_snap_packages: []
|
||||
workstation_snap_packages:
|
||||
- name: glab
|
||||
classic: true
|
||||
workstation_dev_wsl_python_packages:
|
||||
- pypsrp
|
||||
- pyspnego
|
||||
=======
|
||||
workstation_manage_glab: true
|
||||
workstation_glab_version: "1.103.0"
|
||||
workstation_removed_snap_packages:
|
||||
- glab
|
||||
workstation_snap_packages: []
|
||||
workstation_dev_wsl_python_packages:
|
||||
- pypsrp
|
||||
- pyspnego
|
||||
>>>>>>> cb83ed1 (Update gitconfig on WSL)
|
||||
workstation_wsl_systemd_enabled: true
|
||||
workstation_dev_wsl_dotfiles:
|
||||
- src: .gnupg/gpg-agent.conf
|
||||
dest: .gnupg/gpg-agent.conf
|
||||
mode: "0600"
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
emacs_enabled: true
|
||||
|
||||
workstation_host_linux_packages:
|
||||
- croc
|
||||
- gnome-shell-extension-manager
|
||||
- gnome-shell-extensions
|
||||
- gnome-tweaks
|
||||
- gufw
|
||||
- libreoffice
|
||||
- meld
|
||||
- network-manager-openconnect-gnome
|
||||
- pcscd
|
||||
- pinentry-gnome3
|
||||
- podman
|
||||
- podman-compose
|
||||
- yubikey-manager
|
||||
|
||||
workstation_host_linux_dotfiles:
|
||||
- src: .gnupg/gpg-agent.conf
|
||||
dest: .gnupg/gpg-agent.conf
|
||||
mode: "0600"
|
||||
|
||||
workstation_firewall_backend: ufw
|
||||
workstation_manage_google_chrome: true
|
||||
|
||||
workstation_removed_snap_packages:
|
||||
- firefox
|
||||
|
||||
workstation_snap_packages:
|
||||
- name: intellij-idea-ultimate
|
||||
classic: true
|
||||
channel: latest/stable
|
||||
- name: postman
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: thunderbird
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: code
|
||||
classic: true
|
||||
channel: latest/stable
|
||||
- name: xournalpp
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: pdfarranger
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: spotify
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: telegram-desktop
|
||||
classic: false
|
||||
channel: latest/stable
|
||||
- name: dbeaver-ce
|
||||
classic: true
|
||||
channel: latest/stable
|
||||
|
||||
workstation_gnome_extensions:
|
||||
- id: 9308
|
||||
uuid: bluetooth-battery-monitor@v8v88v8v88.com
|
||||
version_tag: 68559
|
||||
enabled: true
|
||||
- id: 1401
|
||||
uuid: bluetooth-quick-connect@bjarosze.gmail.com
|
||||
version_tag: 65323
|
||||
enabled: true
|
||||
- id: 6099
|
||||
uuid: paperwm@paperwm.github.com
|
||||
version_tag: 68525
|
||||
enabled: true
|
||||
|
||||
workstation_disabled_gnome_extensions:
|
||||
- tiling-assistant@ubuntu.com
|
||||
- ubuntu-dock@ubuntu.com
|
||||
20
ansible/inventory/host_vars/aegis.yml
Normal file
20
ansible/inventory/host_vars/aegis.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
ansible_host: aegis
|
||||
ansible_connection: ssh
|
||||
ansible_user: pi
|
||||
ansible_become: true
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
# Avoid PTY framing around module JSON on this remote Fedora IoT host.
|
||||
ansible_ssh_use_tty: false
|
||||
|
||||
aegis_lan_subnet: 192.168.178.0/24
|
||||
aegis_adguard_web_port: 80
|
||||
aegis_ssh_authorized_keys:
|
||||
- name: ikaros
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||
- name: nymph
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||
- name: siren
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||
|
||||
aegis_icloudpd_apple_id: "{{ vault_aegis_icloudpd_apple_id | default('') }}"
|
||||
116
ansible/inventory/host_vars/atlas.yml
Normal file
116
ansible/inventory/host_vars/atlas.yml
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
# Atlas declares its intended steady state; set a feature flag false only for a deliberate suspension.
|
||||
hostname: atlas
|
||||
ansible_host: 192.168.178.55
|
||||
ansible_user: "{{ atlas_connection_username }}"
|
||||
ansible_port: 22
|
||||
ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
|
||||
|
||||
atlas_admin_username: admin
|
||||
atlas_connection_username: "{{ atlas_admin_username }}"
|
||||
atlas_admin_group: "{{ atlas_admin_username }}"
|
||||
atlas_admin_home: "/home/{{ atlas_admin_username }}"
|
||||
atlas_admin_uid: 1000
|
||||
atlas_admin_gid: 1000
|
||||
atlas_admin_ssh_keys:
|
||||
- name: ikaros
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||
- name: nymph
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||
- name: siren
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||
atlas_admin_password_hash: "{{ vault_atlas_admin_password_hash | default('!') }}"
|
||||
atlas_ssh_allow_tcp_forwarding: local
|
||||
|
||||
atlas_lan_subnet: 192.168.178.0/24
|
||||
atlas_aegis_ip: 192.168.178.54
|
||||
atlas_manage_firewall: true
|
||||
atlas_firewalld_zone: public
|
||||
atlas_zfs_pool: zpool
|
||||
# Populate only for the first pool bootstrap with four real persistent disk paths.
|
||||
# Confirmed empty 4 TB IronWolf data disks; the NVMe system disk is intentionally excluded.
|
||||
atlas_zpool_disks:
|
||||
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1XM
|
||||
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A73T2
|
||||
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A6VJK
|
||||
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1LM
|
||||
atlas_create_pool: false
|
||||
atlas_zfs_dataset_work: work
|
||||
atlas_zfs_dataset_archive: archive
|
||||
atlas_zfs_dataset_app_data: archive/app_data
|
||||
atlas_zfs_dataset_navidrome: archive/app_data/navidrome
|
||||
atlas_zfs_dataset_syncthing: archive/app_data/syncthing
|
||||
atlas_zfs_dataset_media: media
|
||||
atlas_zfs_dataset_music: media/music
|
||||
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
||||
atlas_zfs_dataset_photobook: media/photobook
|
||||
atlas_zfs_dataset_backups: backups
|
||||
atlas_zfs_dataset_service_backups: backups/services
|
||||
atlas_mount_root: /zpool
|
||||
atlas_manage_storage: true
|
||||
atlas_manage_sharing: true
|
||||
atlas_manage_media_stack: false
|
||||
|
||||
wireguard_overlay_enabled: true
|
||||
wireguard_address: 10.0.0.2/24
|
||||
wireguard_peers:
|
||||
- name: prometheus
|
||||
host: prometheus
|
||||
endpoint: "{{ hostvars['prometheus']['ansible_host'] }}:{{ hostvars['prometheus']['wireguard_listen_port'] }}"
|
||||
allowed_ips:
|
||||
- 10.0.0.0/24
|
||||
persistent_keepalive: 25
|
||||
|
||||
backend_phase1_enabled: true
|
||||
backend_phase1_start_services: false
|
||||
backend_phase1_wireguard_address: 10.0.0.2
|
||||
|
||||
rocky_manage_openzfs_repo: true
|
||||
rocky_manage_syncthing_binary: false
|
||||
rocky_manage_podman: true
|
||||
rocky_podman_packages:
|
||||
- podman
|
||||
|
||||
host_packages:
|
||||
- cockpit
|
||||
- nfs-utils
|
||||
- policycoreutils
|
||||
- policycoreutils-python-utils
|
||||
- python3-libselinux
|
||||
- samba
|
||||
- samba-client
|
||||
- samba-common-tools
|
||||
- zfs
|
||||
|
||||
atlas_nfs_exports:
|
||||
- path: "{{ atlas_photobook_mountpoint }}"
|
||||
client: "{{ atlas_aegis_ip }}"
|
||||
options:
|
||||
- rw
|
||||
- sync
|
||||
- no_subtree_check
|
||||
- all_squash
|
||||
- "anonuid={{ atlas_immich_uid }}"
|
||||
- "anongid={{ atlas_immich_gid }}"
|
||||
|
||||
atlas_samba_share_name: Archive
|
||||
atlas_samba_workgroup: WORKGROUP
|
||||
atlas_samba_encryption: required
|
||||
# Append any pre-existing laptop Unix account here and keep its Samba password in Vault.
|
||||
atlas_samba_accounts:
|
||||
- username: "{{ atlas_admin_username }}"
|
||||
password: "{{ vault_atlas_samba_password | default('') }}"
|
||||
atlas_samba_valid_users: "{{ atlas_samba_accounts | map(attribute='username') | list }}"
|
||||
|
||||
atlas_immich_db_password: "{{ vault_atlas_immich_db_password | default('') }}"
|
||||
|
||||
atlas_firewalld_rich_rules:
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="22" protocol="tcp" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="9090" protocol="tcp" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_aegis_ip }}" port port="2049" protocol="tcp" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="445" protocol="tcp" accept'
|
||||
|
||||
host_enabled_services:
|
||||
- sshd
|
||||
- cockpit.socket
|
||||
- zfs.target
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
hostname: deadalus-wsl
|
||||
workstation_wsl_windows_user_home: /mnt/c/Users/FABIOSCOTTODISANTOLO
|
||||
|
||||
host_packages: []
|
||||
host_enabled_services: []
|
||||
13
ansible/inventory/host_vars/deadalus.yml
Normal file
13
ansible/inventory/host_vars/deadalus.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
hostname: deadalus
|
||||
workstation_wsl_windows_user_home: /mnt/c/Users/FABIOSCOTTODISANTOLO
|
||||
|
||||
host_packages: []
|
||||
host_enabled_services: []
|
||||
|
||||
# Fedora WSL must not receive alternative application runtimes.
|
||||
fedora_excluded_packages:
|
||||
- flatpak
|
||||
fedora_removed_packages:
|
||||
- flatpak
|
||||
- snapd
|
||||
@@ -229,7 +229,7 @@ gnome_gsettings_settings:
|
||||
- schema: org.gnome.shell
|
||||
key: app-picker-layout
|
||||
value: >-
|
||||
[{'org.fedoraproject.MediaWriter.desktop': <{'position': <0>}>, 'org.gnome.Contacts.desktop': <{'position': <1>}>, 'org.gnome.Weather.desktop': <{'position': <2>}>, 'org.gnome.clocks.desktop': <{'position': <3>}>, 'org.gnome.Maps.desktop': <{'position': <4>}>, 'org.gnome.SimpleScan.desktop': <{'position': <5>}>, 'org.gnome.Settings.desktop': <{'position': <6>}>, 'org.gnome.Boxes.desktop': <{'position': <7>}>, 'org.gnome.Showtime.desktop': <{'position': <8>}>, 'org.gnome.Snapshot.desktop': <{'position': <9>}>, 'org.gnome.Characters.desktop': <{'position': <10>}>, 'Utilities': <{'position': <11>}>, 'System': <{'position': <12>}>, 'org.gnome.Tour.desktop': <{'position': <13>}>, 'org.gnome.Yelp.desktop': <{'position': <14>}>, 'htop.desktop': <{'position': <15>}>, 'libreoffice-writer.desktop': <{'position': <16>}>, 'libreoffice-calc.desktop': <{'position': <17>}>, 'libreoffice-impress.desktop': <{'position': <18>}>, 'libreoffice-draw.desktop': <{'position': <19>}>, 'libreoffice-math.desktop': <{'position': <20>}>, 'libreoffice-base.desktop': <{'position': <21>}>, 'org.gnome.Meld.desktop': <{'position': <22>}>, 'mpv.desktop': <{'position': <23>}>}, {'com.github.jeromerobert.pdfarranger.desktop': <{'position': <0>}>, 'org.remmina.Remmina.desktop': <{'position': <1>}>, 'ch.protonmail.protonmail-bridge.desktop': <{'position': <2>}>, 'org.gnome.TextEditor.desktop': <{'position': <3>}>, 'org.gnome.Software.desktop': <{'position': <4>}>, 'org.gnome.Calculator.desktop': <{'position': <5>}>, 'org.gnome.Calendar.desktop': <{'position': <6>}>, 'com.mattjakeman.ExtensionManager.desktop': <{'position': <7>}>, 'be.alexandervanhee.gradia.desktop': <{'position': <8>}>, 'syncthing-start.desktop': <{'position': <9>}>, 'syncthing-ui.desktop': <{'position': <10>}>}]
|
||||
[{'System': <{'position': <0>}>, 'Utilities': <{'position': <1>}>, 'org.fedoraproject.MediaWriter.desktop': <{'position': <2>}>, 'org.gnome.Contacts.desktop': <{'position': <3>}>, 'org.gnome.Calendar.desktop': <{'position': <4>}>, 'org.gnome.Calculator.desktop': <{'position': <5>}>, 'org.gnome.Weather.desktop': <{'position': <6>}>, 'org.gnome.clocks.desktop': <{'position': <7>}>, 'org.gnome.Maps.desktop': <{'position': <8>}>, 'org.gnome.SimpleScan.desktop': <{'position': <9>}>, 'org.gnome.TextEditor.desktop': <{'position': <10>}>, 'org.gnome.Settings.desktop': <{'position': <11>}>, 'org.gnome.Boxes.desktop': <{'position': <12>}>, 'org.gnome.Showtime.desktop': <{'position': <13>}>, 'org.gnome.Snapshot.desktop': <{'position': <14>}>, 'org.gnome.Characters.desktop': <{'position': <15>}>, 'org.gnome.Tour.desktop': <{'position': <16>}>, 'org.gnome.Yelp.desktop': <{'position': <17>}>, 'org.gnome.Software.desktop': <{'position': <18>}>, 'be.alexandervanhee.gradia.desktop': <{'position': <19>}>, 'com.github.jeromerobert.pdfarranger.desktop': <{'position': <20>}>, 'org.onlyoffice.desktopeditors.desktop': <{'position': <21>}>, 'org.gnome.Meld.desktop': <{'position': <22>}>, 'com.mattjakeman.ExtensionManager.desktop': <{'position': <23>}>}, {'org.remmina.Remmina.desktop': <{'position': <0>}>, 'ch.protonmail.protonmail-bridge.desktop': <{'position': <1>}>, 'syncthing-start.desktop': <{'position': <2>}>, 'syncthing-ui.desktop': <{'position': <3>}>, 'emacs.desktop': <{'position': <4>}>, 'emacs-client.desktop': <{'position': <5>}>, 'org.gnome.gitlab.somas.Apostrophe.desktop': <{'position': <6>}>, 'mpv.desktop': <{'position': <7>}>, 'htop.desktop': <{'position': <8>}>, 'org.mozilla.thunderbird.desktop': <{'position': <9>}>}]
|
||||
- schema: org.gnome.shell
|
||||
key: disabled-extensions
|
||||
value: >-
|
||||
@@ -241,7 +241,7 @@ gnome_gsettings_settings:
|
||||
- schema: org.gnome.shell
|
||||
key: favorite-apps
|
||||
value: >-
|
||||
['org.gnome.Nautilus.desktop', 'org.mozilla.firefox.desktop', 'com.spotify.Client.desktop', 'org.mozilla.thunderbird.desktop', 'org.telegram.desktop.desktop', 'org.gnome.Ptyxis.desktop', 'virt-manager.desktop']
|
||||
['org.gnome.Nautilus.desktop', 'org.mozilla.firefox.desktop', 'com.spotify.Client.desktop', 'org.gnome.Evolution.desktop', 'org.telegram.desktop.desktop', 'org.gnome.Ptyxis.desktop', 'virt-manager.desktop']
|
||||
- schema: org.gnome.shell
|
||||
key: remember-mount-password
|
||||
value: "true"
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
---
|
||||
hostname: prometheus
|
||||
ansible_host: 179.237.102.172
|
||||
ansible_user: rocky
|
||||
ansible_port: 22
|
||||
ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
|
||||
|
||||
host_packages: []
|
||||
host_enabled_services: []
|
||||
server_username: rocky
|
||||
server_duckdns_domain: fscotto
|
||||
server_ssh_authorized_keys:
|
||||
- name: ikaros
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||
- name: nymph
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||
- name: siren
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||
|
||||
wireguard_overlay_enabled: true
|
||||
wireguard_address: 10.0.0.1/24
|
||||
wireguard_listen_port: 51820
|
||||
wireguard_enable_ipv4_forwarding: true
|
||||
wireguard_reload_rootful_podman_networks: true
|
||||
wireguard_peers:
|
||||
- name: atlas
|
||||
host: atlas
|
||||
allowed_ips:
|
||||
- 10.0.0.2/32
|
||||
|
||||
host_packages:
|
||||
- cockpit
|
||||
- cockpit-navigator
|
||||
- cockpit-podman
|
||||
|
||||
host_enabled_services:
|
||||
- cockpit.socket
|
||||
|
||||
@@ -10,9 +10,18 @@ all:
|
||||
ansible_connection: local
|
||||
nymph:
|
||||
ansible_connection: local
|
||||
deadalus:
|
||||
ansible_connection: local
|
||||
|
||||
platform_freebsd:
|
||||
hosts: {}
|
||||
platform_fedora_iot:
|
||||
hosts:
|
||||
aegis:
|
||||
|
||||
platform_rocky:
|
||||
hosts:
|
||||
atlas:
|
||||
children:
|
||||
rocky_server:
|
||||
|
||||
void:
|
||||
children:
|
||||
@@ -21,6 +30,11 @@ all:
|
||||
fedora:
|
||||
children:
|
||||
platform_fedora:
|
||||
platform_fedora_iot:
|
||||
|
||||
rocky:
|
||||
children:
|
||||
platform_rocky:
|
||||
|
||||
graphical_desktop:
|
||||
hosts:
|
||||
@@ -38,6 +52,10 @@ all:
|
||||
ikaros:
|
||||
ansible_connection: local
|
||||
|
||||
role_aegis:
|
||||
hosts:
|
||||
aegis:
|
||||
|
||||
desktop_sway:
|
||||
hosts: {}
|
||||
|
||||
@@ -51,20 +69,10 @@ all:
|
||||
nymph:
|
||||
ansible_connection: local
|
||||
|
||||
ubuntu:
|
||||
children:
|
||||
workstation_dev_wsl:
|
||||
ubuntu_server:
|
||||
|
||||
workstation:
|
||||
children:
|
||||
workstation_host:
|
||||
workstation_dev:
|
||||
|
||||
workstation_host:
|
||||
children:
|
||||
workstation_host_linux:
|
||||
|
||||
workstation_dev:
|
||||
children:
|
||||
workstation_dev_fedora:
|
||||
@@ -72,23 +80,27 @@ all:
|
||||
|
||||
server:
|
||||
children:
|
||||
ubuntu_server:
|
||||
|
||||
fedora_workstation:
|
||||
hosts: {}
|
||||
|
||||
workstation_host_linux:
|
||||
hosts: {}
|
||||
rocky_server:
|
||||
|
||||
workstation_dev_fedora:
|
||||
hosts: {}
|
||||
hosts:
|
||||
deadalus:
|
||||
ansible_connection: local
|
||||
|
||||
workstation_dev_wsl:
|
||||
hosts:
|
||||
deadalus-wsl:
|
||||
deadalus:
|
||||
ansible_connection: local
|
||||
|
||||
ubuntu_server:
|
||||
rocky_server:
|
||||
hosts:
|
||||
prometheus:
|
||||
ansible_connection: local
|
||||
|
||||
wireguard_overlay:
|
||||
hosts:
|
||||
prometheus:
|
||||
atlas:
|
||||
|
||||
role_backend_phase1:
|
||||
hosts:
|
||||
atlas:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
dest: "{{ effective_user_home }}/Templates"
|
||||
owner: "{{ effective_username }}"
|
||||
group: "{{ effective_user_group }}"
|
||||
creates: "{{ effective_user_home }}/Templates/code/main.c"
|
||||
when: "'desktop' in group_names"
|
||||
|
||||
- name: Ensure Emacs authoring directories exist
|
||||
@@ -86,13 +87,25 @@
|
||||
group: "{{ effective_user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: >-
|
||||
{{
|
||||
(common_dotfiles | default([]))
|
||||
+ ((ai_agents_dotfiles | default([])) if (ai_agents_enabled | default(false)) else [])
|
||||
}}
|
||||
{{ common_dotfiles | default([]) }}
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Deploy AI coding-agent dotfiles
|
||||
tags: [dotfiles, dotfiles:common, ai_agents]
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../dotfiles/common/{{ item.src }}"
|
||||
dest: "{{ effective_user_home }}/{{ item.dest }}"
|
||||
owner: "{{ effective_username }}"
|
||||
group: "{{ effective_user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ ai_agents_dotfiles | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
when:
|
||||
- (item.agents | intersect(ai_agents_deploy_enabled_names)) | length > 0
|
||||
- (ai_agents_dotfiles | default([])) | length > 0
|
||||
|
||||
- name: Install distro packages for Vim plugins
|
||||
tags: [packages, vim, fzf]
|
||||
ansible.builtin.package:
|
||||
@@ -141,7 +154,7 @@
|
||||
- (vim_plugin_source_plugins | default([])) | length > 0
|
||||
|
||||
- name: Ensure AI config directories exist
|
||||
tags: [dotfiles, dotfiles:common]
|
||||
tags: [dotfiles, dotfiles:common, ai_agents]
|
||||
ansible.builtin.file:
|
||||
path: "{{ effective_user_home }}/{{ item }}"
|
||||
state: directory
|
||||
@@ -151,11 +164,11 @@
|
||||
loop:
|
||||
- .codex
|
||||
when:
|
||||
- ai_agents_enabled | default(false)
|
||||
- "'codex' in ai_agents_deploy_enabled_names"
|
||||
- (ai_agents_templates | default([])) | length > 0
|
||||
|
||||
- name: Render AI agent templates
|
||||
tags: [dotfiles, dotfiles:common]
|
||||
- name: Render AI coding-agent templates
|
||||
tags: [dotfiles, dotfiles:common, ai_agents]
|
||||
ansible.builtin.template:
|
||||
src: "{{ playbook_dir }}/../dotfiles/common/{{ item.src }}"
|
||||
dest: "{{ effective_user_home }}/{{ item.dest }}"
|
||||
@@ -165,7 +178,9 @@
|
||||
loop: "{{ ai_agents_templates | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
when: ai_agents_enabled | default(false)
|
||||
when:
|
||||
- (item.agents | intersect(ai_agents_deploy_enabled_names)) | length > 0
|
||||
- (ai_agents_templates | default([])) | length > 0
|
||||
|
||||
- name: Refresh bat cache
|
||||
tags: [dotfiles, dotfiles:common]
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
---
|
||||
- name: Gather installed package facts
|
||||
tags: [packages]
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Copy Fedora dotfiles
|
||||
tags: [dotfiles, dotfiles:common]
|
||||
ansible.builtin.copy:
|
||||
@@ -30,46 +25,19 @@
|
||||
- "'workstation_dev_fedora' in group_names"
|
||||
- fedora_manage_docker_repo | default(false)
|
||||
|
||||
- name: Configure Google Chrome repository on Fedora
|
||||
tags: [packages]
|
||||
ansible.builtin.yum_repository:
|
||||
name: google-chrome
|
||||
description: Google Chrome
|
||||
baseurl: "https://dl.google.com/linux/chrome/rpm/stable/$basearch"
|
||||
enabled: true
|
||||
gpgcheck: true
|
||||
gpgkey: https://dl.google.com/linux/linux_signing_key.pub
|
||||
- name: Install COPR support on Fedora WSL
|
||||
tags: [packages, mise]
|
||||
ansible.builtin.dnf:
|
||||
name: dnf-plugins-core
|
||||
state: present
|
||||
when:
|
||||
- workstation_manage_google_chrome | default(false)
|
||||
- fedora_manage_google_chrome_repo | default(false)
|
||||
when: "'workstation_dev_wsl' in group_names"
|
||||
|
||||
- name: Configure Visual Studio Code repository on Fedora
|
||||
tags: [packages, vscode]
|
||||
ansible.builtin.yum_repository:
|
||||
name: vscode
|
||||
description: Visual Studio Code
|
||||
baseurl: https://packages.microsoft.com/yumrepos/vscode
|
||||
enabled: true
|
||||
gpgcheck: true
|
||||
gpgkey: https://packages.microsoft.com/keys/microsoft.asc
|
||||
state: present
|
||||
when:
|
||||
- "'workstation_host_linux' in group_names"
|
||||
- fedora_manage_vscode_repo | default(false)
|
||||
|
||||
- name: Configure IntelliJ IDEA COPR repository on Fedora
|
||||
tags: [packages]
|
||||
ansible.builtin.get_url:
|
||||
url: >-
|
||||
https://copr.fedorainfracloud.org/coprs/{{ fedora_intellij_copr_owner }}/{{ fedora_intellij_copr_project }}/repo/fedora-{{ ansible_facts['distribution_major_version'] }}/{{ fedora_intellij_copr_owner }}-{{ fedora_intellij_copr_project }}-fedora-{{ ansible_facts['distribution_major_version'] }}.repo
|
||||
dest: /etc/yum.repos.d/{{ fedora_intellij_copr_owner }}-{{ fedora_intellij_copr_project }}-fedora-{{ ansible_facts['distribution_major_version'] }}.repo
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- "'workstation_host_linux' in group_names"
|
||||
- fedora_manage_intellij_repo | default(false)
|
||||
- name: Configure Mise COPR repository on Fedora WSL
|
||||
tags: [packages, mise]
|
||||
community.general.copr:
|
||||
name: jdxcode/mise
|
||||
state: enabled
|
||||
when: "'workstation_dev_wsl' in group_names"
|
||||
|
||||
- name: Refresh dnf package metadata
|
||||
tags: [packages]
|
||||
@@ -89,11 +57,6 @@
|
||||
if 'workstation_dev_fedora' in group_names
|
||||
else []
|
||||
)
|
||||
+ (
|
||||
['google-chrome-stable']
|
||||
if workstation_manage_google_chrome | default(false)
|
||||
else []
|
||||
)
|
||||
+ (profile_packages | default([]))
|
||||
+ (
|
||||
(fedora_desktop_packages | default([]))
|
||||
@@ -108,29 +71,29 @@
|
||||
else []
|
||||
)
|
||||
+ (
|
||||
(fedora_workstation_dev_packages | default(workstation_dev_packages | default([])))
|
||||
(fedora_dev_packages | default([]))
|
||||
if 'workstation_dev_fedora' in group_names
|
||||
else []
|
||||
)
|
||||
+ (
|
||||
(workstation_host_linux_packages_fedora | default(workstation_host_linux_packages | default([])))
|
||||
if 'workstation_host_linux' in group_names
|
||||
else []
|
||||
)
|
||||
+ (
|
||||
[fedora_intellij_package_name]
|
||||
if (
|
||||
'workstation_host_linux' in group_names
|
||||
and fedora_manage_intellij_repo | default(false)
|
||||
)
|
||||
(workstation_dev_wsl_packages | default([]))
|
||||
if 'workstation_dev_wsl' in group_names
|
||||
else []
|
||||
)
|
||||
+ (host_packages | default([]))
|
||||
)
|
||||
| difference(fedora_excluded_packages | default([]))
|
||||
| unique
|
||||
}}
|
||||
state: present
|
||||
|
||||
- name: Remove excluded Fedora packages
|
||||
tags: [packages]
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ fedora_removed_packages | default([]) }}"
|
||||
state: absent
|
||||
when: (fedora_removed_packages | default([])) | length > 0
|
||||
|
||||
- name: Add user to docker group
|
||||
tags: [packages]
|
||||
ansible.builtin.user:
|
||||
@@ -141,16 +104,31 @@
|
||||
- "'workstation_dev_fedora' in group_names"
|
||||
- (fedora_docker_packages | default([])) | length > 0
|
||||
|
||||
- name: Install Fedora npm packages
|
||||
- name: Install shared AI coding agents on Fedora
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.name }}"
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: "{{ item.state | default('present') }}"
|
||||
loop: "{{ fedora_npm_packages | default([]) }}"
|
||||
when: (fedora_npm_packages | default([])) | length > 0
|
||||
state: latest
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- item.value.install_enabled | bool
|
||||
- "'workstation_dev_fedora' not in group_names"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Uninstall shared AI coding agents on Fedora
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: absent
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- item.value.uninstall_enabled | bool
|
||||
- "'workstation_dev_fedora' not in group_names"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Define Fedora Flatpak applications
|
||||
tags: [packages]
|
||||
@@ -160,11 +138,6 @@
|
||||
(
|
||||
(fedora_flatpak_packages | default([]))
|
||||
+ (host_flatpak_packages | default([]))
|
||||
+ (
|
||||
(workstation_flatpak_packages | default([]))
|
||||
if 'workstation_host_linux' in group_names
|
||||
else []
|
||||
)
|
||||
)
|
||||
| unique
|
||||
}}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
- name: Install packages on FreeBSD
|
||||
tags: [packages]
|
||||
community.general.pkgng:
|
||||
name: >-
|
||||
{{
|
||||
(
|
||||
(freebsd_packages_base | default([]))
|
||||
+ (freebsd_development_packages | default([]))
|
||||
+ (host_packages | default([]))
|
||||
)
|
||||
| unique
|
||||
}}
|
||||
state: present
|
||||
196
ansible/roles/packages_rocky/tasks/main.yml
Normal file
196
ansible/roles/packages_rocky/tasks/main.yml
Normal file
@@ -0,0 +1,196 @@
|
||||
---
|
||||
- name: Require Rocky Linux 9
|
||||
tags: [packages]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] == 'Rocky'
|
||||
- ansible_facts['distribution_major_version'] == '9'
|
||||
fail_msg: This platform role supports Rocky Linux 9 only.
|
||||
|
||||
- name: Install DNF configuration plugin
|
||||
tags: [packages]
|
||||
ansible.builtin.dnf:
|
||||
name: dnf-plugins-core
|
||||
state: present
|
||||
|
||||
- name: Enable Rocky CodeReady Builder repository
|
||||
tags: [packages]
|
||||
community.general.dnf_config_manager:
|
||||
name:
|
||||
- crb
|
||||
state: enabled
|
||||
when: rocky_enable_crb | default(true) | bool
|
||||
|
||||
- name: Install EPEL repository package
|
||||
tags: [packages]
|
||||
ansible.builtin.dnf:
|
||||
name: epel-release
|
||||
state: present
|
||||
|
||||
- name: Import official OpenZFS EL9+ signing key
|
||||
tags: [packages, storage]
|
||||
ansible.builtin.rpm_key:
|
||||
state: present
|
||||
key: "{{ rocky_openzfs_gpg_key_url }}"
|
||||
fingerprint: "{{ rocky_openzfs_gpg_key_fingerprint }}"
|
||||
when: rocky_manage_openzfs_repo | bool
|
||||
|
||||
- name: Install official OpenZFS repository package
|
||||
tags: [packages, storage]
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ rocky_openzfs_release_rpm }}"
|
||||
state: present
|
||||
when: rocky_manage_openzfs_repo | bool
|
||||
|
||||
- name: Disable OpenZFS DKMS repository
|
||||
tags: [packages, storage]
|
||||
community.general.dnf_config_manager:
|
||||
name:
|
||||
- zfs
|
||||
state: disabled
|
||||
when: rocky_manage_openzfs_repo | bool
|
||||
|
||||
- name: Enable OpenZFS kmod repository
|
||||
tags: [packages, storage]
|
||||
community.general.dnf_config_manager:
|
||||
name:
|
||||
- zfs-kmod
|
||||
state: enabled
|
||||
when: rocky_manage_openzfs_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: Refresh Rocky package metadata
|
||||
tags: [packages]
|
||||
ansible.builtin.dnf:
|
||||
update_cache: true
|
||||
|
||||
- name: Install packages on Rocky Linux
|
||||
tags: [packages]
|
||||
ansible.builtin.dnf:
|
||||
name: >-
|
||||
{{
|
||||
(
|
||||
(rocky_common_packages | default([]))
|
||||
+ (rocky_packages_base | default([]))
|
||||
+ (rocky_profile_packages | default([]))
|
||||
+ (rocky_podman_packages | default([]))
|
||||
+ (host_packages | default([]))
|
||||
) | unique
|
||||
}}
|
||||
state: present
|
||||
|
||||
- name: Ensure Podman runtime socket directory exists
|
||||
tags: [packages, podman]
|
||||
ansible.builtin.file:
|
||||
path: /run/podman
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
when: rocky_manage_podman | bool
|
||||
|
||||
- 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]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/local/bin/syncthing
|
||||
- --version
|
||||
register: rocky_syncthing_version_check
|
||||
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]
|
||||
ansible.builtin.set_fact:
|
||||
rocky_syncthing_install_required: >-
|
||||
{{
|
||||
('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]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/syncthing-{{ rocky_syncthing_version }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when:
|
||||
- rocky_manage_syncthing_binary | bool
|
||||
- rocky_syncthing_install_required | bool
|
||||
|
||||
- name: Download pinned Syncthing release
|
||||
tags: [packages, syncthing]
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ rocky_syncthing_archive_url }}"
|
||||
dest: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz"
|
||||
checksum: "{{ rocky_syncthing_archive_checksum }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- rocky_manage_syncthing_binary | bool
|
||||
- rocky_syncthing_install_required | bool
|
||||
|
||||
- name: Extract pinned Syncthing release
|
||||
tags: [packages, syncthing]
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz"
|
||||
dest: "/tmp/syncthing-{{ rocky_syncthing_version }}"
|
||||
remote_src: true
|
||||
when:
|
||||
- rocky_manage_syncthing_binary | bool
|
||||
- rocky_syncthing_install_required | bool
|
||||
|
||||
- name: Install pinned Syncthing binary
|
||||
tags: [packages, syncthing]
|
||||
ansible.builtin.copy:
|
||||
src: >-
|
||||
{{
|
||||
'/tmp/syncthing-' ~ rocky_syncthing_version
|
||||
~ '/syncthing-linux-amd64-v' ~ rocky_syncthing_version
|
||||
~ '/syncthing'
|
||||
}}
|
||||
dest: /usr/local/bin/syncthing
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
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
|
||||
@@ -1,297 +0,0 @@
|
||||
---
|
||||
- name: Gather installed package facts
|
||||
tags: [packages]
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Copy Ubuntu dotfiles
|
||||
tags: [dotfiles, dotfiles:common]
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../dotfiles/ubuntu/{{ item.src }}"
|
||||
dest: "{{ effective_user_home }}/{{ item.dest }}"
|
||||
owner: "{{ effective_username }}"
|
||||
group: "{{ effective_user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ ubuntu_dotfiles | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Ensure architecture is supported for Google Chrome
|
||||
tags: [packages]
|
||||
ansible.builtin.fail:
|
||||
msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for Google Chrome stable package"
|
||||
when:
|
||||
- workstation_manage_google_chrome | default(false)
|
||||
- ansible_facts['architecture'] != 'x86_64'
|
||||
|
||||
- name: Download Google Chrome Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.get_url:
|
||||
url: https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
dest: /tmp/google-chrome-stable_current_amd64.deb
|
||||
mode: "0644"
|
||||
when:
|
||||
- workstation_manage_google_chrome | default(false)
|
||||
- "'google-chrome-stable' not in (ansible_facts.packages | default({}))"
|
||||
|
||||
- name: Install Google Chrome from downloaded Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.apt:
|
||||
deb: /tmp/google-chrome-stable_current_amd64.deb
|
||||
state: present
|
||||
when:
|
||||
- workstation_manage_google_chrome | default(false)
|
||||
- "'google-chrome-stable' not in (ansible_facts.packages | default({}))"
|
||||
|
||||
- name: Remove downloaded Google Chrome Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: /tmp/google-chrome-stable_current_amd64.deb
|
||||
state: absent
|
||||
when:
|
||||
- workstation_manage_google_chrome | default(false)
|
||||
- "'google-chrome-stable' not in (ansible_facts.packages | default({}))"
|
||||
|
||||
- name: Download glab Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.get_url:
|
||||
url: >-
|
||||
https://gitlab.com/gitlab-org/cli/-/releases/v{{ workstation_glab_version }}/downloads/glab_{{ workstation_glab_version }}_linux_{{
|
||||
'amd64' if ansible_facts['architecture'] == 'x86_64'
|
||||
else 'arm64' if ansible_facts['architecture'] in ['aarch64', 'arm64']
|
||||
else ansible_facts['architecture']
|
||||
}}.deb
|
||||
dest: "/tmp/glab_{{ workstation_glab_version }}.deb"
|
||||
mode: "0644"
|
||||
when:
|
||||
- workstation_manage_glab | default(false)
|
||||
- ansible_facts.packages['glab'] is not defined or ansible_facts.packages['glab'][0].version != workstation_glab_version
|
||||
|
||||
- name: Install glab from downloaded Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.apt:
|
||||
deb: "/tmp/glab_{{ workstation_glab_version }}.deb"
|
||||
state: present
|
||||
when:
|
||||
- workstation_manage_glab | default(false)
|
||||
- ansible_facts.packages['glab'] is not defined or ansible_facts.packages['glab'][0].version != workstation_glab_version
|
||||
|
||||
- name: Remove downloaded glab Debian package
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/glab_{{ workstation_glab_version }}.deb"
|
||||
state: absent
|
||||
when:
|
||||
- workstation_manage_glab | default(false)
|
||||
- ansible_facts.packages['glab'] is not defined or ansible_facts.packages['glab'][0].version != workstation_glab_version
|
||||
|
||||
- name: Ensure Docker apt keyrings directory exists
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: ubuntu_manage_docker_repo | default(false)
|
||||
|
||||
- name: Download Docker apt repository signing key
|
||||
tags: [packages]
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ubuntu_manage_docker_repo | default(false)
|
||||
|
||||
- name: Configure Docker apt repository
|
||||
tags: [packages]
|
||||
ansible.builtin.apt_repository:
|
||||
repo: >-
|
||||
deb [arch={{ 'amd64' if ansible_facts['architecture'] == 'x86_64'
|
||||
else 'arm64' if ansible_facts['architecture'] in ['aarch64', 'arm64']
|
||||
else ansible_facts['architecture'] }}
|
||||
signed-by=/etc/apt/keyrings/docker.asc]
|
||||
https://download.docker.com/linux/ubuntu
|
||||
{{ ansible_facts['distribution_release'] }} stable
|
||||
filename: docker
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ubuntu_manage_docker_repo | default(false)
|
||||
|
||||
- name: Refresh apt package cache
|
||||
tags: [packages]
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install packages on Ubuntu
|
||||
tags: [packages]
|
||||
ansible.builtin.apt:
|
||||
name: >-
|
||||
{{
|
||||
(
|
||||
(common_packages | default([]))
|
||||
+ (ubuntu_packages_base | default([]))
|
||||
+ (ubuntu_docker_packages | default([]))
|
||||
+ (profile_packages | default([]))
|
||||
+ (
|
||||
(ubuntu_emacs_packages | default([]))
|
||||
if (emacs_enabled | default(false) | bool)
|
||||
else []
|
||||
)
|
||||
+ (workstation_dev_packages | default([]))
|
||||
+ (
|
||||
(workstation_host_linux_packages | default([]))
|
||||
if 'workstation_host_linux' in group_names
|
||||
else []
|
||||
)
|
||||
+ (
|
||||
(workstation_dev_wsl_packages | default([]))
|
||||
if 'workstation_dev_wsl' in group_names
|
||||
else []
|
||||
)
|
||||
+ (desktop_common_packages | default([]))
|
||||
+ (host_packages | default([]))
|
||||
)
|
||||
| difference(
|
||||
(workstation_dev_wsl_excluded_packages | default([]))
|
||||
if 'workstation_dev_wsl' in group_names
|
||||
else []
|
||||
)
|
||||
| unique
|
||||
}}
|
||||
state: present
|
||||
|
||||
- name: Add user to docker group
|
||||
tags: [packages]
|
||||
ansible.builtin.user:
|
||||
name: "{{ effective_username }}"
|
||||
groups: docker
|
||||
append: true
|
||||
when: (ubuntu_docker_packages | default([])) | length > 0
|
||||
|
||||
- name: Ensure architecture is supported for croc GitHub release
|
||||
tags: [packages]
|
||||
ansible.builtin.fail:
|
||||
msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for croc GitHub release"
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- ansible_facts['architecture'] not in ['x86_64', 'aarch64', 'arm64']
|
||||
|
||||
- name: Read installed croc version
|
||||
tags: [packages]
|
||||
ansible.builtin.command: /usr/local/bin/croc --version
|
||||
register: croc_version_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
when: workstation_manage_croc | default(false)
|
||||
|
||||
- name: Ensure temporary croc extraction directory is absent
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/croc_{{ workstation_croc_version }}"
|
||||
state: absent
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Create temporary croc extraction directory
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/croc_{{ workstation_croc_version }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Download croc GitHub release archive
|
||||
tags: [packages]
|
||||
ansible.builtin.get_url:
|
||||
url: >-
|
||||
{{
|
||||
'https://github.com/schollz/croc/releases/download/'
|
||||
~ workstation_croc_version
|
||||
~ '/croc_'
|
||||
~ workstation_croc_version
|
||||
~ '_Linux-'
|
||||
~ (
|
||||
'64bit' if ansible_facts['architecture'] == 'x86_64'
|
||||
else 'ARM64' if ansible_facts['architecture'] in ['aarch64', 'arm64']
|
||||
else ansible_facts['architecture']
|
||||
)
|
||||
~ '.tar.gz'
|
||||
}}
|
||||
dest: "/tmp/croc_{{ workstation_croc_version }}.tar.gz"
|
||||
mode: "0644"
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Extract croc GitHub release archive
|
||||
tags: [packages]
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/croc_{{ workstation_croc_version }}.tar.gz"
|
||||
dest: "/tmp/croc_{{ workstation_croc_version }}"
|
||||
remote_src: true
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Install croc binary
|
||||
tags: [packages]
|
||||
ansible.builtin.copy:
|
||||
src: "/tmp/croc_{{ workstation_croc_version }}/croc"
|
||||
dest: /usr/local/bin/croc
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
remote_src: true
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Remove downloaded croc archive
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/croc_{{ workstation_croc_version }}.tar.gz"
|
||||
state: absent
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Remove temporary croc extraction directory
|
||||
tags: [packages]
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/croc_{{ workstation_croc_version }}"
|
||||
state: absent
|
||||
when:
|
||||
- workstation_manage_croc | default(false)
|
||||
- workstation_croc_version not in (croc_version_check.stdout | default(''))
|
||||
|
||||
- name: Remove workstation snap packages
|
||||
tags: [packages]
|
||||
community.general.snap:
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
loop: "{{ workstation_removed_snap_packages | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: (workstation_removed_snap_packages | default([])) | length > 0
|
||||
|
||||
- name: Install workstation snap packages
|
||||
tags: [packages]
|
||||
community.general.snap:
|
||||
name: "{{ item.name }}"
|
||||
classic: "{{ item.classic | default(false) }}"
|
||||
channel: "{{ item.channel | default(omit) }}"
|
||||
state: present
|
||||
loop: "{{ workstation_snap_packages | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: (workstation_snap_packages | default([])) | length > 0
|
||||
17
ansible/roles/profile_aegis/defaults/main.yml
Normal file
17
ansible/roles/profile_aegis/defaults/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
aegis_hostname: aegis
|
||||
aegis_adguard_image: docker.io/adguard/adguardhome:latest
|
||||
aegis_icloudpd_image: docker.io/boredazfcuk/icloudpd:latest
|
||||
aegis_icloudpd_folder_structure: '{:%Y/%m/%d}'
|
||||
aegis_icloudpd_synchronisation_interval: 86400
|
||||
aegis_icloudpd_apple_id: ""
|
||||
aegis_ikaros_mac_address: aa:bb:cc:dd:ee:ff
|
||||
aegis_wol_port: 9
|
||||
|
||||
aegis_lan_subnet: CHANGEME_LAN_SUBNET
|
||||
aegis_firewalld_zone: public
|
||||
aegis_adguard_web_port: 80
|
||||
aegis_ssh_allowed_users:
|
||||
- "{{ ansible_user }}"
|
||||
aegis_ssh_user_home: "/var/home/{{ ansible_user }}"
|
||||
aegis_ssh_authorized_keys: []
|
||||
21
ansible/roles/profile_aegis/handlers/main.yml
Normal file
21
ansible/roles/profile_aegis/handlers/main.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Restart Aegis systemd-resolved
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-resolved.service
|
||||
state: restarted
|
||||
|
||||
- name: Reload Aegis SSH
|
||||
ansible.builtin.systemd:
|
||||
name: sshd.service
|
||||
state: reloaded
|
||||
|
||||
- name: Restart Aegis Quadlet services
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
loop:
|
||||
- adguardhome.service
|
||||
- icloudpd.service
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
291
ansible/roles/profile_aegis/tasks/main.yml
Normal file
291
ansible/roles/profile_aegis/tasks/main.yml
Normal file
@@ -0,0 +1,291 @@
|
||||
---
|
||||
- name: Require Aegis iCloudPD Apple ID
|
||||
tags: [aegis, icloudpd]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aegis_icloudpd_apple_id | length > 0
|
||||
fail_msg: Define vault_aegis_icloudpd_apple_id before applying the Aegis profile.
|
||||
no_log: true
|
||||
|
||||
- name: Require completed Aegis network placeholders
|
||||
tags: [aegis, firewall, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aegis_lan_subnet != 'CHANGEME_LAN_SUBNET'
|
||||
- aegis_firewalld_zone | length > 0
|
||||
- aegis_adguard_web_port | int > 0
|
||||
- aegis_adguard_web_port | int < 65536
|
||||
- aegis_ssh_allowed_users | length > 0
|
||||
fail_msg: Define the Aegis LAN subnet, firewalld zone, AdGuard web port, and SSH users.
|
||||
|
||||
- name: Set Aegis hostname
|
||||
tags: [aegis, services]
|
||||
ansible.builtin.hostname:
|
||||
name: "{{ aegis_hostname }}"
|
||||
|
||||
- name: Create Aegis persistent container directories
|
||||
tags: [aegis, containers]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- /var/lib/adguard/work
|
||||
- /var/lib/adguard/conf
|
||||
- /var/lib/icloudpd/data
|
||||
- /var/lib/icloudpd/config
|
||||
|
||||
- name: Create Quadlet configuration directory
|
||||
tags: [aegis, containers]
|
||||
ansible.builtin.file:
|
||||
path: /etc/containers/systemd
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Render Aegis Quadlet units
|
||||
tags: [aegis, containers]
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "/etc/containers/systemd/{{ item.dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- src: adguardhome.container.j2
|
||||
dest: adguardhome.container
|
||||
- src: icloudpd.container.j2
|
||||
dest: icloudpd.container
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
no_log: "{{ item.dest == 'icloudpd.container' }}"
|
||||
notify: Restart Aegis Quadlet services
|
||||
|
||||
- name: Create Aegis systemd-resolved configuration directory
|
||||
tags: [aegis, adguard, dns, services]
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Disable Aegis systemd-resolved DNS stub listener
|
||||
tags: [aegis, adguard, dns, services]
|
||||
ansible.builtin.template:
|
||||
src: 10-adguard-dns.conf.j2
|
||||
dest: /etc/systemd/resolved.conf.d/10-adguard-dns.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify:
|
||||
- Restart Aegis systemd-resolved
|
||||
- Restart Aegis Quadlet services
|
||||
|
||||
- name: Point Aegis resolver at the full systemd-resolved configuration
|
||||
tags: [aegis, adguard, dns, services]
|
||||
ansible.builtin.file:
|
||||
src: ../run/systemd/resolve/resolv.conf
|
||||
dest: /etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
notify: Restart Aegis systemd-resolved
|
||||
|
||||
- name: Enable Aegis firewalld
|
||||
tags: [aegis, firewall, services]
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld.service
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Render Aegis AdGuard web firewalld service
|
||||
tags: [aegis, firewall]
|
||||
ansible.builtin.template:
|
||||
src: aegis-adguard-web.xml.j2
|
||||
dest: /etc/firewalld/services/aegis-adguard-web.xml
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Reload firewalld custom services
|
||||
tags: [aegis, firewall]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- firewall-cmd
|
||||
- --reload
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Remove unrestricted Aegis services from firewalld zone
|
||||
tags: [aegis, firewall]
|
||||
ansible.posix.firewalld:
|
||||
service: "{{ item }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: disabled
|
||||
zone: "{{ aegis_firewalld_zone }}"
|
||||
loop:
|
||||
- ssh
|
||||
- dns
|
||||
- http
|
||||
- aegis-adguard-web
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: Remove retired Aegis TLS access rule
|
||||
tags: [aegis, firewall]
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: 'rule family="ipv4" source address="{{ aegis_lan_subnet }}" port port="853" protocol="tcp" accept'
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: disabled
|
||||
zone: "{{ aegis_firewalld_zone }}"
|
||||
|
||||
- name: Allow Aegis services from the LAN only
|
||||
tags: [aegis, firewall]
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: "{{ item }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
zone: "{{ aegis_firewalld_zone }}"
|
||||
loop:
|
||||
- 'rule family="ipv4" source address="{{ aegis_lan_subnet }}" service name="ssh" accept'
|
||||
- 'rule family="ipv4" source address="{{ aegis_lan_subnet }}" service name="dns" accept'
|
||||
- 'rule family="ipv4" source address="{{ aegis_lan_subnet }}" service name="aegis-adguard-web" accept'
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: Check the standard Aegis SSH authorized keys file
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys"
|
||||
register: aegis_authorized_keys
|
||||
|
||||
- name: Ensure Aegis SSH authorized key fragments directory exists
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.file:
|
||||
path: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys.d"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: "0700"
|
||||
when: aegis_ssh_authorized_keys | length > 0
|
||||
|
||||
- name: Manage Aegis SSH authorized key fragments
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.key }}\n"
|
||||
dest: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys.d/{{ item.name }}"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: "0600"
|
||||
loop: "{{ aegis_ssh_authorized_keys }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: aegis_ssh_authorized_keys | length > 0
|
||||
|
||||
- name: Find Aegis SSH authorized key fragments
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys.d"
|
||||
file_type: file
|
||||
recurse: false
|
||||
register: aegis_authorized_key_fragments
|
||||
|
||||
- name: Require an Aegis SSH authorized key before hardening
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
(aegis_ssh_authorized_keys | length > 0)
|
||||
or (aegis_authorized_keys.stat.exists and aegis_authorized_keys.stat.size | int > 0)
|
||||
or aegis_authorized_key_fragments.matched | int > 0
|
||||
fail_msg: Add a public key for the Ansible SSH user before disabling password authentication.
|
||||
|
||||
- name: Ensure Aegis SSH configuration drop-in directory exists
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Ensure Aegis SSH drop-ins are loaded before other settings
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf\s*$'
|
||||
line: Include /etc/ssh/sshd_config.d/*.conf
|
||||
insertbefore: BOF
|
||||
state: present
|
||||
validate: "sshd -t -f %s"
|
||||
|
||||
- name: Render Aegis SSH hardening drop-in
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.template:
|
||||
src: 00-aegis-hardening.conf.j2
|
||||
dest: /etc/ssh/sshd_config.d/00-aegis-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: Reload Aegis SSH
|
||||
|
||||
- name: Validate Aegis SSH configuration
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- sshd
|
||||
- -t
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Read effective Aegis SSH daemon configuration
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- sshd
|
||||
- -T
|
||||
- -C
|
||||
- "user={{ ansible_user }},host={{ aegis_hostname }},addr=127.0.0.1"
|
||||
register: aegis_sshd_effective_configuration
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify effective Aegis SSH hardening
|
||||
tags: [aegis, ssh, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'permitrootlogin no' in aegis_sshd_effective_configuration.stdout_lines"
|
||||
- "'pubkeyauthentication yes' in aegis_sshd_effective_configuration.stdout_lines"
|
||||
- "'passwordauthentication no' in aegis_sshd_effective_configuration.stdout_lines"
|
||||
- "'kbdinteractiveauthentication no' in aegis_sshd_effective_configuration.stdout_lines"
|
||||
- "'allowusers ' + (aegis_ssh_allowed_users | join(' ')) in aegis_sshd_effective_configuration.stdout_lines"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Install Wake-on-LAN helper for Ikaros
|
||||
tags: [aegis, wol]
|
||||
ansible.builtin.template:
|
||||
src: wake-ikaros.j2
|
||||
dest: /usr/local/bin/wake-ikaros
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Enable Aegis Quadlet services and automatic updates
|
||||
tags: [aegis, containers, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
loop:
|
||||
- adguardhome.service
|
||||
- icloudpd.service
|
||||
- podman-auto-update.timer
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
@@ -0,0 +1,6 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
PermitRootLogin no
|
||||
PubkeyAuthentication yes
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
AllowUsers {{ aegis_ssh_allowed_users | join(' ') }}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Resolve]
|
||||
DNSStubListener=no
|
||||
@@ -0,0 +1,18 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=AdGuard Home DNS sinkhole
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Container]
|
||||
Image={{ aegis_adguard_image }}
|
||||
Network=host
|
||||
Volume=/var/lib/adguard/work:/opt/adguardhome/work:Z
|
||||
Volume=/var/lib/adguard/conf:/opt/adguardhome/conf:Z
|
||||
AutoUpdate=registry
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<service>
|
||||
<short>Aegis AdGuard Home web interface</short>
|
||||
<description>AdGuard Home administrative web interface on Aegis.</description>
|
||||
<port protocol="tcp" port="{{ aegis_adguard_web_port }}"/>
|
||||
</service>
|
||||
20
ansible/roles/profile_aegis/templates/icloudpd.container.j2
Normal file
20
ansible/roles/profile_aegis/templates/icloudpd.container.j2
Normal file
@@ -0,0 +1,20 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=iCloud Photos Downloader
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Container]
|
||||
Image={{ aegis_icloudpd_image }}
|
||||
Environment=apple_id={{ aegis_icloudpd_apple_id }}
|
||||
Environment=folder_structure={{ aegis_icloudpd_folder_structure }}
|
||||
Environment=synchronisation_interval={{ aegis_icloudpd_synchronisation_interval }}
|
||||
Volume=/var/lib/icloudpd/data:/home/root/iCloud:Z
|
||||
Volume=/var/lib/icloudpd/config:/config:Z
|
||||
AutoUpdate=registry
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
21
ansible/roles/profile_aegis/templates/wake-ikaros.j2
Normal file
21
ansible/roles/profile_aegis/templates/wake-ikaros.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
set -euo pipefail
|
||||
|
||||
mac='{{ aegis_ikaros_mac_address }}'
|
||||
port='{{ aegis_wol_port }}'
|
||||
mac_hex="${mac//:/}"
|
||||
|
||||
if [[ ! $mac_hex =~ ^[[:xdigit:]]{12}$ ]]; then
|
||||
printf 'Indirizzo MAC non valido: %s\n' "$mac" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
packet='\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||
for _ in {1..16}; do
|
||||
packet+="$(printf '\\x%s\\x%s\\x%s\\x%s\\x%s\\x%s' \
|
||||
"${mac_hex:0:2}" "${mac_hex:2:2}" "${mac_hex:4:2}" \
|
||||
"${mac_hex:6:2}" "${mac_hex:8:2}" "${mac_hex:10:2}")"
|
||||
done
|
||||
|
||||
printf '%b' "$packet" > "/dev/udp/255.255.255.255/${port}"
|
||||
142
ansible/roles/profile_atlas/defaults/main.yml
Normal file
142
ansible/roles/profile_atlas/defaults/main.yml
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
atlas_manage_storage: false
|
||||
atlas_manage_sharing: false
|
||||
# Destructive first-boot action; normally false once the pool exists.
|
||||
atlas_create_pool: false
|
||||
atlas_zpool_disks: []
|
||||
atlas_admin_username: CHANGEME_ATLAS_ADMIN
|
||||
atlas_admin_group: "{{ atlas_admin_username }}"
|
||||
atlas_admin_home: "/home/{{ atlas_admin_username }}"
|
||||
atlas_admin_uid: 1000
|
||||
atlas_admin_gid: 1000
|
||||
atlas_admin_ssh_keys: []
|
||||
atlas_admin_ssh_key_directory: "{{ atlas_admin_home }}/.ssh/authorized_keys.d"
|
||||
atlas_admin_password_hash: "!"
|
||||
# Local forwarding permits an administrator to tunnel a private service without allowing remote forwards.
|
||||
atlas_ssh_allow_tcp_forwarding: local
|
||||
|
||||
atlas_immich_username: immich
|
||||
atlas_immich_group: immich
|
||||
atlas_immich_uid: 1100
|
||||
atlas_immich_gid: 1100
|
||||
atlas_immich_supplementary_groups:
|
||||
- video
|
||||
- render
|
||||
|
||||
atlas_lan_subnet: CHANGEME_LAN_SUBNET
|
||||
atlas_aegis_ip: CHANGEME_AEGIS_IP
|
||||
atlas_manage_firewall: false
|
||||
atlas_firewalld_zone: public
|
||||
atlas_hardening_sysctls:
|
||||
net.ipv4.conf.all.accept_redirects: "0"
|
||||
net.ipv4.conf.default.accept_redirects: "0"
|
||||
net.ipv4.conf.all.send_redirects: "0"
|
||||
net.ipv4.conf.default.send_redirects: "0"
|
||||
net.ipv4.conf.all.accept_source_route: "0"
|
||||
net.ipv4.conf.default.accept_source_route: "0"
|
||||
net.ipv4.conf.all.log_martians: "1"
|
||||
net.ipv4.conf.default.log_martians: "1"
|
||||
net.ipv4.conf.all.rp_filter: "2"
|
||||
net.ipv4.conf.default.rp_filter: "2"
|
||||
net.ipv4.tcp_syncookies: "1"
|
||||
net.ipv4.icmp_echo_ignore_broadcasts: "1"
|
||||
net.ipv4.icmp_ignore_bogus_error_responses: "1"
|
||||
net.ipv4.ip_forward: "0"
|
||||
net.ipv6.conf.all.accept_redirects: "0"
|
||||
net.ipv6.conf.default.accept_redirects: "0"
|
||||
net.ipv6.conf.all.accept_source_route: "0"
|
||||
net.ipv6.conf.default.accept_source_route: "0"
|
||||
atlas_zfs_pool: CHANGEME_ZFS_POOL
|
||||
atlas_zfs_dataset_work: work
|
||||
atlas_zfs_dataset_archive: archive
|
||||
atlas_zfs_dataset_app_data: archive/app_data
|
||||
atlas_zfs_dataset_navidrome: archive/app_data/navidrome
|
||||
atlas_zfs_dataset_syncthing: archive/app_data/syncthing
|
||||
atlas_zfs_dataset_media: media
|
||||
atlas_zfs_dataset_music: media/music
|
||||
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
||||
atlas_zfs_dataset_photobook: media/photobook
|
||||
atlas_zfs_dataset_backups: backups
|
||||
atlas_zfs_dataset_service_backups: backups/services
|
||||
atlas_zfs_service_backups_refreservation: 500G
|
||||
atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT
|
||||
|
||||
atlas_archive_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_archive }}"
|
||||
atlas_app_data_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
atlas_navidrome_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||
atlas_syncthing_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||
atlas_media_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_media }}"
|
||||
atlas_music_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}"
|
||||
atlas_work_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_work }}"
|
||||
atlas_backup_prometheus_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backup_prometheus }}"
|
||||
atlas_photobook_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_photobook }}"
|
||||
atlas_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backups }}"
|
||||
atlas_service_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||
|
||||
atlas_45drives_repo_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo
|
||||
atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo
|
||||
atlas_45drives_packages:
|
||||
- cockpit-file-sharing
|
||||
- cockpit-navigator
|
||||
- cockpit-identities
|
||||
- cockpit-zfs
|
||||
- cockpit-scheduler
|
||||
|
||||
atlas_nfs_exports:
|
||||
- path: "{{ atlas_photobook_mountpoint }}"
|
||||
client: "{{ atlas_aegis_ip }}"
|
||||
options:
|
||||
- rw
|
||||
- sync
|
||||
- no_subtree_check
|
||||
- all_squash
|
||||
- "anonuid={{ atlas_immich_uid }}"
|
||||
- "anongid={{ atlas_immich_gid }}"
|
||||
atlas_samba_share_name: Archive
|
||||
atlas_samba_workgroup: WORKGROUP
|
||||
# Keep encryption mandatory unless a verified client-compatibility exception is explicitly required.
|
||||
atlas_samba_encryption: required
|
||||
atlas_samba_valid_users: []
|
||||
atlas_samba_accounts: []
|
||||
atlas_samba_password_marker_dir: /var/lib/samba/private/ansible-passwords
|
||||
atlas_firewalld_rich_rules: []
|
||||
atlas_firewalld_restricted_services:
|
||||
- ssh
|
||||
- cockpit
|
||||
- nfs
|
||||
- samba
|
||||
- http
|
||||
- https
|
||||
atlas_selinux_booleans:
|
||||
- samba_export_all_rw
|
||||
- nfs_export_all_rw
|
||||
|
||||
atlas_manage_media_stack: false
|
||||
atlas_container_quadlet_dir: /etc/containers/systemd
|
||||
atlas_container_data_root: /var/lib/atlas-containers
|
||||
atlas_npm_data_dir: "{{ atlas_container_data_root }}/npm/data"
|
||||
atlas_npm_letsencrypt_dir: "{{ atlas_container_data_root }}/npm/letsencrypt"
|
||||
atlas_npm_admin_bind_address: 127.0.0.1
|
||||
atlas_timezone: Europe/Rome
|
||||
atlas_immich_upload_dir: "{{ atlas_container_data_root }}/immich/upload"
|
||||
atlas_immich_model_cache_dir: "{{ atlas_container_data_root }}/immich/model-cache"
|
||||
atlas_immich_machine_learning_config_dir: "{{ atlas_container_data_root }}/immich/machine-learning-config"
|
||||
atlas_immich_machine_learning_cache_dir: "{{ atlas_container_data_root }}/immich/machine-learning-cache"
|
||||
atlas_immich_redis_data_dir: "{{ atlas_container_data_root }}/immich/redis"
|
||||
atlas_immich_postgres_data_dir: "{{ atlas_container_data_root }}/immich/postgres"
|
||||
|
||||
atlas_npm_image: docker.io/jc21/nginx-proxy-manager:latest
|
||||
atlas_immich_version: release
|
||||
atlas_immich_server_image: "ghcr.io/immich-app/immich-server:{{ atlas_immich_version }}"
|
||||
atlas_immich_machine_learning_image: "ghcr.io/immich-app/immich-machine-learning:{{ atlas_immich_version }}-openvino"
|
||||
atlas_immich_redis_image: docker.io/valkey/valkey:9
|
||||
atlas_immich_postgres_image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
atlas_immich_db_username: postgres
|
||||
atlas_immich_db_name: immich
|
||||
atlas_immich_db_password: "{{ vault_atlas_immich_db_password | default('') }}"
|
||||
atlas_media_quadlet_services:
|
||||
- atlas-immich-redis.service
|
||||
- atlas-immich-postgres.service
|
||||
- atlas-immich-machine-learning.service
|
||||
- atlas-immich-server.service
|
||||
- atlas-npm.service
|
||||
41
ansible/roles/profile_atlas/handlers/main.yml
Normal file
41
ansible/roles/profile_atlas/handlers/main.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
- name: Reload SSH service
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: reloaded
|
||||
|
||||
- name: Reload NFS exports
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- exportfs
|
||||
- -ra
|
||||
changed_when: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Restart NFS server
|
||||
ansible.builtin.systemd:
|
||||
name: nfs-server
|
||||
state: restarted
|
||||
|
||||
- name: Restart NFS mount daemon
|
||||
ansible.builtin.systemd:
|
||||
name: nfs-mountd
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart Samba service
|
||||
ansible.builtin.systemd:
|
||||
name: smb
|
||||
state: restarted
|
||||
|
||||
- name: Restart Atlas media Quadlets
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
loop: "{{ atlas_media_quadlet_services }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- not ansible_check_mode
|
||||
180
ansible/roles/profile_atlas/tasks/account.yml
Normal file
180
ansible/roles/profile_atlas/tasks/account.yml
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
- name: Reject incomplete Atlas account configuration
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_admin_username != 'CHANGEME_ATLAS_ADMIN'
|
||||
- (atlas_admin_ssh_keys | default([])) | length > 0
|
||||
- atlas_admin_password_hash not in ['', '!', '*']
|
||||
- atlas_ssh_allow_tcp_forwarding in ['no', 'yes', 'local', 'remote', 'all']
|
||||
- "'wheel' not in atlas_immich_supplementary_groups"
|
||||
fail_msg: >-
|
||||
Define atlas_admin_username, atlas_admin_ssh_keys and vault_atlas_admin_password_hash
|
||||
before applying the Atlas profile.
|
||||
no_log: true
|
||||
|
||||
- name: Create Atlas administrator group
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.group:
|
||||
name: "{{ atlas_admin_group }}"
|
||||
gid: "{{ atlas_admin_gid }}"
|
||||
state: present
|
||||
|
||||
- name: Create Atlas administrator account
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.user:
|
||||
name: "{{ atlas_admin_username }}"
|
||||
uid: "{{ atlas_admin_uid }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
home: "{{ atlas_admin_home }}"
|
||||
shell: /bin/bash
|
||||
password: "{{ atlas_admin_password_hash }}"
|
||||
create_home: true
|
||||
state: present
|
||||
no_log: true
|
||||
|
||||
- name: Create Immich primary group
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.group:
|
||||
name: "{{ atlas_immich_group }}"
|
||||
gid: "{{ atlas_immich_gid }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure Immich hardware-access groups exist
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.group:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ atlas_immich_supplementary_groups }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: Create unprivileged Immich account
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.user:
|
||||
name: "{{ atlas_immich_username }}"
|
||||
uid: "{{ atlas_immich_uid }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
groups: "{{ atlas_immich_supplementary_groups }}"
|
||||
append: false
|
||||
home: /nonexistent
|
||||
create_home: false
|
||||
shell: /sbin/nologin
|
||||
system: true
|
||||
state: present
|
||||
|
||||
- name: Read Immich hardware-access group IDs
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.getent:
|
||||
database: group
|
||||
|
||||
- name: Grant Atlas administrator passwordless sudo
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ atlas_admin_username }} ALL=(ALL) NOPASSWD: ALL\n"
|
||||
dest: "/etc/sudoers.d/{{ atlas_admin_username }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
|
||||
- name: Ensure Atlas administrator SSH authorized key fragments directory exists
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.file:
|
||||
path: "{{ atlas_admin_ssh_key_directory }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Manage Atlas administrator SSH authorized key fragments
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.key }}\n"
|
||||
dest: "{{ atlas_admin_ssh_key_directory }}/{{ item.name }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0600"
|
||||
loop: "{{ atlas_admin_ssh_keys }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Check whether the Atlas SSH host key exists
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.stat:
|
||||
path: /etc/ssh/ssh_host_ed25519_key
|
||||
register: atlas_ssh_host_ed25519_key
|
||||
|
||||
- name: Generate missing Atlas SSH host keys
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- ssh-keygen
|
||||
- -A
|
||||
changed_when: true
|
||||
when: not atlas_ssh_host_ed25519_key.stat.exists
|
||||
|
||||
- name: Ensure Atlas SSH configuration drop-in directory exists
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Ensure Atlas SSH drop-ins are loaded before other settings
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf\s*$'
|
||||
line: Include /etc/ssh/sshd_config.d/*.conf
|
||||
insertbefore: BOF
|
||||
state: present
|
||||
validate: "sshd -t -f %s"
|
||||
|
||||
- name: Render Atlas SSH hardening drop-in
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.template:
|
||||
src: 00-atlas-hardening.conf.j2
|
||||
dest: /etc/ssh/sshd_config.d/00-atlas-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: Reload SSH service
|
||||
|
||||
- name: Read effective Atlas SSH daemon configuration
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- sshd
|
||||
- -T
|
||||
- -C
|
||||
- "user={{ atlas_admin_username }},host=atlas,addr=127.0.0.1"
|
||||
register: atlas_sshd_effective_configuration
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify effective Atlas SSH hardening
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'permitrootlogin no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'pubkeyauthentication yes' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'passwordauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'kbdinteractiveauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'authenticationmethods publickey' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'x11forwarding no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "('allowtcpforwarding ' ~ atlas_ssh_allow_tcp_forwarding) in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'allowagentforwarding no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'gatewayports no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'permittunnel no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'permituserenvironment no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'maxauthtries 3' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'logingracetime 30' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'clientaliveinterval 300' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'clientalivecountmax 2' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "'loglevel VERBOSE' in atlas_sshd_effective_configuration.stdout_lines"
|
||||
- "('allowusers ' ~ atlas_admin_username) in atlas_sshd_effective_configuration.stdout_lines"
|
||||
fail_msg: The effective Atlas SSH configuration does not match the required hardening.
|
||||
when: not ansible_check_mode
|
||||
15
ansible/roles/profile_atlas/tasks/cockpit.yml
Normal file
15
ansible/roles/profile_atlas/tasks/cockpit.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Configure the official 45Drives repository
|
||||
tags: [atlas, packages]
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ atlas_45drives_repo_url }}"
|
||||
dest: "{{ atlas_45drives_repo_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Install 45Drives Cockpit plugins
|
||||
tags: [atlas, packages]
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ atlas_45drives_packages }}"
|
||||
state: present
|
||||
140
ansible/roles/profile_atlas/tasks/containers.yml
Normal file
140
ansible/roles/profile_atlas/tasks/containers.yml
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
- name: Require completed Atlas media-stack configuration
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_manage_storage | bool
|
||||
- atlas_zfs_pool != 'CHANGEME_ZFS_POOL'
|
||||
- atlas_mount_root != '/CHANGEME_ATLAS_MOUNT_ROOT'
|
||||
- atlas_immich_db_password | length > 0
|
||||
- atlas_npm_admin_bind_address == '127.0.0.1'
|
||||
fail_msg: >-
|
||||
Enable and configure Atlas storage, provide vault_atlas_immich_db_password,
|
||||
and keep the NPM administration port bound to loopback before enabling the media stack.
|
||||
no_log: true
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Check Atlas Intel graphics device
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.stat:
|
||||
path: /dev/dri
|
||||
register: atlas_dri_device
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Require Atlas Intel graphics device
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_dri_device.stat.isdir | default(false)
|
||||
fail_msg: /dev/dri is required for Immich QuickSync and OpenVINO acceleration.
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas rootful Quadlet directory
|
||||
tags: [atlas, containers]
|
||||
ansible.builtin.file:
|
||||
path: "{{ atlas_container_quadlet_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas Immich configuration directory
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.file:
|
||||
path: /etc/immich
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0750"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas Immich persistent directories
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_immich_username }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ atlas_immich_upload_dir }}"
|
||||
- "{{ atlas_immich_model_cache_dir }}"
|
||||
- "{{ atlas_immich_machine_learning_config_dir }}"
|
||||
- "{{ atlas_immich_machine_learning_cache_dir }}"
|
||||
- "{{ atlas_immich_redis_data_dir }}"
|
||||
- "{{ atlas_immich_postgres_data_dir }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas NPM persistent directories
|
||||
tags: [atlas, containers, npm]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ atlas_npm_data_dir }}"
|
||||
- "{{ atlas_npm_letsencrypt_dir }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Allow confined containers to use Atlas graphics devices
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.posix.seboolean:
|
||||
name: container_use_devices
|
||||
state: true
|
||||
persistent: true
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
|
||||
|
||||
- name: Render Vault-backed Immich environment
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.template:
|
||||
src: immich.env.j2
|
||||
dest: /etc/immich/immich.env
|
||||
owner: root
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
diff: false
|
||||
notify: Restart Atlas media Quadlets
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Render Atlas media Quadlets
|
||||
tags: [atlas, containers]
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "{{ atlas_container_quadlet_dir }}/{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- atlas-media.network
|
||||
- atlas-immich-redis.container
|
||||
- atlas-immich-postgres.container
|
||||
- atlas-immich-machine-learning.container
|
||||
- atlas-immich-server.container
|
||||
- atlas-npm.container
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
notify: Restart Atlas media Quadlets
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Start Atlas media Quadlet services
|
||||
tags: [atlas, containers, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
state: started
|
||||
daemon_reload: true
|
||||
loop: "{{ atlas_media_quadlet_services }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- not ansible_check_mode
|
||||
45
ansible/roles/profile_atlas/tasks/hardening.yml
Normal file
45
ansible/roles/profile_atlas/tasks/hardening.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
- name: Enforce targeted SELinux on Atlas
|
||||
tags: [atlas, hardening, security]
|
||||
ansible.posix.selinux:
|
||||
policy: targeted
|
||||
state: enforcing
|
||||
update_kernel_param: true
|
||||
register: atlas_selinux_enforcement
|
||||
|
||||
- name: Report when Atlas requires a reboot for SELinux enforcement
|
||||
tags: [atlas, hardening, security]
|
||||
ansible.builtin.debug:
|
||||
msg: Reboot Atlas before relying on SELinux enforcement.
|
||||
when: atlas_selinux_enforcement.reboot_required | default(false)
|
||||
|
||||
- name: Apply Atlas network-kernel hardening
|
||||
tags: [atlas, hardening, security]
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.key }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
sysctl_set: true
|
||||
reload: true
|
||||
loop: "{{ atlas_hardening_sysctls | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}={{ item.value }}"
|
||||
|
||||
- name: Read active Atlas SELinux mode
|
||||
tags: [atlas, hardening, security]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- getenforce
|
||||
register: atlas_selinux_mode
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Require active SELinux enforcement on Atlas
|
||||
tags: [atlas, hardening, security]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_selinux_mode.stdout == 'Enforcing'
|
||||
fail_msg: >-
|
||||
Atlas SELinux is not enforcing. Reboot if requested by the preceding
|
||||
SELinux task, then rerun the hardening role before enabling services.
|
||||
when: not ansible_check_mode
|
||||
21
ansible/roles/profile_atlas/tasks/main.yml
Normal file
21
ansible/roles/profile_atlas/tasks/main.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Import Atlas account tasks
|
||||
ansible.builtin.import_tasks: account.yml
|
||||
|
||||
- name: Import Atlas 45Drives tasks
|
||||
ansible.builtin.import_tasks: cockpit.yml
|
||||
|
||||
- name: Import Atlas network hardening tasks
|
||||
ansible.builtin.import_tasks: hardening.yml
|
||||
|
||||
- name: Import Atlas pool bootstrap tasks
|
||||
ansible.builtin.import_tasks: pool.yml
|
||||
|
||||
- name: Import Atlas storage tasks
|
||||
ansible.builtin.import_tasks: storage.yml
|
||||
|
||||
- name: Import Atlas file sharing tasks
|
||||
ansible.builtin.import_tasks: sharing.yml
|
||||
|
||||
- name: Import Atlas media-container tasks
|
||||
ansible.builtin.import_tasks: containers.yml
|
||||
57
ansible/roles/profile_atlas/tasks/pool.yml
Normal file
57
ansible/roles/profile_atlas/tasks/pool.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
- name: Bootstrap Atlas ZFS pool
|
||||
tags: [atlas, storage, pool]
|
||||
when: atlas_create_pool | bool
|
||||
block:
|
||||
- name: Validate Atlas pool bootstrap inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_zpool_disks | length == 4
|
||||
- atlas_zpool_disks | unique | length == 4
|
||||
- atlas_zpool_disks | select('match', '^/dev/disk/by-id/') | list | length == 4
|
||||
fail_msg: >-
|
||||
Set exactly four distinct persistent /dev/disk/by-id paths in
|
||||
atlas_zpool_disks before creating the RAIDZ2 pool.
|
||||
|
||||
- name: Inspect declared Atlas pool disks
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
follow: true
|
||||
loop: "{{ atlas_zpool_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
register: atlas_zpool_disk_stats
|
||||
|
||||
- name: Require every declared Atlas pool disk
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.stat.exists
|
||||
- item.stat.isblk | default(false)
|
||||
fail_msg: "Declared Atlas pool disk is unavailable or is not a block device: {{ item.item }}"
|
||||
loop: "{{ atlas_zpool_disk_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
|
||||
- name: Check whether the Atlas ZFS pool already exists
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- zpool
|
||||
- list
|
||||
- -H
|
||||
- -o
|
||||
- name
|
||||
- "{{ atlas_zfs_pool }}"
|
||||
register: atlas_zpool_bootstrap_check
|
||||
changed_when: false
|
||||
failed_when: atlas_zpool_bootstrap_check.rc not in [0, 1]
|
||||
|
||||
- name: Create the Atlas RAIDZ2 pool when absent
|
||||
community.general.zpool:
|
||||
name: "{{ atlas_zfs_pool }}"
|
||||
state: present
|
||||
mountpoint: "{{ atlas_mount_root }}"
|
||||
force: false
|
||||
vdevs:
|
||||
- type: raidz2
|
||||
disks: "{{ atlas_zpool_disks }}"
|
||||
when: atlas_zpool_bootstrap_check.rc == 1
|
||||
64
ansible/roles/profile_atlas/tasks/samba_account.yml
Normal file
64
ansible/roles/profile_atlas/tasks/samba_account.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
- name: Require an existing Unix account for Atlas Samba
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ atlas_samba_account.username }}"
|
||||
|
||||
- name: Read Atlas Samba account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- pdbedit
|
||||
- --list
|
||||
- --user
|
||||
- "{{ atlas_samba_account.username }}"
|
||||
register: atlas_samba_account_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Check Atlas Samba password marker
|
||||
ansible.builtin.stat:
|
||||
path: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
register: atlas_samba_password_marker_stat
|
||||
|
||||
- name: Read Atlas Samba password marker
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
register: atlas_samba_password_marker_content
|
||||
no_log: true
|
||||
when: atlas_samba_password_marker_stat.stat.exists
|
||||
|
||||
- name: Determine whether Atlas Samba credentials must change
|
||||
ansible.builtin.set_fact:
|
||||
atlas_samba_password_digest: "{{ atlas_samba_account.password | hash('sha256') }}"
|
||||
atlas_samba_password_update_required: >-
|
||||
{{
|
||||
atlas_samba_account_check.rc != 0
|
||||
or not atlas_samba_password_marker_stat.stat.exists
|
||||
or (
|
||||
atlas_samba_password_marker_content.content | default('')
|
||||
| b64decode | trim
|
||||
) != (atlas_samba_account.password | hash('sha256'))
|
||||
}}
|
||||
no_log: true
|
||||
|
||||
- name: Set Atlas Samba account password
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- smbpasswd
|
||||
- -s
|
||||
- -a
|
||||
- "{{ atlas_samba_account.username }}"
|
||||
stdin: "{{ atlas_samba_account.password }}\n{{ atlas_samba_account.password }}"
|
||||
changed_when: true
|
||||
no_log: true
|
||||
when: atlas_samba_password_update_required | bool
|
||||
|
||||
- name: Record managed Atlas Samba password digest
|
||||
ansible.builtin.copy:
|
||||
content: "{{ atlas_samba_password_digest }}\n"
|
||||
dest: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
when: atlas_samba_password_update_required | bool
|
||||
271
ansible/roles/profile_atlas/tasks/sharing.yml
Normal file
271
ansible/roles/profile_atlas/tasks/sharing.yml
Normal file
@@ -0,0 +1,271 @@
|
||||
- name: Require completed Atlas firewall placeholders
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_lan_subnet != 'CHANGEME_LAN_SUBNET'
|
||||
- atlas_aegis_ip != 'CHANGEME_AEGIS_IP'
|
||||
- atlas_firewalld_zone | length > 0
|
||||
- ansible_facts.default_ipv4.interface | default('') | length > 0
|
||||
fail_msg: Replace the Atlas LAN, Aegis and firewall-zone placeholders.
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Apply Atlas firewalld rich rules
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: "{{ item }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
zone: "{{ atlas_firewalld_zone }}"
|
||||
loop: "{{ atlas_firewalld_rich_rules }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Assign primary Atlas LAN interface to managed firewalld zone
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.posix.firewalld:
|
||||
interface: "{{ ansible_facts.default_ipv4.interface }}"
|
||||
zone: "{{ atlas_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Remove unrestricted Atlas services from firewalld zone
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.posix.firewalld:
|
||||
service: "{{ item }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: disabled
|
||||
zone: "{{ atlas_firewalld_zone }}"
|
||||
loop: "{{ atlas_firewalld_restricted_services }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Render Atlas NFS exports
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.template:
|
||||
src: atlas.exports.j2
|
||||
dest: /etc/exports.d/atlas.exports
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload NFS exports
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Ensure Atlas NFS configuration drop-in directory exists
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.file:
|
||||
path: /etc/nfs.conf.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Configure Atlas NFSv4-only service
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.template:
|
||||
src: atlas-nfs.conf.j2
|
||||
dest: /etc/nfs.conf.d/atlas.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart NFS server
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Mask Atlas NFSv3 RPC services
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: false
|
||||
state: stopped
|
||||
masked: true
|
||||
loop:
|
||||
- rpc-statd.service
|
||||
- rpcbind.service
|
||||
- rpcbind.socket
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Ensure Atlas NFS mount daemon drop-in directory exists
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/nfs-mountd.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Disable Atlas NFSv3 mount daemon listeners
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.builtin.template:
|
||||
src: nfs-mountd-v4only.conf.j2
|
||||
dest: /etc/systemd/system/nfs-mountd.service.d/v4only.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart NFS mount daemon
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Enable SELinux access for Atlas file sharing
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.posix.seboolean:
|
||||
name: "{{ item }}"
|
||||
state: true
|
||||
persistent: true
|
||||
loop: "{{ atlas_selinux_booleans }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- atlas_manage_sharing | bool
|
||||
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
|
||||
|
||||
- name: Render Atlas Samba configuration
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.template:
|
||||
src: smb.conf.j2
|
||||
dest: /etc/samba/smb.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
validate: "testparm --suppress-prompt %s"
|
||||
notify: Restart Samba service
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Require Vault-backed Atlas Samba accounts
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_samba_encryption in ['required', 'desired']
|
||||
- atlas_samba_accounts | length > 0
|
||||
- >-
|
||||
atlas_samba_accounts | map(attribute='username') | list
|
||||
| difference(atlas_samba_valid_users) | length == 0
|
||||
- >-
|
||||
atlas_samba_valid_users
|
||||
| difference(atlas_samba_accounts | map(attribute='username') | list) | length == 0
|
||||
- atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0
|
||||
fail_msg: Define every authorized Samba account and its Vault-backed password.
|
||||
no_log: true
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Ensure Atlas Samba private state directory exists
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.file:
|
||||
path: "{{ atlas_samba_password_marker_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Manage Vault-backed Atlas Samba credentials
|
||||
tags: [atlas, sharing]
|
||||
ansible.builtin.include_tasks: samba_account.yml
|
||||
loop: "{{ atlas_samba_accounts }}"
|
||||
loop_control:
|
||||
loop_var: atlas_samba_account
|
||||
label: "{{ atlas_samba_account.username }}"
|
||||
no_log: true
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Enable Atlas file-sharing services
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
loop:
|
||||
- nfs-server.service
|
||||
- smb.service
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_sharing | bool
|
||||
|
||||
- name: Gather effective Atlas firewalld zone configuration
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.posix.firewalld_info:
|
||||
zones:
|
||||
- "{{ atlas_firewalld_zone }}"
|
||||
register: atlas_firewalld_info
|
||||
when:
|
||||
- atlas_manage_firewall | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Verify effective Atlas firewalld restrictions
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_firewalld_zone in atlas_firewalld_info.firewalld_info.zones
|
||||
- >-
|
||||
ansible_facts.default_ipv4.interface
|
||||
in atlas_firewalld_info.firewalld_info.zones[atlas_firewalld_zone].interfaces
|
||||
- >-
|
||||
atlas_firewalld_restricted_services
|
||||
| intersect(atlas_firewalld_info.firewalld_info.zones[atlas_firewalld_zone].services)
|
||||
| length == 0
|
||||
fail_msg: >-
|
||||
The effective Atlas firewalld zone does not restrict the primary LAN
|
||||
interface and unmanaged service exposure as declared.
|
||||
when:
|
||||
- atlas_manage_firewall | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Apply pending Atlas sharing handlers before verification
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.builtin.meta: flush_handlers
|
||||
when:
|
||||
- atlas_manage_sharing | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Check active Atlas file-sharing services
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- systemctl
|
||||
- is-active
|
||||
- --quiet
|
||||
- "{{ item }}"
|
||||
loop:
|
||||
- nfs-server.service
|
||||
- smb.service
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
register: atlas_file_sharing_service_activity
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when:
|
||||
- atlas_manage_sharing | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Require active Atlas file-sharing services
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_file_sharing_service_activity.results | map(attribute='rc') | list == [0, 0]
|
||||
fail_msg: Atlas NFSv4 or SMB3 did not start after its managed configuration was applied.
|
||||
when:
|
||||
- atlas_manage_sharing | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Require Atlas file-sharing TCP listeners
|
||||
tags: [atlas, sharing, services, security]
|
||||
ansible.builtin.wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ item }}"
|
||||
state: started
|
||||
timeout: 15
|
||||
loop:
|
||||
- 2049
|
||||
- 445
|
||||
loop_control:
|
||||
label: "{{ item }}/tcp"
|
||||
when:
|
||||
- atlas_manage_sharing | bool
|
||||
- not ansible_check_mode
|
||||
144
ansible/roles/profile_atlas/tasks/storage.yml
Normal file
144
ansible/roles/profile_atlas/tasks/storage.yml
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
- name: Define Atlas ZFS datasets
|
||||
tags: [atlas, storage]
|
||||
ansible.builtin.set_fact:
|
||||
atlas_zfs_datasets:
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_work }}"
|
||||
mountpoint: "{{ atlas_work_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
mountpoint: "{{ atlas_work_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_archive }}"
|
||||
mountpoint: "{{ atlas_archive_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: zstd
|
||||
mountpoint: "{{ atlas_archive_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
mountpoint: "{{ atlas_app_data_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: zstd
|
||||
mountpoint: "{{ atlas_app_data_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||
mountpoint: "{{ atlas_navidrome_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: zstd
|
||||
mountpoint: "{{ atlas_navidrome_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_media }}"
|
||||
mountpoint: "{{ atlas_media_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_media_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_music }}"
|
||||
mountpoint: "{{ atlas_music_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_music_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backup_prometheus }}"
|
||||
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_photobook }}"
|
||||
mountpoint: "{{ atlas_photobook_mountpoint }}"
|
||||
owner: "{{ atlas_immich_username }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_photobook_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backups }}"
|
||||
mountpoint: "{{ atlas_backups_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_backups_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
||||
refreservation: "{{ atlas_zfs_service_backups_refreservation }}"
|
||||
|
||||
- name: Require completed Atlas storage placeholders
|
||||
tags: [atlas, storage]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_zfs_pool != 'CHANGEME_ZFS_POOL'
|
||||
- atlas_mount_root != '/CHANGEME_ATLAS_MOUNT_ROOT'
|
||||
- atlas_mount_root.startswith('/')
|
||||
- (atlas_zfs_datasets | map(attribute='name') | unique | list | length) == (atlas_zfs_datasets | length)
|
||||
- (atlas_zfs_datasets | map(attribute='mountpoint') | unique | list | length) == (atlas_zfs_datasets | length)
|
||||
fail_msg: >-
|
||||
Replace the Atlas ZFS pool and mount-root placeholders, then set
|
||||
atlas_manage_storage to true before managing datasets.
|
||||
when: atlas_manage_storage | bool
|
||||
|
||||
- name: Check whether the configured Atlas ZFS pool exists
|
||||
tags: [atlas, storage]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- zpool
|
||||
- list
|
||||
- -H
|
||||
- -o
|
||||
- name
|
||||
- "{{ atlas_zfs_pool }}"
|
||||
register: atlas_zfs_pool_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: atlas_manage_storage | bool
|
||||
|
||||
- name: Require the configured Atlas ZFS pool
|
||||
tags: [atlas, storage]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_zfs_pool_check.rc == 0
|
||||
fail_msg: >-
|
||||
The configured Atlas ZFS pool does not exist. The Atlas profile only
|
||||
manages child datasets and never creates pools.
|
||||
when: atlas_manage_storage | bool
|
||||
|
||||
- name: Manage Atlas ZFS datasets declaratively
|
||||
tags: [atlas, storage]
|
||||
community.general.zfs:
|
||||
name: "{{ item.name }}"
|
||||
state: present
|
||||
extra_zfs_properties: "{{ item.properties }}"
|
||||
loop: "{{ atlas_zfs_datasets }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: atlas_manage_storage | bool
|
||||
|
||||
- name: Set Atlas dataset ownership
|
||||
tags: [atlas, storage]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.mountpoint }}"
|
||||
state: directory
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "0770"
|
||||
loop: "{{ atlas_zfs_datasets }}"
|
||||
loop_control:
|
||||
label: "{{ item.mountpoint }}"
|
||||
when: atlas_manage_storage | bool
|
||||
@@ -0,0 +1,24 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
PermitRootLogin no
|
||||
PubkeyAuthentication yes
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
AuthenticationMethods publickey
|
||||
AuthorizedKeysFile {{
|
||||
atlas_admin_ssh_keys
|
||||
| map(attribute='name')
|
||||
| map('regex_replace', '^', '%h/.ssh/authorized_keys.d/')
|
||||
| join(' ')
|
||||
}}
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding {{ atlas_ssh_allow_tcp_forwarding }}
|
||||
AllowAgentForwarding no
|
||||
GatewayPorts no
|
||||
PermitTunnel no
|
||||
PermitUserEnvironment no
|
||||
MaxAuthTries 3
|
||||
LoginGraceTime 30
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
LogLevel VERBOSE
|
||||
AllowUsers {{ atlas_admin_username }}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich machine learning
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-machine-learning
|
||||
Image={{ atlas_immich_machine_learning_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
{% for group_name in atlas_immich_supplementary_groups %}
|
||||
GroupAdd={{ ansible_facts['getent_group'][group_name][1] }}
|
||||
{% endfor %}
|
||||
AddDevice=/dev/dri
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-machine-learning
|
||||
Environment=TZ={{ atlas_timezone }}
|
||||
Volume={{ atlas_immich_model_cache_dir }}:/cache:Z
|
||||
Volume={{ atlas_immich_machine_learning_config_dir }}:/.config:Z
|
||||
Volume={{ atlas_immich_machine_learning_cache_dir }}:/.cache:Z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,22 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich PostgreSQL database
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-postgres
|
||||
Image={{ atlas_immich_postgres_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-postgres
|
||||
EnvironmentFile=/etc/immich/immich.env
|
||||
Volume={{ atlas_immich_postgres_data_dir }}:/var/lib/postgresql/data:Z
|
||||
ShmSize=128m
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,20 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich Redis-compatible cache
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-redis
|
||||
Image={{ atlas_immich_redis_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-redis
|
||||
Volume={{ atlas_immich_redis_data_dir }}:/data:Z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,29 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich server
|
||||
Requires=atlas-immich-redis.service atlas-immich-postgres.service atlas-immich-machine-learning.service
|
||||
After=atlas-immich-redis.service atlas-immich-postgres.service atlas-immich-machine-learning.service
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-server
|
||||
Image={{ atlas_immich_server_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
{% for group_name in atlas_immich_supplementary_groups %}
|
||||
GroupAdd={{ ansible_facts['getent_group'][group_name][1] }}
|
||||
{% endfor %}
|
||||
AddDevice=/dev/dri
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-server
|
||||
EnvironmentFile=/etc/immich/immich.env
|
||||
Volume={{ atlas_immich_upload_dir }}:/data:Z
|
||||
Volume={{ atlas_photobook_mountpoint }}:/external/photobook:ro,Z
|
||||
Volume=/etc/localtime:/etc/localtime:ro
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,3 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Network]
|
||||
NetworkName=atlas-media
|
||||
3
ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2
Normal file
3
ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2
Normal file
@@ -0,0 +1,3 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[nfsd]
|
||||
vers3 = n
|
||||
21
ansible/roles/profile_atlas/templates/atlas-npm.container.j2
Normal file
21
ansible/roles/profile_atlas/templates/atlas-npm.container.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Nginx Proxy Manager
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-npm
|
||||
Image={{ atlas_npm_image }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-npm
|
||||
PublishPort=80:80
|
||||
PublishPort=443:443
|
||||
PublishPort={{ atlas_npm_admin_bind_address }}:81:81
|
||||
Volume={{ atlas_npm_data_dir }}:/data:Z
|
||||
Volume={{ atlas_npm_letsencrypt_dir }}:/etc/letsencrypt:Z
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
4
ansible/roles/profile_atlas/templates/atlas.exports.j2
Normal file
4
ansible/roles/profile_atlas/templates/atlas.exports.j2
Normal file
@@ -0,0 +1,4 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
{% for export in atlas_nfs_exports %}
|
||||
{{ export.path }} {{ export.client }}({{ export.options | join(',') }})
|
||||
{% endfor %}
|
||||
13
ansible/roles/profile_atlas/templates/immich.env.j2
Normal file
13
ansible/roles/profile_atlas/templates/immich.env.j2
Normal file
@@ -0,0 +1,13 @@
|
||||
# Managed by Ansible. Contains a Vault secret; do not edit or copy.
|
||||
TZ={{ atlas_timezone | to_json }}
|
||||
DB_HOSTNAME="atlas-immich-postgres"
|
||||
DB_USERNAME={{ atlas_immich_db_username | to_json }}
|
||||
DB_PASSWORD={{ atlas_immich_db_password | to_json }}
|
||||
DB_DATABASE_NAME={{ atlas_immich_db_name | to_json }}
|
||||
DB_VECTOR_EXTENSION="vectorchord"
|
||||
POSTGRES_USER={{ atlas_immich_db_username | to_json }}
|
||||
POSTGRES_PASSWORD={{ atlas_immich_db_password | to_json }}
|
||||
POSTGRES_DB={{ atlas_immich_db_name | to_json }}
|
||||
POSTGRES_INITDB_ARGS="--data-checksums"
|
||||
REDIS_HOSTNAME="atlas-immich-redis"
|
||||
IMMICH_MACHINE_LEARNING_URL="http://atlas-immich-machine-learning:3003"
|
||||
@@ -0,0 +1,3 @@
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/sbin/rpc.mountd --no-tcp --no-udp
|
||||
21
ansible/roles/profile_atlas/templates/smb.conf.j2
Normal file
21
ansible/roles/profile_atlas/templates/smb.conf.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[global]
|
||||
workgroup = {{ atlas_samba_workgroup }}
|
||||
security = user
|
||||
map to guest = Never
|
||||
server min protocol = SMB3
|
||||
server signing = mandatory
|
||||
smb encrypt = {{ atlas_samba_encryption }}
|
||||
disable netbios = yes
|
||||
smb ports = 445
|
||||
hosts allow = {{ atlas_lan_subnet }}
|
||||
include = registry
|
||||
|
||||
[{{ atlas_samba_share_name }}]
|
||||
path = {{ atlas_archive_mountpoint }}
|
||||
browseable = yes
|
||||
read only = no
|
||||
valid users = {{ atlas_samba_valid_users | join(' ') }}
|
||||
force group = {{ atlas_admin_group }}
|
||||
create mask = 0660
|
||||
directory mask = 0770
|
||||
28
ansible/roles/profile_backend_phase1/defaults/main.yml
Normal file
28
ansible/roles/profile_backend_phase1/defaults/main.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
backend_phase1_enabled: false
|
||||
backend_phase1_start_services: false
|
||||
backend_phase1_username: "{{ atlas_admin_username }}"
|
||||
backend_phase1_user_group: "{{ atlas_admin_group }}"
|
||||
backend_phase1_user_home: "{{ atlas_admin_home }}"
|
||||
backend_phase1_quadlet_dir: "{{ backend_phase1_user_home }}/.config/containers/systemd"
|
||||
backend_phase1_wireguard_interface: wg0
|
||||
backend_phase1_wireguard_address: CHANGEME_ATLAS_WIREGUARD_ADDRESS
|
||||
backend_phase1_wireguard_firewalld_zone: wireguard
|
||||
backend_phase1_music_dir: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}"
|
||||
backend_phase1_app_data_root: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
backend_phase1_navidrome_data_dir: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||
backend_phase1_syncthing_root: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||
backend_phase1_music_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_music }}"
|
||||
backend_phase1_app_data_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
backend_phase1_navidrome_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||
backend_phase1_syncthing_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||
backend_phase1_navidrome_image: docker.io/deluan/navidrome:0.63.2
|
||||
backend_phase1_syncthing_image: docker.io/syncthing/syncthing:2.1.3
|
||||
backend_phase1_navidrome_port: 4533
|
||||
backend_phase1_syncthing_gui_port: 8384
|
||||
backend_phase1_syncthing_transfer_port: 22000
|
||||
backend_phase1_syncthing_discovery_port: 21027
|
||||
backend_phase1_timezone: Europe/Rome
|
||||
backend_phase1_services:
|
||||
- atlas-navidrome.service
|
||||
- atlas-syncthing.service
|
||||
32
ansible/roles/profile_backend_phase1/handlers/main.yml
Normal file
32
ansible/roles/profile_backend_phase1/handlers/main.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Restart Atlas Navidrome Quadlet
|
||||
tags: [backend_phase1, navidrome, podman, services]
|
||||
become_user: "{{ backend_phase1_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: atlas-navidrome.service
|
||||
scope: user
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||
when:
|
||||
- backend_phase1_enabled | bool
|
||||
- backend_phase1_start_services | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Restart Atlas Syncthing Quadlet
|
||||
tags: [backend_phase1, syncthing, podman, services]
|
||||
become_user: "{{ backend_phase1_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: atlas-syncthing.service
|
||||
scope: user
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||
when:
|
||||
- backend_phase1_enabled | bool
|
||||
- backend_phase1_start_services | bool
|
||||
- not ansible_check_mode
|
||||
163
ansible/roles/profile_backend_phase1/tasks/main.yml
Normal file
163
ansible/roles/profile_backend_phase1/tasks/main.yml
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
- name: Configure Atlas phase-one backend services
|
||||
tags: [backend_phase1, podman]
|
||||
when: backend_phase1_enabled | bool
|
||||
block:
|
||||
- name: Validate phase-one backend inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not (atlas_manage_media_stack | bool)
|
||||
- backend_phase1_wireguard_interface in ansible_facts.interfaces
|
||||
- backend_phase1_wireguard_address != 'CHANGEME_ATLAS_WIREGUARD_ADDRESS'
|
||||
- backend_phase1_music_dir.startswith('/')
|
||||
- backend_phase1_app_data_root.startswith('/')
|
||||
- backend_phase1_navidrome_data_dir.startswith(backend_phase1_app_data_root + '/')
|
||||
- backend_phase1_syncthing_root.startswith(backend_phase1_app_data_root + '/')
|
||||
fail_msg: >-
|
||||
Disable the rootful media-stack gate and provide the active
|
||||
WireGuard interface/address and absolute ZFS-backed paths before
|
||||
enabling phase one. This role does not manage Prometheus or migrate
|
||||
Navidrome application data.
|
||||
|
||||
- name: Read the rootless service account
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ backend_phase1_username }}"
|
||||
|
||||
- name: Record rootless service account IDs
|
||||
ansible.builtin.set_fact:
|
||||
backend_phase1_uid: "{{ ansible_facts['getent_passwd'][backend_phase1_username][1] }}"
|
||||
backend_phase1_gid: "{{ ansible_facts['getent_passwd'][backend_phase1_username][2] }}"
|
||||
|
||||
- name: Read system service state before starting rootless Syncthing
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Refuse to overlap a system-level Atlas Syncthing service
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
ansible_facts.services['atlas-syncthing.service'] is not defined
|
||||
or ansible_facts.services['atlas-syncthing.service'].state != 'running'
|
||||
fail_msg: >-
|
||||
Remove or stop the system-level atlas-syncthing.service before
|
||||
enabling the rootless Syncthing Quadlet.
|
||||
|
||||
- name: Inspect required ZFS datasets
|
||||
community.general.zfs_facts:
|
||||
name: "{{ item.dataset }}"
|
||||
properties: name,mounted,mountpoint
|
||||
loop:
|
||||
- dataset: "{{ backend_phase1_music_dataset }}"
|
||||
mountpoint: "{{ backend_phase1_music_dir }}"
|
||||
- dataset: "{{ backend_phase1_app_data_dataset }}"
|
||||
mountpoint: "{{ backend_phase1_app_data_root }}"
|
||||
- dataset: "{{ backend_phase1_navidrome_dataset }}"
|
||||
mountpoint: "{{ backend_phase1_navidrome_data_dir }}"
|
||||
- dataset: "{{ backend_phase1_syncthing_dataset }}"
|
||||
mountpoint: "{{ backend_phase1_syncthing_root }}"
|
||||
loop_control:
|
||||
label: "{{ item.dataset }}"
|
||||
register: backend_phase1_zfs_facts
|
||||
|
||||
- name: Require mounted datasets at the declared paths
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.ansible_facts.ansible_zfs_datasets | length == 1
|
||||
- item.ansible_facts.ansible_zfs_datasets[0].mounted == 'yes'
|
||||
- item.ansible_facts.ansible_zfs_datasets[0].mountpoint == item.item.mountpoint
|
||||
fail_msg: >-
|
||||
{{ item.item.dataset }} must already be mounted at
|
||||
{{ item.item.mountpoint }}. The phase-one role never creates or
|
||||
imports the pool.
|
||||
loop: "{{ backend_phase1_zfs_facts.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.dataset }}"
|
||||
|
||||
- name: Enable lingering for the rootless service account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- loginctl
|
||||
- enable-linger
|
||||
- "{{ backend_phase1_username }}"
|
||||
creates: "/var/lib/systemd/linger/{{ backend_phase1_username }}"
|
||||
|
||||
- name: Start the rootless user systemd manager
|
||||
ansible.builtin.systemd:
|
||||
name: "user@{{ backend_phase1_uid }}.service"
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Create rootless Quadlet and application directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ backend_phase1_username }}"
|
||||
group: "{{ backend_phase1_user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- path: "{{ backend_phase1_quadlet_dir }}"
|
||||
mode: "0700"
|
||||
- path: "{{ backend_phase1_navidrome_data_dir }}"
|
||||
mode: "0750"
|
||||
- path: "{{ backend_phase1_syncthing_root }}"
|
||||
mode: "0750"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Render the rootless Navidrome Quadlet
|
||||
ansible.builtin.template:
|
||||
src: atlas-navidrome.container.j2
|
||||
dest: "{{ backend_phase1_quadlet_dir }}/atlas-navidrome.container"
|
||||
owner: "{{ backend_phase1_username }}"
|
||||
group: "{{ backend_phase1_user_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart Atlas Navidrome Quadlet
|
||||
|
||||
- name: Render the rootless Syncthing Quadlet
|
||||
ansible.builtin.template:
|
||||
src: atlas-syncthing.container.j2
|
||||
dest: "{{ backend_phase1_quadlet_dir }}/atlas-syncthing.container"
|
||||
owner: "{{ backend_phase1_username }}"
|
||||
group: "{{ backend_phase1_user_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart Atlas Syncthing Quadlet
|
||||
|
||||
- name: Reload the rootless user systemd manager
|
||||
become_user: "{{ backend_phase1_username }}"
|
||||
ansible.builtin.systemd:
|
||||
scope: user
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Permit phase-one services only through the WireGuard zone
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ item }}"
|
||||
zone: "{{ backend_phase1_wireguard_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop:
|
||||
- "{{ backend_phase1_navidrome_port }}/tcp"
|
||||
- "{{ backend_phase1_syncthing_gui_port }}/tcp"
|
||||
- "{{ backend_phase1_syncthing_transfer_port }}/tcp"
|
||||
- "{{ backend_phase1_syncthing_transfer_port }}/udp"
|
||||
- "{{ backend_phase1_syncthing_discovery_port }}/udp"
|
||||
|
||||
- name: Start rootless phase-one Quadlets
|
||||
become_user: "{{ backend_phase1_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
scope: user
|
||||
state: started
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||
loop: "{{ backend_phase1_services }}"
|
||||
when:
|
||||
- backend_phase1_start_services | bool
|
||||
- not ansible_check_mode
|
||||
@@ -0,0 +1,28 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas rootless Navidrome backend
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-navidrome
|
||||
Image={{ backend_phase1_navidrome_image }}
|
||||
UserNS=keep-id
|
||||
User={{ backend_phase1_uid }}
|
||||
Group={{ backend_phase1_gid }}
|
||||
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_navidrome_port }}:4533
|
||||
Environment=ND_LOGLEVEL=info
|
||||
Environment=ND_SCANSCHEDULE=1h
|
||||
Environment=ND_SESSIONTIMEOUT=24h
|
||||
Environment=ND_ENABLETRANSCODING=true
|
||||
Environment=TZ={{ backend_phase1_timezone }}
|
||||
Volume={{ backend_phase1_navidrome_data_dir }}:/data:Z
|
||||
Volume={{ backend_phase1_music_dir }}:/music:ro,z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=all
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,30 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas rootless Syncthing backend
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-syncthing
|
||||
Image={{ backend_phase1_syncthing_image }}
|
||||
HostName=atlas-syncthing
|
||||
UserNS=keep-id
|
||||
User={{ backend_phase1_uid }}
|
||||
Group={{ backend_phase1_gid }}
|
||||
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_gui_port }}:{{ backend_phase1_syncthing_gui_port }}
|
||||
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}
|
||||
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}/udp
|
||||
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_discovery_port }}:{{ backend_phase1_syncthing_discovery_port }}/udp
|
||||
Environment=HOME=/var/syncthing
|
||||
Environment=STHOMEDIR=/var/syncthing/config
|
||||
Environment=STGUIADDRESS=0.0.0.0:{{ backend_phase1_syncthing_gui_port }}
|
||||
Environment=TZ={{ backend_phase1_timezone }}
|
||||
Volume={{ backend_phase1_syncthing_root }}:/var/syncthing:Z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=all
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -556,6 +556,30 @@
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Install shared AI coding agents on Void desktop
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: latest
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- item.value.install_enabled | bool
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Uninstall shared AI coding agents on Void desktop
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: absent
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- item.value.uninstall_enabled | bool
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Install desktop Python tools with uv
|
||||
tags: [packages]
|
||||
ansible.builtin.command:
|
||||
|
||||
@@ -10,15 +10,3 @@
|
||||
loop: "{{ personal_workstation_directories | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Copy personal workstation dotfiles
|
||||
tags: [dotfiles, dotfiles:workstation]
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../dotfiles/workstation/{{ item.src }}"
|
||||
dest: "{{ user_home }}/{{ item.dest }}"
|
||||
owner: "{{ username }}"
|
||||
group: "{{ user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ personal_workstation_dotfiles | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
@@ -2,5 +2,10 @@
|
||||
- name: Reload SSH service
|
||||
tags: [services]
|
||||
ansible.builtin.service:
|
||||
name: ssh
|
||||
name: "{{ server_sshd_service_name }}"
|
||||
state: reloaded
|
||||
|
||||
- name: Reload systemd for Podman Compose
|
||||
tags: [services]
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
33
ansible/roles/profile_server/tasks/duckdns.yml
Normal file
33
ansible/roles/profile_server/tasks/duckdns.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
- name: Require DuckDNS domain and Vault token before deployment
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
server_duckdns_domain | default('') is
|
||||
regex('[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', match_type='fullmatch')
|
||||
- >-
|
||||
vault_duckdns_token | default('') is
|
||||
regex('[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}', match_type='fullmatch')
|
||||
fail_msg: >-
|
||||
Define server_duckdns_domain in host_vars and the rotated vault_duckdns_token
|
||||
in encrypted Vault or an untracked local vars file before deploying DuckDNS.
|
||||
no_log: true
|
||||
|
||||
- name: Ensure private DuckDNS directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ server_user_home }}/duckdns"
|
||||
state: directory
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Render DuckDNS updater with the Vault token
|
||||
ansible.builtin.template:
|
||||
src: duck.sh.j2
|
||||
dest: "{{ server_user_home }}/duckdns/duck.sh"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0700"
|
||||
validate: /bin/sh -n %s
|
||||
no_log: true
|
||||
diff: false
|
||||
@@ -1,5 +1,37 @@
|
||||
---
|
||||
|
||||
- name: Require supported server firewall backend
|
||||
tags: [services, packages]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- server_firewall_backend == 'firewalld'
|
||||
fail_msg: >-
|
||||
server_firewall_backend must be firewalld for the Rocky server profile.
|
||||
|
||||
- name: Configure DuckDNS updater
|
||||
tags: [dotfiles, dotfiles:server, duckdns]
|
||||
ansible.builtin.import_tasks: duckdns.yml
|
||||
|
||||
- name: Ensure server directories exist
|
||||
tags: [dotfiles, services]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: >-
|
||||
{{
|
||||
(server_directories | default([]))
|
||||
+ (
|
||||
server_syncthing_directories | default([])
|
||||
if server_syncthing_enabled | default(true) | bool
|
||||
else []
|
||||
)
|
||||
}}
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Copy server dotfiles
|
||||
tags: [dotfiles, dotfiles:server]
|
||||
ansible.builtin.copy:
|
||||
@@ -12,28 +44,6 @@
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Require server container secret variables
|
||||
tags: [dotfiles, dotfiles:server, services]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (vault_navidrome_db_password | default('')) | length > 0
|
||||
- (vault_postgres_root_password | default('')) | length > 0
|
||||
fail_msg: >-
|
||||
Server container secrets are missing. Define vault_navidrome_db_password and
|
||||
vault_postgres_root_password in secrets/vault.yml or another vars source.
|
||||
|
||||
- name: Ensure server directories exist
|
||||
tags: [dotfiles, services]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ server_directories | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Render server templates
|
||||
tags: [dotfiles, dotfiles:server]
|
||||
ansible.builtin.template:
|
||||
@@ -47,6 +57,46 @@
|
||||
label: "{{ item.dest }}"
|
||||
no_log: "{{ item.no_log | default(false) }}"
|
||||
|
||||
- name: Manage Podman Compose stack
|
||||
tags: [services, podman]
|
||||
ansible.builtin.include_tasks: podman-compose.yml
|
||||
|
||||
- name: Ensure server SSH authorized key fragments directory exists
|
||||
tags: [services, ssh]
|
||||
ansible.builtin.file:
|
||||
path: "{{ server_ssh_authorized_key_directory }}"
|
||||
state: directory
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0700"
|
||||
when: server_ssh_authorized_keys | length > 0
|
||||
|
||||
- name: Manage server SSH authorized key fragments
|
||||
tags: [services, ssh]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.key }}\n"
|
||||
dest: "{{ server_ssh_authorized_key_directory }}/{{ item.name }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0600"
|
||||
loop: "{{ server_ssh_authorized_keys }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: server_ssh_authorized_keys | length > 0
|
||||
|
||||
- name: Configure server SSH authorized key fragments
|
||||
tags: [services, ssh]
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^\s*AuthorizedKeysFile\s+'
|
||||
line: >-
|
||||
AuthorizedKeysFile {{ server_ssh_authorized_keys | map(attribute='name')
|
||||
| map('regex_replace', '^', '%h/.ssh/authorized_keys.d/') | join(' ') }}
|
||||
state: present
|
||||
validate: "sshd -t -f %s"
|
||||
notify: Reload SSH service
|
||||
when: server_ssh_authorized_keys | length > 0
|
||||
|
||||
- name: Disable SSH root login on server
|
||||
tags: [services]
|
||||
ansible.builtin.lineinfile:
|
||||
@@ -68,33 +118,28 @@
|
||||
notify: Reload SSH service
|
||||
when: (server_sshd_allow_users | default([])) | length > 0
|
||||
|
||||
- name: Define effective server UFW rules
|
||||
- name: Allow server services through firewalld
|
||||
tags: [services, packages]
|
||||
ansible.builtin.set_fact:
|
||||
server_ufw_rules_effective: "{{ server_ufw_rules | default([]) }}"
|
||||
|
||||
- name: Apply server UFW rules
|
||||
tags: [services, packages]
|
||||
community.general.ufw:
|
||||
rule: "{{ item.rule }}"
|
||||
name: "{{ item.name | default(omit) }}"
|
||||
port: "{{ item.port | default(omit) }}"
|
||||
proto: "{{ item.proto | default(omit) }}"
|
||||
from_ip: "{{ item.src | default(omit) }}"
|
||||
to_ip: "{{ item.dest | default(omit) }}"
|
||||
from_port: "{{ item.from_port | default(omit) }}"
|
||||
direction: "{{ item.direction | default(omit) }}"
|
||||
interface: "{{ item.interface | default(omit) }}"
|
||||
interface_in: "{{ item.interface_in | default(omit) }}"
|
||||
interface_out: "{{ item.interface_out | default(omit) }}"
|
||||
route: "{{ item.route | default(omit) }}"
|
||||
comment: "{{ item.comment | default(omit) }}"
|
||||
loop: "{{ server_ufw_rules_effective }}"
|
||||
loop_control:
|
||||
label: "{{ item.name | default(item.port) }}"
|
||||
|
||||
- name: Enable UFW firewall on server
|
||||
tags: [services, packages]
|
||||
community.general.ufw:
|
||||
ansible.posix.firewalld:
|
||||
service: "{{ item }}"
|
||||
zone: "{{ server_firewalld_zone }}"
|
||||
state: enabled
|
||||
when: (server_ufw_rules_effective | default([])) | length > 0
|
||||
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'
|
||||
|
||||
10
ansible/roles/profile_server/tasks/podman-compose.yml
Normal file
10
ansible/roles/profile_server/tasks/podman-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Render Podman Compose systemd unit
|
||||
tags: [services, podman]
|
||||
ansible.builtin.template:
|
||||
src: podman-compose-server.service.j2
|
||||
dest: /etc/systemd/system/podman-compose-server.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload systemd for Podman Compose
|
||||
24
ansible/roles/profile_server/templates/duck.sh.j2
Normal file
24
ansible/roles/profile_server/templates/duck.sh.j2
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# Managed by Ansible. Contains a Vault token; never copy this file into Git.
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
log_file={{ (server_user_home ~ '/duckdns/duck.log') | quote }}
|
||||
|
||||
# Keep the token out of process arguments and verify the HTTPS certificate.
|
||||
if ! response=$(curl --fail --silent --show-error --connect-timeout 10 --max-time 30 --config - <<'DUCKDNS_CONFIG'
|
||||
url = "https://www.duckdns.org/update?domains={{ server_duckdns_domain }}&token={{ vault_duckdns_token }}&ip="
|
||||
DUCKDNS_CONFIG
|
||||
); then
|
||||
printf 'ERROR\n' > "$log_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$response" in
|
||||
OK) printf 'OK\n' > "$log_file" ;;
|
||||
*)
|
||||
printf 'KO\n' > "$log_file"
|
||||
printf 'DuckDNS update failed.\n' >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Podman Compose stack for {{ server_username }}
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory={{ server_container_stack_dir }}
|
||||
ExecStart=/usr/bin/podman-compose up -d
|
||||
ExecStop=/usr/bin/podman-compose down
|
||||
ExecReload=/usr/bin/podman-compose up -d
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -35,19 +35,31 @@
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Install workstation npm packages
|
||||
tags: [packages, npm]
|
||||
- name: Install shared AI coding agents on workstation
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.name }}"
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: "{{ item.state | default('present') }}"
|
||||
state: latest
|
||||
become: true
|
||||
loop: "{{ workstation_npm_packages | default([]) }}"
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- workstation_manage_opencode | default(false)
|
||||
- workstation_npm_packages | length > 0
|
||||
- item.value.install_enabled | bool
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Uninstall shared AI coding agents on workstation
|
||||
tags: [packages, npm, ai_agents]
|
||||
community.general.npm:
|
||||
name: "{{ item.value.npm_package }}"
|
||||
global: true
|
||||
state: absent
|
||||
become: true
|
||||
loop: "{{ ai_agents | dict2items | selectattr('value.npm_package', 'defined') | list }}"
|
||||
when:
|
||||
- item.value.uninstall_enabled | bool
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Install IBM Bob coding agent
|
||||
tags: [packages, ai_agents]
|
||||
@@ -59,4 +71,15 @@
|
||||
become: true
|
||||
when:
|
||||
- workstation_manage_ibm_bob | default(false)
|
||||
- ai_agents.ibm_bob.install_enabled | bool
|
||||
- workstation_ibm_bob_install_url | length > 0
|
||||
|
||||
- name: Uninstall IBM Bob coding agent binary
|
||||
tags: [packages, ai_agents]
|
||||
ansible.builtin.file:
|
||||
path: /usr/local/bin/bob
|
||||
state: absent
|
||||
become: true
|
||||
when:
|
||||
- workstation_manage_ibm_bob | default(false)
|
||||
- ai_agents.ibm_bob.uninstall_enabled | bool
|
||||
|
||||
@@ -55,6 +55,39 @@
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Check whether the pinned Java version is installed with Mise
|
||||
tags: [packages, mise, java, wsl]
|
||||
ansible.builtin.command:
|
||||
cmd: "mise where java@{{ workstation_mise_java_version }}"
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
register: workstation_mise_java_where
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Check whether the pinned Maven version is installed with Mise
|
||||
tags: [packages, mise, maven, wsl]
|
||||
ansible.builtin.command:
|
||||
cmd: "mise where maven@{{ workstation_mise_maven_version }}"
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
register: workstation_mise_maven_where
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install the pinned Mise tools
|
||||
tags: [packages, mise, java, maven, wsl]
|
||||
ansible.builtin.command:
|
||||
cmd: mise install
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
when: >-
|
||||
workstation_mise_java_where.rc != 0 or
|
||||
workstation_mise_maven_where.rc != 0
|
||||
|
||||
- name: Ensure WSL boot configuration file exists
|
||||
tags: [packages, services]
|
||||
ansible.builtin.file:
|
||||
@@ -76,15 +109,6 @@
|
||||
register: workstation_wsl_systemd_config
|
||||
when: workstation_wsl_systemd_enabled | default(false)
|
||||
|
||||
- name: Install WSL Python packages for Windows remoting
|
||||
tags: [packages]
|
||||
ansible.builtin.pip:
|
||||
name: "{{ workstation_dev_wsl_python_packages | default([]) }}"
|
||||
executable: pip3
|
||||
extra_args: --break-system-packages
|
||||
state: present
|
||||
when: (workstation_dev_wsl_python_packages | default([])) | length > 0
|
||||
|
||||
- name: Note when WSL must be restarted
|
||||
tags: [packages, services]
|
||||
ansible.builtin.debug:
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
---
|
||||
- name: Copy workstation host Linux dotfiles
|
||||
tags: [dotfiles, dotfiles:workstation, gnome]
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../dotfiles/workstation_host_linux/{{ item.src }}"
|
||||
dest: "{{ user_home }}/{{ item.dest }}"
|
||||
owner: "{{ username }}"
|
||||
group: "{{ user_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ workstation_host_linux_dotfiles | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Ensure GNOME extension directories exist
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ username }}"
|
||||
group: "{{ user_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ user_home }}/.cache/gnome-shell/extensions"
|
||||
- "{{ user_home }}/.local/share/gnome-shell/extensions"
|
||||
|
||||
- name: Gather workstation user account data
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ username }}"
|
||||
|
||||
- name: Set workstation GNOME session environment
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.set_fact:
|
||||
workstation_user_uid: "{{ ansible_facts.getent_passwd[username][1] }}"
|
||||
workstation_gnome_extension_dir: "{{ user_home }}/.cache/gnome-shell/extensions"
|
||||
workstation_gnome_environment:
|
||||
HOME: "{{ user_home }}"
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ ansible_facts.getent_passwd[username][1] }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ ansible_facts.getent_passwd[username][1] }}/bus"
|
||||
|
||||
- name: Read current workstation GNOME managed settings
|
||||
tags: [gnome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- gsettings
|
||||
- get
|
||||
- "{{ item.schema }}{{ ':' ~ item.path if item.path is defined else '' }}"
|
||||
- "{{ item.key }}"
|
||||
become_user: "{{ username }}"
|
||||
environment: "{{ workstation_gnome_environment }}"
|
||||
loop: "{{ workstation_gnome_managed_settings | default([]) }}"
|
||||
loop_control:
|
||||
label: >-
|
||||
{{ item.schema }}{{ ':' ~ item.path if item.path is defined else '' }} {{ item.key }}
|
||||
register: workstation_gnome_managed_settings_current
|
||||
changed_when: false
|
||||
|
||||
- name: Apply workstation GNOME managed settings
|
||||
tags: [gnome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- gsettings
|
||||
- set
|
||||
- "{{ item.item.schema }}{{ ':' ~ item.item.path if item.item.path is defined else '' }}"
|
||||
- "{{ item.item.key }}"
|
||||
- "{{ item.item.value }}"
|
||||
become_user: "{{ username }}"
|
||||
environment: "{{ workstation_gnome_environment }}"
|
||||
loop: "{{ workstation_gnome_managed_settings_current.results | default([]) }}"
|
||||
loop_control:
|
||||
label: >-
|
||||
{{ item.item.schema }}{{ ':' ~ item.item.path if item.item.path is defined else '' }} {{ item.item.key }}
|
||||
changed_when: true
|
||||
when: item.stdout | trim != item.item.value
|
||||
|
||||
- name: Apply workstation GNOME extension dconf settings
|
||||
tags: [gnome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- dconf
|
||||
- write
|
||||
- "{{ item.path }}{{ item.key }}"
|
||||
- "{{ item.value }}"
|
||||
become_user: "{{ username }}"
|
||||
loop: "{{ workstation_gnome_extension_dconf_settings | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}{{ item.key }}"
|
||||
changed_when: true
|
||||
|
||||
- name: Check whether VS Code CLI is available on workstation host
|
||||
tags: [packages, vscode]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- code
|
||||
- --version
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
register: workstation_vscode_cli
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: (workstation_host_vscode_extensions | default([])) | length > 0
|
||||
|
||||
- name: Ensure VS Code CLI is available before managing extensions
|
||||
tags: [packages, vscode]
|
||||
ansible.builtin.fail:
|
||||
msg: Ensure Visual Studio Code is installed before managing workstation VS Code extensions.
|
||||
when:
|
||||
- (workstation_host_vscode_extensions | default([])) | length > 0
|
||||
- workstation_vscode_cli.rc != 0
|
||||
|
||||
- name: Read installed VS Code extensions on workstation host
|
||||
tags: [packages, vscode]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- code
|
||||
- --list-extensions
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
register: workstation_vscode_extensions_current
|
||||
changed_when: false
|
||||
when:
|
||||
- (workstation_host_vscode_extensions | default([])) | length > 0
|
||||
- workstation_vscode_cli.rc == 0
|
||||
|
||||
- name: Install VS Code extensions on workstation host
|
||||
tags: [packages, vscode]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- code
|
||||
- --install-extension
|
||||
- "{{ item }}"
|
||||
- --force
|
||||
become_user: "{{ username }}"
|
||||
environment:
|
||||
HOME: "{{ user_home }}"
|
||||
loop: "{{ workstation_host_vscode_extensions | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- workstation_vscode_cli.rc == 0
|
||||
- item not in (workstation_vscode_extensions_current.stdout_lines | default([]))
|
||||
|
||||
- name: Download workstation GNOME extension archives
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.get_url:
|
||||
url: >-
|
||||
https://extensions.gnome.org/download-extension/{{ item.uuid }}.shell-extension.zip?version_tag={{ item.version_tag }}
|
||||
dest: "{{ workstation_gnome_extension_dir }}/{{ item.uuid }}.zip"
|
||||
owner: "{{ username }}"
|
||||
group: "{{ user_group }}"
|
||||
mode: "0644"
|
||||
loop: "{{ workstation_gnome_extensions | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.uuid }}"
|
||||
|
||||
- name: Check installed workstation GNOME extensions
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.stat:
|
||||
path: "{{ user_home }}/.local/share/gnome-shell/extensions/{{ item.uuid }}/metadata.json"
|
||||
loop: "{{ workstation_gnome_extensions | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.uuid }}"
|
||||
register: workstation_gnome_extension_install_state
|
||||
|
||||
- name: Install workstation GNOME extensions from downloaded archives
|
||||
tags: [packages, gnome]
|
||||
ansible.builtin.command:
|
||||
cmd: "gnome-extensions install --force {{ workstation_gnome_extension_dir }}/{{ item.uuid }}.zip"
|
||||
become_user: "{{ username }}"
|
||||
environment: "{{ workstation_gnome_environment }}"
|
||||
loop: "{{ workstation_gnome_extensions | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.uuid }}"
|
||||
when: >-
|
||||
not (
|
||||
workstation_gnome_extension_install_state.results
|
||||
| selectattr('item.uuid', 'equalto', item.uuid)
|
||||
| map(attribute='stat.exists')
|
||||
| first
|
||||
| default(false)
|
||||
)
|
||||
|
||||
- name: Read current workstation GNOME enabled extensions
|
||||
tags: [gnome]
|
||||
ansible.builtin.command:
|
||||
cmd: gsettings get org.gnome.shell enabled-extensions
|
||||
become_user: "{{ username }}"
|
||||
environment: "{{ workstation_gnome_environment }}"
|
||||
register: workstation_enabled_gnome_extensions_current
|
||||
changed_when: false
|
||||
|
||||
- name: Compute desired workstation GNOME enabled extensions
|
||||
tags: [gnome]
|
||||
ansible.builtin.set_fact:
|
||||
workstation_enabled_gnome_extensions_current_list: >-
|
||||
{{
|
||||
(
|
||||
workstation_enabled_gnome_extensions_current.stdout
|
||||
| default('')
|
||||
| regex_search('\[.*\]')
|
||||
| default('[]', true)
|
||||
)
|
||||
| from_yaml
|
||||
}}
|
||||
|
||||
- name: Build desired workstation GNOME enabled extensions list
|
||||
tags: [gnome]
|
||||
ansible.builtin.set_fact:
|
||||
workstation_enabled_gnome_extensions_desired: >-
|
||||
{{
|
||||
(
|
||||
workstation_enabled_gnome_extensions_current_list
|
||||
+ (
|
||||
workstation_gnome_extensions
|
||||
| default([])
|
||||
| selectattr('enabled', 'defined')
|
||||
| selectattr('enabled')
|
||||
| map(attribute='uuid')
|
||||
| list
|
||||
)
|
||||
)
|
||||
| difference(workstation_disabled_gnome_extensions | default([]))
|
||||
| unique
|
||||
| sort
|
||||
}}
|
||||
|
||||
- name: Build workstation GNOME extensions gsettings payload
|
||||
tags: [gnome]
|
||||
ansible.builtin.set_fact:
|
||||
workstation_enabled_gnome_extensions_desired_gsettings: >-
|
||||
[{% for extension_uuid in workstation_enabled_gnome_extensions_desired -%}
|
||||
'{{ extension_uuid | replace("'", "\\'") }}'{% if not loop.last %}, {% endif %}
|
||||
{%- endfor %}]
|
||||
|
||||
- name: Determine whether workstation GNOME enabled extensions must change
|
||||
tags: [gnome]
|
||||
ansible.builtin.set_fact:
|
||||
workstation_gnome_extensions_state_changed: >-
|
||||
{{ (workstation_enabled_gnome_extensions_current_list | sort) != workstation_enabled_gnome_extensions_desired }}
|
||||
|
||||
- name: Apply workstation GNOME enabled extensions list
|
||||
tags: [gnome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- gsettings
|
||||
- set
|
||||
- org.gnome.shell
|
||||
- enabled-extensions
|
||||
- "{{ workstation_enabled_gnome_extensions_desired_gsettings }}"
|
||||
become_user: "{{ username }}"
|
||||
environment: "{{ workstation_gnome_environment }}"
|
||||
changed_when: workstation_gnome_extensions_state_changed
|
||||
when: workstation_gnome_extensions_state_changed
|
||||
|
||||
- name: Enable UFW firewall on workstation
|
||||
tags: [services, packages]
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
when: workstation_firewall_backend | default('ufw') == 'ufw'
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
- name: Enable FreeBSD rc services
|
||||
tags: [services, packages]
|
||||
ansible.builtin.service:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
loop: "{{ host_enabled_services | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
18
ansible/roles/wireguard_overlay/defaults/main.yml
Normal file
18
ansible/roles/wireguard_overlay/defaults/main.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
wireguard_overlay_enabled: false
|
||||
wireguard_interface: wg0
|
||||
wireguard_config_dir: /etc/wireguard
|
||||
wireguard_private_key_path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.key"
|
||||
wireguard_config_path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.conf"
|
||||
wireguard_address: CHANGEME_WIREGUARD_ADDRESS
|
||||
wireguard_listen_port: 0
|
||||
wireguard_mtu: 1420
|
||||
wireguard_firewalld_zone: wireguard
|
||||
wireguard_public_firewalld_zone: public
|
||||
wireguard_enable_ipv4_forwarding: false
|
||||
wireguard_reload_rootful_podman_networks: false
|
||||
wireguard_handshake_retries: 12
|
||||
wireguard_handshake_delay: 5
|
||||
wireguard_peers: []
|
||||
wireguard_packages:
|
||||
- wireguard-tools
|
||||
10
ansible/roles/wireguard_overlay/handlers/main.yml
Normal file
10
ansible/roles/wireguard_overlay/handlers/main.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Restart WireGuard interface
|
||||
tags: [wireguard, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "wg-quick@{{ wireguard_interface }}.service"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when:
|
||||
- wireguard_overlay_enabled | bool
|
||||
- not ansible_check_mode
|
||||
192
ansible/roles/wireguard_overlay/tasks/main.yml
Normal file
192
ansible/roles/wireguard_overlay/tasks/main.yml
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
- name: Configure WireGuard overlay
|
||||
tags: [wireguard, services]
|
||||
when: wireguard_overlay_enabled | bool
|
||||
block:
|
||||
- name: Validate WireGuard host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wireguard_address != 'CHANGEME_WIREGUARD_ADDRESS'
|
||||
- wireguard_address is match('^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$')
|
||||
- wireguard_peers | length > 0
|
||||
- wireguard_peers | map(attribute='host') | difference(ansible_play_hosts_all) | length == 0
|
||||
fail_msg: >-
|
||||
Configure this host's WireGuard address and peers, and run the first
|
||||
key bootstrap against every peer in the same play.
|
||||
|
||||
- name: Install WireGuard userspace tools
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ wireguard_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Create private WireGuard configuration directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ wireguard_config_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
|
||||
- name: Check for an existing WireGuard private key
|
||||
ansible.builtin.stat:
|
||||
path: "{{ wireguard_private_key_path }}"
|
||||
register: wireguard_private_key_stat
|
||||
|
||||
- name: Generate a missing WireGuard private key
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- wg
|
||||
- genkey
|
||||
register: wireguard_generated_private_key
|
||||
changed_when: true
|
||||
no_log: true
|
||||
when:
|
||||
- not wireguard_private_key_stat.stat.exists
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Persist the generated WireGuard private key
|
||||
ansible.builtin.copy:
|
||||
content: "{{ wireguard_generated_private_key.stdout }}\n"
|
||||
dest: "{{ wireguard_private_key_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
when:
|
||||
- not wireguard_private_key_stat.stat.exists
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Require a private key during check mode
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wireguard_private_key_stat.stat.exists
|
||||
fail_msg: >-
|
||||
The initial WireGuard key generation cannot be simulated safely in
|
||||
check mode. Run the gated WireGuard play once without --check.
|
||||
when: ansible_check_mode
|
||||
|
||||
- name: Read the persisted WireGuard private key
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ wireguard_private_key_path }}"
|
||||
register: wireguard_private_key_material
|
||||
no_log: true
|
||||
|
||||
- name: Derive this host's WireGuard public key
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- wg
|
||||
- pubkey
|
||||
stdin: "{{ wireguard_private_key_material.content | b64decode | trim }}"
|
||||
register: wireguard_derived_public_key
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Publish this host's WireGuard public key
|
||||
ansible.builtin.set_fact:
|
||||
wireguard_public_key: "{{ wireguard_derived_public_key.stdout }}"
|
||||
|
||||
- name: Require every peer's generated public key
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostvars[item.host].wireguard_public_key is defined
|
||||
- hostvars[item.host].wireguard_public_key | length > 0
|
||||
fail_msg: >-
|
||||
The public key for {{ item.host }} is unavailable. The first
|
||||
WireGuard run must include every overlay host.
|
||||
loop: "{{ wireguard_peers }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Render the private WireGuard interface configuration
|
||||
ansible.builtin.template:
|
||||
src: wg.conf.j2
|
||||
dest: "{{ wireguard_config_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
diff: false
|
||||
no_log: true
|
||||
notify: Restart WireGuard interface
|
||||
|
||||
- name: Enable IPv4 forwarding for the ingress host
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: "1"
|
||||
state: present
|
||||
sysctl_set: true
|
||||
reload: true
|
||||
when: wireguard_enable_ipv4_forwarding | bool
|
||||
|
||||
- name: Create the WireGuard firewalld zone
|
||||
ansible.posix.firewalld:
|
||||
zone: "{{ wireguard_firewalld_zone }}"
|
||||
state: present
|
||||
permanent: true
|
||||
register: wireguard_firewalld_zone_result
|
||||
|
||||
- name: Reload firewalld after creating the WireGuard zone
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld.service
|
||||
state: reloaded
|
||||
when:
|
||||
- wireguard_firewalld_zone_result is changed
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Restore rootful Podman networking after firewalld reload
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- network
|
||||
- reload
|
||||
- --all
|
||||
register: wireguard_podman_network_reload
|
||||
changed_when: wireguard_podman_network_reload.stdout_lines | length > 0
|
||||
when:
|
||||
- wireguard_firewalld_zone_result is changed
|
||||
- wireguard_reload_rootful_podman_networks | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Assign the WireGuard interface to its firewalld zone
|
||||
ansible.posix.firewalld:
|
||||
interface: "{{ wireguard_interface }}"
|
||||
zone: "{{ wireguard_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
|
||||
- name: Permit this host's public WireGuard listener
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ wireguard_listen_port }}/udp"
|
||||
zone: "{{ wireguard_public_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
when: wireguard_listen_port | int > 0
|
||||
|
||||
- name: Enable the WireGuard interface
|
||||
ansible.builtin.systemd:
|
||||
name: "wg-quick@{{ wireguard_interface }}.service"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Apply pending WireGuard handlers before verification
|
||||
ansible.builtin.meta: flush_handlers
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Wait for every WireGuard peer handshake
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- wg
|
||||
- show
|
||||
- "{{ wireguard_interface }}"
|
||||
- latest-handshakes
|
||||
register: wireguard_latest_handshakes
|
||||
changed_when: false
|
||||
retries: "{{ wireguard_handshake_retries }}"
|
||||
delay: "{{ wireguard_handshake_delay }}"
|
||||
until:
|
||||
- wireguard_latest_handshakes.stdout_lines | length == wireguard_peers | length
|
||||
- wireguard_latest_handshakes.stdout_lines | select('search', '\t0$') | list | length == 0
|
||||
when: not ansible_check_mode
|
||||
25
ansible/roles/wireguard_overlay/templates/wg.conf.j2
Normal file
25
ansible/roles/wireguard_overlay/templates/wg.conf.j2
Normal file
@@ -0,0 +1,25 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Interface]
|
||||
Address = {{ wireguard_address }}
|
||||
PrivateKey = {{ wireguard_private_key_material.content | b64decode | trim }}
|
||||
MTU = {{ wireguard_mtu }}
|
||||
SaveConfig = false
|
||||
{% if wireguard_listen_port | int > 0 %}
|
||||
ListenPort = {{ wireguard_listen_port }}
|
||||
{% endif %}
|
||||
|
||||
{% for peer in wireguard_peers %}
|
||||
[Peer]
|
||||
# {{ peer.name }}
|
||||
PublicKey = {{ hostvars[peer.host].wireguard_public_key }}
|
||||
AllowedIPs = {{ peer.allowed_ips | join(', ') }}
|
||||
{% if peer.endpoint is defined %}
|
||||
Endpoint = {{ peer.endpoint }}
|
||||
{% endif %}
|
||||
{% if peer.persistent_keepalive | default(0) | int > 0 %}
|
||||
PersistentKeepalive = {{ peer.persistent_keepalive }}
|
||||
{% endif %}
|
||||
{% if not loop.last %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -7,6 +7,7 @@
|
||||
tags: [always]
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/vault.yml"
|
||||
no_log: true
|
||||
when: >-
|
||||
lookup('ansible.builtin.fileglob', playbook_dir + '/../secrets/vault.yml',
|
||||
errors='ignore', wantlist=True) | length > 0
|
||||
@@ -15,12 +16,28 @@
|
||||
tags: [always]
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/vault.local.yml"
|
||||
no_log: true
|
||||
when: >-
|
||||
lookup('ansible.builtin.fileglob', playbook_dir + '/../secrets/vault.local.yml',
|
||||
errors='ignore', wantlist=True) | length > 0
|
||||
|
||||
- name: Reject conflicting AI coding-agent lifecycle flags
|
||||
tags: [always, ai_agents]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not (item.value.install_enabled | bool and item.value.uninstall_enabled | bool)
|
||||
fail_msg: >-
|
||||
{{ item.key }} has both install_enabled and uninstall_enabled set to true.
|
||||
Choose either installation or removal before running the playbook.
|
||||
loop: "{{ ai_agents | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
roles:
|
||||
- dotfiles_common
|
||||
- role: dotfiles_common
|
||||
when:
|
||||
- "'platform_rocky' not in group_names"
|
||||
- "'platform_fedora_iot' not in group_names"
|
||||
|
||||
- name: Configure Void platform
|
||||
hosts: platform_void
|
||||
@@ -40,14 +57,6 @@
|
||||
- profile_desktop_niri
|
||||
- profile_desktop_host
|
||||
|
||||
- name: Configure FreeBSD platform
|
||||
hosts: platform_freebsd
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- packages_freebsd
|
||||
- services_freebsd
|
||||
|
||||
- name: Configure Fedora platform
|
||||
hosts: platform_fedora
|
||||
become: true
|
||||
@@ -56,6 +65,50 @@
|
||||
- packages_fedora
|
||||
- services_systemd
|
||||
|
||||
- name: Configure Rocky Linux platform
|
||||
hosts: platform_rocky
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- packages_rocky
|
||||
- services_systemd
|
||||
|
||||
- name: Configure WireGuard overlay
|
||||
hosts: wireguard_overlay
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- wireguard_overlay
|
||||
|
||||
- name: Configure Aegis Fedora IoT profile
|
||||
hosts: role_aegis
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- profile_aegis
|
||||
|
||||
- name: Configure Atlas NAS profile
|
||||
hosts: atlas
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- profile_atlas
|
||||
|
||||
- name: Configure Atlas phase-one backend services
|
||||
hosts: role_backend_phase1
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- profile_backend_phase1
|
||||
|
||||
- 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
|
||||
@@ -77,28 +130,9 @@
|
||||
roles:
|
||||
- profile_workstation_dev_common
|
||||
|
||||
- name: Configure native Linux workstation host layer
|
||||
hosts: workstation_host_linux
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- profile_workstation_gnome
|
||||
|
||||
- name: Configure WSL workstation development layer
|
||||
- name: Configure Fedora WSL workstation development layer
|
||||
hosts: workstation_dev_wsl
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- packages_ubuntu
|
||||
- services_systemd
|
||||
- profile_workstation_dev_common
|
||||
- profile_workstation_dev_wsl
|
||||
|
||||
- name: Configure Ubuntu server
|
||||
hosts: ubuntu_server
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- packages_ubuntu
|
||||
- services_systemd
|
||||
- profile_server
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
---
|
||||
version: "3.8"
|
||||
{% set selinux_volume_option = server_compose_selinux_mount_option | default('') %}
|
||||
name: server
|
||||
|
||||
services:
|
||||
navidrome:
|
||||
image: deluan/navidrome:latest
|
||||
container_name: navidrome
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "4533"
|
||||
environment:
|
||||
ND_DATABASE_URL: "postgres://navidrome:{{ vault_navidrome_db_password }}@navidromedb:5432/navidrome_db?sslmode=disable"
|
||||
ND_SESSIONTIMEOUT: 24h
|
||||
ND_ENABLETRANSCODING: "true"
|
||||
|
||||
volumes:
|
||||
- "/opt/navidrome/data:/data"
|
||||
- "/opt/music:/music:ro"
|
||||
user: "1000:1000"
|
||||
networks:
|
||||
- web
|
||||
depends_on:
|
||||
- navidromedb
|
||||
nginx-proxy-manager:
|
||||
image: jc21/nginx-proxy-manager:latest
|
||||
image: docker.io/jc21/nginx-proxy-manager:latest
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "81:81"
|
||||
- "127.0.0.1:81:81"
|
||||
extra_hosts:
|
||||
- "host.containers.internal:host-gateway"
|
||||
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
|
||||
|
||||
# Disabled: prometheus does not have enough resources to run Nextcloud AIO.
|
||||
# nextcloud-aio-mastercontainer:
|
||||
@@ -55,20 +38,6 @@ services:
|
||||
# networks:
|
||||
# - web
|
||||
|
||||
navidromedb:
|
||||
image: postgres:13
|
||||
container_name: navidromedb
|
||||
restart: unless-stopped
|
||||
mem_limit: 2048m
|
||||
environment:
|
||||
POSTGRES_DB: "navidrome_db"
|
||||
POSTGRES_USER: "navidrome"
|
||||
POSTGRES_PASSWORD: "{{ vault_postgres_root_password }}"
|
||||
|
||||
volumes:
|
||||
- "/opt/postgres/data:/var/lib/postgresql/data"
|
||||
networks:
|
||||
- web
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1.25.2
|
||||
container_name: gitea
|
||||
@@ -77,39 +46,39 @@ services:
|
||||
- USER_GID=1100
|
||||
restart: always
|
||||
networks:
|
||||
- gitea
|
||||
- web
|
||||
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"
|
||||
|
||||
{% if server_syncthing_enabled | default(true) | bool %}
|
||||
syncthing:
|
||||
image: syncthing/syncthing:2
|
||||
image: docker.io/syncthing/syncthing:2
|
||||
container_name: syncthing
|
||||
hostname: syncthing
|
||||
restart: unless-stopped
|
||||
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"
|
||||
- "21027:21027/udp"
|
||||
networks:
|
||||
- web
|
||||
{% endif %}
|
||||
|
||||
networks:
|
||||
web:
|
||||
name: server_web
|
||||
external: false
|
||||
gitea:
|
||||
external: false
|
||||
|
||||
# volumes:
|
||||
# nextcloud_aio_mastercontainer:
|
||||
|
||||
@@ -24,19 +24,19 @@
|
||||
|
||||
[merge]
|
||||
conflictstyle = diff3
|
||||
tool = vscode
|
||||
tool = bobide
|
||||
|
||||
[mergetool]
|
||||
prompt = false
|
||||
|
||||
[mergetool "vscode"]
|
||||
cmd = code --reuse-window --wait $MERGED
|
||||
[mergetool "bobide"]
|
||||
cmd = bobide --reuse-window --wait $MERGED
|
||||
|
||||
[diff]
|
||||
tool = vscode
|
||||
tool = bobide
|
||||
|
||||
[difftool "vscode"]
|
||||
cmd = code --reuse-window --wait --diff $LOCAL $REMOTE
|
||||
[difftool "bobide"]
|
||||
cmd = bobide --reuse-window --wait --diff $LOCAL $REMOTE
|
||||
|
||||
[color]
|
||||
ui = always
|
||||
@@ -89,11 +89,11 @@
|
||||
side-by-side = true
|
||||
|
||||
[http "https://gitlab.ecaas.datacenter.comune.roma"]
|
||||
sslBackend = gnutls
|
||||
sslBackend = openssl
|
||||
sslCAInfo = ~/.local/share/certs/ecaas.datacenter.comune.roma.crt
|
||||
|
||||
[http "https://gitlab.datacenter.comune.roma"]
|
||||
sslBackend = gnutls
|
||||
sslBackend = openssl
|
||||
sslCAInfo = ~/.local/share/certs/datacenter.comune.roma.crt
|
||||
|
||||
[credential]
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
# =========================
|
||||
# Portable Bash config
|
||||
# Target: Void Linux + FreeBSD
|
||||
# =========================
|
||||
|
||||
# Exit if not interactive
|
||||
[[ $- != *i* ]] && return
|
||||
|
||||
# --- environment detection
|
||||
case "$(uname -s)" in
|
||||
Linux) PLATFORM="linux" ;;
|
||||
FreeBSD) PLATFORM="freebsd" ;;
|
||||
*) PLATFORM="other" ;;
|
||||
esac
|
||||
|
||||
export PLATFORM
|
||||
|
||||
# --- history
|
||||
HISTSIZE=10000
|
||||
HISTFILESIZE=20000
|
||||
@@ -438,15 +428,10 @@ extract() {
|
||||
}
|
||||
|
||||
# =========================
|
||||
# OS-specific small touches
|
||||
# Small touches
|
||||
# =========================
|
||||
|
||||
if [ "$PLATFORM" = "freebsd" ]; then
|
||||
alias df='df -h'
|
||||
alias du='du -h'
|
||||
elif [ "$PLATFORM" = "linux" ]; then
|
||||
alias df='df -h'
|
||||
alias du='du -h'
|
||||
fi
|
||||
alias df='df -h'
|
||||
alias du='du -h'
|
||||
|
||||
[ -r "$HOME/.bashrc.aliases" ] && . "$HOME/.bashrc.aliases"
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
# Additional Bash aliases.
|
||||
# Keep this file as the extension point for shared aliases.
|
||||
|
||||
# Nginx Proxy Manager administration tunnel on Prometheus.
|
||||
alias npm-tunnel='ssh -N -o ExitOnForwardFailure=yes -L 8181:127.0.0.1:81 rocky@179.237.102.172'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Server
|
||||
|
||||
- Ubuntu minimal server
|
||||
- Rocky Linux 9 server
|
||||
- Self-hosting services
|
||||
- Low attack surface
|
||||
|
||||
@@ -61,49 +61,49 @@ video/x-msvideo=mpv.desktop
|
||||
video/x-ogm+ogg=mpv.desktop
|
||||
video/x-theora=mpv.desktop
|
||||
video/x-theora+ogg=mpv.desktop
|
||||
application/msword=libreoffice-writer.desktop
|
||||
application/rtf=libreoffice-writer.desktop
|
||||
application/vnd.ms-word=libreoffice-writer.desktop
|
||||
application/vnd.ms-word.document.macroEnabled.12=libreoffice-writer.desktop
|
||||
application/vnd.ms-word.template.macroEnabled.12=libreoffice-writer.desktop
|
||||
application/vnd.oasis.opendocument.text=libreoffice-writer.desktop
|
||||
application/vnd.oasis.opendocument.text-template=libreoffice-writer.desktop
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document=libreoffice-writer.desktop
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.template=libreoffice-writer.desktop
|
||||
application/x-doc=libreoffice-writer.desktop
|
||||
text/rtf=libreoffice-writer.desktop
|
||||
application/csv=libreoffice-calc.desktop
|
||||
application/excel=libreoffice-calc.desktop
|
||||
application/msexcel=libreoffice-calc.desktop
|
||||
application/tab-separated-values=libreoffice-calc.desktop
|
||||
application/vnd.ms-excel=libreoffice-calc.desktop
|
||||
application/vnd.ms-excel.sheet.binary.macroEnabled.12=libreoffice-calc.desktop
|
||||
application/vnd.ms-excel.sheet.macroEnabled.12=libreoffice-calc.desktop
|
||||
application/vnd.ms-excel.template.macroEnabled.12=libreoffice-calc.desktop
|
||||
application/vnd.oasis.opendocument.spreadsheet=libreoffice-calc.desktop
|
||||
application/vnd.oasis.opendocument.spreadsheet-template=libreoffice-calc.desktop
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=libreoffice-calc.desktop
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.template=libreoffice-calc.desktop
|
||||
application/x-dos_ms_excel=libreoffice-calc.desktop
|
||||
application/x-excel=libreoffice-calc.desktop
|
||||
application/x-ms-excel=libreoffice-calc.desktop
|
||||
application/x-msexcel=libreoffice-calc.desktop
|
||||
text/comma-separated-values=libreoffice-calc.desktop
|
||||
text/csv=libreoffice-calc.desktop
|
||||
text/tab-separated-values=libreoffice-calc.desktop
|
||||
text/x-comma-separated-values=libreoffice-calc.desktop
|
||||
text/x-csv=libreoffice-calc.desktop
|
||||
application/mspowerpoint=libreoffice-impress.desktop
|
||||
application/vnd.ms-powerpoint=libreoffice-impress.desktop
|
||||
application/vnd.ms-powerpoint.presentation.macroEnabled.12=libreoffice-impress.desktop
|
||||
application/vnd.ms-powerpoint.slideshow.macroEnabled.12=libreoffice-impress.desktop
|
||||
application/vnd.ms-powerpoint.template.macroEnabled.12=libreoffice-impress.desktop
|
||||
application/vnd.oasis.opendocument.presentation=libreoffice-impress.desktop
|
||||
application/vnd.oasis.opendocument.presentation-template=libreoffice-impress.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation=libreoffice-impress.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slide=libreoffice-impress.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slideshow=libreoffice-impress.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.template=libreoffice-impress.desktop
|
||||
application/msword=org.onlyoffice.desktopeditors.desktop
|
||||
application/rtf=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-word=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-word.document.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-word.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.text=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.text-template=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.template=org.onlyoffice.desktopeditors.desktop
|
||||
application/x-doc=org.onlyoffice.desktopeditors.desktop
|
||||
text/rtf=org.onlyoffice.desktopeditors.desktop
|
||||
application/csv=org.onlyoffice.desktopeditors.desktop
|
||||
application/excel=org.onlyoffice.desktopeditors.desktop
|
||||
application/msexcel=org.onlyoffice.desktopeditors.desktop
|
||||
application/tab-separated-values=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-excel=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-excel.sheet.binary.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-excel.sheet.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-excel.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.spreadsheet=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.spreadsheet-template=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.template=org.onlyoffice.desktopeditors.desktop
|
||||
application/x-dos_ms_excel=org.onlyoffice.desktopeditors.desktop
|
||||
application/x-excel=org.onlyoffice.desktopeditors.desktop
|
||||
application/x-ms-excel=org.onlyoffice.desktopeditors.desktop
|
||||
application/x-msexcel=org.onlyoffice.desktopeditors.desktop
|
||||
text/comma-separated-values=org.onlyoffice.desktopeditors.desktop
|
||||
text/csv=org.onlyoffice.desktopeditors.desktop
|
||||
text/tab-separated-values=org.onlyoffice.desktopeditors.desktop
|
||||
text/x-comma-separated-values=org.onlyoffice.desktopeditors.desktop
|
||||
text/x-csv=org.onlyoffice.desktopeditors.desktop
|
||||
application/mspowerpoint=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-powerpoint=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-powerpoint.presentation.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-powerpoint.slideshow.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.ms-powerpoint.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.presentation=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.oasis.opendocument.presentation-template=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slide=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slideshow=org.onlyoffice.desktopeditors.desktop
|
||||
application/vnd.openxmlformats-officedocument.presentationml.template=org.onlyoffice.desktopeditors.desktop
|
||||
application/gzip=xarchiver.desktop
|
||||
application/vnd.debian.binary-package=xarchiver.desktop
|
||||
application/vnd.ms-cab-compressed=xarchiver.desktop
|
||||
@@ -213,49 +213,49 @@ video/x-msvideo=mpv.desktop;
|
||||
video/x-ogm+ogg=mpv.desktop;
|
||||
video/x-theora=mpv.desktop;
|
||||
video/x-theora+ogg=mpv.desktop;
|
||||
application/msword=libreoffice-writer.desktop;
|
||||
application/rtf=libreoffice-writer.desktop;
|
||||
application/vnd.ms-word=libreoffice-writer.desktop;
|
||||
application/vnd.ms-word.document.macroEnabled.12=libreoffice-writer.desktop;
|
||||
application/vnd.ms-word.template.macroEnabled.12=libreoffice-writer.desktop;
|
||||
application/vnd.oasis.opendocument.text=libreoffice-writer.desktop;
|
||||
application/vnd.oasis.opendocument.text-template=libreoffice-writer.desktop;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document=libreoffice-writer.desktop;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.template=libreoffice-writer.desktop;
|
||||
application/x-doc=libreoffice-writer.desktop;
|
||||
text/rtf=libreoffice-writer.desktop;
|
||||
application/csv=libreoffice-calc.desktop;
|
||||
application/excel=libreoffice-calc.desktop;
|
||||
application/msexcel=libreoffice-calc.desktop;
|
||||
application/tab-separated-values=libreoffice-calc.desktop;
|
||||
application/vnd.ms-excel=libreoffice-calc.desktop;
|
||||
application/vnd.ms-excel.sheet.binary.macroEnabled.12=libreoffice-calc.desktop;
|
||||
application/vnd.ms-excel.sheet.macroEnabled.12=libreoffice-calc.desktop;
|
||||
application/vnd.ms-excel.template.macroEnabled.12=libreoffice-calc.desktop;
|
||||
application/vnd.oasis.opendocument.spreadsheet=libreoffice-calc.desktop;
|
||||
application/vnd.oasis.opendocument.spreadsheet-template=libreoffice-calc.desktop;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=libreoffice-calc.desktop;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.template=libreoffice-calc.desktop;
|
||||
application/x-dos_ms_excel=libreoffice-calc.desktop;
|
||||
application/x-excel=libreoffice-calc.desktop;
|
||||
application/x-ms-excel=libreoffice-calc.desktop;
|
||||
application/x-msexcel=libreoffice-calc.desktop;
|
||||
text/comma-separated-values=libreoffice-calc.desktop;
|
||||
text/csv=libreoffice-calc.desktop;
|
||||
text/tab-separated-values=libreoffice-calc.desktop;
|
||||
text/x-comma-separated-values=libreoffice-calc.desktop;
|
||||
text/x-csv=libreoffice-calc.desktop;
|
||||
application/mspowerpoint=libreoffice-impress.desktop;
|
||||
application/vnd.ms-powerpoint=libreoffice-impress.desktop;
|
||||
application/vnd.ms-powerpoint.presentation.macroEnabled.12=libreoffice-impress.desktop;
|
||||
application/vnd.ms-powerpoint.slideshow.macroEnabled.12=libreoffice-impress.desktop;
|
||||
application/vnd.ms-powerpoint.template.macroEnabled.12=libreoffice-impress.desktop;
|
||||
application/vnd.oasis.opendocument.presentation=libreoffice-impress.desktop;
|
||||
application/vnd.oasis.opendocument.presentation-template=libreoffice-impress.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation=libreoffice-impress.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slide=libreoffice-impress.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slideshow=libreoffice-impress.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.template=libreoffice-impress.desktop;
|
||||
application/msword=org.onlyoffice.desktopeditors.desktop;
|
||||
application/rtf=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-word=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-word.document.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-word.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.text=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.text-template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/x-doc=org.onlyoffice.desktopeditors.desktop;
|
||||
text/rtf=org.onlyoffice.desktopeditors.desktop;
|
||||
application/csv=org.onlyoffice.desktopeditors.desktop;
|
||||
application/excel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/msexcel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/tab-separated-values=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-excel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-excel.sheet.binary.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-excel.sheet.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-excel.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.spreadsheet=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.spreadsheet-template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/x-dos_ms_excel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/x-excel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/x-ms-excel=org.onlyoffice.desktopeditors.desktop;
|
||||
application/x-msexcel=org.onlyoffice.desktopeditors.desktop;
|
||||
text/comma-separated-values=org.onlyoffice.desktopeditors.desktop;
|
||||
text/csv=org.onlyoffice.desktopeditors.desktop;
|
||||
text/tab-separated-values=org.onlyoffice.desktopeditors.desktop;
|
||||
text/x-comma-separated-values=org.onlyoffice.desktopeditors.desktop;
|
||||
text/x-csv=org.onlyoffice.desktopeditors.desktop;
|
||||
application/mspowerpoint=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-powerpoint=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-powerpoint.presentation.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-powerpoint.slideshow.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.ms-powerpoint.template.macroEnabled.12=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.presentation=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.oasis.opendocument.presentation-template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slide=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.slideshow=org.onlyoffice.desktopeditors.desktop;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.template=org.onlyoffice.desktopeditors.desktop;
|
||||
application/gzip=xarchiver.desktop;
|
||||
application/vnd.debian.binary-package=xarchiver.desktop;
|
||||
application/vnd.ms-cab-compressed=xarchiver.desktop;
|
||||
|
||||
@@ -7,8 +7,15 @@
|
||||
:config
|
||||
(load-theme 'nordic-night t))
|
||||
|
||||
(set-frame-font "UbuntuSansMono Nerd Font 14" nil t)
|
||||
(add-to-list 'default-frame-alist '(font . "UbuntuSansMono Nerd Font-14"))
|
||||
(defconst fscotto/default-font
|
||||
(pcase system-type
|
||||
('gnu/linux "UbuntuSansMono Nerd Font 14")
|
||||
('windows-nt "JetBrainsMono NF 12")
|
||||
(_ "monospace 14"))
|
||||
"Default font for graphical Emacs frames on the current platform.")
|
||||
|
||||
(set-frame-font fscotto/default-font nil t)
|
||||
(add-to-list 'default-frame-alist `(font . ,fscotto/default-font))
|
||||
(add-to-list 'default-frame-alist '(fullscreen . maximized))
|
||||
|
||||
(tool-bar-mode -1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Host vps
|
||||
Host prometheus-ubuntu
|
||||
IdentityFile ~/.ssh/id_rsa_vps
|
||||
|
||||
Host *
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user