A de Byl Technologies LLC Nextcloud cloned from the Skudak instance: LibreSign signing for people without an account, registration off (admin-created accounts only), no Group Folders. DNS is a terraform-managed ALIAS to fulfillr.debyltech.com. - containers/debyltech/cloud.yml: nextcloud/mariadb/redis on port 8091. It installs unattended on the first deploy, sends mail through SES as noreply@debyltech.com, and re-asserts the Skudak LibreSign settings. - files/debyltechmail: skudakmail rebranded, with a new black-and-white wordmark and white web-UI logos. - LibreSign is pinned to 14.2.2 from the GitHub release (sha256-checked) rather than `occ app:install`. The app store served a same-day 14.2.3 whose tarball has no binary-signature metadata. 14.2.x also doesn't create its own download dirs, so they're pre-created. - The backup runs nightly at 04:15 to TrueNAS /mnt/glacier/debyltechcloud and reaches personal iDrive via the "iDrive E2 Backup" task; the TrueNAS side excludes /debyltechcloud/_backup/config/**. - Fix the libresign:configure:check gate in both instances: '\berror\b' becomes a backspace in Jinja and never matched, so a check reporting three errors passed clean. Now '\\berror\\b'. - vault: cloud_debyltech_* secrets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Overview
This is a home infrastructure deployment repository using Ansible for automated server configuration and container deployment. The project follows a "one-button deployment" philosophy for managing a home server environment with various self-hosted services.
Development Commands
Core Commands
makeormake lint- Run yamllint on all YAML files. Output may only show "Running yamllint..." and "Done." with no errors listed — this means linting passed. Do NOT run yamllint or ansible-lint manually;make lintis the only lint step needed.make deploy- Deploy all configurations to the home servermake deploy TAGS=sometag- Deploy only specific tagged tasksmake deploy TARGET=specific-host- Deploy to specific host instead of allmake check- Run deployment in dry-run mode showing potential changesmake deploy-labelprint/make check-labelprint- Deploy (or dry-run) the label print proxy Pi. Separate inventory and playbook from the home server - seeansible/roles/labelprint/README.mdmake bootfs BOOTFS=/Volumes/bootfs- Render the label print proxy's cloud-init first-boot files onto a freshly imaged SD cardmake vault- Edit encrypted Ansible vault filemake list-tags- List all available Ansible tagsmake list-tasks- List all Ansible tasksmake git-crypt-backup- Backup git-crypt symmetric key (encrypted with GPG)make git-crypt-restore- Restore git-crypt symmetric key from backup
Environment Setup
The project uses Python virtualenv for dependency management:
- Dependencies are locked in
requirements.txt(ansible + yamllint) - Makefile automatically creates
.venv/and installs dependencies - Vault password is sourced from password manager via
.pass.sh
Git Workflow
- Work directly on
master- no feature branches and no pull requests in this repo. - Commit to
masterand push toorigin/masterwhen asked; don't create a branch first. - Pushing to
masteralso triggers.gitea/workflows/ci-images.yml(see Gitea Actions CI images below), which only rebuilds images whose inputs changed.
Architecture
Directory Structure
ansible/
├── deploy.yml # Main playbook entry point (imports deploy_home.yml)
├── deploy_home.yml # Core playbook with role definitions
├── deploy_labelprint.yml # Label print proxy Pi (separate host, Debian)
├── inventories/home/ # Inventory configuration
├── inventories/labelprint/ # Label print proxy inventory
├── roles/ # Ansible roles organized by function
│ ├── common/ # Base system configuration
│ ├── git/ # Git repository management
│ ├── podman/ # Container orchestration
│ ├── ssl/ # Legacy SSL management (deprecated - Caddy handles certificates automatically)
│ ├── github-actions/# CI/CD runner setup
│ ├── labelprint/ # 4x6 label print proxy (Raspberry Pi, CUPS/TSPL)
│ ├── gitea-actions/ # Gitea Actions runners + CI job images (see its README)
│ └── pihole/ # DNS filtering
└── vars/
└── vault.yml # Encrypted secrets
Container Organization
Containers are organized in ansible/roles/podman/tasks/containers/:
base/- Core infrastructure containers (Caddy web server, AWS DDNS)home/- Home-specific services (Home Assistant, PartKeepr, Immich photos, Nextcloud, Redis)debyltech/- Personal/business services (Fulfillr, Nextcloud at cloud.debyltech.com - cloned from the Skudak instance, backs up to personal iDrive via TrueNAS)skudak/- Additional services (BookStack wiki, Nextcloud)
Security Model
- Ansible vault for encrypted secrets management
- Password sourced from external password manager
- Git-crypt for repository-level encryption (see
.gitattributes)- Symmetric key can be backed up locally in
.git-crypt-backup/(encrypted with GPG) - Use
make git-crypt-backupto create a local encrypted backup - Use
make git-crypt-restoreto recover from git-crypt corruption
- Symmetric key can be backed up locally in
- SSH key-based authentication to target hosts
- Caddy provides automatic HTTPS with LetsEncrypt certificates
- Built-in security headers and IP-based access restrictions
Key Patterns
Role Structure
Each Ansible role follows standard structure:
tasks/main.yml- Main task entry pointdefaults/main.yml- Default variableshandlers/main.yml- Event handlersmeta/main.yml- Role metadata and dependencies
Container Deployment Pattern
Container tasks follow consistent patterns:
- Firewall configuration
- Container image specification via variables
- Service configuration through imported task files
- Tag-based selective deployment
Tagging Strategy
Tasks are tagged by service/component for selective deployment:
caddy- Web server tasks (replaced nginx)ddns- Dynamic DNS tasksdrone- CI/CD tasks (decommissioned)hass- Home Assistant tasksgitea-actions- Gitea Actions runners and their CI job images- Common infrastructure tags like
common,ssl
Configuration Files
ansible.cfg- Ansible configuration with performance optimizations.yamllint.yml- YAML linting rules (braces disabled).lint-vars.sh- Ansible-lint skip configurationrequirements.txt- Python dependencies with pinned versions
Target Environment
- Primary target host:
home.debyl.io - OS: Fedora (ansible_user: fedora)
- Container runtime: Podman
- Web server: Caddy with automatic HTTPS and built-in security (replaced nginx + ModSecurity)
- All services accessible via HTTPS with automatic certificate renewal
CI/CD: Drone CI infrastructure completely decommissioned
Label print proxy
- Second target host:
stickah.local(Raspberry Pi 3B+, Raspberry Pi OS 64-bit, apt not dnf) with a Phomemo PM246 4x6 label printer on USB - Deployed only via
make deploy-labelprint- it is in its own inventory so the Fedora roles can never run against it - The SD card image is written by hand with rpi-imager, but its cloud-init files
come from
make bootfs, rendered from the same templates the role uses - Login is
stickah/stickahwith SSH key auth preferred; password auth is a deliberate LAN-only fallback so the box is never unreachable - LAN-only (192.168.1.0/24), discovered over mDNS, no DNS record
- Falls back to its own rescue Wi-Fi AP at 192.168.4.1 when the home SSID is unreachable
Gitea Actions CI images
The runner's job images (gitea-ci, gitea-ci-espidf, gitea-ci-platformio)
are built by .gitea/workflows/ci-images.yml and published to the Gitea
container registry under git.debyl.io/gitbot/. The gitea-actions role only
pulls them - do NOT add build steps back to it. To change an image, edit
ansible/roles/gitea-actions/files/Containerfile.* (or a version pin in that
role's defaults/main.yml) and push to master; CI rebuilds only what changed.
See ansible/roles/gitea-actions/README.md for the registry rationale, the
prune-exemption label, and the bootstrap path when CI itself cannot build.
Remote SSH Commands for Service Users
The podman user (and other service users) have /bin/nologin as their shell. To run commands as these users via SSH:
- One-off commands:
sudo -H -u podman bash -c 'cd; command here'— thecd;preamble is REQUIRED (it moves into the podman user's home so podman finds its rootless storage/config; without it commands fail). Replacecommand herewith whatever you need to run. - Interactive shell:
sudo -H -u podman bash -c 'cd; bash' - systemctl --user requires
XDG_RUNTIME_DIR:sudo -H -u podman bash -c 'export XDG_RUNTIME_DIR=/run/user/$(id -u); systemctl --user <action> <service>'
Podman is a user-specific (rootless) container runtime, not a system service like Docker. The user context matters for all podman and systemctl --user operations. The default SSH user (fedora) has sudo access and can run commands directly.