Technical deep dive into how the project is structured and how it works.
What you'll learn:
- Directory structure - Where everything lives
- Execution flow - Bootstrap → main → optional
- Configuration management - How settings work
- Branching strategy - Version-specific branches
- Security model - Vault encryption and SSH keys
Best for: Contributors, system administrators, and curious developers who want to understand the internals.
Time to read: 8-10 minutes
fedora-desktop/
├── ansible.cfg # Ansible configuration
├── requirements.yml # Ansible Galaxy dependencies
├── run.bash # Bootstrap installer script
├── vault-pass.secret # Vault password (gitignored)
├── CLAUDE.md # Claude Code instructions
│
├── vars/
│ └── fedora-version.yml # Target Fedora version
│
├── environment/
│ └── localhost/
│ ├── hosts.yml # Inventory definition
│ └── host_vars/
│ └── localhost.yml # User-specific variables
│
├── playbooks/
│ ├── playbook-main.yml # Main orchestrator
│ └── imports/
│ ├── play-*.yml # Core playbooks
│ └── optional/
│ ├── common/ # General optional features
│ ├── hardware-specific/# Hardware drivers/configs
│ ├── experimental/ # Bleeding-edge features
│ ├── untested/ # Playbooks not yet verified
│ └── archived/ # Deprecated playbooks
│
├── docs/ # User-facing documentation
├── CLAUDE/ # Agent topic docs (style, QA, security, plans)
├── extensions/ # GNOME Shell extensions source
├── helpers/ # Stdlib-only Python helpers (TDD'd, CI-tested)
├── tasks/ # Standalone Ansible task files
├── tests/ # Test suite
├── fedora-install/ # Fedora installation helpers (ISO/kickstart)
│
├── files/ # Static configuration files
│ ├── etc/ # System configs
│ ├── home/ # User configs
│ ├── opt/ # Opt-tree payloads
│ ├── usr/ # /usr payloads
│ └── var/ # Variable data
│
├── scripts/ # Utility scripts
├── roles/ # Ansible roles
│ └── vendor/ # Third-party roles (from requirements.yml)
│
└── untracked/ # Runtime data (gitignored)
└── facts/ # Ansible fact cache
The bootstrap script:
- Validates system requirements
- Checks Fedora version against
vars/fedora-version.yml - Installs core dependencies
- Configures GitHub CLI authentication
- Generates SSH keys
- Clones the repository
- Collects user configuration
- Initializes Ansible vault
- Executes main playbook
playbook-main.yml orchestrates these playbooks in order (all run by default — none are optional):
- play-AA-preflight-sanity.yml: Version and dependency checks
- play-AB-dnf-upgrade.yml: Full system package upgrade
- play-basic-configs.yml: System packages and base configuration
- play-prevent-ssh-suspend.yml: Prevent SSH session suspend
- play-network-wait-tuning.yml: Network startup tuning
- play-mask-intel-lpmd.yml: Mask
intel_lpmd.servicewhere it only adds boot noise- No-op on hosts where the unit is absent (e.g. AMD), so it imports unconditionally.
- play-systemd-user-tweaks.yml: Systemd user session tweaks
- play-nvm-install.yml: Node Version Manager setup
- play-git-configure-and-tools.yml: Git configuration and tools
- play-git-hooks-security.yml: Security pre-commit hooks
- play-firefox.yml: Firefox browser configuration
- play-github-cli-multi.yml: GitHub CLI multi-account support
- play-ms-fonts.yml: Microsoft fonts installation
- play-rpm-fusion.yml: Third-party repository setup
- play-browsers.yml: Additional browser setup
- play-toolbox-install.yml: JetBrains Toolbox
- play-lxc-install-config.yml: LXC container support
- Rootful Docker is optional (
imports/optional/common/play-docker.yml, podman-first policy). On Docker hosts, run Docker before this play so theDOCKER-USERiptables chain exists when LXC reconciles outbound connectivity; on podman-only hosts the Docker-coexistence block is skipped. See the "Reconcile iptables" block inplay-lxc-install-config.yml.
- Rootful Docker is optional (
- play-podman.yml: Rootless Podman (default container engine)
- play-python.yml: Python/pyenv setup
- play-claude-yolo.yml: CCY (Claude container wrapper) installation
- Ordering constraint: CCY must run before
play-claude-code.ymlbecause theccwrapper sources CCY lib files at runtime, andplay-claude-code.ymlasserts the lib is present before deploying it. SeeCLAUDE/Plan/00048-cc-token-source-parity.
- Ordering constraint: CCY must run before
- play-claude-code.yml: Claude Code CLI and
ccwrapper - play-comms.yml: Communication applications
- play-gnome-shell.yml: GNOME Shell configuration
- play-gnome-shell-extensions.yml: GNOME Shell extensions
- play-markless.yml: Markless tool setup
- play-terminal-emulators.yml: Terminal emulator configuration
- play-vscode.yml: Visual Studio Code
- play-vpn.yml: VPN configuration
- play-gsettings.yml: GNOME settings
- play-ZZ-repo-cleanup.yml: Post-run repository cleanup
This repo provisions both a Fedora desktop and a headless Fedora server from the same source
tree. That is the defining architectural constraint, and it is what the scope line in every
play exists to serve.
Which target is being built is auto-detected with zero flags into provisioning_profile
(environment/localhost/group_vars/desktop.yml), from systemctl get-default:
systemctl get-default |
provisioning_profile |
|---|---|
graphical.target |
desktop |
| anything else | server |
The detection is server-biased when uncertain — a box that cannot prove it is a desktop is provisioned as a server, so GUI work is never done on a headless host by accident.
Override for testing or CI (extra-vars have the highest precedence, so the lookup is skipped entirely rather than merely outvoted):
./run.bash -e provisioning_profile=desktop
./run.bash -e provisioning_profile=serverEvery play declares a scope in its play-level vars: block — general | gnome | server.
A play whose scope does not match the detected profile ends immediately via a two-task guard
that is byte-identical everywhere (the QA gate compares the text). general plays run on both.
Full rules: CLAUDE/AnsibleStyle.md §Provisioning Profile Self-Guard. Per-playbook behaviour: playbooks.md.
Manually executed based on needs:
- common/: Development tools, applications
- hardware-specific/: NVIDIA, DisplayLink, laptop power/thermal management
- experimental/: LXDE, VirtualBox
- archived/: Deprecated playbooks (e.g.
play-tlp-battery-optimisation.ymlmoved here)
Key settings:
- Inventory:
./environment/localhost - Connection: Local transport (not SSH)
- Privilege Escalation: sudo with
-HEflags - Vault: Password file at
./vault-pass.secret - Fact Caching: JSON files in
./untracked/facts/
- Global Variables:
vars/fedora-version.yml - Host Variables:
environment/localhost/host_vars/localhost.yml - Playbook Variables: Defined in individual playbooks
- Vault-encrypted: API keys and secrets
Static files are organized by destination:
files/etc/: System configuration filesfiles/home/: User configuration filesfiles/var/: Variable data and scripts
- Each Fedora version has its own branch (F42, F43, etc.)
- Branch name corresponds to Fedora version
vars/fedora-version.ymldefines target version- Default branch updated to current working version
- Vault encryption for sensitive data
- SSH key generation and management
- Passwordless sudo configuration
- GitHub CLI multi-account support
- Vault password file (plaintext, gitignored) — it holds the key that decrypts the vaulted values, so it is by definition not itself encrypted