init
This commit is contained in:
commit
883f31932e
169 changed files with 5676 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/letsencrypt/
|
||||
/.pytest_cache/
|
||||
/.ssh/
|
||||
/.local/
|
||||
/.tox/
|
||||
/.vault-pass
|
||||
35
.ssh.vault
Normal file
35
.ssh.vault
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
39316330396162386166363162646363343365343430393266666638356139383832353833326132
|
||||
6531356666643331363735373863323037303562393437620a303135613738303166346234663137
|
||||
65303562666362383266323161323832666165323237313033303961363935313561343731363934
|
||||
3632666365663637640a666666616336343362356164366239373065656239656237373331303166
|
||||
34343539666565636462343064666163333132373236343065323735333562616434323333386438
|
||||
38393137306438656463333135316539663264353736636233663035623131623835636534633336
|
||||
63306666343238656233653232663663323837666437346565616137633638613439393861306636
|
||||
38646663356237623765626237653732336362393739373835363431393963663065633535366632
|
||||
33383031303434396334373834663662653735353030336563646236313532656464333863636263
|
||||
34656262646233646230646335376565666237366138303733326339656166636133333433346432
|
||||
30616139393936383261393763636563386138393237343738613933303562343837353236626665
|
||||
37323635343535353032663866666238646635346138646666646562386433303063333862393761
|
||||
39346234633962326231333032653732393435336434616664663961633062353739383937383866
|
||||
61343030383530353235346561346531633832613634616533306136653930326634393530383730
|
||||
35316438636333313930333235316361393863303961666165616130616639356632356330383139
|
||||
39346335396532636334636336346434613934376131643165333438373630653732643033376537
|
||||
32383066623565643964623535333262376236316564356666336532613337653333613338613865
|
||||
34616561613533333636306163303131323834653934666661653866666562363638616630376566
|
||||
30643330393031613836353936636661653164353531303163623666666563643837346462376436
|
||||
39653665656164303135343738373436383265613363366664373466333963333532353335323436
|
||||
38633163656532356435356334376164373864383031366332633131316162336161643034653664
|
||||
61356538356563356232653964623165373239323664616339383263333365333633616564383138
|
||||
63343435343630353461376662626365626565616366373937306637346635313462393834393430
|
||||
35343962316630663238376262396332336639643136626434626336346437373438393963623863
|
||||
31623237336530376364626634653663313837306165376165346363306166343739393866623537
|
||||
30633735646434633762303065306231346363306339316636373066373464383764636634323163
|
||||
31356431333339653266393138353061643261306135336563303462343261393261376139613364
|
||||
31373861333065646237376535366138353438336463333363623464303431333135353133363461
|
||||
61646262343734343362643432633837623234646633336639336566623038346561393863303636
|
||||
36393861636631333363646438376133616461303834653262616565303362396634626630646137
|
||||
37616631383264656333383035396262656162613162653039366139623634393030656137656563
|
||||
65613135333364636437376163303230323435353834636262656539363964626331343465373038
|
||||
64333735643234326665653036303465646561646362626662653966303966623130356433313034
|
||||
3661666134663430643830613463663761333162393766636263
|
||||
1
.vault-pass-debug
Normal file
1
.vault-pass-debug
Normal file
|
|
@ -0,0 +1 @@
|
|||
&0geF%B46!H30#fnuY4ad8yCAxpM&C*!
|
||||
55
CONTRIBUTING.md
Normal file
55
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Contributing
|
||||
|
||||
## Code style
|
||||
|
||||
- YAML files use 4-space indentation, formatted with `yamlfix`.
|
||||
- Jinja2 templates use the file extension `.j2`.
|
||||
- Role names use snake_case (e.g., `docker_registry`).
|
||||
- Task names are human-readable sentences (e.g., "Ensure SSL private keys are readable by containers").
|
||||
|
||||
## Role structure
|
||||
|
||||
Each role follows the standard Ansible layout:
|
||||
|
||||
```
|
||||
roles/<name>/
|
||||
├── defaults/main.yml # Default variables
|
||||
├── tasks/
|
||||
│ ├── main.yml # Entry point
|
||||
│ └── *.yml # Additional task files (deploy, restore, etc.)
|
||||
├── templates/ # Jinja2 templates (.j2)
|
||||
├── files/ # Static files
|
||||
└── meta/main.yml # Role dependencies
|
||||
```
|
||||
|
||||
## Adding a new service
|
||||
|
||||
1. Create a new role in `ansible/roles/<service>/`.
|
||||
2. Add it to the proxy or IDP play in `ansible/playbooks/setup.yml` with a
|
||||
`tags: [<service>]` annotation.
|
||||
3. If the role depends on docker or apache, those roles handle themselves
|
||||
internally — just declare the dependency in `meta/main.yml`.
|
||||
## Testing changes
|
||||
|
||||
Always test locally before deploying to production:
|
||||
|
||||
```bash
|
||||
# Run against local VMs
|
||||
pipenv run ansible-playbook -i ansible/inventories/debug/hosts.ini ansible/playbooks/setup.yml \
|
||||
--tags <service>
|
||||
```
|
||||
|
||||
## Committing
|
||||
|
||||
- Keep commits focused: one logical change per commit.
|
||||
- Use conventional-ish commit messages:
|
||||
- `feat: add offline email notifications to prosody`
|
||||
- `fix: correct registry vhost name collision`
|
||||
- `chore: migrate collection roles to local roles/`
|
||||
|
||||
## Vault changes
|
||||
|
||||
When adding new secrets, add them to the vault file and document the variable
|
||||
name (but not the value) somewhere discoverable — either in the role's
|
||||
`defaults/main.yml` as a commented placeholder or in the playbook next to the
|
||||
role invocation.
|
||||
168
DEVELOPMENT.md
Normal file
168
DEVELOPMENT.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Development
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
sudo apt install -y qemu-system-x86 libvirt-daemon-system libvirt-clients \
|
||||
genisoimage ovmf screen
|
||||
sudo usermod -aG libvirt $(whoami)
|
||||
sudo usermod -aG kvm $(whoami)
|
||||
pipenv install --dev
|
||||
pipenv run ansible-galaxy collection install -r ansible/requirements.yml
|
||||
```
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
servers/
|
||||
├── ansible/
|
||||
│ ├── inventories/
|
||||
│ │ ├── debug/ # Local VMs (vm-proxy, vm-idp)
|
||||
│ │ └── prod/ # Production VPS hosts
|
||||
│ ├── playbooks/
|
||||
│ │ ├── setup.yml # Main deployment playbook
|
||||
│ │ ├── backup.yml # Backup playbook
|
||||
│ │ └── restore.yml # Restore playbook
|
||||
│ ├── roles/ # All roles (formerly a separate collection)
|
||||
│ └── requirements.yml # Ansible Galaxy dependencies
|
||||
├── scripts/
|
||||
│ ├── provision.sh # One-time: create VMs + full playbook + snapshot
|
||||
│ ├── deploy.sh # Iterate: restore + deploy a single service
|
||||
│ ├── reset.sh # Restore VMs to a snapshot
|
||||
│ ├── prod-deploy.sh # Deploy to production
|
||||
│ └── vm/ # Low-level VM lifecycle (wrapping qemu-vm)
|
||||
├── letsencrypt/ # TLS certificates (not committed)
|
||||
├── Pipfile # Python dependencies
|
||||
└── ansible.cfg # Ansible config (roles_path, vault)
|
||||
```
|
||||
|
||||
## Development workflow
|
||||
|
||||
The workflow is: develop and test locally against VMs, then deploy to
|
||||
production once changes are idempotent and working.
|
||||
|
||||
All scripts are meant to be run inside `pipenv shell` or via `pipenv run`.
|
||||
|
||||
### 1. Provision (once)
|
||||
|
||||
Creates VMs, runs the full ansible playbook, and snapshots the result as
|
||||
`provisioned`. Prompts for sudo to set up the bridge network:
|
||||
|
||||
```bash
|
||||
pipenv run scripts/provision.sh
|
||||
```
|
||||
|
||||
This gives you two VMs:
|
||||
- **vm-proxy** at `10.10.0.2` (screen session `vm-proxy`)
|
||||
- **vm-idp** at `10.10.0.3` (screen session `vm-idp`)
|
||||
|
||||
Attach to a VM's serial console with `screen -r vm-proxy`.
|
||||
|
||||
### 2. Iterate on a service
|
||||
|
||||
Deploy a single service to the local VMs:
|
||||
|
||||
```bash
|
||||
# Deploy bugzilla on top of current VM state
|
||||
pipenv run scripts/deploy.sh bugzilla
|
||||
|
||||
# Skip dependencies you know haven't changed
|
||||
pipenv run scripts/deploy.sh bugzilla --skip docker,apache
|
||||
|
||||
# Restore to clean state first, then deploy
|
||||
pipenv run scripts/deploy.sh prosody --restore
|
||||
|
||||
# Restore to a specific snapshot first
|
||||
pipenv run scripts/deploy.sh authentik --restore initialized
|
||||
```
|
||||
|
||||
### 3. Reset
|
||||
|
||||
Go back to a known state without deploying anything:
|
||||
|
||||
```bash
|
||||
# Reset to bare VMs (pre-ansible)
|
||||
pipenv run scripts/reset.sh
|
||||
|
||||
# Reset to fully provisioned state
|
||||
pipenv run scripts/reset.sh provisioned
|
||||
```
|
||||
|
||||
### 4. Custom snapshots
|
||||
|
||||
Save and restore intermediate states during development:
|
||||
|
||||
```bash
|
||||
# Save current state
|
||||
pipenv run scripts/vm/snapshot.sh after-bugzilla
|
||||
|
||||
# Restore it later
|
||||
pipenv run scripts/reset.sh after-bugzilla
|
||||
```
|
||||
|
||||
### 5. Destroy and recreate
|
||||
|
||||
Start completely from scratch:
|
||||
|
||||
```bash
|
||||
pipenv run scripts/vm/destroy.sh
|
||||
pipenv run scripts/provision.sh
|
||||
```
|
||||
|
||||
### 6. Deploy to production
|
||||
|
||||
Once your changes work locally and are idempotent:
|
||||
|
||||
```bash
|
||||
# Full deployment
|
||||
pipenv run scripts/prod-deploy.sh
|
||||
|
||||
# Targeted deployment
|
||||
pipenv run scripts/prod-deploy.sh prosody
|
||||
|
||||
# Skip dependencies
|
||||
pipenv run scripts/prod-deploy.sh bugzilla --skip docker,apache
|
||||
```
|
||||
|
||||
## Low-level VM management
|
||||
|
||||
The `scripts/vm/` directory contains building-block scripts for direct
|
||||
VM control:
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `vm/start.sh` | Start VMs in screen sessions |
|
||||
| `vm/stop.sh` | Stop VMs gracefully |
|
||||
| `vm/status.sh` | Show instances, volumes, screen sessions |
|
||||
| `vm/snapshot.sh <label>` | Stop + snapshot both volumes |
|
||||
| `vm/restore.sh <label>` | Restore + start both VMs |
|
||||
| `vm/create.sh` | First-time VM creation + cloud-init |
|
||||
| `vm/destroy.sh` | Delete VMs and volumes |
|
||||
| `vm/setup-network.sh` | Create bridge network (requires root) |
|
||||
|
||||
## Working with roles
|
||||
|
||||
Roles live directly in `ansible/roles/`. No collection build/install step is
|
||||
needed — Ansible finds them via `roles_path` in `ansible.cfg`.
|
||||
|
||||
Each role is referenced by its directory name (e.g., `include_role: { name: prosody }`).
|
||||
Cross-role dependencies use the same short names.
|
||||
|
||||
## Working with vault
|
||||
|
||||
```bash
|
||||
# Edit encrypted variables
|
||||
pipenv run ansible-vault edit ansible/inventories/prod/group_vars/all/vault.yml
|
||||
|
||||
# Encrypt/decrypt files
|
||||
ansible-vault-dir encrypt <file>
|
||||
ansible-vault-dir decrypt <file>
|
||||
```
|
||||
|
||||
## YAML formatting
|
||||
|
||||
The project uses `yamlfix` for consistent YAML formatting:
|
||||
|
||||
```bash
|
||||
pipenv run yamlfix ansible/playbooks/setup.yml
|
||||
```
|
||||
60
NOTE-containerd-referrers.md
Normal file
60
NOTE-containerd-referrers.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# containerd-snapshotter: pull-through mirror fails with "failed to decode referrers index"
|
||||
|
||||
## Summary
|
||||
|
||||
When Docker Engine uses the containerd image store (`"features": {"containerd-snapshotter": true}`)
|
||||
and a pull-through registry mirror (Docker Distribution `registry:2`), image pulls fail with:
|
||||
|
||||
```
|
||||
failed to decode referrers index: invalid character '<' looking for beginning of value
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```
|
||||
failed to unpack image on snapshotter overlayfs: unexpected media type text/html for sha256:...: not found
|
||||
```
|
||||
|
||||
## Root cause
|
||||
|
||||
After pulling an image through the mirror, containerd makes a **referrers API request**
|
||||
(OCI Distribution Spec 1.1) directly to the **upstream registry** (e.g. `registry-1.docker.io`),
|
||||
bypassing the mirror entirely. The upstream sometimes returns an HTML error page instead of
|
||||
a valid JSON response for the referrers endpoint, causing containerd to fail the entire pull.
|
||||
|
||||
Key observations:
|
||||
- The image layers and manifests pull successfully through the mirror
|
||||
- The referrers request goes **directly to upstream**, not through the mirror
|
||||
- The `hosts.toml` mirror config with `capabilities = ["pull", "resolve"]` does not
|
||||
cover referrers requests
|
||||
- The error is **not** intermittent — it happens consistently for certain images
|
||||
- Without `containerd-snapshotter` (classic Docker storage driver), the issue does not occur
|
||||
- Affects both Docker 28.x and 29.x when containerd-snapshotter is enabled
|
||||
|
||||
## Affected versions
|
||||
|
||||
- Docker Engine 28.x and 29.x with `containerd-snapshotter: true`
|
||||
- containerd 2.x (bundled with Docker)
|
||||
- Registry mirror: Docker Distribution `registry:2` (v2.8.3)
|
||||
- Tested on Debian 12 (bookworm), amd64
|
||||
|
||||
## Workaround
|
||||
|
||||
Disable the containerd image store and use the classic Docker storage driver.
|
||||
The classic driver does not make referrers API requests.
|
||||
|
||||
Remove from `/etc/docker/daemon.json`:
|
||||
```json
|
||||
{
|
||||
"features": {
|
||||
"containerd-snapshotter": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `registry-mirrors` in `daemon.json` instead of `hosts.toml` for Docker Hub mirroring.
|
||||
Note: `registry-mirrors` only supports Docker Hub, not per-registry mirrors (e.g. ghcr.io).
|
||||
|
||||
## Reproduction
|
||||
|
||||
See `scripts/reproduce-containerd-referrers.sh` for a self-contained reproduction script.
|
||||
16
Pipfile
Normal file
16
Pipfile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
ansible = "*"
|
||||
"byteb4rb1e.utils" = {file = "../../byteb4rb1e/sphinx-courseware/sphinxcontrib.h5p.utils.pkg/vendor/py-utils.git"}
|
||||
"byteb4rb1e.devutils" = {file = "../../byteb4rb1e/devutils"}
|
||||
|
||||
[dev-packages]
|
||||
"byteb4rb1e.utils" = {file = "../../byteb4rb1e/sphinx-courseware/sphinxcontrib.h5p.utils.pkg/vendor/py-utils.git", editable = true}
|
||||
"byteb4rb1e.devutils" = {file = "../../byteb4rb1e/devutils", editable = true}
|
||||
|
||||
[requires]
|
||||
python_version = "3.13"
|
||||
402
Pipfile.lock
generated
Normal file
402
Pipfile.lock
generated
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3ab63f11f8687a954fea0a1a3324efaaf2aaf321f7d09a3eef8f26a3ef128839"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.13"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"ansible": {
|
||||
"hashes": [
|
||||
"sha256:6d741511abc1f724443aa16992fb615cc822a22da427ade925ff937ccd691eb1",
|
||||
"sha256:8c869fcc07954b53c5b125f1e914957cc7b541a92a7d512496207d80385a78eb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.12'",
|
||||
"version": "==13.4.0"
|
||||
},
|
||||
"ansible-core": {
|
||||
"hashes": [
|
||||
"sha256:38670ab2d2ffd67ee7e9e562f7c94e0927c8b7b4b0eb69233bc008c29e32d591",
|
||||
"sha256:7fe2da953192cac5d5eb37bfc984bf9339daa252e0e3d1deb5526eb1f8be9f7f"
|
||||
],
|
||||
"markers": "python_version >= '3.12'",
|
||||
"version": "==2.20.3"
|
||||
},
|
||||
"byteb4rb1e.devutils": {
|
||||
"file": "../../byteb4rb1e/devutils"
|
||||
},
|
||||
"byteb4rb1e.utils": {
|
||||
"file": "../../byteb4rb1e/sphinx-courseware/sphinxcontrib.h5p.utils.pkg/vendor/py-utils.git"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb",
|
||||
"sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b",
|
||||
"sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f",
|
||||
"sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9",
|
||||
"sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44",
|
||||
"sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2",
|
||||
"sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c",
|
||||
"sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75",
|
||||
"sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65",
|
||||
"sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e",
|
||||
"sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a",
|
||||
"sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e",
|
||||
"sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25",
|
||||
"sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a",
|
||||
"sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe",
|
||||
"sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b",
|
||||
"sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91",
|
||||
"sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592",
|
||||
"sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187",
|
||||
"sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c",
|
||||
"sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1",
|
||||
"sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94",
|
||||
"sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba",
|
||||
"sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb",
|
||||
"sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165",
|
||||
"sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529",
|
||||
"sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca",
|
||||
"sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c",
|
||||
"sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6",
|
||||
"sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c",
|
||||
"sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0",
|
||||
"sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743",
|
||||
"sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63",
|
||||
"sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5",
|
||||
"sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5",
|
||||
"sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4",
|
||||
"sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d",
|
||||
"sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b",
|
||||
"sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93",
|
||||
"sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205",
|
||||
"sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27",
|
||||
"sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512",
|
||||
"sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d",
|
||||
"sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c",
|
||||
"sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037",
|
||||
"sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26",
|
||||
"sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322",
|
||||
"sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb",
|
||||
"sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c",
|
||||
"sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8",
|
||||
"sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4",
|
||||
"sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414",
|
||||
"sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9",
|
||||
"sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664",
|
||||
"sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9",
|
||||
"sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775",
|
||||
"sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739",
|
||||
"sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc",
|
||||
"sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062",
|
||||
"sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe",
|
||||
"sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9",
|
||||
"sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92",
|
||||
"sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5",
|
||||
"sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13",
|
||||
"sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d",
|
||||
"sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26",
|
||||
"sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f",
|
||||
"sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495",
|
||||
"sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b",
|
||||
"sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6",
|
||||
"sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c",
|
||||
"sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef",
|
||||
"sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5",
|
||||
"sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18",
|
||||
"sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad",
|
||||
"sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3",
|
||||
"sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7",
|
||||
"sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5",
|
||||
"sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534",
|
||||
"sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49",
|
||||
"sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2",
|
||||
"sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5",
|
||||
"sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453",
|
||||
"sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72",
|
||||
"sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235",
|
||||
"sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9",
|
||||
"sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356",
|
||||
"sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257",
|
||||
"sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad",
|
||||
"sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4",
|
||||
"sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c",
|
||||
"sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614",
|
||||
"sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed",
|
||||
"sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31",
|
||||
"sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229",
|
||||
"sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0",
|
||||
"sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731",
|
||||
"sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b",
|
||||
"sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4",
|
||||
"sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4",
|
||||
"sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263",
|
||||
"sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595",
|
||||
"sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1",
|
||||
"sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678",
|
||||
"sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48",
|
||||
"sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76",
|
||||
"sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0",
|
||||
"sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18",
|
||||
"sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d",
|
||||
"sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d",
|
||||
"sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1",
|
||||
"sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981",
|
||||
"sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7",
|
||||
"sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82",
|
||||
"sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2",
|
||||
"sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4",
|
||||
"sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663",
|
||||
"sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c",
|
||||
"sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d",
|
||||
"sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a",
|
||||
"sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a",
|
||||
"sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d",
|
||||
"sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b",
|
||||
"sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a",
|
||||
"sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826",
|
||||
"sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee",
|
||||
"sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9",
|
||||
"sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648",
|
||||
"sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da",
|
||||
"sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2",
|
||||
"sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2",
|
||||
"sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87"
|
||||
],
|
||||
"markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'",
|
||||
"version": "==46.0.5"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d",
|
||||
"sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.1.6"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f",
|
||||
"sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a",
|
||||
"sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf",
|
||||
"sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19",
|
||||
"sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf",
|
||||
"sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c",
|
||||
"sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175",
|
||||
"sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219",
|
||||
"sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb",
|
||||
"sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6",
|
||||
"sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab",
|
||||
"sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26",
|
||||
"sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1",
|
||||
"sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce",
|
||||
"sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218",
|
||||
"sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634",
|
||||
"sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695",
|
||||
"sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad",
|
||||
"sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73",
|
||||
"sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c",
|
||||
"sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe",
|
||||
"sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa",
|
||||
"sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559",
|
||||
"sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa",
|
||||
"sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37",
|
||||
"sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758",
|
||||
"sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f",
|
||||
"sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8",
|
||||
"sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d",
|
||||
"sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c",
|
||||
"sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97",
|
||||
"sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a",
|
||||
"sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19",
|
||||
"sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9",
|
||||
"sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9",
|
||||
"sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc",
|
||||
"sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2",
|
||||
"sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4",
|
||||
"sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354",
|
||||
"sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50",
|
||||
"sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698",
|
||||
"sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9",
|
||||
"sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b",
|
||||
"sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc",
|
||||
"sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115",
|
||||
"sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e",
|
||||
"sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485",
|
||||
"sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f",
|
||||
"sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12",
|
||||
"sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025",
|
||||
"sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009",
|
||||
"sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d",
|
||||
"sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b",
|
||||
"sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a",
|
||||
"sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5",
|
||||
"sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f",
|
||||
"sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d",
|
||||
"sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1",
|
||||
"sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287",
|
||||
"sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6",
|
||||
"sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f",
|
||||
"sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581",
|
||||
"sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed",
|
||||
"sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b",
|
||||
"sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c",
|
||||
"sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026",
|
||||
"sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8",
|
||||
"sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676",
|
||||
"sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6",
|
||||
"sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e",
|
||||
"sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d",
|
||||
"sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d",
|
||||
"sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01",
|
||||
"sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7",
|
||||
"sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419",
|
||||
"sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795",
|
||||
"sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1",
|
||||
"sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5",
|
||||
"sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d",
|
||||
"sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42",
|
||||
"sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe",
|
||||
"sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda",
|
||||
"sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e",
|
||||
"sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737",
|
||||
"sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523",
|
||||
"sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591",
|
||||
"sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc",
|
||||
"sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a",
|
||||
"sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==3.0.3"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4",
|
||||
"sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==26.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29",
|
||||
"sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"
|
||||
],
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==3.0"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c",
|
||||
"sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a",
|
||||
"sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3",
|
||||
"sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956",
|
||||
"sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6",
|
||||
"sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c",
|
||||
"sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65",
|
||||
"sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a",
|
||||
"sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0",
|
||||
"sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b",
|
||||
"sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1",
|
||||
"sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6",
|
||||
"sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7",
|
||||
"sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e",
|
||||
"sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007",
|
||||
"sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310",
|
||||
"sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4",
|
||||
"sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9",
|
||||
"sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295",
|
||||
"sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea",
|
||||
"sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0",
|
||||
"sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e",
|
||||
"sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac",
|
||||
"sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9",
|
||||
"sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7",
|
||||
"sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35",
|
||||
"sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb",
|
||||
"sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b",
|
||||
"sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69",
|
||||
"sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5",
|
||||
"sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b",
|
||||
"sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c",
|
||||
"sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369",
|
||||
"sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd",
|
||||
"sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824",
|
||||
"sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198",
|
||||
"sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065",
|
||||
"sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c",
|
||||
"sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c",
|
||||
"sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764",
|
||||
"sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196",
|
||||
"sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b",
|
||||
"sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00",
|
||||
"sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac",
|
||||
"sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8",
|
||||
"sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e",
|
||||
"sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28",
|
||||
"sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3",
|
||||
"sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5",
|
||||
"sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4",
|
||||
"sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b",
|
||||
"sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf",
|
||||
"sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5",
|
||||
"sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702",
|
||||
"sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8",
|
||||
"sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788",
|
||||
"sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da",
|
||||
"sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d",
|
||||
"sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc",
|
||||
"sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c",
|
||||
"sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba",
|
||||
"sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f",
|
||||
"sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917",
|
||||
"sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5",
|
||||
"sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26",
|
||||
"sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f",
|
||||
"sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b",
|
||||
"sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be",
|
||||
"sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c",
|
||||
"sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3",
|
||||
"sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6",
|
||||
"sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926",
|
||||
"sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==6.0.3"
|
||||
},
|
||||
"resolvelib": {
|
||||
"hashes": [
|
||||
"sha256:7d08a2022f6e16ce405d60b68c390f054efcfd0477d4b9bd019cc941c28fad1c",
|
||||
"sha256:fb06b66c8da04172d9e72a21d7d06186d8919e32ae5ab5cdf5b9d920be805ac2"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==1.2.1"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"byteb4rb1e.devutils": {
|
||||
"file": "../../byteb4rb1e/devutils"
|
||||
},
|
||||
"byteb4rb1e.utils": {
|
||||
"file": "../../byteb4rb1e/sphinx-courseware/sphinxcontrib.h5p.utils.pkg/vendor/py-utils.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
107
README.md
Normal file
107
README.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# servers
|
||||
|
||||
Homelab infrastructure managed with Ansible and tested against local QEMU VMs.
|
||||
Two production VPS hosts (proxy and IDP) are connected via WireGuard and run
|
||||
everything behind Apache reverse proxies with Let's Encrypt TLS.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Proxy (10.0.0.1) │
|
||||
│ ┌───────────┐ ┌──────────┐ ┌────────────────┐ │
|
||||
│ │ Apache │ │ Prosody │ │ Docker Registry│ │
|
||||
│ │ (reverse │ │ (XMPP) │ │ (pull-through) │ │
|
||||
│ │ proxy) │ │ │ │ │ │
|
||||
│ ├───────────┤ ├──────────┤ ├────────────────┤ │
|
||||
│ │ ConverseJS│ │ Bugzilla │ │ Comentario │ │
|
||||
│ │ Kellnr │ │ DevPI │ │ dnsmasq │ │
|
||||
│ └───────────┘ └──────────┘ └────────────────┘ │
|
||||
│ WireGuard ─────────────────────────┐ │
|
||||
└─────────────────────────────────────┼────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────┼────────────┐
|
||||
│ IDP (10.0.0.2) │ │
|
||||
│ ┌──────────────────────────────────┘ │
|
||||
│ │ Authentik (identity provider) │
|
||||
│ │ PostgreSQL + Redis │
|
||||
│ └───────────────────────────────────────────────┘
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Clients (phone, workstation) connect over WireGuard and resolve names via
|
||||
dnsmasq on the proxy.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Install Python dependencies
|
||||
pipenv install
|
||||
|
||||
# Install Ansible collection dependencies
|
||||
pipenv run ansible-galaxy collection install -r ansible/requirements.yml
|
||||
|
||||
# Deploy to production
|
||||
pipenv run ansible-playbook -i ansible/inventories/prod/hosts.ini ansible/playbooks/setup.yml
|
||||
|
||||
# Deploy a single service (e.g. bugzilla)
|
||||
pipenv run ansible-playbook -i ansible/inventories/prod/hosts.ini ansible/playbooks/setup.yml --tags bugzilla
|
||||
|
||||
# Deploy a service without its dependencies
|
||||
pipenv run ansible-playbook -i ansible/inventories/prod/hosts.ini ansible/playbooks/setup.yml --tags bugzilla --skip-tags docker,apache
|
||||
```
|
||||
|
||||
See [DEVELOPMENT.md](DEVELOPMENT.md) for the full local development workflow.
|
||||
|
||||
## Available tags
|
||||
|
||||
| Tag | What it deploys | Host |
|
||||
|-----|----------------|------|
|
||||
| `host` | Base host config (SSH keys, users, swap) | all |
|
||||
| `docker` | Docker engine + registry mirrors | docker_hosts |
|
||||
| `wireguard` | WireGuard tunnels | proxy, idp |
|
||||
| `apache` | Apache + Let's Encrypt certs | proxy |
|
||||
| `dnsmasq` | DNS resolver | proxy |
|
||||
| `docker-registry` | Pull-through registry mirrors | proxy |
|
||||
| `restic` | Backup infrastructure | proxy, idp |
|
||||
| `prosody` | XMPP server + upload proxy | proxy |
|
||||
| `conversejs` | Web XMPP client | proxy |
|
||||
| `kellnr` | Rust crate registry | proxy |
|
||||
| `devpi` | Python package index | proxy |
|
||||
| `comentario` | Comment system | proxy |
|
||||
| `bugzilla` | Bug tracker | proxy |
|
||||
| `authentik` | Identity provider + reverse proxy | idp, proxy |
|
||||
| `blog` | Static blog site | proxy |
|
||||
| `spec` | Static spec site | proxy |
|
||||
|
||||
## Backups
|
||||
|
||||
```bash
|
||||
# Run backup
|
||||
pipenv run ansible-playbook -i ansible/inventories/prod/hosts.ini ansible/playbooks/backup.yml
|
||||
|
||||
# Restore
|
||||
pipenv run ansible-playbook -i ansible/inventories/prod/hosts.ini ansible/playbooks/restore.yml
|
||||
```
|
||||
|
||||
Automated backups run via systemd timer (bi-weekly by default).
|
||||
|
||||
## Vault
|
||||
|
||||
Sensitive variables live in `ansible/inventories/prod/group_vars/all/vault.yml`,
|
||||
encrypted with `ansible-vault`. The vault password file is `.vault-pass`
|
||||
(not committed).
|
||||
|
||||
```bash
|
||||
# Edit vault
|
||||
pipenv run ansible-vault edit ansible/inventories/prod/group_vars/all/vault.yml
|
||||
```
|
||||
|
||||
## Authentik notes
|
||||
|
||||
App passwords with more than 30 minutes expiration require a user or group
|
||||
attribute:
|
||||
|
||||
```
|
||||
goauthentik.io/user/token-maximum-lifetime: days=365
|
||||
```
|
||||
3
ansible.cfg
Normal file
3
ansible.cfg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[defaults]
|
||||
roles_path = ansible/roles
|
||||
vault_password_file = .vault-pass
|
||||
35
ansible/README.md
Normal file
35
ansible/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
Install collection dependencies:
|
||||
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
Run setup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/setup.yml
|
||||
|
||||
Run backup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/backup.yml
|
||||
|
||||
Run restore:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/restore.yml
|
||||
|
||||
Manual backup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/backup.yml
|
||||
|
||||
Automated backups run via systemd timer (bi-weekly by default).
|
||||
|
||||
Vault variables (inventories/prod/group_vars/all/vault.yml):
|
||||
|
||||
vault_kellnr_admin_pwd: "..."
|
||||
vault_pg_password: "..."
|
||||
vault_secret_key: "random-long-django-secret"
|
||||
vault_restic_password: "..."
|
||||
vault_accounts_ssh_pubkey: "ssh-ed25519 ..."
|
||||
vault_accounts_ssh_private_key: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
...
|
||||
vault_rclone_proton_username: "user@proton.me"
|
||||
vault_rclone_proton_password: "rclone-obscured-password"
|
||||
vault_rclone_proton_2fa: "TOTP-SECRET"
|
||||
27
ansible/inventories/debug/group_vars/all/secrets.yml
Normal file
27
ansible/inventories/debug/group_vars/all/secrets.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
vault_kellnr_admin_pwd: debug
|
||||
vault_pg_password: debug
|
||||
vault_secret_key: debug
|
||||
s3_bucket: backups-testing
|
||||
vault_xmpp_admin_user: tiara
|
||||
vault_xmpp_oauth_client_id: xmpp-client-id
|
||||
vault_xmpp_oauth_client_secret: xmpp-client-secret
|
||||
vault_xmpp_ropc_client_id: xmpp-ropc-client-id
|
||||
vault_xmpp_ropc_client_secret: xmpp-ropc-client-secret
|
||||
vault_comentario_oauth_client_id: comentario-client-id
|
||||
vault_comentario_oauth_client_secret: comentario-client-secret
|
||||
vault_bugzilla_oauth_client_id: bugzilla-client-id
|
||||
vault_bugzilla_oauth_client_secret: bugzilla-client-secret
|
||||
vault_bugzilla_oidc_passphrase: bugzilla-oidc-passphrase
|
||||
vault_bugzilla_db_password: debug
|
||||
vault_bugzilla_admin_pwd: debugdebug
|
||||
vault_social_google_client_id: google-client-id-placeholder
|
||||
vault_social_google_client_secret: google-client-secret-placeholder
|
||||
vault_social_microsoft_client_id: microsoft-client-id-placeholder
|
||||
vault_social_microsoft_client_secret: microsoft-client-secret-placeholder
|
||||
vault_social_apple_client_id: apple-client-id-placeholder
|
||||
vault_social_apple_client_secret: apple-client-secret-placeholder
|
||||
vault_social_facebook_client_id: facebook-client-id-placeholder
|
||||
vault_social_facebook_client_secret: facebook-client-secret-placeholder
|
||||
vault_social_twitter_client_id: twitter-client-id-placeholder
|
||||
vault_social_twitter_client_secret: twitter-client-secret-placeholder
|
||||
54
ansible/inventories/debug/group_vars/all/vault.yml
Normal file
54
ansible/inventories/debug/group_vars/all/vault.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
34646335663961323835326462363434393133316464393063356666396362626637306263626332
|
||||
3137613332313531333364643331373934363237363231640a393362313738383035373131633537
|
||||
30623432363962313538323131323836613539643533623364666631663733353134316461373930
|
||||
6364393465613665620a336261336630346630646530653135336163653136336532333130333338
|
||||
64626264353737363235353865303039656339653062633335623964643939326364393263393832
|
||||
36663030646363636130353164313063356133396635643334653561343462333662656237633766
|
||||
39646363646161303165316439646164613530636266373761396135663830383932663837383661
|
||||
62636334383861396133353839616239316366303437383934333265343063366139613564346131
|
||||
39656434653662316534386263383361333131383037313163326464353963643239343661663932
|
||||
37303065366463653761623062326439633363663030616432346538373461353830353338623230
|
||||
64306261326135363566656232313330353037613164616264656431363439326331613837376662
|
||||
36646134366463383665393136616437393339346134363638666431363933636637366632366439
|
||||
39656138366234646138353131313166656431303463316332663138303330326135666261363533
|
||||
34393833626239323036663035646239316266323434366435323931366532623639653837373261
|
||||
37633333663233646332393239613264363562343164653033303865313233393432306539646536
|
||||
39643264623862393833653362323436396661353065306233646234306235306366666139666130
|
||||
61643364613133326461383331366664363531643963376137653561376532393736393633313831
|
||||
31303861373037363766316664623234633563386338353431396663383034343061643962366535
|
||||
31653833393765663961613338336334366162656466663863306662383838386664326631336434
|
||||
65366563333533396434333861333937333466313861626661363061303939373538666130326238
|
||||
62363130633566336163653238356636623066306662326536366463613532626536633564353430
|
||||
64303165353436626266636131333864666336386535663832313564313533333163633639393038
|
||||
64633236333236616535396634653162396464363337393163623262653866666664303534376164
|
||||
35303066626665386535656537666432636132656639646431663463363630333466316534303739
|
||||
30396466356131616666626130383963333166396339623861303662333535343239643364633535
|
||||
37656466363535363163643036653464336538623334633565346163343630663463633235383831
|
||||
66306462396163313634666130666264336138643163643563663635333362623262373330643766
|
||||
33626633303062656462313862393763616163363231323036306638633536313230656261303835
|
||||
35333738396430356139346331643839386236343464323137663964336561633766353262353863
|
||||
32313635323632643065343139366436373864636465376538376365333835366232613032333230
|
||||
35376134343861373333346537623838303165366639653063303364303638626431316535656466
|
||||
38353261393262306433383035353261363265653838653661636463636638636465383665383763
|
||||
34303238323838313364373137613566663737323938306365376565353066656162656135376363
|
||||
39326366623830616330336163363738326263663832313337323164656331636334333937383737
|
||||
61653336613033363766636435353365356265373263656165326162316130333134336230353637
|
||||
36656364646336336163333338663966613934356266313865643934373566343964363062326366
|
||||
38646562373261313638383338636139333835626239346363336331663536306431636463336630
|
||||
38656236366436316234393236646234613264353264333030626362343463333863373065343336
|
||||
63663135363365316265636464663864313765663763613131613030636133643637336364316234
|
||||
62393061346337383563313866303463633362356263356238323336663262646532343136393439
|
||||
64333362383461366230663435396164393031616432323537396237383634353363393332376536
|
||||
39646631386365646266366530623762616264663164346533323239643863363036663565323438
|
||||
63663537306133363337363962646462323131353065313537626432633534653432393639626337
|
||||
66393234313835613163663030363332336361316365616238643964333363386134663232323766
|
||||
33306165626431373730373135306431373937613235333861636266666662336431356265623638
|
||||
38623065623933646237326339353932353439383932663631346131316431316234323537353062
|
||||
37353632333961386334646232393930666234373432653463613931643537623536656364626632
|
||||
61393063636333663961336562326330653461393562313064626566626436383765356264633838
|
||||
39356232626362323935626362316563363430383134636230373665613930336462383363393831
|
||||
37356261663238303337383833353930383736386434666539333939616434626532393831643866
|
||||
35386137663632383639376538386435373065333138643064383136656136373634323839636366
|
||||
31356561396137326134643562396264303561363639636637313362616438646231663162656332
|
||||
3739
|
||||
4
ansible/inventories/debug/group_vars/idp/firewall.yml
Normal file
4
ansible/inventories/debug/group_vars/idp/firewall.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "9000", proto: tcp, from: "10.0.0.0/24" }
|
||||
9
ansible/inventories/debug/group_vars/proxy/firewall.yml
Normal file
9
ansible/inventories/debug/group_vars/proxy/firewall.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "80" }
|
||||
- { port: "443" }
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "5000", proto: tcp }
|
||||
- { port: "5222", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "5269", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "53", proto: udp, from: "10.0.0.0/24" }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
wg_client_peers:
|
||||
-
|
||||
name: "Ansible controller"
|
||||
public_key: "{{ lookup('file', '/etc/wireguard/public.key') }}"
|
||||
allowed_ips: "10.0.0.3/32"
|
||||
13
ansible/inventories/debug/hosts.ini
Normal file
13
ansible/inventories/debug/hosts.ini
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[proxy]
|
||||
vm-proxy ansible_host=10.10.0.2 ansible_user=debian wg_endpoint=10.10.0.2
|
||||
|
||||
[idp]
|
||||
vm-idp ansible_host=10.10.0.3 ansible_user=debian
|
||||
|
||||
[docker_hosts:children]
|
||||
proxy
|
||||
idp
|
||||
|
||||
[wg_peers:children]
|
||||
proxy
|
||||
idp
|
||||
126
ansible/inventories/prod/group_vars/all/vault.yml
Normal file
126
ansible/inventories/prod/group_vars/all/vault.yml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
65656630393666376132613061363366363432303437313961303434323431333832636331633535
|
||||
3364623638626435326236656563646435396661353939610a366337363936633038623065323134
|
||||
63353766653532623164353764353761363633633366633538633566323465303835323831656238
|
||||
3565353965646338630a343232386563313764313465383666356136326336336230303432346538
|
||||
38316130663339386235343265616237373536613762626338376564666432366464323436353639
|
||||
36326137373232316438383837633830366163633437643565313863613137323033353430356638
|
||||
33663736623032323263353831343430313734373065393232663065643432333864323563663265
|
||||
33386465326335373635333435623764346666353765336630643764373737306637343834656639
|
||||
35653466366666383332663464626463646235303737306166373832396531623038376532656665
|
||||
65663964373565303538343732393137396361613330613336326564613633363662306232643534
|
||||
37393939323563343332613833613339313630396434663235656232643631303031393735396263
|
||||
63613863366162383637363736393430643233326533383534623539646330313737383332396165
|
||||
32393134323739363866386633646563393036373235353332333062316639653362626261376434
|
||||
31366437383465616534653465386636376163653430663764613236366134336435636631623963
|
||||
37346430393933306331636633333636666239333962323137393466356265626363376361393435
|
||||
30656432353333646337346662653637303430313739353431666237373037666534316566666564
|
||||
65663838643461316566616532343461343138323637356231393730393062363164633537363539
|
||||
36623035386636376239306633303766616636333935336464323636626437393466356139393164
|
||||
32373039326138346465383736616433343234303762343161313661363563353864663538316662
|
||||
30613432356438663561626137316636623431656432346238303735313865326230633231633636
|
||||
33666434343038656266323866663634653063353864366163653737363938336235316565306635
|
||||
35646137313936303738623630356333623261303839346632363562646263616130643734353066
|
||||
61663761326264313732396166303362313231333166346231306165346630636331666262373631
|
||||
61636137356430633566323732303233656635643639353539626238613761386532336136663535
|
||||
32356561613635633230303733303836336635396338666134303464376437316163633238373365
|
||||
34363230383933613136343761326563633034306539623866376661336539303535336565393962
|
||||
62613138623334376134373566623034313365616238353864616265383533363037363463343835
|
||||
32623334663832333630326565633833373936376633343937363633363533633162646164393537
|
||||
35663765626362366234626432663064346231386265376132396335353130336432356435336337
|
||||
37636136303631366333316636313733376137343961393062613762333962616263373233633366
|
||||
30383236386462393732373464333363376638663465363634396238376564326363383432386438
|
||||
36363638383437643233393436383539333162373730633533666533343864346432623130626237
|
||||
33643436653732333532666333613038316539626536366265386332623563353133363663663530
|
||||
31643131653566323935393932353537343533343131666239663864653232633331363961663031
|
||||
37346664383031646264316132356433616631373936376435613666653139313630316665636237
|
||||
61626137653765623638393132303436366364353162333036353138376535626635343731666432
|
||||
34663032643936393732373966363534303435313234323334393964313235333263373136313962
|
||||
35386539393238326132346134346333626437353464333538623966363965663237353331363438
|
||||
34336630343930633363663062316639336630396164653236303538383537333062636535366461
|
||||
36393534336462633062316535376665303666643538303630653234396533323463323131303365
|
||||
38336636666238316332646162323164343436303264396335646466643165623937363934363734
|
||||
33366532396332393231633366353632363561663662633362343838653431356633633462393738
|
||||
62333236613831316334343837333465303535383036353933396436346235653162326266663934
|
||||
36633434633630303234333336633834623465613433333833666430353538663332396366316237
|
||||
39656638303763633535393932326132346266666635353462623132306331626266303336396362
|
||||
62346564636365353031366264656534376665366664386636633136373738323038663132643034
|
||||
39303261313333333465656666636435323338336335313166343431396238643738633534366364
|
||||
34383733383366626265313936353039613630626237636439343930393831323238333161656637
|
||||
34303763386635373237393463623838303738383434306639303161336337313831653862346535
|
||||
32363237333932343633653165663138393761656365363632393539356665613837343663396438
|
||||
62313333306262303435386330616233313230323338666537346265343562343065326362643461
|
||||
37373330386531646465393039656165333032383735643566366465366130313263653930336434
|
||||
32346234626561373236333934633130366134626239323963346536626339336139326536346238
|
||||
38396663323837393330343633313939656430343366626637396564653036303830633065396336
|
||||
61383234653130623933666262353339363737633230636239323339643032323264306636343161
|
||||
62636133656364303231666535393764633035393363313732306331663131393331366436373764
|
||||
32386134303532653934306239323362306637396263363361386638343631633761653534643061
|
||||
66613761336333643062306635313834333464343936333438636664666561393237303139366666
|
||||
30653263623738383839613564623635336236356630353031323636333631373339376366323834
|
||||
63393839656535313966643335636135336366336135376561303661633332363338363266646364
|
||||
30346536323835326165663336326337336431376337356161613566356562326334333566343963
|
||||
34316433343231343735313236313432323732616363653634323262393235333063386234376135
|
||||
62616536383339333461393462313365633834326638656433383032336639633861363466636462
|
||||
33643066363631633438363432316366313836383864663266633336373865633733353933323531
|
||||
61373531336130303834643739336237343966626239613731373034313232343764613637356564
|
||||
66366330346431663031613535633435356239376361663936646334383466373261316531653762
|
||||
63306138306334616333373139663432326366646534396331613265313465646338323063653735
|
||||
31653739616534303138363465653966613436613634616166323665613538363263346334336338
|
||||
34623235636630363332343566616336323636646235663132373633313230623639623962643466
|
||||
39356137313239653431383339646135616636616437623830353639616634306534663636386232
|
||||
39363864303733363164313130666536366664303733366132663439656333626562343063386336
|
||||
33396261663030333332333139353163623133663134363736326538653763373730326563313937
|
||||
62353434356232376630306538633731626435613639303165616535316164336637343362353465
|
||||
39633031366231633032313162336437336362626131613363353131663532646266306636396138
|
||||
62333939383065653866356633663834373265646366353261653661353465613766633638333836
|
||||
66643335396563316166393535623734303730346138386434323034643735313137313362306636
|
||||
31386565383032663937323334346462656436376633306638323562376238363339323530353230
|
||||
38353537333232306536386238653765393061646432646162626438636365323564363363316362
|
||||
64646662633036316530326437366530353234346231303865306665343061613134613338336664
|
||||
64313237643137633361643466313938623137366561376532646133366261633637623837653233
|
||||
33306263666665636136633531393866323232356562656135653464333836363830323634343338
|
||||
63633836386565336563646131333861666332336261393034326337323836393035346262663631
|
||||
64666635623362626235373433613132303037396136663630353334353938656434656337386134
|
||||
31666364626335626639396334396162326138626631353635323364383364616162353735303336
|
||||
38306330666538326130316635323562393963343635663666333038383564323832646239323062
|
||||
66356363323664663637656332363461363430393938313930316364623766303437623865633461
|
||||
31663133626235626265323661353531386232323836353737313138353330653936306339343235
|
||||
38663563653439626435373361383530646262656238306239643136373161396134336233366566
|
||||
38663863616139383738303061383431313763323336353463636361613132616363326133323339
|
||||
37363562363633393664643334386266366337353464336333336363373161663034303835396262
|
||||
61623233666137343963613161643164333336323130353538636565363735376561356561313133
|
||||
63333033336666393538396534353463393331363033353631393162653132396532623864333536
|
||||
61656231353437356532353066663261633861616530313332343263643635613034393563373961
|
||||
64663631383736613835376633633735626466636661353765303765643038623039303733636264
|
||||
66353133313464316633626661663665646264666133356133383435646563346332383865306235
|
||||
64353265393463326633646434633264303734626532663935326665363632393435333565306635
|
||||
35666464643364313039363264333261623365373330316637653038616639643637663437366663
|
||||
39643134613465376162396238376562643930323632643465393038333432663062383434626432
|
||||
34366665663761316564656436326331373165626237383839343736373063383765383032383762
|
||||
65303433353535393665323638666239336538326239363263646534653239333039643034656362
|
||||
35353062636334353264353964303938636563306631356664303965306331613134326264653730
|
||||
33613061633931663066616139646663343334323763343630636432353931313265303931343833
|
||||
63303831333032383839633263626539653139313561626432393731623932383733666464313061
|
||||
39393537393563343238306438313032353134383630386436643534306266393364303966393361
|
||||
32333538383863353031373735353932633633613538316261396164653230336463666165313965
|
||||
35653533393662393230343736303466643737353437333966653966613861646361303637323933
|
||||
34393365643235373762323631633930316538333835303230613265366265393938646432613964
|
||||
62323637326166303936303663313038393133316336653462313931663335316230306536356165
|
||||
62346432366234623232353666313131613330636436303830363261323465363164366637353238
|
||||
66656463353633343133326531333131396438663964623861323037623162303666613565663161
|
||||
66376531373739396463653461356131643131623561663633343331346237393063303932306239
|
||||
37363533323632323336373934323237656338623962323539376230616139353463336631306264
|
||||
38383233313261333466613462356234653530356535633439646432303862316236613834393765
|
||||
38373263636338343030366161316464656362653433353137323532356438643238373665346336
|
||||
63343230633163633066623936343661656565356533333433613838353937613233326464386462
|
||||
61613261333739376630363036656564363363616665313639313463323239666636396366303663
|
||||
38393466613561663436653736393431633530346263363964393961613833376235383664313331
|
||||
65363233326165643533373139663233636538653937313835316263633834326139386230643338
|
||||
66663133373739623236633338663435663838376633333534633337366135343438663365626533
|
||||
63393437613637383033363237633363633531356535366339386334363535643364356564313765
|
||||
33343039303035303739366239643333326461653736353362373133663331306362616339366236
|
||||
38636237366230313332336437376539333233626361333138663164383334336138643333363030
|
||||
63623166366163656431666233323564386236306165623162386639316131633939356136666336
|
||||
33376366353866343033373264626334303334383766623564313262303135666232313765653536
|
||||
34653430363436303237373833363765383963613865323566633436653266313036
|
||||
4
ansible/inventories/prod/group_vars/idp/firewall.yml
Normal file
4
ansible/inventories/prod/group_vars/idp/firewall.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "9000", proto: tcp, from: "10.0.0.0/24" }
|
||||
9
ansible/inventories/prod/group_vars/proxy/firewall.yml
Normal file
9
ansible/inventories/prod/group_vars/proxy/firewall.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "80" }
|
||||
- { port: "443" }
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "5000", proto: tcp }
|
||||
- { port: "5222", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "5269", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "53", proto: udp, from: "10.0.0.0/24" }
|
||||
10
ansible/inventories/prod/group_vars/wg_peers/wireguard.yml
Normal file
10
ansible/inventories/prod/group_vars/wg_peers/wireguard.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
wg_client_peers:
|
||||
-
|
||||
name: "Tiara's Pixel 8 Pro"
|
||||
public_key: "0bMudTNuiRCaOdFKvWy+N6X2czYWt8nKe7OIiFS5LEY="
|
||||
allowed_ips: "10.0.0.3/32"
|
||||
-
|
||||
name: "Tiara's Workstation"
|
||||
public_key: "8Cwcyqu3Xo0th6Lkk5arcG7MdgwEejocYrA/+RRbrgA="
|
||||
allowed_ips: "10.0.0.4/32"
|
||||
13
ansible/inventories/prod/hosts.ini
Normal file
13
ansible/inventories/prod/hosts.ini
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[proxy]
|
||||
hwsrv-953720.hostwindsdns.com ansible_user=tiara wg_endpoint=tiararodney.com
|
||||
|
||||
[idp]
|
||||
hwsrv-1317920.hostwindsdns.com ansible_user=tiara
|
||||
|
||||
[docker_hosts:children]
|
||||
proxy
|
||||
idp
|
||||
|
||||
[wg_peers:children]
|
||||
proxy
|
||||
idp
|
||||
20
ansible/playbooks/backup.yml
Normal file
20
ansible/playbooks/backup.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger backup
|
||||
systemd:
|
||||
name: restic-backup.service
|
||||
state: started
|
||||
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger backup
|
||||
systemd:
|
||||
name: restic-backup.service
|
||||
state: started
|
||||
28
ansible/playbooks/restore.yml
Normal file
28
ansible/playbooks/restore.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: restic, tasks_from: restore-restic }
|
||||
vars:
|
||||
host_id: proxy
|
||||
-
|
||||
include_role: { name: kellnr, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: devpi, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: prosody, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: comentario, tasks_from: restore }
|
||||
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: restic, tasks_from: restore-restic }
|
||||
vars:
|
||||
host_id: idp
|
||||
-
|
||||
include_role: { name: authentik, tasks_from: restore-authentik }
|
||||
425
ansible/playbooks/setup.yml
Normal file
425
ansible/playbooks/setup.yml
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
---
|
||||
-
|
||||
hosts: all
|
||||
become: yes
|
||||
tags: [host]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: host }
|
||||
vars:
|
||||
ssh_pubkey_dir: "{{ playbook_dir }}/../../.ssh"
|
||||
-
|
||||
hosts: docker_hosts
|
||||
become: yes
|
||||
tags: [docker]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: docker }
|
||||
vars:
|
||||
registry_mirror_ip: "10.0.0.1"
|
||||
registry_mirrors:
|
||||
-
|
||||
upstream: docker.io
|
||||
mirror: "https://dockerhub.oci.code.tiararodney.com"
|
||||
-
|
||||
upstream: ghcr.io
|
||||
mirror: "https://ghcr.oci.code.tiararodney.com"
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
apply: { tags: [restic] }
|
||||
tags: [restic]
|
||||
vars:
|
||||
host_id: proxy
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
apply: { tags: [restic] }
|
||||
tags: [restic]
|
||||
vars:
|
||||
host_id: idp
|
||||
-
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: [letsencrypt, apache]
|
||||
tasks:
|
||||
-
|
||||
name: Create letsencrypt certificate archive
|
||||
command:
|
||||
cmd: "tar czf /tmp/letsencrypt.tar.gz --dereference -C {{ (playbook_dir + '/../../letsencrypt') | realpath }} ."
|
||||
creates: /tmp/letsencrypt.tar.gz
|
||||
-
|
||||
hosts: wg_peers
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: wireguard }
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: generate-keys }
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
name: Build WireGuard peer list
|
||||
set_fact:
|
||||
wg_peers:
|
||||
-
|
||||
public_key: "{{ hostvars[groups['idp'][0]]['wg_public_key'] }}"
|
||||
allowed_ips: "10.0.0.2/32"
|
||||
when: groups['idp'][0] in hostvars and 'wg_public_key' in hostvars[groups['idp'][0]]
|
||||
-
|
||||
name: Append client peers
|
||||
set_fact:
|
||||
wg_peers: "{{ wg_peers + wg_client_peers }}"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: deploy-wireguard }
|
||||
vars:
|
||||
wg_address: "10.0.0.1/24"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
name: Display proxy WireGuard public key
|
||||
debug:
|
||||
msg: "Proxy WG public key: {{ wg_public_key }}"
|
||||
when: wg_public_key is defined
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
name: Build WireGuard peer list
|
||||
set_fact:
|
||||
wg_peers:
|
||||
-
|
||||
public_key: "{{ hostvars[groups['proxy'][0]]['wg_public_key'] }}"
|
||||
allowed_ips: "10.0.0.1/32"
|
||||
endpoint: "{{ hostvars[groups['proxy'][0]]['wg_endpoint'] }}:51820"
|
||||
persistent_keepalive: true
|
||||
when: groups['proxy'][0] in hostvars and 'wg_public_key' in hostvars[groups['proxy'][0]]
|
||||
-
|
||||
name: Append client peers
|
||||
set_fact:
|
||||
wg_peers: "{{ wg_peers + wg_client_peers }}"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: deploy-wireguard }
|
||||
vars:
|
||||
wg_address: "10.0.0.2/24"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
vars:
|
||||
chat_domain: chat.tiararodney.com
|
||||
authentik_url: https://accounts.tiararodney.com
|
||||
authentik_internal_url: http://10.0.0.2:9000
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: host
|
||||
tasks_from: setup-swap
|
||||
apply: { tags: [host, swap] }
|
||||
tags: [host, swap]
|
||||
-
|
||||
name: Ensure accounts.tiararodney.com resolves to localhost for mod_auth_openidc
|
||||
tags: [apache, bugzilla]
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: "accounts\\.tiararodney\\.com"
|
||||
line: "127.0.0.1 accounts.tiararodney.com"
|
||||
-
|
||||
include_role:
|
||||
name: dnsmasq
|
||||
apply: { tags: [dnsmasq] }
|
||||
tags: [dnsmasq]
|
||||
vars:
|
||||
dns_records:
|
||||
- { domain: tiararodney.com, ip: "10.0.0.1" }
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
apply: { tags: [apache] }
|
||||
tags: [apache]
|
||||
vars:
|
||||
letsencrypt_archive: /tmp/letsencrypt.tar.gz
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
apply: { tags: [docker-registry] }
|
||||
tags: [docker-registry]
|
||||
vars:
|
||||
hostname: dockerhub.oci.code.tiararodney.com
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
apply: { tags: [docker-registry] }
|
||||
tags: [docker-registry]
|
||||
vars:
|
||||
hostname: ghcr.oci.code.tiararodney.com
|
||||
install_dir: /opt/docker-registry-ghcr
|
||||
port: 5051
|
||||
remote_url: "https://ghcr.io"
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
tasks_from: restore-restic
|
||||
apply: { tags: [registry-restore, never] }
|
||||
tags: [registry-restore, never]
|
||||
vars:
|
||||
host_id: proxy
|
||||
restore_include: /var/backups/docker-registry
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
tasks_from: restore-registry
|
||||
apply: { tags: [registry-restore, never] }
|
||||
tags: [registry-restore, never]
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-static-site
|
||||
apply: { tags: [blog] }
|
||||
tags: [blog]
|
||||
vars:
|
||||
name: blog
|
||||
server_name: blog.tiararodney.com
|
||||
document_root: /var/www/blog.tiararodney.com
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-static-site
|
||||
apply: { tags: [spec] }
|
||||
tags: [spec]
|
||||
vars:
|
||||
name: spec
|
||||
server_name: specs.code.tiararodney.com
|
||||
document_root: /var/www/specs.code.tiararodney.com
|
||||
directory_index: "README.html README.md README.txt"
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
-
|
||||
include_role:
|
||||
name: kellnr
|
||||
apply: { tags: [kellnr] }
|
||||
tags: [kellnr]
|
||||
vars:
|
||||
version: "6.0.0-rc.2"
|
||||
hostname: crates.code.tiararodney.com
|
||||
admin_pwd: "{{ vault_kellnr_admin_pwd }}"
|
||||
-
|
||||
include_role:
|
||||
name: devpi
|
||||
apply: { tags: [devpi] }
|
||||
tags: [devpi]
|
||||
vars:
|
||||
hostname: pypi.code.tiararodney.com
|
||||
-
|
||||
include_role:
|
||||
name: prosody
|
||||
apply: { tags: [prosody] }
|
||||
tags: [prosody]
|
||||
vars:
|
||||
version: "13.0"
|
||||
domain: "{{ chat_domain }}"
|
||||
admin_jid: "{{ vault_xmpp_admin_user }}@{{ chat_domain }}"
|
||||
bind_address: "10.0.0.1"
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
oauth_client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
oauth_userinfo_url: "{{ authentik_internal_url }}/application/o/userinfo/"
|
||||
oauth_ropc_client_id: "{{ vault_xmpp_ropc_client_id }}"
|
||||
oauth_ropc_client_secret: "{{ vault_xmpp_ropc_client_secret }}"
|
||||
oauth_token_url: "{{ authentik_internal_url }}/application/o/token/"
|
||||
session_timeout: 1800
|
||||
smtp_host: "{{ vault_prosody_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_prosody_smtp_username }}"
|
||||
smtp_password: "{{ vault_prosody_smtp_password }}"
|
||||
default_contacts:
|
||||
-
|
||||
jid: "{{ vault_xmpp_admin_user }}@{{ chat_domain }}"
|
||||
name: Tiara
|
||||
-
|
||||
include_role:
|
||||
name: conversejs
|
||||
apply: { tags: [conversejs] }
|
||||
tags: [conversejs]
|
||||
vars:
|
||||
version: "12.0.0"
|
||||
domain: "{{ chat_domain }}"
|
||||
oauth_client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
oauth_authorize_url: "{{ authentik_url }}/application/o/authorize/"
|
||||
oauth_token_url: "{{ authentik_url }}/application/o/token/"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
apply: { tags: [prosody, xmpp-upload] }
|
||||
tags: [prosody, xmpp-upload]
|
||||
vars:
|
||||
vhost_name: xmpp-upload
|
||||
server_name: "upload.{{ chat_domain }}"
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
backend_port: 5280
|
||||
-
|
||||
include_role:
|
||||
name: comentario
|
||||
apply: { tags: [comentario] }
|
||||
tags: [comentario]
|
||||
vars:
|
||||
version: "latest"
|
||||
domain: comments.tiararodney.com
|
||||
oauth_issuer_url: "{{ authentik_url }}/application/o/comentario"
|
||||
oauth_client_id: "{{ vault_comentario_oauth_client_id }}"
|
||||
oauth_client_secret: "{{ vault_comentario_oauth_client_secret }}"
|
||||
smtp_host: "{{ vault_comentario_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_comentario_smtp_username }}"
|
||||
smtp_password: "{{ vault_comentario_smtp_password }}"
|
||||
-
|
||||
include_role:
|
||||
name: bugzilla
|
||||
apply: { tags: [bugzilla] }
|
||||
tags: [bugzilla]
|
||||
vars:
|
||||
version: "5.0.4.1"
|
||||
domain: bugs.code.tiararodney.com
|
||||
db_password: "{{ vault_bugzilla_db_password }}"
|
||||
admin_email: "me@tiararodney.com"
|
||||
admin_pwd: "{{ vault_bugzilla_admin_pwd }}"
|
||||
oauth_issuer_url: "{{ authentik_url }}/application/o/bugs"
|
||||
oauth_authorize_url: "{{ authentik_url }}/application/o/authorize/"
|
||||
oauth_token_url: "{{ authentik_url }}/application/o/token/"
|
||||
oauth_userinfo_url: "{{ authentik_url }}/application/o/userinfo/"
|
||||
oauth_jwks_url: "{{ authentik_url }}/application/o/bugs/jwks/"
|
||||
oauth_client_id: "{{ vault_bugzilla_oauth_client_id }}"
|
||||
oauth_client_secret: "{{ vault_bugzilla_oauth_client_secret }}"
|
||||
oauth_crypto_passphrase: "{{ vault_bugzilla_oidc_passphrase }}"
|
||||
smtp_host: "{{ vault_bugzilla_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_bugzilla_smtp_username }}"
|
||||
smtp_password: "{{ vault_bugzilla_smtp_password }}"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
apply: { tags: [authentik] }
|
||||
tags: [authentik]
|
||||
vars:
|
||||
vhost_name: accounts
|
||||
server_name: accounts.tiararodney.com
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
backend_host: "10.0.0.2"
|
||||
backend_port: 9000
|
||||
websocket: true
|
||||
restricted_locations:
|
||||
-
|
||||
path: "/if/admin/"
|
||||
allowed_ips: ["10.0.0.0/24"]
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tags: [authentik]
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: host
|
||||
tasks_from: setup-zram
|
||||
apply: { tags: [host, swap, zram] }
|
||||
tags: [host, swap, zram]
|
||||
-
|
||||
include_role: { name: authentik }
|
||||
vars:
|
||||
version: "2026.2.1"
|
||||
domain: "accounts.tiararodney.com"
|
||||
pg_password: "{{ vault_pg_password }}"
|
||||
secret_key: "{{ vault_secret_key }}"
|
||||
bind_address: "10.0.0.2"
|
||||
smtp_host: "{{ vault_authentik_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_authentik_smtp_username }}"
|
||||
smtp_password: "{{ vault_authentik_smtp_password }}"
|
||||
oauth_applications:
|
||||
-
|
||||
name: Chat
|
||||
slug: chat
|
||||
client_type: public
|
||||
client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
redirect_uris:
|
||||
- "https://chat.tiararodney.com/"
|
||||
-
|
||||
name: Chat XMPP
|
||||
slug: chat-xmpp
|
||||
client_id: "{{ vault_xmpp_ropc_client_id }}"
|
||||
client_secret: "{{ vault_xmpp_ropc_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://chat.tiararodney.com/"
|
||||
-
|
||||
name: Comments
|
||||
slug: comments
|
||||
client_id: "{{ vault_comentario_oauth_client_id }}"
|
||||
client_secret: "{{ vault_comentario_oauth_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://comments.tiararodney.com/api/oauth/oidc/callback/authentik"
|
||||
-
|
||||
name: Bugs
|
||||
slug: bugs
|
||||
client_id: "{{ vault_bugzilla_oauth_client_id }}"
|
||||
client_secret: "{{ vault_bugzilla_oauth_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://bugs.code.tiararodney.com/oidc-callback"
|
||||
social_login_sources:
|
||||
-
|
||||
name: Google Account
|
||||
slug: google
|
||||
provider_type: google
|
||||
client_id: "{{ vault_social_google_client_id }}"
|
||||
client_secret: "{{ vault_social_google_client_secret }}"
|
||||
-
|
||||
name: Microsoft Account
|
||||
slug: microsoft
|
||||
provider_type: entraid
|
||||
client_id: "{{ vault_social_microsoft_client_id }}"
|
||||
client_secret: "{{ vault_social_microsoft_client_secret }}"
|
||||
-
|
||||
name: Apple ID
|
||||
slug: apple
|
||||
provider_type: apple
|
||||
client_id: "{{ vault_social_apple_client_id }}"
|
||||
client_secret: "{{ vault_social_apple_client_secret }}"
|
||||
-
|
||||
name: Facebook Account
|
||||
slug: facebook
|
||||
provider_type: facebook
|
||||
client_id: "{{ vault_social_facebook_client_id }}"
|
||||
client_secret: "{{ vault_social_facebook_client_secret }}"
|
||||
-
|
||||
name: X (formerly Twitter) Account
|
||||
slug: twitter
|
||||
provider_type: twitter
|
||||
client_id: "{{ vault_social_twitter_client_id }}"
|
||||
client_secret: "{{ vault_social_twitter_client_secret }}"
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger registry backups
|
||||
tags: [registry-backup, never]
|
||||
command: "{{ item }}"
|
||||
loop:
|
||||
- /etc/restic/pre-backup.d/docker-registry.sh
|
||||
- /etc/restic/pre-backup.d/docker-registry-ghcr.sh
|
||||
4
ansible/requirements.yml
Normal file
4
ansible/requirements.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
collections:
|
||||
- name: community.docker
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
5
ansible/roles/apache/defaults/main.yml
Normal file
5
ansible/roles/apache/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
ssl_cert_tiararodney: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key_tiararodney: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
ssl_cert_administratrix: /etc/letsencrypt/live/administratrix.io/fullchain.pem
|
||||
ssl_key_administratrix: /etc/letsencrypt/live/administratrix.io/privkey.pem
|
||||
6
ansible/roles/apache/handlers/main.yml
Normal file
6
ansible/roles/apache/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
-
|
||||
name: reload apache
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: reloaded
|
||||
2
ansible/roles/apache/meta/main.yml
Normal file
2
ansible/roles/apache/meta/main.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
dependencies: []
|
||||
18
ansible/roles/apache/tasks/deploy-reverse-proxy.yml
Normal file
18
ansible/roles/apache/tasks/deploy-reverse-proxy.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
-
|
||||
name: Ensure Apache is installed
|
||||
include_tasks: main.yml
|
||||
|
||||
-
|
||||
name: "Deploy {{ vhost_name }} reverse proxy vhost"
|
||||
template:
|
||||
src: reverse-proxy-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/{{ vhost_name }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: "Enable {{ vhost_name }} site"
|
||||
command: "{{ apache_enable_site_cmd }} {{ vhost_name }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/{{ vhost_name }}.conf"
|
||||
notify: reload apache
|
||||
27
ansible/roles/apache/tasks/deploy-static-site.yml
Normal file
27
ansible/roles/apache/tasks/deploy-static-site.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
-
|
||||
name: Ensure Apache is installed
|
||||
include_tasks: main.yml
|
||||
|
||||
-
|
||||
name: Ensure document root exists
|
||||
file:
|
||||
path: "{{ document_root }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy vhost configuration
|
||||
template:
|
||||
src: static-site-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/{{ name }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable site
|
||||
command: "{{ apache_enable_site_cmd }} {{ name }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/{{ name }}.conf"
|
||||
notify: reload apache
|
||||
83
ansible/roles/apache/tasks/install-apache.yml
Normal file
83
ansible/roles/apache/tasks/install-apache.yml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
-
|
||||
name: Ensure letsencrypt directory exists
|
||||
file:
|
||||
path: /etc/letsencrypt
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
-
|
||||
name: Deploy SSL certificates
|
||||
unarchive:
|
||||
src: "{{ letsencrypt_archive }}"
|
||||
dest: /etc/letsencrypt/
|
||||
when: letsencrypt_archive is defined
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure SSL private keys are readable by containers
|
||||
shell: find /etc/letsencrypt -name 'privkey*.pem' -exec chmod 644 {} +
|
||||
changed_when: false
|
||||
when: letsencrypt_archive is defined
|
||||
|
||||
-
|
||||
name: Install Apache
|
||||
apt:
|
||||
name: "{{ apache_package }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Enable Apache modules
|
||||
community.general.apache2_module:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- proxy
|
||||
- proxy_http
|
||||
- proxy_wstunnel
|
||||
- ssl
|
||||
- rewrite
|
||||
- headers
|
||||
- auth_basic
|
||||
- autoindex
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Disable default site
|
||||
command: "{{ apache_disable_site_cmd }} 000-default"
|
||||
args:
|
||||
removes: "{{ apache_sites_enabled }}/000-default.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure tiararodney.com document root exists
|
||||
file:
|
||||
path: /var/www/tiararodney.com
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy tiararodney.com vhost
|
||||
template:
|
||||
src: 000-default-redirect.conf.j2
|
||||
dest: "{{ apache_sites_available }}/000-default-redirect.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable tiararodney.com redirect vhost
|
||||
command: "{{ apache_enable_site_cmd }} 000-default-redirect"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/000-default-redirect.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure Apache is started and enabled
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
-
|
||||
name: Ensure Apache is reloaded with current config
|
||||
meta: flush_handlers
|
||||
8
ansible/roles/apache/tasks/main.yml
Normal file
8
ansible/roles/apache/tasks/main.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
-
|
||||
name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Install and configure Apache
|
||||
ansible.builtin.include_tasks: install-apache.yml
|
||||
18
ansible/roles/apache/templates/000-default-redirect.conf.j2
Normal file
18
ansible/roles/apache/templates/000-default-redirect.conf.j2
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName tiararodney.com
|
||||
Redirect permanent / https://tiararodney.com/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName tiararodney.com
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert_tiararodney }}
|
||||
SSLCertificateKeyFile {{ ssl_key_tiararodney }}
|
||||
|
||||
DocumentRoot /var/www/tiararodney.com
|
||||
<Directory /var/www/tiararodney.com>
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
31
ansible/roles/apache/templates/reverse-proxy-vhost.conf.j2
Normal file
31
ansible/roles/apache/templates/reverse-proxy-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ server_name }}
|
||||
Redirect permanent / https://{{ server_name }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ server_name }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
{% for loc in restricted_locations | default([]) %}
|
||||
<Location {{ loc.path }}>
|
||||
{% for ip in loc.allowed_ips %}
|
||||
Require ip {{ ip }}
|
||||
{% endfor %}
|
||||
</Location>
|
||||
{% endfor %}
|
||||
|
||||
ProxyPreserveHost on
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Forwarded-Ssl "on"
|
||||
{% if websocket | default(false) %}
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
||||
RewriteRule ^/(.*) ws://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/$1 [P,L]
|
||||
{% endif %}
|
||||
ProxyPass / http://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/
|
||||
ProxyPassReverse / http://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/
|
||||
</VirtualHost>
|
||||
21
ansible/roles/apache/templates/static-site-vhost.conf.j2
Normal file
21
ansible/roles/apache/templates/static-site-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ server_name }}
|
||||
Redirect permanent / https://{{ server_name }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ server_name }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
DocumentRoot {{ document_root }}
|
||||
<Directory {{ document_root }}>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
{% if directory_index is defined %}
|
||||
DirectoryIndex {{ directory_index }}
|
||||
{% endif %}
|
||||
</VirtualHost>
|
||||
7
ansible/roles/apache/vars/Debian.yml
Normal file
7
ansible/roles/apache/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
apache_package: apache2
|
||||
apache_service: apache2
|
||||
apache_sites_available: /etc/apache2/sites-available
|
||||
apache_sites_enabled: /etc/apache2/sites-enabled
|
||||
apache_enable_site_cmd: a2ensite
|
||||
apache_disable_site_cmd: a2dissite
|
||||
9
ansible/roles/authentik/defaults/main.yml
Normal file
9
ansible/roles/authentik/defaults/main.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
install_dir: /opt/authentik
|
||||
bind_address: "0.0.0.0"
|
||||
port: 9000
|
||||
postgres_shared_buffers: 64MB
|
||||
postgres_work_mem: 4MB
|
||||
postgres_maintenance_work_mem: 32MB
|
||||
postgres_effective_cache_size: 256MB
|
||||
redis_maxmemory: 80mb
|
||||
0
ansible/roles/authentik/files/branding/.gitkeep
Normal file
0
ansible/roles/authentik/files/branding/.gitkeep
Normal file
4
ansible/roles/authentik/meta/main.yml
Normal file
4
ansible/roles/authentik/meta/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
92
ansible/roles/authentik/tasks/deploy-authentik.yml
Normal file
92
ansible/roles/authentik/tasks/deploy-authentik.yml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy environment file
|
||||
template:
|
||||
src: env.j2
|
||||
dest: "{{ install_dir }}/.env"
|
||||
|
||||
-
|
||||
name: Ensure blueprints directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/blueprints"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy OAuth2 blueprint
|
||||
template:
|
||||
src: blueprint-oauth2.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/oauth2-applications.yaml"
|
||||
when: oauth_applications is defined and oauth_applications | length > 0
|
||||
|
||||
-
|
||||
name: Deploy enrollment blueprint
|
||||
template:
|
||||
src: blueprint-enrollment.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/enrollment.yaml"
|
||||
|
||||
-
|
||||
name: Deploy social login blueprint
|
||||
template:
|
||||
src: blueprint-social-logins.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/social-logins.yaml"
|
||||
when: social_login_sources is defined and social_login_sources | length > 0
|
||||
|
||||
-
|
||||
name: Ensure media directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/media/public"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Copy branding assets
|
||||
copy:
|
||||
src: branding/
|
||||
dest: "{{ install_dir }}/media/public/"
|
||||
mode: "0644"
|
||||
when: branding_assets | default(false)
|
||||
|
||||
-
|
||||
name: Ensure custom-templates email directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/custom-templates/email"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy custom email templates
|
||||
template:
|
||||
src: "email/{{ item }}.j2"
|
||||
dest: "{{ install_dir }}/custom-templates/email/{{ item }}"
|
||||
loop:
|
||||
- account-confirmation.html
|
||||
- password-reset.html
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start Authentik stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Deploy Authentik backup script
|
||||
template:
|
||||
src: backup.sh.j2
|
||||
dest: /etc/restic/pre-backup.d/authentik.sh
|
||||
mode: "0755"
|
||||
4
ansible/roles/authentik/tasks/main.yml
Normal file
4
ansible/roles/authentik/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy Authentik
|
||||
ansible.builtin.include_tasks: deploy-authentik.yml
|
||||
46
ansible/roles/authentik/tasks/restore-authentik.yml
Normal file
46
ansible/roles/authentik/tasks/restore-authentik.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_authentik_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/authentik"
|
||||
|
||||
-
|
||||
name: Stop Authentik stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore config files
|
||||
copy:
|
||||
src: "{{ _authentik_backup_dir }}/{{ item }}"
|
||||
dest: "{{ install_dir }}/{{ item }}"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
loop:
|
||||
- .env
|
||||
- docker-compose.yml
|
||||
|
||||
-
|
||||
name: Start Postgres only
|
||||
command: >
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml
|
||||
up -d postgres
|
||||
|
||||
-
|
||||
name: Wait for Postgres to be ready
|
||||
pause:
|
||||
seconds: 10
|
||||
|
||||
-
|
||||
name: Restore Postgres dump
|
||||
shell: >
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml
|
||||
exec -T postgres psql -U authentik authentik
|
||||
< {{ _authentik_backup_dir }}/authentik.sql
|
||||
|
||||
-
|
||||
name: Start full Authentik stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
7
ansible/roles/authentik/templates/backup.sh.j2
Normal file
7
ansible/roles/authentik/templates/backup.sh.j2
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
BACKUP_DIR="{{ backup_staging_dir | default('/var/backups') }}/authentik"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml exec -T postgres pg_dump -U authentik authentik > "$BACKUP_DIR/authentik.sql"
|
||||
cp "{{ install_dir }}/.env" "$BACKUP_DIR/.env"
|
||||
cp "{{ install_dir }}/docker-compose.yml" "$BACKUP_DIR/docker-compose.yml"
|
||||
326
ansible/roles/authentik/templates/blueprint-enrollment.yml.j2
Normal file
326
ansible/roles/authentik/templates/blueprint-enrollment.yml.j2
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: enrollment-flow
|
||||
entries:
|
||||
# --- Brand configuration ---
|
||||
- model: authentik_brands.brand
|
||||
identifiers:
|
||||
domain: authentik-default
|
||||
state: present
|
||||
attrs:
|
||||
branding_title: {{ domain }}
|
||||
{% if branding_logo is defined %}
|
||||
branding_logo: {{ branding_logo }}
|
||||
{% endif %}
|
||||
{% if branding_favicon is defined %}
|
||||
branding_favicon: {{ branding_favicon }}
|
||||
{% endif %}
|
||||
|
||||
# --- Enrollment flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: enrollment-flow
|
||||
identifiers:
|
||||
slug: default-enrollment-flow
|
||||
attrs:
|
||||
name: Sign Up
|
||||
title: Create an account
|
||||
designation: enrollment
|
||||
authentication: require_unauthenticated
|
||||
|
||||
# --- Recovery flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: recovery-flow
|
||||
identifiers:
|
||||
slug: default-recovery-flow
|
||||
attrs:
|
||||
name: Password Recovery
|
||||
title: Reset your password
|
||||
designation: recovery
|
||||
authentication: require_unauthenticated
|
||||
|
||||
# --- Prompt fields ---
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-email
|
||||
identifiers:
|
||||
name: enrollment-field-email
|
||||
attrs:
|
||||
field_key: email
|
||||
label: Email
|
||||
type: email
|
||||
required: true
|
||||
placeholder: Email
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-username
|
||||
identifiers:
|
||||
name: enrollment-field-username
|
||||
attrs:
|
||||
field_key: username
|
||||
label: Username
|
||||
type: username
|
||||
required: true
|
||||
placeholder: Username
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-password
|
||||
identifiers:
|
||||
name: enrollment-field-password
|
||||
attrs:
|
||||
field_key: password
|
||||
label: Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password
|
||||
placeholder_expression: false
|
||||
order: 2
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-password-repeat
|
||||
identifiers:
|
||||
name: enrollment-field-password-repeat
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 3
|
||||
|
||||
# --- Password policy ---
|
||||
- model: authentik_policies_password.passwordpolicy
|
||||
id: password-policy
|
||||
identifiers:
|
||||
name: enrollment-password-policy
|
||||
attrs:
|
||||
name: enrollment-password-policy
|
||||
length_min: 10
|
||||
amount_uppercase: 1
|
||||
amount_lowercase: 1
|
||||
amount_digits: 1
|
||||
amount_symbols: 1
|
||||
check_static_rules: true
|
||||
check_have_i_been_pwned: true
|
||||
check_zxcvbn: true
|
||||
zxcvbn_score_threshold: 3
|
||||
error_message: "Password must be at least 10 characters with uppercase, lowercase, digit, and symbol."
|
||||
|
||||
# --- Enrollment stages ---
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
id: enrollment-prompt-stage
|
||||
identifiers:
|
||||
name: enrollment-prompt
|
||||
attrs:
|
||||
fields:
|
||||
- !KeyOf field-email
|
||||
- !KeyOf field-username
|
||||
- !KeyOf field-password
|
||||
- !KeyOf field-password-repeat
|
||||
validation_policies:
|
||||
- !KeyOf password-policy
|
||||
|
||||
- model: authentik_stages_user_write.userwritestage
|
||||
id: enrollment-user-write
|
||||
identifiers:
|
||||
name: enrollment-user-write
|
||||
attrs:
|
||||
user_creation_mode: always_create
|
||||
create_users_as_inactive: true
|
||||
|
||||
- model: authentik_stages_email.emailstage
|
||||
id: enrollment-email-verification
|
||||
identifiers:
|
||||
name: enrollment-email-verification
|
||||
attrs:
|
||||
use_global_settings: true
|
||||
activate_user_on_success: true
|
||||
subject: Verify your email address
|
||||
template: email/account-confirmation.html
|
||||
|
||||
- model: authentik_stages_user_login.userloginstage
|
||||
id: enrollment-user-login
|
||||
identifiers:
|
||||
name: enrollment-user-login
|
||||
|
||||
# --- Enrollment flow stage bindings ---
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-prompt-stage
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-user-write
|
||||
order: 20
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-email-verification
|
||||
order: 30
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-user-login
|
||||
order: 100
|
||||
|
||||
# --- Recovery stages ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
id: recovery-identification
|
||||
identifiers:
|
||||
name: recovery-identification
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
|
||||
- model: authentik_stages_email.emailstage
|
||||
id: recovery-email
|
||||
identifiers:
|
||||
name: recovery-email
|
||||
attrs:
|
||||
use_global_settings: true
|
||||
subject: Reset your password
|
||||
template: email/password-reset.html
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-recovery-password
|
||||
identifiers:
|
||||
name: recovery-field-password
|
||||
attrs:
|
||||
field_key: password
|
||||
label: New Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: New Password
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-recovery-password-repeat
|
||||
identifiers:
|
||||
name: recovery-field-password-repeat
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: New Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: New Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
id: recovery-password-stage
|
||||
identifiers:
|
||||
name: recovery-password-prompt
|
||||
attrs:
|
||||
fields:
|
||||
- !KeyOf field-recovery-password
|
||||
- !KeyOf field-recovery-password-repeat
|
||||
validation_policies:
|
||||
- !KeyOf password-policy
|
||||
|
||||
- model: authentik_stages_user_write.userwritestage
|
||||
id: recovery-user-write
|
||||
identifiers:
|
||||
name: recovery-user-write
|
||||
attrs:
|
||||
user_creation_mode: never_create
|
||||
|
||||
- model: authentik_stages_user_login.userloginstage
|
||||
id: recovery-user-login
|
||||
identifiers:
|
||||
name: recovery-user-login
|
||||
|
||||
# --- Recovery flow stage bindings ---
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-identification
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-email
|
||||
order: 20
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-password-stage
|
||||
order: 30
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-user-write
|
||||
order: 40
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-user-login
|
||||
order: 100
|
||||
|
||||
# --- Unenrollment (account deletion) flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: unenrollment-flow
|
||||
identifiers:
|
||||
slug: default-unenrollment-flow
|
||||
attrs:
|
||||
name: Delete Account
|
||||
title: Delete your account
|
||||
designation: unenrollment
|
||||
|
||||
- model: authentik_stages_consent.consentstage
|
||||
id: unenrollment-consent
|
||||
identifiers:
|
||||
name: unenrollment-consent
|
||||
attrs:
|
||||
mode: always_require
|
||||
|
||||
- model: authentik_stages_user_delete.userdeletestage
|
||||
id: unenrollment-user-delete
|
||||
identifiers:
|
||||
name: unenrollment-user-delete
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf unenrollment-flow
|
||||
stage: !KeyOf unenrollment-consent
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf unenrollment-flow
|
||||
stage: !KeyOf unenrollment-user-delete
|
||||
order: 20
|
||||
|
||||
# --- Set recovery and unenrollment flows on brand ---
|
||||
- model: authentik_brands.brand
|
||||
identifiers:
|
||||
domain: authentik-default
|
||||
state: present
|
||||
attrs:
|
||||
flow_recovery: !KeyOf recovery-flow
|
||||
flow_unenrollment: !KeyOf unenrollment-flow
|
||||
|
||||
{% if social_login_sources is not defined or social_login_sources | length == 0 %}
|
||||
# --- Bind enrollment flow to the default login identification stage ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
identifiers:
|
||||
name: default-authentication-identification
|
||||
state: present
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
- username
|
||||
enrollment_flow: !KeyOf enrollment-flow
|
||||
{% endif %}
|
||||
67
ansible/roles/authentik/templates/blueprint-oauth2.yml.j2
Normal file
67
ansible/roles/authentik/templates/blueprint-oauth2.yml.j2
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: oauth2-applications
|
||||
entries:
|
||||
{% for app in oauth_applications %}
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
identifiers:
|
||||
name: {{ app.name }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ app.name }}
|
||||
client_type: {{ app.client_type | default('confidential') }}
|
||||
client_id: {{ app.client_id }}
|
||||
{% if app.client_secret is defined %}
|
||||
client_secret: {{ app.client_secret }}
|
||||
{% endif %}
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
{% for uri in app.redirect_uris %}
|
||||
- url: "{{ uri }}"
|
||||
matching_mode: strict
|
||||
{% endfor %}
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: {{ app.slug }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ app.name }}
|
||||
slug: {{ app.slug }}
|
||||
provider: !Find [authentik_providers_oauth2.oauth2provider, [name, {{ app.name }}]]
|
||||
policy_engine_mode: any
|
||||
|
||||
{% endfor %}
|
||||
- model: authentik_policies_expression.expressionpolicy
|
||||
identifiers:
|
||||
name: enforce-unique-email
|
||||
state: present
|
||||
attrs:
|
||||
name: enforce-unique-email
|
||||
expression: |
|
||||
from authentik.core.models import User
|
||||
email = request.context.get("prompt_data", {}).get("email", "")
|
||||
if not email:
|
||||
return True
|
||||
if User.objects.filter(email=email).exists():
|
||||
ak_message("This email address is already in use.")
|
||||
return False
|
||||
return True
|
||||
execution_logging: true
|
||||
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
identifiers:
|
||||
name: default-source-enrollment-prompt
|
||||
state: present
|
||||
attrs:
|
||||
fields:
|
||||
- !Find [authentik_stages_prompt.prompt, [name, default-source-enrollment-field-username]]
|
||||
validation_policies:
|
||||
- !Find [authentik_policies_expression.expressionpolicy, [name, enforce-unique-email]]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: social-login-sources
|
||||
entries:
|
||||
{% for source in social_login_sources %}
|
||||
- model: authentik_sources_oauth.oauthsource
|
||||
id: source-{{ source.slug }}
|
||||
identifiers:
|
||||
slug: {{ source.slug }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ source.name }}
|
||||
slug: {{ source.slug }}
|
||||
provider_type: {{ source.provider_type }}
|
||||
consumer_key: {{ source.client_id }}
|
||||
consumer_secret: {{ source.client_secret }}
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-source-authentication]]
|
||||
enrollment_flow: !Find [authentik_flows.flow, [slug, default-source-enrollment]]
|
||||
|
||||
{% endfor %}
|
||||
# --- Add social login sources to the login identification stage ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
identifiers:
|
||||
name: default-authentication-identification
|
||||
state: present
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
- username
|
||||
enrollment_flow: !Find [authentik_flows.flow, [slug, default-enrollment-flow]]
|
||||
recovery_flow: !Find [authentik_flows.flow, [slug, default-recovery-flow]]
|
||||
sources:
|
||||
{% for source in social_login_sources %}
|
||||
- !KeyOf source-{{ source.slug }}
|
||||
{% endfor %}
|
||||
51
ansible/roles/authentik/templates/docker-compose.yml.j2
Normal file
51
ansible/roles/authentik/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${AUTHENTIK_POSTGRESQL__PASSWORD}"
|
||||
POSTGRES_USER: "authentik"
|
||||
POSTGRES_DB: "authentik"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
command: >
|
||||
postgres -c shared_buffers={{ postgres_shared_buffers }}
|
||||
-c work_mem={{ postgres_work_mem }}
|
||||
-c maintenance_work_mem={{ postgres_maintenance_work_mem }}
|
||||
-c effective_cache_size={{ postgres_effective_cache_size }}
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
command: ["redis-server", "--maxmemory", "{{ redis_maxmemory }}", "--maxmemory-policy", "allkeys-lru"]
|
||||
restart: unless-stopped
|
||||
|
||||
server:
|
||||
image: ghcr.io/goauthentik/server:{{ version }}
|
||||
command: server
|
||||
env_file: .env
|
||||
ports:
|
||||
- "{{ bind_address }}:{{ port }}:9000"
|
||||
volumes:
|
||||
- ./blueprints:/blueprints/custom:ro
|
||||
- ./media:/media:ro
|
||||
- ./custom-templates:/templates:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
worker:
|
||||
image: ghcr.io/goauthentik/server:{{ version }}
|
||||
command: worker
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./blueprints:/blueprints/custom:ro
|
||||
- ./media:/media:ro
|
||||
- ./custom-templates:/templates:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{% raw %}{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% blocktrans with domain=branding_title %}Verify your email - {{ domain }}{% endblocktrans %}</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
|
||||
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
|
||||
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>{% blocktrans with domain=branding_title %}Welcome to {{ domain }}{% endblocktrans %}</h1>
|
||||
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
|
||||
<p>{% blocktrans %}Please click the button below to verify your email address and activate your account.{% endblocktrans %}</p>
|
||||
<p style="text-align: center; margin: 32px 0;">
|
||||
<a href="{{ url }}" class="btn">{% trans "Verify Email" %}</a>
|
||||
</p>
|
||||
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
|
||||
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not create an account, you can safely ignore this email.{% endblocktrans %}</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>{{ branding_title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>{% endraw %}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{% raw %}{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% blocktrans with domain=branding_title %}Reset your password - {{ domain }}{% endblocktrans %}</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
|
||||
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
|
||||
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>{% trans "Reset your password" %}</h1>
|
||||
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
|
||||
<p>{% blocktrans with domain=branding_title %}A password reset was requested for your account on {{ domain }}.{% endblocktrans %}</p>
|
||||
<p style="text-align: center; margin: 32px 0;">
|
||||
<a href="{{ url }}" class="btn">{% trans "Reset Password" %}</a>
|
||||
</p>
|
||||
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
|
||||
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not request a password reset, you can safely ignore this email.{% endblocktrans %}</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>{{ branding_title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>{% endraw %}
|
||||
19
ansible/roles/authentik/templates/env.j2
Normal file
19
ansible/roles/authentik/templates/env.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
AUTHENTIK_SECRET_KEY={{ secret_key }}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD={{ pg_password }}
|
||||
AUTHENTIK_POSTGRESQL__HOST=postgres
|
||||
AUTHENTIK_POSTGRESQL__USER=authentik
|
||||
AUTHENTIK_POSTGRESQL__NAME=authentik
|
||||
AUTHENTIK_REDIS__HOST=redis
|
||||
AUTHENTIK_HOST={{ domain }}
|
||||
AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS=10.0.0.0/24,172.16.0.0/12
|
||||
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
|
||||
AUTHENTIK_DISABLE_UPDATE_CHECK=true
|
||||
AUTHENTIK_ERROR_REPORTING__ENABLED=false
|
||||
{% if smtp_host is defined %}
|
||||
AUTHENTIK_EMAIL__HOST={{ smtp_host }}
|
||||
AUTHENTIK_EMAIL__PORT={{ smtp_port | default(587) }}
|
||||
AUTHENTIK_EMAIL__USERNAME={{ smtp_username }}
|
||||
AUTHENTIK_EMAIL__PASSWORD={{ smtp_password }}
|
||||
AUTHENTIK_EMAIL__USE_TLS=true
|
||||
AUTHENTIK_EMAIL__FROM={{ smtp_from | default(smtp_username) }}
|
||||
{% endif %}
|
||||
9
ansible/roles/bugzilla/defaults/main.yml
Normal file
9
ansible/roles/bugzilla/defaults/main.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
install_dir: /opt/bugzilla
|
||||
bugzilla_dir: /opt/bugzilla/bugzilla
|
||||
bugzilla_download_url: "https://ftp.mozilla.org/pub/webtools/bugzilla/5.0-branch/bugzilla-{{ version }}.tar.gz"
|
||||
db_port: 5433
|
||||
db_name: bugzilla
|
||||
db_user: bugzilla
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
6
ansible/roles/bugzilla/meta/main.yml
Normal file
6
ansible/roles/bugzilla/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
160
ansible/roles/bugzilla/tasks/deploy-bugzilla.yml
Normal file
160
ansible/roles/bugzilla/tasks/deploy-bugzilla.yml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
-
|
||||
name: Include OS-specific variables
|
||||
include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Install Bugzilla Perl dependencies
|
||||
apt:
|
||||
name: "{{ bugzilla_packages }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Enable Apache modules for Bugzilla
|
||||
community.general.apache2_module:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- cgid
|
||||
- expires
|
||||
- auth_openidc
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start bugzilla database
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Download Bugzilla
|
||||
unarchive:
|
||||
src: "{{ bugzilla_download_url }}"
|
||||
dest: "{{ install_dir }}"
|
||||
remote_src: yes
|
||||
creates: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
|
||||
-
|
||||
name: Symlink versioned directory to bugzilla_dir
|
||||
file:
|
||||
src: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
dest: "{{ bugzilla_dir }}"
|
||||
state: link
|
||||
when: bugzilla_dir != install_dir + '/bugzilla-' + version
|
||||
|
||||
-
|
||||
name: Deploy localconfig
|
||||
template:
|
||||
src: localconfig.j2
|
||||
dest: "{{ bugzilla_dir }}/localconfig"
|
||||
mode: "0640"
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy checksetup answers file
|
||||
template:
|
||||
src: checksetup-answers.j2
|
||||
dest: "{{ install_dir }}/checksetup-answers.pl"
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Wait for PostgreSQL to be ready
|
||||
wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ db_port }}"
|
||||
delay: 2
|
||||
timeout: 30
|
||||
|
||||
-
|
||||
name: Run Bugzilla checksetup
|
||||
command:
|
||||
cmd: "perl checksetup.pl {{ install_dir }}/checksetup-answers.pl"
|
||||
chdir: "{{ bugzilla_dir }}"
|
||||
register: checksetup_result
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: checksetup_result.rc == 0
|
||||
|
||||
-
|
||||
name: Run Bugzilla checksetup again to generate params.json
|
||||
command:
|
||||
cmd: "perl checksetup.pl {{ install_dir }}/checksetup-answers.pl"
|
||||
chdir: "{{ bugzilla_dir }}"
|
||||
creates: "{{ bugzilla_dir }}/data/params.json"
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth login class
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"user_info_class"\s*:\s*"CGI"'
|
||||
replace: '"user_info_class" : "Env,CGI"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth email variable
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"auth_env_email"\s*:\s*""'
|
||||
replace: '"auth_env_email" : "OIDC_CLAIM_email"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth realname variable
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"auth_env_realname"\s*:\s*""'
|
||||
replace: '"auth_env_realname" : "OIDC_CLAIM_name"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Set Bugzilla file ownership
|
||||
file:
|
||||
path: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
recurse: yes
|
||||
|
||||
-
|
||||
name: Deploy bugzilla vhost
|
||||
template:
|
||||
src: bugzilla-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/bugzilla.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable bugzilla site
|
||||
command: "{{ apache_enable_site_cmd }} bugzilla"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/bugzilla.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy bugzilla backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: bugzilla
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- bugzilla_postgres_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ bugzilla_dir }}/localconfig"
|
||||
4
ansible/roles/bugzilla/tasks/main.yml
Normal file
4
ansible/roles/bugzilla/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy bugzilla
|
||||
ansible.builtin.include_tasks: deploy-bugzilla.yml
|
||||
42
ansible/roles/bugzilla/tasks/restore.yml
Normal file
42
ansible/roles/bugzilla/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_bugzilla_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/bugzilla"
|
||||
|
||||
-
|
||||
name: Stop bugzilla database
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _bugzilla_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore localconfig
|
||||
copy:
|
||||
src: "{{ _bugzilla_backup_dir }}/localconfig"
|
||||
dest: "{{ bugzilla_dir }}/localconfig"
|
||||
remote_src: yes
|
||||
mode: "0640"
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Restore bugzilla postgres volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v bugzilla_postgres_data:/data
|
||||
-v {{ _bugzilla_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/postgres_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start bugzilla database
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
52
ansible/roles/bugzilla/templates/bugzilla-vhost.conf.j2
Normal file
52
ansible/roles/bugzilla/templates/bugzilla-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ domain }}
|
||||
Redirect permanent / https://{{ domain }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ domain }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
{% if oauth_client_id is defined %}
|
||||
OIDCProviderIssuer {{ oauth_issuer_url }}
|
||||
OIDCProviderAuthorizationEndpoint {{ oauth_authorize_url }}
|
||||
OIDCProviderTokenEndpoint {{ oauth_token_url }}
|
||||
OIDCProviderTokenEndpointAuth client_secret_post
|
||||
OIDCProviderUserInfoEndpoint {{ oauth_userinfo_url }}
|
||||
OIDCProviderJwksUri {{ oauth_jwks_url }}
|
||||
OIDCClientID {{ oauth_client_id }}
|
||||
OIDCClientSecret {{ oauth_client_secret }}
|
||||
OIDCRedirectURI https://{{ domain }}/oidc-callback
|
||||
OIDCCryptoPassphrase {{ oauth_crypto_passphrase }}
|
||||
OIDCScope "openid profile email"
|
||||
OIDCRemoteUserClaim preferred_username
|
||||
OIDCPassClaimsAs environment
|
||||
OIDCSSLValidateServer Off
|
||||
OIDCProviderEndSessionEndpoint {{ oauth_issuer_url }}/end-session/
|
||||
|
||||
<Location />
|
||||
AuthType openid-connect
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
||||
<Location /oidc-callback>
|
||||
AuthType openid-connect
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
||||
RedirectMatch ^/logout$ /oidc-callback?logout=https%3A%2F%2F{{ domain }}%2F
|
||||
{% endif %}
|
||||
|
||||
DocumentRoot {{ bugzilla_dir }}
|
||||
<Directory {{ bugzilla_dir }}>
|
||||
AddHandler cgi-script .cgi
|
||||
Options +ExecCGI +FollowSymLinks
|
||||
DirectoryIndex index.cgi index.html
|
||||
AllowOverride All
|
||||
{% if oauth_client_id is not defined %}
|
||||
Require all granted
|
||||
{% endif %}
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
4
ansible/roles/bugzilla/templates/checksetup-answers.j2
Normal file
4
ansible/roles/bugzilla/templates/checksetup-answers.j2
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
$answer{'ADMIN_EMAIL'} = '{{ admin_email }}';
|
||||
$answer{'ADMIN_PASSWORD'} = '{{ admin_pwd }}';
|
||||
$answer{'ADMIN_REALNAME'} = '{{ admin_name | default("Admin") }}';
|
||||
$answer{'NO_PAUSE'} = 1;
|
||||
15
ansible/roles/bugzilla/templates/docker-compose.yml.j2
Normal file
15
ansible/roles/bugzilla/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: {{ db_name }}
|
||||
POSTGRES_USER: {{ db_user }}
|
||||
POSTGRES_PASSWORD: "{{ db_password }}"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:{{ db_port }}:5432"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
15
ansible/roles/bugzilla/templates/localconfig.j2
Normal file
15
ansible/roles/bugzilla/templates/localconfig.j2
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
$db_driver = 'Pg';
|
||||
$db_host = '127.0.0.1';
|
||||
$db_port = {{ db_port }};
|
||||
$db_name = '{{ db_name }}';
|
||||
$db_user = '{{ db_user }}';
|
||||
$db_pass = '{{ db_password }}';
|
||||
$webservergroup = 'www-data';
|
||||
{% if smtp_host is defined %}
|
||||
$smtp_server = '{{ smtp_host }}';
|
||||
$smtp_port = {{ smtp_port | default(587) }};
|
||||
$smtp_username = '{{ smtp_username }}';
|
||||
$smtp_password = '{{ smtp_password }}';
|
||||
$smtp_ssl = 'On';
|
||||
$mailfrom = '{{ smtp_from | default(smtp_username) }}';
|
||||
{% endif %}
|
||||
31
ansible/roles/bugzilla/vars/Debian.yml
Normal file
31
ansible/roles/bugzilla/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
bugzilla_packages:
|
||||
- build-essential
|
||||
- libgd-dev
|
||||
- libappconfig-perl
|
||||
- libdate-calc-perl
|
||||
- libtemplate-perl
|
||||
- libmime-tools-perl
|
||||
- libdbi-perl
|
||||
- libdbd-pg-perl
|
||||
- libcgi-pm-perl
|
||||
- libmath-random-isaac-perl
|
||||
- libapache2-mod-perl2
|
||||
- libchart-perl
|
||||
- libxml-twig-perl
|
||||
- libgd-perl
|
||||
- libgd-graph-perl
|
||||
- libtemplate-plugin-gd-perl
|
||||
- libsoap-lite-perl
|
||||
- libhtml-scrubber-perl
|
||||
- libemail-sender-perl
|
||||
- libemail-mime-perl
|
||||
- libjson-xs-perl
|
||||
- libencode-detect-perl
|
||||
- libtheschwartz-perl
|
||||
- libdaemon-generic-perl
|
||||
- libdatetime-perl
|
||||
- libdatetime-timezone-perl
|
||||
- libemail-address-perl
|
||||
- perlmagick
|
||||
- libapache2-mod-auth-openidc
|
||||
5
ansible/roles/comentario/defaults/main.yml
Normal file
5
ansible/roles/comentario/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/comentario
|
||||
port: 8060
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
6
ansible/roles/comentario/meta/main.yml
Normal file
6
ansible/roles/comentario/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
52
ansible/roles/comentario/tasks/deploy-comentario.yml
Normal file
52
ansible/roles/comentario/tasks/deploy-comentario.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy secrets file
|
||||
template:
|
||||
src: secrets.yaml.j2
|
||||
dest: "{{ install_dir }}/secrets.yaml"
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start comentario stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Deploy comentario vhost
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
vars:
|
||||
vhost_name: comentario
|
||||
server_name: "{{ domain }}"
|
||||
backend_port: "{{ port }}"
|
||||
|
||||
-
|
||||
name: Deploy comentario backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: comentario
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- comentario_comentario_postgres_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ install_dir }}/secrets.yaml"
|
||||
4
ansible/roles/comentario/tasks/main.yml
Normal file
4
ansible/roles/comentario/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy comentario
|
||||
ansible.builtin.include_tasks: deploy-comentario.yml
|
||||
41
ansible/roles/comentario/tasks/restore.yml
Normal file
41
ansible/roles/comentario/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_comentario_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/comentario"
|
||||
|
||||
-
|
||||
name: Stop comentario stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _comentario_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore secrets file
|
||||
copy:
|
||||
src: "{{ _comentario_backup_dir }}/secrets.yaml"
|
||||
dest: "{{ install_dir }}/secrets.yaml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore comentario postgres volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v comentario_comentario_postgres_data:/data
|
||||
-v {{ _comentario_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/comentario_postgres_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start comentario stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
26
ansible/roles/comentario/templates/docker-compose.yml.j2
Normal file
26
ansible/roles/comentario/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: comentario
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
comentario:
|
||||
image: registry.gitlab.com/comentario/comentario:{{ version }}
|
||||
ports:
|
||||
- "127.0.0.1:{{ port }}:80"
|
||||
environment:
|
||||
BASE_URL: https://{{ domain }}
|
||||
SECRETS_FILE: /secrets.yaml
|
||||
volumes:
|
||||
- ./secrets.yaml:/secrets.yaml:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
27
ansible/roles/comentario/templates/secrets.yaml.j2
Normal file
27
ansible/roles/comentario/templates/secrets.yaml.j2
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
postgres:
|
||||
host: postgres
|
||||
port: 5432
|
||||
database: comentario
|
||||
username: postgres
|
||||
password: postgres
|
||||
{% if smtp_host is defined %}
|
||||
smtp:
|
||||
host: {{ smtp_host }}
|
||||
port: {{ smtp_port | default(587) }}
|
||||
username: {{ smtp_username }}
|
||||
password: {{ smtp_password }}
|
||||
from: {{ smtp_from | default(smtp_username) }}
|
||||
{% endif %}
|
||||
{% if oauth_client_id is defined %}
|
||||
idp:
|
||||
oidc:
|
||||
- id: authentik
|
||||
name: Authentik
|
||||
url: {{ oauth_issuer_url }}
|
||||
key: {{ oauth_client_id }}
|
||||
secret: {{ oauth_client_secret }}
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
{% endif %}
|
||||
5
ansible/roles/conversejs/defaults/main.yml
Normal file
5
ansible/roles/conversejs/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /var/www/chat.tiararodney.com
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
prosody_port: 5280
|
||||
4
ansible/roles/conversejs/meta/main.yml
Normal file
4
ansible/roles/conversejs/meta/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: apache
|
||||
49
ansible/roles/conversejs/tasks/deploy-conversejs.yml
Normal file
49
ansible/roles/conversejs/tasks/deploy-conversejs.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
-
|
||||
name: Ensure converse.js document root exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Download converse.js release
|
||||
unarchive:
|
||||
src: "https://github.com/conversejs/converse.js/releases/download/v{{ version }}/converse.js-{{ version }}.tgz"
|
||||
dest: "{{ install_dir }}"
|
||||
remote_src: yes
|
||||
extra_opts: ["--strip-components=1"]
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Download libsignal-protocol for OMEMO
|
||||
get_url:
|
||||
url: "https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"
|
||||
dest: "{{ install_dir }}/dist/libsignal-protocol.min.js"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy converse.js index page
|
||||
template:
|
||||
src: index.html.j2
|
||||
dest: "{{ install_dir }}/index.html"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy chat vhost
|
||||
template:
|
||||
src: vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/chat.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable chat site
|
||||
command: "{{ apache_enable_site_cmd }} chat"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/chat.conf"
|
||||
notify: reload apache
|
||||
4
ansible/roles/conversejs/tasks/main.yml
Normal file
4
ansible/roles/conversejs/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy conversejs
|
||||
ansible.builtin.include_tasks: deploy-conversejs.yml
|
||||
195
ansible/roles/conversejs/templates/index.html.j2
Normal file
195
ansible/roles/conversejs/templates/index.html.j2
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Chat - {{ domain }}</title>
|
||||
<link rel="stylesheet" href="dist/converse.min.css">
|
||||
<script src="dist/libsignal-protocol.min.js"></script>
|
||||
{% if oauth_client_id is defined %}
|
||||
<style>
|
||||
#oauth-login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: #2e3436;
|
||||
}
|
||||
#oauth-login a {
|
||||
padding: 16px 32px;
|
||||
background: #3584e4;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#oauth-login a:hover { background: #1c71d8; }
|
||||
</style>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
{% if oauth_client_id is defined %}
|
||||
<div id="oauth-login">
|
||||
<a id="login-btn" href="#">Login with Authentik</a>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
const CLIENT_ID = '{{ oauth_client_id }}';
|
||||
const AUTHORIZE_URL = '{{ oauth_authorize_url }}';
|
||||
const TOKEN_URL = '{{ oauth_token_url }}';
|
||||
const REDIRECT_URI = window.location.origin + window.location.pathname;
|
||||
const DOMAIN = '{{ domain }}';
|
||||
|
||||
function generateCodeVerifier() {
|
||||
const arr = new Uint8Array(32);
|
||||
crypto.getRandomValues(arr);
|
||||
return btoa(String.fromCharCode(...arr))
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
|
||||
async function generateCodeChallenge(verifier) {
|
||||
const data = new TextEncoder().encode(verifier);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
return btoa(String.fromCharCode(...new Uint8Array(hash)))
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
|
||||
async function startOAuth() {
|
||||
const state = generateCodeVerifier();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
sessionStorage.setItem('oauth_state', state);
|
||||
sessionStorage.setItem('oauth_code_verifier', codeVerifier);
|
||||
const params = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
scope: 'openid profile email',
|
||||
state: state,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
window.location.href = AUTHORIZE_URL + '?' + params.toString();
|
||||
}
|
||||
|
||||
async function exchangeCode(code) {
|
||||
const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
|
||||
const resp = await fetch(TOKEN_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: CLIENT_ID,
|
||||
code: code,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
code_verifier: codeVerifier
|
||||
})
|
||||
});
|
||||
const data = await resp.json();
|
||||
sessionStorage.removeItem('oauth_state');
|
||||
sessionStorage.removeItem('oauth_code_verifier');
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
function parseJwt(token) {
|
||||
const base64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
return JSON.parse(atob(base64));
|
||||
}
|
||||
|
||||
function loadConverse() {
|
||||
return new Promise(function(resolve) {
|
||||
var s = document.createElement('script');
|
||||
s.src = 'dist/converse.min.js';
|
||||
s.onload = resolve;
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
function isTokenExpired(token) {
|
||||
try {
|
||||
const claims = parseJwt(token);
|
||||
return claims.exp && (claims.exp * 1000) < Date.now();
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function initConverse(token) {
|
||||
if (isTokenExpired(token)) {
|
||||
sessionStorage.removeItem('oauth_token');
|
||||
startOAuth();
|
||||
return;
|
||||
}
|
||||
document.getElementById('oauth-login').style.display = 'none';
|
||||
const claims = parseJwt(token);
|
||||
const jid = claims.preferred_username + '@' + DOMAIN;
|
||||
loadConverse().then(function() {
|
||||
converse.initialize({
|
||||
bosh_service_url: 'https://' + DOMAIN + '/http-bind',
|
||||
websocket_url: 'wss://' + DOMAIN + '/xmpp-websocket',
|
||||
view_mode: 'fullscreen',
|
||||
authentication: 'login',
|
||||
locked_domain: DOMAIN,
|
||||
muc_domain: 'conference.' + DOMAIN,
|
||||
locked_muc_domain: 'hidden',
|
||||
muc_instant_rooms: true,
|
||||
muc_show_logs_before_join: true,
|
||||
visible_toolbar_buttons: { toggle_occupants: true },
|
||||
jid: jid,
|
||||
password: token,
|
||||
auto_login: true,
|
||||
keepalive: true,
|
||||
omemo_default: true
|
||||
});
|
||||
converse.listen.on('logout', function() {
|
||||
sessionStorage.removeItem('oauth_token');
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
|
||||
if (code && state === sessionStorage.getItem('oauth_state')) {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
exchangeCode(code).then(function(token) {
|
||||
if (token) {
|
||||
sessionStorage.setItem('oauth_token', token);
|
||||
initConverse(token);
|
||||
}
|
||||
});
|
||||
} else if (sessionStorage.getItem('oauth_token')) {
|
||||
initConverse(sessionStorage.getItem('oauth_token'));
|
||||
} else {
|
||||
document.getElementById('login-btn').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
startOAuth();
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% else %}
|
||||
<script src="dist/converse.min.js"></script>
|
||||
<script>
|
||||
converse.initialize({
|
||||
bosh_service_url: 'https://{{ domain }}/http-bind',
|
||||
websocket_url: 'wss://{{ domain }}/xmpp-websocket',
|
||||
view_mode: 'fullscreen',
|
||||
authentication: 'login',
|
||||
locked_domain: '{{ domain }}',
|
||||
muc_domain: 'conference.{{ domain }}',
|
||||
locked_muc_domain: 'hidden',
|
||||
muc_instant_rooms: false,
|
||||
muc_show_logs_before_join: true,
|
||||
visible_toolbar_buttons: {
|
||||
toggle_occupants: true
|
||||
},
|
||||
omemo_default: true
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
31
ansible/roles/conversejs/templates/vhost.conf.j2
Normal file
31
ansible/roles/conversejs/templates/vhost.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ domain }}
|
||||
Redirect permanent / https://{{ domain }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ domain }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
DocumentRoot {{ install_dir }}
|
||||
<Directory {{ install_dir }}>
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Proxy BOSH requests to Prosody
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /http-bind http://127.0.0.1:{{ prosody_port }}/http-bind
|
||||
ProxyPassReverse /http-bind http://127.0.0.1:{{ prosody_port }}/http-bind
|
||||
|
||||
# Proxy WebSocket requests to Prosody
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
||||
RewriteRule ^/xmpp-websocket(.*) ws://127.0.0.1:{{ prosody_port }}/xmpp-websocket$1 [P,L]
|
||||
ProxyPass /xmpp-websocket http://127.0.0.1:{{ prosody_port }}/xmpp-websocket
|
||||
ProxyPassReverse /xmpp-websocket http://127.0.0.1:{{ prosody_port }}/xmpp-websocket
|
||||
</VirtualHost>
|
||||
5
ansible/roles/devpi/defaults/main.yml
Normal file
5
ansible/roles/devpi/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/devpi
|
||||
port: 3141
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
11
ansible/roles/devpi/files/Dockerfile
Normal file
11
ansible/roles/devpi/files/Dockerfile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
RUN pip install --no-cache-dir devpi-server devpi-web
|
||||
|
||||
RUN devpi-init --serverdir /data || true
|
||||
|
||||
EXPOSE 3141
|
||||
|
||||
VOLUME /data
|
||||
|
||||
CMD ["devpi-server", "--serverdir", "/data", "--host", "0.0.0.0", "--port", "3141"]
|
||||
6
ansible/roles/devpi/meta/main.yml
Normal file
6
ansible/roles/devpi/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
51
ansible/roles/devpi/tasks/deploy-devpi.yml
Normal file
51
ansible/roles/devpi/tasks/deploy-devpi.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Copy Dockerfile
|
||||
copy:
|
||||
src: Dockerfile
|
||||
dest: "{{ install_dir }}/Dockerfile"
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start devpi stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
compose_build: policy
|
||||
|
||||
-
|
||||
name: Deploy devpi vhost
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
vars:
|
||||
vhost_name: devpi
|
||||
server_name: "{{ hostname }}"
|
||||
backend_port: "{{ port }}"
|
||||
|
||||
-
|
||||
name: Deploy devpi backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: devpi
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- devpi_devpi_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
4
ansible/roles/devpi/tasks/main.yml
Normal file
4
ansible/roles/devpi/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy devpi
|
||||
ansible.builtin.include_tasks: deploy-devpi.yml
|
||||
33
ansible/roles/devpi/tasks/restore.yml
Normal file
33
ansible/roles/devpi/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_devpi_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/devpi"
|
||||
|
||||
-
|
||||
name: Stop devpi stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _devpi_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore devpi data volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v devpi_devpi_data:/data
|
||||
-v {{ _devpi_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/devpi_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start devpi stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
11
ansible/roles/devpi/templates/docker-compose.yml.j2
Normal file
11
ansible/roles/devpi/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
services:
|
||||
devpi:
|
||||
build: .
|
||||
ports:
|
||||
- "127.0.0.1:{{ port }}:3141"
|
||||
volumes:
|
||||
- devpi_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
devpi_data:
|
||||
7
ansible/roles/dnsmasq/defaults/main.yml
Normal file
7
ansible/roles/dnsmasq/defaults/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
dns_listen_address: "10.0.0.1"
|
||||
dns_port: 53
|
||||
dns_upstream:
|
||||
- "1.1.1.1"
|
||||
- "1.0.0.1"
|
||||
dns_records: []
|
||||
6
ansible/roles/dnsmasq/handlers/main.yml
Normal file
6
ansible/roles/dnsmasq/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
-
|
||||
name: restart dnsmasq
|
||||
service:
|
||||
name: dnsmasq
|
||||
state: restarted
|
||||
20
ansible/roles/dnsmasq/tasks/main.yml
Normal file
20
ansible/roles/dnsmasq/tasks/main.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
-
|
||||
name: Install dnsmasq
|
||||
apt:
|
||||
name: dnsmasq
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Deploy dnsmasq configuration
|
||||
template:
|
||||
src: dnsmasq.conf.j2
|
||||
dest: /etc/dnsmasq.d/local.conf
|
||||
notify: restart dnsmasq
|
||||
|
||||
-
|
||||
name: Ensure dnsmasq is running
|
||||
service:
|
||||
name: dnsmasq
|
||||
state: started
|
||||
enabled: yes
|
||||
11
ansible/roles/dnsmasq/templates/dnsmasq.conf.j2
Normal file
11
ansible/roles/dnsmasq/templates/dnsmasq.conf.j2
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
listen-address={{ dns_listen_address }}
|
||||
port={{ dns_port }}
|
||||
bind-interfaces
|
||||
no-resolv
|
||||
no-hosts
|
||||
{% for server in dns_upstream %}
|
||||
server={{ server }}
|
||||
{% endfor %}
|
||||
{% for record in dns_records %}
|
||||
address=/{{ record.domain }}/{{ record.ip }}
|
||||
{% endfor %}
|
||||
11
ansible/roles/docker/handlers/main.yml
Normal file
11
ansible/roles/docker/handlers/main.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
-
|
||||
name: restart containerd
|
||||
service:
|
||||
name: containerd
|
||||
state: restarted
|
||||
-
|
||||
name: restart docker
|
||||
service:
|
||||
name: docker
|
||||
state: restarted
|
||||
2
ansible/roles/docker/meta/main.yml
Normal file
2
ansible/roles/docker/meta/main.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
dependencies: []
|
||||
29
ansible/roles/docker/tasks/configure-mirror.yml
Normal file
29
ansible/roles/docker/tasks/configure-mirror.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
-
|
||||
name: Add registry mirror host entries
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: "{{ item.mirror | urlsplit('hostname') | regex_escape }}"
|
||||
line: "{{ registry_mirror_ip }} {{ item.mirror | urlsplit('hostname') }}"
|
||||
loop: "{{ registry_mirrors }}"
|
||||
when: registry_mirror_ip is defined
|
||||
|
||||
-
|
||||
name: Configure Docker Hub registry mirror
|
||||
copy:
|
||||
dest: /etc/docker/daemon.json
|
||||
content: |
|
||||
{
|
||||
"registry-mirrors": [
|
||||
{% for item in registry_mirrors if item.upstream == 'docker.io' %}
|
||||
"{{ item.mirror }}"{% if not loop.last %},{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
mode: "0644"
|
||||
notify: restart docker
|
||||
|
||||
-
|
||||
name: Ensure Docker is restarted if mirror changed
|
||||
meta: flush_handlers
|
||||
7
ansible/roles/docker/tasks/deploy-backup.yml
Normal file
7
ansible/roles/docker/tasks/deploy-backup.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
-
|
||||
name: "Deploy {{ backup_name }} docker volume backup script"
|
||||
template:
|
||||
src: backup-docker-volumes.sh.j2
|
||||
dest: "{{ backup_hook_dir }}/{{ backup_name }}.sh"
|
||||
mode: "0755"
|
||||
34
ansible/roles/docker/tasks/install-docker.yml
Normal file
34
ansible/roles/docker/tasks/install-docker.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
-
|
||||
name: Install Docker prerequisites
|
||||
apt:
|
||||
name: "{{ docker_prerequisites }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Add Docker GPG key
|
||||
apt_key:
|
||||
url: "{{ docker_gpg_url }}"
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Add Docker repository
|
||||
apt_repository:
|
||||
repo: "{{ docker_repo }}"
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Install Docker Engine and Compose plugin
|
||||
apt:
|
||||
name: "{{ docker_packages }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Ensure Docker service is running
|
||||
service:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
13
ansible/roles/docker/tasks/main.yml
Normal file
13
ansible/roles/docker/tasks/main.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
-
|
||||
name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Install and configure Docker
|
||||
ansible.builtin.include_tasks: install-docker.yml
|
||||
|
||||
-
|
||||
name: Configure registry mirrors
|
||||
ansible.builtin.include_tasks: configure-mirror.yml
|
||||
when: registry_mirrors is defined
|
||||
13
ansible/roles/docker/tasks/start-compose.yml
Normal file
13
ansible/roles/docker/tasks/start-compose.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
-
|
||||
name: "Create {{ compose_project_dir }} directory"
|
||||
file:
|
||||
path: "{{ compose_project_dir }}"
|
||||
state: directory
|
||||
|
||||
-
|
||||
name: "Start docker compose stack in {{ compose_project_dir }}"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ compose_project_dir }}"
|
||||
state: present
|
||||
build: "{{ compose_build | default(omit) }}"
|
||||
10
ansible/roles/docker/templates/backup-docker-volumes.sh.j2
Normal file
10
ansible/roles/docker/templates/backup-docker-volumes.sh.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
BACKUP_DIR="{{ backup_staging_dir | default('/var/backups') }}/{{ backup_name }}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
{% for vol in backup_volumes | default([]) %}
|
||||
docker run --rm -v {{ vol }}:/data:ro -v "$BACKUP_DIR":/backup alpine sh -c "tar czf /backup/{{ vol }}.tar.gz -C /data . || [ \$? -eq 1 ]"
|
||||
{% endfor %}
|
||||
{% for f in backup_files | default([]) %}
|
||||
cp "{{ f }}" "$BACKUP_DIR/"
|
||||
{% endfor %}
|
||||
15
ansible/roles/docker/vars/Debian.yml
Normal file
15
ansible/roles/docker/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
docker_prerequisites:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
docker_packages:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
docker_repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_facts['distribution_release'] }} stable"
|
||||
docker_gpg_url: "https://download.docker.com/linux/debian/gpg"
|
||||
5
ansible/roles/docker_registry/defaults/main.yml
Normal file
5
ansible/roles/docker_registry/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/docker-registry
|
||||
port: 5050
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
11
ansible/roles/docker_registry/handlers/main.yml
Normal file
11
ansible/roles/docker_registry/handlers/main.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
-
|
||||
name: restart docker-registry
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: restarted
|
||||
-
|
||||
name: reload apache
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: reloaded
|
||||
61
ansible/roles/docker_registry/tasks/deploy-registry.yml
Normal file
61
ansible/roles/docker_registry/tasks/deploy-registry.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy registry configuration
|
||||
template:
|
||||
src: config.yml.j2
|
||||
dest: "{{ install_dir }}/config.yml"
|
||||
notify: restart docker-registry
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start registry stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Load Apache variables
|
||||
include_vars:
|
||||
file: "{{ role_path }}/../apache/vars/{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Deploy registry vhost
|
||||
template:
|
||||
src: vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/docker-registry-{{ hostname | regex_replace('\\..*', '') }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable registry vhost
|
||||
command: "{{ apache_enable_site_cmd }} docker-registry-{{ hostname | regex_replace('\\..*', '') }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/docker-registry-{{ hostname | regex_replace('\\..*', '') }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy registry backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: docker-registry
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- docker-registry_registry_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ install_dir }}/config.yml"
|
||||
4
ansible/roles/docker_registry/tasks/main.yml
Normal file
4
ansible/roles/docker_registry/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy registry
|
||||
ansible.builtin.include_tasks: deploy-registry.yml
|
||||
22
ansible/roles/docker_registry/tasks/restore-registry.yml
Normal file
22
ansible/roles/docker_registry/tasks/restore-registry.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
-
|
||||
name: Check if registry backup archive exists
|
||||
stat:
|
||||
path: /var/backups/docker-registry/docker-registry_registry_data.tar.gz
|
||||
register: registry_backup
|
||||
|
||||
-
|
||||
name: Restore registry volume from backup
|
||||
command: >
|
||||
docker run --rm
|
||||
-v docker-registry_registry_data:/data
|
||||
-v /var/backups/docker-registry:/backup:ro
|
||||
alpine sh -c "tar xzf /backup/docker-registry_registry_data.tar.gz -C /data"
|
||||
when: registry_backup.stat.exists
|
||||
|
||||
-
|
||||
name: Restart registry after restore
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: restarted
|
||||
when: registry_backup.stat.exists
|
||||
13
ansible/roles/docker_registry/templates/config.yml.j2
Normal file
13
ansible/roles/docker_registry/templates/config.yml.j2
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
version: 0.1
|
||||
log:
|
||||
fields:
|
||||
service: registry
|
||||
storage:
|
||||
filesystem:
|
||||
rootdirectory: /var/lib/registry
|
||||
delete:
|
||||
enabled: true
|
||||
http:
|
||||
addr: :5000
|
||||
proxy:
|
||||
remoteurl: {{ remote_url | default('https://registry-1.docker.io') }}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue