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>
154 lines
8.0 KiB
Markdown
154 lines
8.0 KiB
Markdown
# 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
|
|
- `make` or `make 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 lint` is the only lint step needed.
|
|
- `make deploy` - Deploy all configurations to the home server
|
|
- `make deploy TAGS=sometag` - Deploy only specific tagged tasks
|
|
- `make deploy TARGET=specific-host` - Deploy to specific host instead of all
|
|
- `make check` - Run deployment in dry-run mode showing potential changes
|
|
- `make deploy-labelprint` / `make check-labelprint` - Deploy (or dry-run) the label print proxy Pi. Separate inventory and playbook from the home server - see `ansible/roles/labelprint/README.md`
|
|
- `make bootfs BOOTFS=/Volumes/bootfs` - Render the label print proxy's cloud-init first-boot files onto a freshly imaged SD card
|
|
- `make vault` - Edit encrypted Ansible vault file
|
|
- `make list-tags` - List all available Ansible tags
|
|
- `make list-tasks` - List all Ansible tasks
|
|
- `make 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 `master` and push to `origin/master` when asked; don't create a branch first.
|
|
- Pushing to `master` also 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-backup` to create a local encrypted backup
|
|
- Use `make git-crypt-restore` to recover from git-crypt corruption
|
|
- 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 point
|
|
- `defaults/main.yml` - Default variables
|
|
- `handlers/main.yml` - Event handlers
|
|
- `meta/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 tasks
|
|
- ~~`drone` - CI/CD tasks (decommissioned)~~
|
|
- `hass` - Home Assistant tasks
|
|
- `gitea-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 configuration
|
|
- `requirements.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` / `stickah` with 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'` — the `cd;` preamble is REQUIRED (it moves into the podman user's home so podman finds its rootless storage/config; without it commands fail). Replace `command here` with whatever you need to run.
|
|
- **Interactive shell**: `sudo -H -u podman bash -c 'cd; bash'`
|
|
- **systemctl --user** requires `XDG_RUNTIME_DIR`:
|
|
```bash
|
|
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. |