Compare commits

..

180 Commits

Author SHA1 Message Date
a31a07bd16 Backup: remove old config
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 16s
2025-12-21 10:13:05 +03:00
54a951b96a Backup: refactor notifications 2025-12-21 10:10:12 +03:00
e1379bc480 Backup: roots parameter 2025-12-20 21:33:21 +03:00
037e0cab9b Backup: restic backup refactoring 2025-12-20 21:31:39 +03:00
2655869814 Backup: support for multiple storages
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 19s
2025-12-20 21:19:06 +03:00
0e96b5030d Backup: refactoring 2025-12-20 21:04:54 +03:00
a217c79e7d Backup: extract restic storage into separate class 2025-12-20 21:03:32 +03:00
6a16ebf084 Backup: parse config to dataclasses
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-20 17:44:02 +03:00
2617aa2bd2 Backup: support multiple roots 2025-12-20 17:27:29 +03:00
b686e4da4d Backup: change config format to toml
With support of multiple config values
2025-12-20 17:13:35 +03:00
439c239ac8 Lefthook: fix python format hook 2025-12-20 11:55:13 +03:00
acf599f905 Lefthook: check py files with mypy
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 18s
2025-12-20 11:38:14 +03:00
eae4f5e27b Lefthook: format py files on commit 2025-12-20 11:35:54 +03:00
4fbe9bd5de Backups: skip system dir lost+found
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 15s
2025-12-20 11:22:24 +03:00
dcc4970b20 Add owner and group to backup-targets files 2025-12-20 11:18:37 +03:00
2eac1362b5 Wanderer: backup all data with restic 2025-12-20 11:18:11 +03:00
e3d8479397 Memos: exclude media files from gobackup
Backup media files with backup-targets
2025-12-20 11:06:56 +03:00
91c5eab236 Gramps: exclude media files from gobackup
Backup media files with backup-targets
2025-12-20 11:04:50 +03:00
ca7f089fe6 Backups: use dataclass Application for app info 2025-12-20 10:48:40 +03:00
479e256b1e Backups: use constants for file names 2025-12-20 10:36:19 +03:00
11e5b5752e Backups: add backup-targets file support 2025-12-20 10:32:00 +03:00
392938d0fb Gitea: upgrade to 1.25.3
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 19s
2025-12-19 20:35:53 +03:00
2cc059104e Netdata: upgrade to 2.8.4
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 20s
2025-12-17 20:18:12 +03:00
d09a26b73a Gramps: update vars
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-16 20:36:51 +03:00
097676f569 Gramps: move assets to local storage 2025-12-16 20:34:25 +03:00
e878661cb3 Specify secret files to ignore 2025-12-16 19:43:40 +03:00
cb50c1c515 Docker: prune images every night
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 18s
2025-12-16 19:34:31 +03:00
33de71a087 Add agents.md file for ai agents 2025-12-16 19:29:52 +03:00
fbd5fa5faa Memos: upgrade to 0.25.3
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 18s
2025-12-16 11:51:21 +03:00
faf7d58f78 Netdata: update config
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 17s
map /etc/hostname config into container
2025-12-14 21:22:00 +03:00
0a75378bbc Remove old ports config
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-14 19:25:33 +03:00
bdd74bdf2e Authelia: add backup for storage database
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 19s
2025-12-14 18:10:59 +03:00
78bee84061 Outline: add postgres health check 2025-12-14 17:58:04 +03:00
7b81858af6 Miniflux: change file names 2025-12-14 17:51:06 +03:00
08fda17561 Gramps: move cache to separate dir 2025-12-13 15:40:56 +03:00
841bd38807 Update valkey to 9.0 2025-12-13 15:35:43 +03:00
fb1fd711c2 Dozzle: upgrade to 8.14.11
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-13 14:57:41 +03:00
ecf714eda7 Gramps: reduce celery workers to 1
And update valkey to 9
2025-12-13 14:57:23 +03:00
81f693938e Netdata: upgrade to 2.8.2
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
Tune config, setup update every 10s instead of 1s
2025-12-13 14:46:15 +03:00
10d67861a0 Netdata: revert to 2.7.3
All checks were successful
Linting / YAML Lint (push) Successful in 10s
Linting / Ansible Lint (push) Successful in 17s
High cpu usage for dockerd and containerd
2025-12-13 13:29:40 +03:00
3f5befb44d Netdata: upgrade to 2.8.2
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-13 09:52:08 +03:00
1b75ddaef2 Disable python docker package 2025-12-13 09:51:49 +03:00
7d6ef77e64 Authelia: fix run-app behavior 2025-12-13 09:51:35 +03:00
ae7c20a7aa Add mount configuration 2025-12-13 09:03:29 +03:00
67df03efca Add combined application playbook
All checks were successful
Linting / YAML Lint (push) Successful in 11s
Linting / Ansible Lint (push) Successful in 21s
2025-12-13 08:46:34 +03:00
48bb8c9d33 Add combined system playbook 2025-12-13 08:38:12 +03:00
5b53cb30ac Add tag 'run-app' for application run
Useful skip run when configure app
2025-12-13 08:22:11 +03:00
f2bc221663 Outline: change postgres owner
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-11 11:26:13 +03:00
b41a50006b Exclude home dir from backups 2025-12-11 10:59:27 +03:00
c2ea2cdb39 Fix app user and group uid and gid
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 16s
Prepare for system upgrade
2025-12-11 10:52:27 +03:00
7e67409393 Applications: move to new base directory
All checks were successful
Linting / YAML Lint (push) Successful in 10s
Linting / Ansible Lint (push) Successful in 21s
2025-12-11 10:14:27 +03:00
6882d61f8e Gitea: move to new app directory
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-07 17:53:08 +03:00
47a63202b8 Fix spaces in file 2025-12-07 17:52:48 +03:00
af289f1e28 Configure stateless apps for new storage
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 18s
2025-12-07 17:40:32 +03:00
b08f681c92 Exclude lost+found dir from applications 2025-12-07 17:40:08 +03:00
8dfd061991 Backup apps from /mnt/applications 2025-12-07 17:16:43 +03:00
306d4bf8d0 Install dust for space usage check 2025-12-07 17:13:20 +03:00
dbd679aa8b Gramps: move to new app directory 2025-12-07 16:48:25 +03:00
47ed9c11c1 Add apps data directory (external drive) 2025-12-07 16:47:59 +03:00
f9ad08fd09 Simplify owner setup
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 19s
2025-12-07 15:56:16 +03:00
4c7338f857 Update eget tools
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 21s
2025-12-07 15:39:21 +03:00
a95da35389 Backups: move secrets to config file
Some checks failed
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled
Allow run backup script with sudo
2025-12-07 15:14:55 +03:00
c74683cfe7 Grams: upgrade to 25.11.2
All checks were successful
Linting / YAML Lint (push) Successful in 44s
Linting / Ansible Lint (push) Successful in 1m8s
2025-12-01 09:18:54 +03:00
9dff413867 Fix linting
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 19s
2025-11-28 20:37:39 +03:00
23a2bae7ec Fix linting
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 18s
2025-11-28 20:13:58 +03:00
942bb7d999 Fix editorconfig for yaml files
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Failing after 19s
2025-11-28 18:30:23 +03:00
6ff7a7e3b4 Add lint workflow 2025-11-28 18:27:34 +03:00
8ae28e64f4 Gitea: upgrade to 1.25.2 2025-11-27 11:13:14 +03:00
f7e8248cac Dozzle: upgrade to 8.14.8 2025-11-18 11:36:51 +03:00
2af9066dec Gramps: upgrade to 25.11.0 2025-11-18 11:36:35 +03:00
e3b2e064c0 Outline: upgrade to 1.1.0 2025-11-18 11:36:15 +03:00
380a54cb25 Authelia: upgrade to 4.39.14 2025-11-18 11:36:02 +03:00
d5078186e7 Gitea: upgrade to 1.25.1 2025-11-05 09:34:07 +03:00
57bb696e6e Memos: configure local storage 2025-11-04 17:19:29 +03:00
c6cc7d4c6c Wakapi: upgrade to 2.16.1 2025-11-04 14:15:44 +03:00
90abc6c833 Gitea: upgrade to 1.25.0 2025-11-04 14:15:31 +03:00
395203f236 Memos: install 0.25 2025-11-04 14:10:39 +03:00
57cc639cc8 Outline: upgrade to 1.0.1 2025-10-29 09:44:15 +03:00
1405a2364e Netdata: upgrade to 2.7.3 2025-10-29 09:44:01 +03:00
b165899f25 Gramps: upgrade to 2.10.1 2025-10-27 11:20:21 +03:00
86147d0103 Authelia: add redis (valkey) as session storage 2025-10-26 20:13:31 +03:00
2c9ade0a8e Tools: add args to secret generation tasks 2025-10-26 20:05:06 +03:00
35c1f727f6 Wakapi: try to setup oidc, but failed
https://github.com/muety/wakapi/issues/856
2025-10-25 15:13:01 +03:00
b7d2fca2f2 Gitea: upgrade to 1.24.7 2025-10-25 13:37:31 +03:00
725c4c02cc Dozzle: upgrade to 8.14.6 2025-10-25 13:37:09 +03:00
328256c6be Transcriber: release transcriber:2fc5a56-1761210134 2025-10-23 12:03:51 +03:00
b08dc862c9 Transcriber: release transcriber:ec637c0-1761209409 2025-10-23 11:51:45 +03:00
0810c6c099 Transcriber: release transcriber:822e168-1761208842 2025-10-23 11:42:10 +03:00
dd6b34e983 Transcriber: release transcriber:822e168-1761208664 2025-10-23 11:39:35 +03:00
6fd6d76594 Transcriber: rewrite deploy
Same as homepage deploy

Prepare for two-step deploy:
- local build and fix local tag
- deploy to remote server
2025-10-23 11:37:24 +03:00
61e2431975 Homepage: release homepage-nginx:f797e17-1761204003 2025-10-23 10:20:14 +03:00
9a23e35126 Homepage: rewrite deploy
Prepare for two-step deploy:
- local build and fix local tag
- deploy to remote server
2025-10-23 10:10:06 +03:00
f2a9e660ed Wakapi: upgrade to 2.16.0 2025-10-21 12:02:41 +03:00
bd5f5ca452 Dozzle: upgrade to 8.14.5 2025-10-21 12:02:18 +03:00
860cfd0450 Authelia: upgrade to 4.39.13 2025-10-21 12:01:50 +03:00
884553892b Update tools: rclone 2025-10-21 12:01:14 +03:00
3e43c3e44d Wanderer: upgrade to 0.18.3 2025-10-06 09:55:59 +03:00
823533a8cb Netdata: upgrade to 2.7.1 2025-10-06 09:55:40 +03:00
f54cac170e Gramps: upgrade to 25.9.0 2025-10-06 09:55:20 +03:00
17950bcfad Dozzle: upgrade to 8.14.4 2025-10-06 09:54:58 +03:00
5f6891d192 Authelia: upgrade to 4.39.11 2025-10-06 09:54:35 +03:00
9c4ff91ccf Upgrade rclone, btop, restic, gobackup, task 2025-10-06 09:54:16 +03:00
541312b5e9 Gilea: upgrade to 1.24.6 2025-09-18 12:03:10 +03:00
c82168561e Outline: upgrade to 0.87.4 2025-09-18 12:02:48 +03:00
c37a5f0d7d Dozzle: upgrade to 8.13.14 2025-09-18 12:02:29 +03:00
8714f1bd95 Authelia: upgrade to 4.39.10 2025-09-18 12:02:01 +03:00
0e2eba5167 Wanderer: upgrade to 0.18.2 2025-09-18 12:01:28 +03:00
a1a94d29a8 Wanderer: install 0.18.1 2025-09-12 20:17:33 +03:00
1d5ce38922 Wakapi: upgrade to 2.15.0 2025-09-07 11:16:30 +03:00
0b9e66f067 Authelia: upgrade to 4.39.8 2025-09-05 09:29:39 +03:00
379a113b86 Dozzle: upgrade to 8.13.12 2025-09-05 09:29:22 +03:00
8538c00175 Outline: upgrade to 0.87.3 2025-09-05 09:29:05 +03:00
645276018b Authelia: upgrade to 4.39.7 2025-09-01 09:30:59 +03:00
ce5d682842 Dozzle: upgrade to 8.13.11 2025-09-01 09:30:35 +03:00
de5b0f66bd Gramps: upgrade to 25.8.0 2025-09-01 09:30:18 +03:00
64602b1db3 Outline: upgrade to 0.87.0 2025-09-01 09:29:55 +03:00
caecb9b57e Caddy: upgrade to 2.10.2 2025-08-29 08:31:52 +03:00
e8be04d5e1 Dozzle: upgrade to 8.13.10 2025-08-29 08:31:32 +03:00
a7f90da43f Netdata: upgrade to 2.6.3 2025-08-25 09:11:55 +03:00
0f80206c62 Gitea: upgrade to 1.24.5 2025-08-16 08:49:35 +03:00
1daff82cc5 Dozzle: upgrade to 8.13.9 2025-08-16 08:49:16 +03:00
9b4293c624 Add transcriber app 2025-08-14 15:13:29 +03:00
0d93e8094c Authelia: more strict policy 2025-08-13 19:21:14 +03:00
b92ab556e5 Dozzle: fix hostname 2025-08-13 19:21:00 +03:00
8086799c7b Dozzle: install version 8.13.8 2025-08-13 19:08:46 +03:00
6ec5df4b66 Netdata: upgrade to 2.6.2 2025-08-13 18:49:18 +03:00
fb91e45806 Outline: upgrade to 0.86.1 2025-08-11 08:15:06 +03:00
44f82434e7 Authelia: upgrade to 4.39.6 2025-08-11 08:14:37 +03:00
31ca27750e Docker: remove unnecessary call
Login to yandex registry only need in app playbooks
2025-08-07 15:46:39 +03:00
4be8d297ba Authelia: move secrets to separate file 2025-08-07 15:07:51 +03:00
bcd8e62691 Backups: rewrite backup script
To avoid specifying individual applications
2025-08-07 12:06:07 +03:00
160f4219c5 RSS-Bridge: upgrade to 2025-08-05 2025-08-07 09:57:05 +03:00
c518125bbd Gitea: upgrade to 1.24.4 2025-08-07 09:55:17 +03:00
e16e23d18c Outline: upgrade to 0.86.0 2025-08-07 09:52:44 +03:00
ede37e7fa3 Miniflux: add restart policy 2025-08-04 09:50:43 +03:00
b4cddb337a Miniflux: run postgres as app user 2025-08-04 09:15:37 +03:00
35f1abd718 Miniflux: change secret storage from env to files 2025-08-04 08:10:07 +03:00
21b52a1887 Secrets: add role for secret deploy 2025-08-04 08:06:58 +03:00
af39ca9de8 Security fixes: backups 2025-08-03 15:13:26 +03:00
e10b37a9f6 Secrets: remove unused variables 2025-08-03 15:03:33 +03:00
85627f8931 Authelia: protect secret files
Word "secrets" activate pre-commit hook
2025-08-03 11:11:50 +03:00
38e2294a65 Security fixes: telegram 2025-08-03 11:06:59 +03:00
dfcb781a90 Security fixes: S3 2025-08-03 11:04:33 +03:00
2c8bd2bb8d Security fixes: postbox 2025-08-03 10:42:35 +03:00
592c3a062b Postbox: refactor smtp tools 2025-08-01 14:04:56 +03:00
04d789f0a4 Secrets: remove unused 2025-08-01 13:43:24 +03:00
16719977c8 Add gitleaks and custom script to check secrets in commits
Additionally add lefthook to manage git hooks
2025-08-01 13:31:44 +03:00
6cd8d3b14b Netdata: add monitoring for postgresql databases 2025-08-01 10:58:07 +03:00
791caab704 Add tasks to clear docker objects 2025-07-20 11:51:10 +03:00
df60296f9d Gitea: upgrade to 1.24.3 2025-07-20 11:35:26 +03:00
232d06a123 Gramps: upgrade to 25.7.2 2025-07-20 11:32:28 +03:00
a3886c8675 Wakapi: upgrade to 2.14.1 2025-07-20 11:31:59 +03:00
db55fcd180 Authelia: upgrade to 4.39.5 2025-07-20 11:31:10 +03:00
53dd462cac Netdata: upgrade to 2.6.0 2025-07-20 11:30:05 +03:00
28faff3c99 Gramps: upgrade to 25.7.1 2025-07-11 10:06:05 +03:00
2619b8f9e6 Outline: upgrade to 0.85.1 2025-07-11 10:02:54 +03:00
8a9b3db287 Gramps: upgrade to 25.7.0 2025-07-02 13:43:33 +03:00
a72c67f070 Wakapi: install 2.14.0
And transfer data from local
2025-07-01 11:21:05 +03:00
47745b7bc9 RSS-Bridge: install version 2025-06-03 2025-06-30 19:18:45 +03:00
c568f00db1 Miniflux: install and configure rss reader 2025-06-28 12:12:19 +03:00
99b6959c84 Tasks: add quick commands for authelia 2025-06-28 11:00:32 +03:00
fa65726096 Authelia: upgrade to 4.39.4 2025-06-28 10:02:57 +03:00
f9eaf7a41e Rename encrypted vars to secrets 2025-06-28 09:59:04 +03:00
d825b1f391 Netdata: upgrade to 2.5.4 2025-06-28 09:57:19 +03:00
b296a3f2fe Netdata: upgrade to 2.5.3 2025-06-22 09:34:57 +03:00
8ff89c9ee1 Gitea: upgrade to 1.24.2 2025-06-22 09:31:46 +03:00
62a4e598bd Gitea: upgrade to v1.24.0 2025-06-11 20:48:51 +03:00
b65aaa5072 Gramps: upgrade to v25.6.0 2025-06-11 20:48:27 +03:00
98b7aff274 Gramps: upgrade to v25.5.2 2025-05-24 12:04:45 +03:00
6eaf7f7390 Netdata: upgrade to 2.5.1 2025-05-21 21:24:22 +03:00
32e80282ef Update ansible roles 2025-05-17 17:17:01 +03:00
c8bd9f4ec3 Netdata: add fail2ban monitoring 2025-05-17 16:58:12 +03:00
d3d189e284 Gitea: upgrade to 1.23.8 2025-05-17 13:51:10 +03:00
71fe688ef8 Caddy: upgrade to 2.10.0 2025-05-17 13:50:47 +03:00
c5d0f96bdf Netdata + Authelia: add monitoring 2025-05-17 13:33:35 +03:00
eea8db6499 Netdata + Caddy: add monitoring for http-server 2025-05-17 11:55:38 +03:00
7893349da4 Netdata: refactoring as docker compose app 2025-05-17 10:27:41 +03:00
a4c61f94e6 Gramps: upgrade to 25.5.1 (with Gramps API 3.0.0) 2025-05-12 15:56:23 +03:00
da0a261ddd Outline: upgrade to 0.84.0 2025-05-12 12:58:21 +03:00
b9954d1bba Authelia: upgrade to 4.39.3 2025-05-12 12:55:41 +03:00
94 changed files with 5468 additions and 4142 deletions

View File

@@ -1,5 +1,6 @@
---
exclude_paths:
- ".ansible/"
- ".gitea/"
- "galaxy.roles/"
- "Taskfile.yml"

4
.crushignore Normal file
View File

@@ -0,0 +1,4 @@
ansible-vault-password-file
*secrets.yml
*secrets.toml

View File

@@ -6,14 +6,9 @@ insert_final_newline = true
indent_style = space
indent_size = 4
[*.yml]
indent_size = 2
[*.yml.j2]
[*.{yml,yaml,yml.j2}]
indent_size = 2
trim_trailing_whitespace = true
[Vagrantfile]
indent_size = 2
[Makefile]
indent_style = tab

50
.gitea/workflows/lint.yml Normal file
View File

@@ -0,0 +1,50 @@
name: Linting
on: push
jobs:
yamllint:
name: YAML Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install yamllint
run: pip install yamllint
- name: Run yamllint
run: yamllint . --format colored
ansible-lint:
name: Ansible Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: pip install ansible ansible-lint
# Создаем пустой vault password file если он указан в конфиге, но отсутствует
- name: Fix vault issue
run: |
if grep -q "vault_password_file" ansible.cfg && [ ! -f ansible-vault-password-file ]; then
echo "Creating empty vault password file for CI..."
echo "foobar" > ansible-vault-password-file
fi
- name: Run ansible-lint
run: ansible-lint .

3
.gitignore vendored
View File

@@ -5,6 +5,7 @@
/galaxy.roles/
/ansible-vault-password-file
/temp
*.retry
test_smtp.py
__pycache__

25
.yamllint.yml Normal file
View File

@@ -0,0 +1,25 @@
extends: default
ignore:
- ".ansible/"
- "galaxy.roles/"
rules:
# Правила, требуемые ansible-lint
comments:
min-spaces-from-content: 1
comments-indentation: false
braces:
max-spaces-inside: 1
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
# Дополнительные настройки (опционально)
line-length:
max: 120
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true
document-start: disable # Не требовать --- в начале файла
truthy:
level: warning

69
AGENTS.md Normal file
View File

@@ -0,0 +1,69 @@
# AGENTS GUIDE
## Overview
Ansible-based server automation for personal services. Playbooks provision Dockerized apps (e.g., gitea, authelia, homepage, miniflux, wakapi, memos) via per-app users, Caddy proxy, and Yandex Docker Registry. Secrets are managed with Ansible Vault.
## Project Layout
- Playbooks: `playbook-*.yml` (per service), `playbook-all-*.yml` for grouped actions.
- Inventory: `production.yml` (ungrouped host `server`).
- Variables: `vars/*.yml` (app configs, images), secrets in `vars/secrets.yml` (vault-encrypted).
- Roles: custom roles under `roles/` (e.g., `eget`, `owner`, `secrets`) plus galaxy roles fetched to `galaxy.roles/`.
- Files/templates: service docker-compose and backup templates under `files/`, shared templates under `templates/`.
- Scripts: helper Python scripts in `scripts/` (SMTP utilities) and `files/backups/backup-all.py`.
- CI: `.gitea/workflows/lint.yml` runs yamllint and ansible-lint.
- Hooks: `lefthook.yml` references local hooks in `/home/av/projects/private/git-hooks` (gitleaks, vault check).
- Formatting: `.editorconfig` enforces LF, trailing newline, 4-space indent; YAML/Jinja use 2-space indent.
## Setup
- Copy vault password sample: `cp ansible-vault-password-file.dist ansible-vault-password-file` (needed for ansible and CI).
- Install galaxy roles: `ansible-galaxy role install --role-file requirements.yml --force` (or `task install-roles`).
- Ensure `yq`, `task`, `ansible` installed per README requirements.
## Tasks (taskfile)
- `task install-roles` — install galaxy roles into `galaxy.roles/`.
- `task ssh` — SSH to target using inventory (`production.yml`).
- `task btop` — run `btop` on remote.
- `task encrypt|decrypt -- <files>` — ansible-vault helpers.
- Authelia helpers:
- `task authelia-cli -- <args>` — run authelia CLI in docker.
- `task authelia-validate-config` — render `files/authelia/configuration.template.yml` with secrets and validate via authelia docker image.
- `task authelia-gen-random-string LEN=64` — generate random string.
- `task authelia-gen-secret-and-hash LEN=72` — generate hashed secret.
- `task format-py-files` — run Black via docker (pyfound/black).
## Ansible Usage
- Inventory: `production.yml` with `server` host. `ansible.cfg` points to `./ansible-vault-password-file` and `./galaxy.roles` for roles path.
- Typical deploy example (from README): `ansible-playbook -i production.yml --diff playbook-gitea.yml`.
- Per-app playbooks: `playbook-<app>.yml`; grouped runs: `playbook-all-setup.yml`, `playbook-all-applications.yml`, `playbook-upgrade.yml`, etc.
- Secrets: encrypted `vars/secrets.yml`; additional `files/<app>/secrets.yml` used for templating (e.g., Authelia). Respect `.crushignore` ignoring vault files.
- Templates: many `docker-compose.template.yml` and `*.template.sh` files under `files/*` plus shared `templates/env.j2`. Use `vars/*.yml` to supply values.
- Custom roles:
- `roles/eget`: installs `eget` tool; see defaults/vars for version/source.
- `roles/owner`: manages user/group and env template.
- `roles/secrets`: manages vault-related items.
## Linting & CI
- Local lint configs: `.yamllint.yml`, `.ansible-lint.yml` (excludes `.ansible/`, `.gitea/`, `galaxy.roles/`, `Taskfile.yml`).
- CI (.gitea/workflows/lint.yml) installs `yamllint` and `ansible-lint` and runs `yamllint .` then `ansible-lint .`; creates dummy vault file if missing.
- Pre-commit via lefthook (local hooks path): runs `gitleaks git --staged` and secret-file vault check script.
## Coding/Templating Conventions
- Indentation: 2 spaces for YAML/Jinja (`.editorconfig`), 4 spaces default elsewhere.
- End-of-line: LF; ensure final newline.
- Template suffixes `.template.yml`, `.yml.j2`, `.template.sh` are rendered via Ansible `template` module.
- Avoid committing real secrets; `.crushignore` excludes `ansible-vault-password-file` and `*secrets.yml`.
- Service directories under `files/` hold docker-compose and backup templates; ensure per-app users and registry settings align with `vars/*.yml`.
## Testing/Validation
- YAML lint: `yamllint .` (CI default).
- Ansible lint: `ansible-lint .` (CI default).
- Authelia config validation: `task authelia-validate-config` (renders with secrets then validates via docker).
- Black formatting for Python helpers: `task format-py-files`.
- Python types validation with mypy: `mypy <file.py>`.
## Operational Notes
- Deployments rely on `production.yml` inventory and per-app playbooks; run with `--diff` for visibility.
- Yandex Docker Registry auth helper: `files/yandex-docker-registry-auth.sh`.
- Backups: templates and scripts under `files/backups/` per service; `backup-all.py` orchestrates.
- Home network/DNS reference in README (Yandex domains).
- Ensure `ansible-vault-password-file` present for vault operations and CI.

View File

@@ -12,8 +12,13 @@ vars:
sh: 'yq .ungrouped.hosts.server.ansible_user {{.HOSTS_FILE}}'
REMOTE_HOST:
sh: 'yq .ungrouped.hosts.server.ansible_host {{.HOSTS_FILE}}'
AUTHELIA_DOCKER: 'docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia'
tasks:
install-roles:
cmds:
- ansible-galaxy role install --role-file requirements.yml --force
ssh:
cmds:
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}}
@@ -22,17 +27,52 @@ tasks:
cmds:
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} -t btop
vars-decrypt:
encrypt:
cmds:
- ansible-vault decrypt vars/vars.yml
- ansible-vault encrypt {{.CLI_ARGS}}
vars-encrypt:
decrypt:
cmds:
- ansible-vault encrypt vars/vars.yml
- ansible-vault decrypt {{.CLI_ARGS}}
authelia-cli:
cmds:
- docker run --rm authelia/authelia:latest authelia {{.CLI_ARGS}}
- "{{.AUTHELIA_DOCKER}} {{.CLI_ARGS}}"
authelia-validate-config:
vars:
DEST_FILE: "temp/configuration.yml"
cmds:
- >
ansible localhost
--module-name template
--args "src=files/authelia/configuration.template.yml dest={{.DEST_FILE}}"
--extra-vars "@vars/secrets.yml"
--extra-vars "@files/authelia/secrets.yml"
- defer: rm -f {{.DEST_FILE}}
- >
{{.AUTHELIA_DOCKER}}
validate-config --config /data/{{.DEST_FILE}}
authelia-gen-random-string:
summary: |
Generate random string.
Usage example:
task authelia-gen-random-string LEN=64
vars:
LEN: '{{ .LEN | default 10 }}'
cmds:
- >
{{.AUTHELIA_DOCKER}}
crypto rand --length {{.LEN}} --charset alphanumeric
authelia-gen-secret-and-hash:
vars:
LEN: '{{ .LEN | default 72 }}'
cmds:
- >
{{.AUTHELIA_DOCKER}}
crypto hash generate pbkdf2 --variant sha512 --random --random.length {{.LEN}} --random.charset rfc3986
format-py-files:
cmds:

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "{{ app_name }}: backup data with gobackups"
(cd "{{ base_dir }}" && gobackup perform --config "{{ gobackup_config }}")
echo "{{ app_name }}: done."

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
services:
authelia_app:
container_name: 'authelia_app'
image: 'docker.io/authelia/authelia:4.39.14'
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
restart: 'unless-stopped'
networks:
- "web_proxy_network"
- "monitoring_network"
volumes:
- "{{ config_dir }}:/config"
- "{{ data_dir }}:/data"
authelia_redis:
image: valkey/valkey:9.0-alpine
container_name: authelia_redis
restart: unless-stopped
networks:
- "monitoring_network"
networks:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -1,15 +0,0 @@
services:
authelia_app:
container_name: 'authelia_app'
image: 'docker.io/authelia/authelia:4.39.1'
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
restart: 'unless-stopped'
networks:
- "{{ web_proxy_network }}"
volumes:
- "{{ config_dir }}:/config"
networks:
{{ web_proxy_network }}:
external: true

View File

@@ -0,0 +1,16 @@
# https://gobackup.github.io/configuration
models:
authelia:
compress_with:
type: 'tgz'
storages:
local:
type: 'local'
path: '{{ backups_dir }}'
keep: 3
databases:
users:
type: sqlite
path: "{{ (data_dir, 'authelia_storage.sqlite3') | path_join }}"

147
files/authelia/secrets.yml Normal file
View File

@@ -0,0 +1,147 @@
$ANSIBLE_VAULT;1.1;AES256
65666236353432623164363161633732363265343935316466326332323638626138633130666663
3566393731353063393466663831623537393939663838640a303935383565376430646431613464
66363161373765663665316433393835323236363561663633623533363166323533623137646437
3438383538613832660a373337323633666535656439383438653132646235623834643730323762
64656336323037653937393836623061343563303337633830346164613731633761376138313666
33343265343830306139386164353861633230326333616539313032323935313435623762316163
63353535663533656166333034636462663266343061626637613830313962333531396339393334
39336233623462303137343264373631396464303038346131633333333033343839393438653330
61646166313762633333353233366430333764646261356438336532653463646264643562346262
32653339353861653964663935393638656563373239633234656131633133643366653833376233
34643033393039616363656330323765346336643664393934376138353266303731613066626231
30313362663430343337663663373132383831333662353034316134623639393938306237396230
65633565643861366434336135313465623537616166356637346137323566333239376361633930
66356664633433306134613166353032363234633961623862646537376365383535623033356639
34336433646430323630313464333631663739343832316362666165623039313535306433643761
61376264616462333839326233613764333633643233663236666562306666353934323636353262
32386333623632326332373438373236646138326537343835383766663463643535383534633633
34343166343362343165333837313362666533353939336533326138386538323961323530626663
63633334663561396565656662363830656133323364383536346163316333356661643261633635
37323536653664613564616466633631616465656266373830373964326566623733396130303031
31663166323863353565353235323235363161366534663539343338633434626232336261356438
38373339383361356565366436376266336234356436363562623431666436626562353065356230
37333737623161303431393461383438643137613962613838386537663564646333356538393337
34373136343361333738396664616436363066323030306438666437333839653336316264343139
38656234623363386561393961363262316535373232353132616136633339613533633333376235
35656632643363386133656438616366643630303337383130386330326532623930346561383739
65636138313566363539323663303631663862323034313062353633646638633463653263333035
66646539393639643834353966396132343437666435626537633336393864326630623961303338
36616663383262363138346331386239653634373135616232656462353562373939356631633433
64356461653336353039643065663538373363666339363231373762363163653762363832373238
37386638373261383533633937613561613961353765363864613031356334303138613766383631
36666433636663343931643462383338613662333233666337363038666531633864333436383737
36363562323936313764386336656639633638643465636131373532313238376565353933336133
37663961623937336230636466643531316463323733626134663331323135396637323231383231
33643736316537646264353662643261666165366562393031613565623630336333353961353661
62633362613863303936623436616136616139363861653233313865343532366465373937306139
61316665653234363033396566316331316164346461353438633864333334653730333065376631
66616238663062666139653062383036636366646364346632396239623233356533343038363733
33316165306130616665326364616231613830313334383961633333303261656131333161323237
38616435633334646533653830393739336363653664373235363863396262623736626435313735
39353065643033343062616137346361646136313265653965313133666130393361306430303638
64643364663335343961373865653564366362396138626531613232313461376463336437336666
30373766666231646264356663626335393233333465386164313630613137303066336430653662
35336531633039633938363430363239653065356230313538323630316561643033623833656164
65366435653063616361366666373561663538373363346264386331316531376262663663383266
38346163653439366430656536666631366534663163396230353531396335663638386261613832
31336339336465393333383761623663383563613930663430626166666635393164366562663063
34323031333939656161643139386532666361663630346632383333373261326134636564393233
38363630633435353730383234663536623166373533333639353963613665353339383837626138
38653730316538626662303636363664613566383033323661363533333032306362346562316464
36303730666531396531386331653466396233623138393763653965323239393237636237303237
66323366343036643765646539366261613062646532306265353430636332386330613962666131
33383063343638616338326533646162306438616434316139313433303636366665336364656534
66353634646563373463633637383766343332346530653033663937613135363233386138306565
39386262663939346432383134333661623637396162623336626137316166613035333138653632
34653364333732366231396637353939653262323934366333373130613932366533346632366164
31313663303034306436393763323361616434306134336231383639346261376439643162643539
35323534366435393531613665333337633365353831326534363737396463363666316639373233
38386431336631363831366261373439376231326465323736356331636136393762383331336265
64356363336639356361326539396234626566653334656561386431616139323433376563303132
32363864663062643065643534623933653165636264653461303262313662663165616463353965
36613134343634393066613533343362393137356464303530653964363031653231663962303037
38633730633766306264613865373736346661623531616563316232393235643931353663383066
39386633383930383732343266326161303663646636383735646332623661303433366161363635
31313839396539343063613166616134636134333639363362343566356435663934646263653061
31616137363031333561656134396534333430613637376465363633663861666262616137336337
38396639333465316431303433653338653338303031313566633330656535666531316235393138
64383332323466653065343765346162343532386438326362626637656130613433393339323564
39633262393336383932306632636563313663336337653164306434393661663265306638636265
37326335616666393262356663326665663561633038333237363234303838636135653861373032
62643332376364386537326336663531613164376261653938333165656632613434343063613565
62623262303632633335303430343164343433646238366137623030323233303661373434666234
62626265623239343634363732623739383536646132656564313032663061383939616162366538
61373738626362386265623739333139613531373738333862396430356635626130663633666439
63333836346364313262613331656531623831646165313036393138636162336138613365396331
63323333366232326365333965363734396565343334613733396437393932616637313738633765
61373437313636666566303032343366376166643864333639326564363935623534386164346365
65373538303765386337383964303937343038663832326665356666646336633835653530396337
63316631333231623861373330303033396138613834623265633263653061623132366235336661
31376633396534613161663462343365653262383133636238613166343366653464636137353165
33623539366339353064373238663838303237343737376131306532333134313561323234336530
64653335653262613738623335343361386261633636616663393035633066653735346431643663
63626331633337363231313835376239636136633262393463643539343333373139616330373634
63366439383232363165356166656664633133313533353236393637386535616536613630616137
37316538653839353932363264663934613936346661343835333666303536363332363234653262
33303530343764316462336634613661663532393864626437353764656564343435613131663339
31616236376563623762326566333933303432646465613138373733363263346337633165616563
61613836363231333966336165393961313930313934323536333334363763393438636138396361
61373264626531393165376538336533303861643663333439653732313337613362346162373931
35333330653531623134396134333938616538666661363737633639643462313034356531633033
37613834356563303639356134323231646461356262636237323061386339306462343035336164
63376664386663306135333635323030396639326639656131333564353265316631336261373562
35663639306361373433633530616162636434373533333263303936343539386439303130366439
37656263363839363339333236333835386537343232636461343338356234616332383330373161
66646533393037353030616264623461626339613538306133393337623264326535343836363165
39626263396162336434323437323133653735663136616266376331633665626234386131393433
34663236623636656237666531353763333861646264646538313964663130353836396236336564
31333132396535653064366430306464363634633032383666313738396265343335663664613662
34626331303834346637383932633832343962666633323838383132323239363965313139373762
36306266313337393235303834356435336138373437306661636535333836396366386336613937
37386136623965373439373433353264393363653534306662316132643761353138303538353037
62316636323338353633313461346161343461613465653463393931363661336638346435666134
38303533633530383466623766393138653564373065373261656165313763663361373235356531
66346663343636633961656639316365396265303632626465666532313338373336376135366138
61303865356436616139373064393939356364623461316266316537363963396263613562613363
37386466633934623062333634303335646232613039633839336365613634613561373436393733
61316162623063636365346236323164333933643662366463313138623561633533653932663065
64363734636663613831663630666432396237303630623234376432316532626165373464333134
30646433646438373961333230343430383232316431313465646136643139353937313761333731
66653335343935636530393361306162616232333935393135623235626238323238303339643863
35373366353365613965656562633633303330383631383736306535366137393638313330343636
35383039313866303239623564666461633161373231303534313466353137643666396133363265
36373133323162666363303862313566613132333739333164663166666565303032306633636632
63366539353663666162633832373264306135646266656563373433376438616530626530393131
36376630306539303865613639643538333134666533643261356662386433626265613431613334
35353634663930653537326666653763393831363637613639653862323730633266323234656662
35666433623739323435626536303561386332313838383432303437633731666435393331353139
32376638343939626237346363623236653639356234633366323464663763366339663536386162
62623831336165393630363263396466643563383330353232373435613364316538613835313332
30623136316437333462333864663164356539656436666539313536653065346337353565656138
30663737333035336137313034626339613631396237326163366364366634346438643831376166
33616238643033313662353031306534666161636133653531303932633231326139326161356564
65323231663562323430633561363838663030346432313930323165313835616230663463316161
63326364376461643035333564643964303030306131396233333439393131336435323134663064
31306162383766333636386633393863663035376562633965666635353939653936626631346534
64656462393335653332646562373361613132643034653536303435343833626433613137346232
65643465323733336162366261336636326136616532343939613363663537336365363966373437
62626566303435356237333238353736383262313933656139326634663934343864373131646461
62653662336162313739663961636430346130666130646364393034636464616362636533353262
62346162386561363239666232306432646336346434636335366638633762666634623737663866
32343339646330353837323665626430646432316163656361323139633336646363643434363731
36616534663330376333623631313332616462383936316238363032363762356531393332343430
34613735633732333762306331666332316265333962343935613936613438353164623031346432
66396264366666383835333538616430326161653839663838313764663664316266623762653463
31383235326363646234306636636564366238663965663331623965373139353064363733363339
34383937303033386566633939366331353333373935353263376235623430336236396135303233
61666434613136396338656334306463666535373364373130343161373866333339333436643036
64346162353530353334343438623835363664396265353762663832303366623735636238313039
37353136626563393231353662633031343435636261616131323833613062643834663634353537
30343430396135363466346236373462356366643539363665373663363932316163346665663935
61343434303039623139323265623538366563373633623065353862303935393434663566303232
37653033343230613766306334316464666533326566386633363835373466326263323861636635
36396437373161346636616664313734343565643330376431633238396462633764386531313165
62666538353239396361653035393636633263613639623038383734326564346261666335383234
36333663303365313066616533333336306639363239663339313766356431316562353836303464
3534373436363831616163383134383266636130316433633635

View File

@@ -0,0 +1,37 @@
$ANSIBLE_VAULT;1.1;AES256
33323463653739626134366261626263396338333966376262313263613131343962326432613263
6430616564313432666436376432383539626231616438330a646161313364353566373833353337
64633361306564646564663736663937303435356332316432666135353863393439663235646462
3136303031383835390a396531366636386133656366653835633833633733326561383066656464
31613933333731643065316130303561383563626636346633396266346332653234373732326535
39663765353938333835646563663633393835633163323435303164663261303661666435306239
34353264633736383565306336633565376436646536623835613330393466363935303031346664
63626465656435383162633761333131393934666632336539386435613362353135383538643836
66373261306139353134393839333539366531393163393266386531613732366431663865343134
64363933616338663966353431396133316561653366396130653232636561343739336265386339
38646238653436663531633465616164303633356233363433623038666465326339656238653233
36323162303233633935646132353835336364303833636563346535316166346533636536656665
64323030616665316133363739393364306462316135636630613262646436643062373138656431
35663334616239623534383564643738616264373762663034376332323637626337306639653830
65386339666465343931303933663561643664313364386662656663643336636264636333666435
66366531613538363233346137383462326334306534333564636232393931393433386664363036
39623134636331646536323531653063326231613363366562643561353939633062663132303035
38303265326136303633666566613966636133666336396133333033643434303138303065666463
36643765316134636133333937396332613233383932663265386264623133633364646237346465
32623965653662336335366639643765393636623236323036396538353666646132393636663536
65646638643236313762373135336430643731643961386264303134366633353934366431333430
34313362633836613166336437323835626537653237666139383230663835626630623933383834
32636136663830643661363663303136393733646133626538333836666135653936323832336433
64396234396430326334656561393264366263313730306631383037643135613765373861356561
37363933383238316232336564363364376637626630373963666262376165343838303530653764
64343937666365646666363939383662313334656236326566373565643637313434616261616635
35646131396432623534396133666239613036386332663038353531313935636139363136666562
62616234663935383262626235313337623332333733383035666633393965336535316234323561
37353563623138343339616565653465633633383563636631356333303435376536393634343031
63653062303432366230643333353634383061313135616533643935316263393366653335353964
36363135356365373064613338393261326265396330323930613538326330663532616163666564
39313631633434353938626637626462376139383536306531633733646331303030333238373161
36336364383939663132366461383264346631366566363638333738386235623264623331343738
34316436393363323165396430343163653837623035626236313663643038336666633535666462
33323566353062653964643362363233346264396365336637376661323730336437333031363830
38303962646561346262

View File

@@ -1,36 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
62333333383237396435306539366633646534333231666238326561306439316632383862646132
3938353633633131396535333764396238356561373739630a353662343534383761383132373530
31343032633261636136313562333331336531386165313264363536363866653562303131313831
6632346465646231360a363264653964633738323637323130366264643639633965636632623933
64383734623763303330313231316535646635646232386663613833653337396538623363303838
30656263346165643632646662363865373531336339393435383935363333393764373664373630
33303435303530656432326634623034616633326432613361333236663661393035623961613035
39376339393935626637316461636263333666616261646661316631376365356562633063636665
63626362336632393235333838313933353637393064616562333235656232613162636631343962
37623930666534373437353231353631333430363865333937356666313062336630396538333832
35353032333461616635616561656330626664306565313462656532393035353439366437336332
37363732373961386533323633323330626533383438663466313438636662343064306236396163
31383430643233393939656631646538663833336239323564363563356234306534343465316466
61333534363862376539626639353634343234623061316265333763633362336336316534643236
61303862386161346533386233303335333136653836666633363738643030633262313232336136
38363561376464313066373631356262626234653464356330646666336536623032333634613863
32303731396365646138343132623534336366633036623231386439376133656235633035663964
36366130633331353464643264383231306639326133633464306638653139666563636563343539
65383261396537383438646237633737343236666662633664346332383264646537643535376638
66653639633762356566306435383039326630373431343935383766303338363438643135616563
33613837613461343036363365356336336631663230326235623965663133336263636636333735
64646162326566643536323165363531633330373536656531666334336364313231393234333631
38363630653439393065316662306463333563323339313438366238653865653265363535616630
64613031653831616666666533346134656630646337633661623163653731656231363232663763
61386566356635663561356264633462643265643361343535303365396537363134303438393234
34633037356162663933396339373932303138633165323438643230666434363666313361386530
38623463663237363335316363333631616133363336326333346436376462663839303961643462
61396433383966356635613663636663383264666165373137346234646236623339323738303263
63623739303037386363623739623034623435643534353765656535323134656263633066313735
35646338613530663633366437353563636364393063386237316265386538616137343735363333
61376434303037323236323761376235633631633036653639333862636466653337303662663234
30613362386338663531616261386265616131356535346437326436326638346636343666303431
63313638313739623761333665616434356561653031666430623530363539626661646638316637
35393531363863333737303865323765636633633633623032353061373838376564393136336662
643366643665653265323065373536653536

488
files/backups/backup-all.py Normal file
View File

@@ -0,0 +1,488 @@
#!/usr/bin/env python3
"""
Backup script for all applications
Automatically discovers and runs backup scripts for all users,
then creates restic backups and sends notifications.
"""
import itertools
import os
import sys
import subprocess
import logging
import pwd
from abc import ABC
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Any
import requests
import tomllib
# Default config path
CONFIG_PATH = Path("/etc/backup/config.toml")
# File name to store directories and files to back up
BACKUP_TARGETS_FILE = "backup-targets"
# Default directory fo backups (relative to app dir)
# Used when backup-targets file not exists
BACKUP_DEFAULT_DIR = "backups"
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("/var/log/backup-all.log"),
],
)
logger = logging.getLogger(__name__)
@dataclass
class Config:
host_name: str
roots: List[Path]
@dataclass
class Application:
path: Path
owner: str
class Storage(ABC):
def backup(self, backup_dirs: List[str]) -> bool:
"""Backup directories"""
raise NotImplementedError()
class ResticStorage(Storage):
TYPE_NAME = "restic"
def __init__(self, name: str, params: Dict[str, Any]):
self.name = name
self.restic_repository = str(params.get("restic_repository", ""))
self.restic_password = str(params.get("restic_password", ""))
self.aws_access_key_id = str(params.get("aws_access_key_id", ""))
self.aws_secret_access_key = str(params.get("aws_secret_access_key", ""))
self.aws_default_region = str(params.get("aws_default_region", ""))
if not all(
[
self.restic_repository,
self.restic_password,
self.aws_access_key_id,
self.aws_secret_access_key,
self.aws_default_region,
]
):
raise ValueError(
f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
)
def backup(self, backup_dirs: List[str]) -> bool:
if not backup_dirs:
logger.warning("No backup directories found")
return True
try:
return self.__backup_internal(backup_dirs)
except Exception as exc: # noqa: BLE001
logger.error("Restic backup process failed: %s", exc)
return False
def __backup_internal(self, backup_dirs: List[str]) -> bool:
logger.info("Starting restic backup")
logger.info("Destination: %s", self.restic_repository)
env = os.environ.copy()
env.update(
{
"RESTIC_REPOSITORY": self.restic_repository,
"RESTIC_PASSWORD": self.restic_password,
"AWS_ACCESS_KEY_ID": self.aws_access_key_id,
"AWS_SECRET_ACCESS_KEY": self.aws_secret_access_key,
"AWS_DEFAULT_REGION": self.aws_default_region,
}
)
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
logger.error("Restic backup failed: %s", result.stderr)
return False
logger.info("Restic backup completed successfully")
check_cmd = ["restic", "check"]
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
logger.error("Restic check failed: %s", result.stderr)
return False
logger.info("Restic check completed successfully")
forget_cmd = [
"restic",
"forget",
"--compact",
"--prune",
"--keep-daily",
"90",
"--keep-monthly",
"36",
]
result = subprocess.run(forget_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
logger.error("Restic forget/prune failed: %s", result.stderr)
return False
logger.info("Restic forget/prune completed successfully")
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
logger.error("Final restic check failed: %s", result.stderr)
return False
logger.info("Final restic check completed successfully")
return True
class Notifier(ABC):
def send(self, html_message: str):
raise NotImplementedError()
class TelegramNotifier(Notifier):
TYPE_NAME = "telegram"
def __init__(self, name: str, params: Dict[str, Any]):
self.name = name
self.telegram_bot_token = str(params.get("telegram_bot_token", ""))
self.telegram_chat_id = str(params.get("telegram_chat_id", ""))
if not all(
[
self.telegram_bot_token,
self.telegram_chat_id,
]
):
raise ValueError(
f"Missing notification configuration values for backend {name}"
)
def send(self, html_message: str):
url = f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage"
data = {
"chat_id": self.telegram_chat_id,
"parse_mode": "HTML",
"text": html_message,
}
response = requests.post(url, data=data, timeout=30)
if response.status_code == 200:
logger.info("Telegram notification sent successfully")
else:
logger.error(
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
)
class BackupManager:
def __init__(
self,
config: Config,
roots: List[Path],
storages: List[Storage],
notifiers: List[Notifier],
):
self.errors: List[str] = []
self.warnings: List[str] = []
self.successful_backups: List[str] = []
self.config = config
self.roots: List[Path] = roots
self.storages = storages
self.notifiers = notifiers
def find_applications(self) -> List[Application]:
"""Get all application directories and their owners."""
applications: List[Application] = []
source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
for app_dir in source_dirs:
if "lost+found" in str(app_dir):
continue
if app_dir.is_dir():
try:
stat_info = app_dir.stat()
owner = pwd.getpwuid(stat_info.st_uid).pw_name
applications.append(Application(path=app_dir, owner=owner))
except (KeyError, OSError) as e:
logger.warning(f"Could not get owner for {app_dir}: {e}")
return applications
def find_backup_script(self, app_dir: str) -> Optional[str]:
"""Find backup script in user's home directory"""
possible_scripts = [
os.path.join(app_dir, "backup.sh"),
os.path.join(app_dir, "backup"),
]
for script_path in possible_scripts:
if os.path.exists(script_path):
# Check if file is executable
if os.access(script_path, os.X_OK):
return script_path
else:
logger.warning(
f"Backup script {script_path} exists but is not executable"
)
return None
def run_app_backup(self, script_path: str, app_dir: str, username: str) -> bool:
"""Run backup script as the specified user"""
try:
logger.info(f"Running backup script {script_path} (user {username})")
# Use su to run the script as the user
cmd = ["su", "--login", username, "--command", script_path]
result = subprocess.run(
cmd,
cwd=app_dir,
capture_output=True,
text=True,
timeout=3600, # 1 hour timeout
)
if result.returncode == 0:
logger.info(f"Backup script for {username} completed successfully")
self.successful_backups.append(username)
return True
else:
error_msg = f"Backup script {script_path} failed with return code {result.returncode}"
if result.stderr:
error_msg += f": {result.stderr}"
logger.error(error_msg)
self.errors.append(f"App {username}: {error_msg}")
return False
except subprocess.TimeoutExpired:
error_msg = f"Backup script {script_path} timed out"
logger.error(error_msg)
self.errors.append(f"App {username}: {error_msg}")
return False
except Exception as e:
error_msg = f"Failed to run backup script {script_path}: {str(e)}"
logger.error(error_msg)
self.errors.append(f"App {username}: {error_msg}")
return False
def get_backup_directories(self) -> List[str]:
"""Collect backup targets according to backup-targets rules"""
backup_dirs: List[str] = []
applications = self.find_applications()
def parse_targets_file(targets_file: Path) -> List[str]:
"""Parse backup-targets file, skipping comments and empty lines."""
targets: List[str] = []
try:
for raw_line in targets_file.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
targets.append(line)
except OSError as e:
warning_msg = f"Could not read backup targets file {targets_file}: {e}"
logger.warning(warning_msg)
self.warnings.append(warning_msg)
return targets
for app in applications:
app_dir = app.path
targets_file = app_dir / BACKUP_TARGETS_FILE
resolved_targets: List[Path] = []
if targets_file.exists():
# Read custom targets defined by the application.
for target_line in parse_targets_file(targets_file):
target_path = Path(target_line)
if not target_path.is_absolute():
target_path = (app_dir / target_path).resolve()
else:
target_path = target_path.resolve()
if target_path.exists():
resolved_targets.append(target_path)
else:
warning_msg = (
f"Backup target does not exist for {app_dir}: {target_path}"
)
logger.warning(warning_msg)
self.warnings.append(warning_msg)
else:
# Fallback to default backups directory when no list is provided.
default_target = (app_dir / BACKUP_DEFAULT_DIR).resolve()
if default_target.exists():
resolved_targets.append(default_target)
else:
warning_msg = f"Default backup path does not exist for {app_dir}: {default_target}"
logger.warning(warning_msg)
self.warnings.append(warning_msg)
for target in resolved_targets:
target_str = str(target)
if target_str not in backup_dirs:
backup_dirs.append(target_str)
return backup_dirs
def send_notification(self, success: bool) -> None:
"""Send notification to Notifiers"""
if success and not self.errors:
message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
if self.successful_backups:
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
else:
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
if self.successful_backups:
message += (
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
)
if self.warnings:
message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
if self.errors:
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
for notificator in self.notifiers:
try:
notificator.send(message)
except Exception as e:
logger.error(f"Failed to send notification: {str(e)}")
def run_backup_process(self) -> bool:
"""Main backup process"""
logger.info("Starting backup process")
# Get all home directories
applications = self.find_applications()
logger.info(f"Found {len(applications)} application directories")
# Process each user's backup
for app in applications:
app_dir = str(app.path)
username = app.owner
logger.info(f"Processing backup for app: {app_dir} (user {username})")
# Find backup script
backup_script = self.find_backup_script(app_dir)
if backup_script is None:
warning_msg = (
f"No backup script found for app: {app_dir} (user {username})"
)
logger.warning(warning_msg)
self.warnings.append(warning_msg)
continue
self.run_app_backup(backup_script, app_dir, username)
# Get backup directories
backup_dirs = self.get_backup_directories()
logger.info(f"Found backup directories: {backup_dirs}")
overall_success = True
for storage in self.storages:
backup_result = storage.backup(backup_dirs)
if not backup_result:
self.errors.append("Restic backup failed")
# Determine overall success
overall_success = overall_success and backup_result
# Send notification
self.send_notification(overall_success)
logger.info("Backup process completed")
if self.errors:
logger.error(f"Backup completed with {len(self.errors)} errors")
return False
elif self.warnings:
logger.warning(f"Backup completed with {len(self.warnings)} warnings")
return True
else:
logger.info("Backup completed successfully")
return True
def initialize(config_path: Path) -> BackupManager:
try:
with config_path.open("rb") as config_file:
raw_config = tomllib.load(config_file)
except OSError as e:
logger.error(f"Failed to read config file {config_path}: {e}")
raise
host_name = str(raw_config.get("host_name", "unknown"))
roots_raw = raw_config.get("roots") or []
if not isinstance(roots_raw, list) or not roots_raw:
raise ValueError("roots must be a non-empty list of paths in config.toml")
roots = [Path(root) for root in roots_raw]
storage_raw = raw_config.get("storage") or {}
storages: List[Storage] = []
for name, params in storage_raw.items():
if not isinstance(params, dict):
raise ValueError(f"Storage config for {name} must be a table")
storage_type = params.get("type", "")
if storage_type == ResticStorage.TYPE_NAME:
storages.append(ResticStorage(name, params))
if not storages:
raise ValueError("At least one storage backend must be configured")
notifications_raw = raw_config.get("notifier") or {}
notifiers: List[Notifier] = []
for name, params in notifications_raw.items():
if not isinstance(params, dict):
raise ValueError(f"Notificator config for {name} must be a table")
notifier_type = params.get("type", "")
if notifier_type == TelegramNotifier.TYPE_NAME:
notifiers.append(TelegramNotifier(name, params))
if not notifiers:
raise ValueError("At least one notification backend must be configured")
config = Config(host_name=host_name, roots=roots)
return BackupManager(
config=config, roots=roots, storages=storages, notifiers=notifiers
)
def main():
try:
backup_manager = initialize(CONFIG_PATH)
success = backup_manager.run_backup_process()
if not success:
sys.exit(1)
except KeyboardInterrupt:
logger.info("Backup process interrupted by user")
sys.exit(130)
except Exception as e:
logger.error(f"Unexpected error in backup process: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "Backup: perform gitea backup"
su --login gitea --command '/home/gitea/backup.sh'
echo "Backup: perform outline backup"
su --login outline --command '/home/outline/backup.sh'
echo "Backup: perform gramps backup"
su --login gramps --command '/home/gramps/backup.sh'
echo "Backup: send backups to remote storage with retic"
restic-shell.sh backup --verbose /home/gitea/backups /home/outline/backups /home/gramps/backups \
&& restic-shell.sh check \
&& restic-shell.sh forget --compact --prune --keep-daily 90 --keep-monthly 36 \
&& restic-shell.sh check
echo "Backup: send notification"
curl -s -X POST 'https://api.telegram.org/bot{{ notifications_tg_bot_token }}/sendMessage' \
-d 'chat_id={{ notifications_tg_chat_id }}' \
-d 'parse_mode=HTML' \
-d 'text=<b>{{ notifications_name }}</b>: бекап успешно завершен!'
echo -e "\nBackup: done"

View File

@@ -0,0 +1,18 @@
host_name = "{{ notifications_name }}"
roots = [
"{{ application_dir }}"
]
[storage.yandex_cloud_s3]
type = "restic"
restic_repository = "{{ restic_repository }}"
restic_password = "{{ restic_password }}"
aws_access_key_id = "{{ restic_s3_access_key }}"
aws_secret_access_key = "{{ restic_s3_access_secret }}"
aws_default_region = "{{ restic_s3_region }}"
[notifier.server_notifications_channel]
type = "telegram"
telegram_bot_token = "{{ notifications_tg_bot_token }}"
telegram_chat_id = "{{ notifications_tg_chat_id }}"

View File

@@ -4,6 +4,12 @@
{
grace_period 15s
admin :2019
# Enable metrics in Prometheus format
# https://caddyserver.com/docs/metrics
metrics
}
# -------------------------------------------------------------------
# Applications
@@ -58,3 +64,73 @@ gramps.vakhrushev.me {
}
}
miniflux.vakhrushev.me {
tls anwinged@ya.ru
reverse_proxy {
to miniflux_app:8080
}
}
wakapi.vakhrushev.me {
tls anwinged@ya.ru
reverse_proxy {
to wakapi_app:3000
}
}
wanderer.vakhrushev.me {
tls anwinged@ya.ru
reverse_proxy {
to wanderer_web:3000
}
}
memos.vakhrushev.me {
tls anwinged@ya.ru
reverse_proxy {
to memos_app:5230
}
}
wanderbase.vakhrushev.me {
tls anwinged@ya.ru
forward_auth authelia_app:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy {
to wanderer_db:8090
}
}
rssbridge.vakhrushev.me {
tls anwinged@ya.ru
forward_auth authelia_app:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy {
to rssbridge_app:80
}
}
dozzle.vakhrushev.me {
tls anwinged@ya.ru
forward_auth authelia_app:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name Remote-Filter
}
reverse_proxy dozzle_app:8080
}

View File

@@ -1,7 +1,7 @@
services:
{{ service_name }}:
image: caddy:2.9.1
image: caddy:2.10.2
restart: unless-stopped
container_name: {{ service_name }}
ports:
@@ -15,8 +15,8 @@ services:
- {{ data_dir }}:/data
- {{ config_dir }}:/config
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -0,0 +1,23 @@
services:
dozzle_app:
image: amir20/dozzle:v8.14.11
container_name: dozzle_app
restart: unless-stopped
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
- "web_proxy_network"
environment:
DOZZLE_HOSTNAME: vakhrushev.me
DOZZLE_AUTH_PROVIDER: forward-proxy
healthcheck:
test: ["CMD", "/dozzle", "healthcheck"]
interval: 3s
timeout: 30s
retries: 5
start_period: 30s
networks:
web_proxy_network:
external: true

View File

@@ -1,11 +1,10 @@
services:
gitea_app:
image: gitea/gitea:1.23.7
image: gitea/gitea:1.25.3
restart: unless-stopped
container_name: gitea_app
ports:
- "127.0.0.1:{{ gitea_port }}:3000"
- "2222:22"
volumes:
- {{ data_dir }}:/data
@@ -13,10 +12,10 @@ services:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
environment:
- "USER_UID=${USER_UID}"
- "USER_GID=${USER_GID}"
- "USER_UID={{ user_create_result.uid }}"
- "USER_GID={{ user_create_result.group }}"
- "GITEA__server__SSH_PORT=2222"
# Mailer
@@ -29,5 +28,5 @@ services:
- "GITEA__mailer__FROM=gitea@vakhrushev.me"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -3,24 +3,22 @@
services:
gramps_app: &gramps_app
image: ghcr.io/gramps-project/grampsweb:v25.4.1
image: ghcr.io/gramps-project/grampsweb:25.11.2
container_name: gramps_app
depends_on:
- gramps_redis
restart: unless-stopped
ports:
- "127.0.0.1:{{ gramps_port }}:5000" # host:docker
networks:
- "gramps_network"
- "{{ web_proxy_network }}"
- "web_proxy_network"
volumes:
- "{{ (data_dir, 'gramps_db') | path_join }}:/root/.gramps/grampsdb" # persist Gramps database
- "{{ (data_dir, 'gramps_users') | path_join }}:/app/users" # persist user database
- "{{ (data_dir, 'gramps_index') | path_join }}:/app/indexdir" # persist search index
- "{{ (data_dir, 'gramps_thumb_cache') | path_join }}:/app/thumbnail_cache" # persist thumbnails
- "{{ (data_dir, 'gramps_cache') | path_join }}:/app/cache" # persist export and report caches
- "{{ (data_dir, 'gramps_secret') | path_join }}:/app/secret" # persist flask secret
- "{{ (data_dir, 'gramps_media') | path_join }}:/app/media" # persist media files
- "{{ (cache_dir, 'gramps_thumb_cache') | path_join }}:/app/thumbnail_cache" # persist thumbnails
- "{{ (cache_dir, 'gramps_cache') | path_join }}:/app/cache" # persist export and report caches
- "{{ media_dir }}:/app/media" # persist media files
environment:
GRAMPSWEB_TREE: "Gramps" # will create a new tree if not exists
GRAMPSWEB_SECRET_KEY: "{{ gramps_secret_key }}"
@@ -39,12 +37,8 @@ services:
GRAMPSWEB_EMAIL_USE_TLS: "false"
GRAMPSWEB_DEFAULT_FROM_EMAIL: "gramps@vakhrushev.me"
# media storage at s3
GRAMPSWEB_MEDIA_BASE_DIR: "s3://av-gramps-media-storage"
AWS_ENDPOINT_URL: "{{ gramps_s3_endpoint }}"
AWS_ACCESS_KEY_ID: "{{ gramps_s3_access_key_id }}"
AWS_SECRET_ACCESS_KEY: "{{ gramps_s3_secret_access_key }}"
AWS_DEFAULT_REGION: "{{ gramps_s3_region }}"
# media storage
GRAMPSWEB_MEDIA_BASE_DIR: "/app/media"
gramps_celery:
<<: *gramps_app # YAML merge key copying the entire grampsweb service config
@@ -55,17 +49,20 @@ services:
ports: []
networks:
- "gramps_network"
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=1
gramps_redis:
image: valkey/valkey:8.1.1-alpine
image: valkey/valkey:9.0-alpine
container_name: gramps_redis
restart: unless-stopped
networks:
- "gramps_network"
- "monitoring_network"
networks:
gramps_network:
driver: bridge
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -23,10 +23,3 @@ models:
undo:
type: sqlite
path: "{{ (data_dir, 'gramps_db/59a0f3d6-1c3d-4410-8c1d-1c9c6689659f/undo.db') | path_join }}"
archive:
includes:
- "{{ data_dir }}"
excludes:
- "{{ (data_dir, 'gramps_cache') | path_join }}"
- "{{ (data_dir, 'gramps_thumb_cache') | path_join }}"
- "{{ (data_dir, 'gramps_tmp') | path_join }}"

65
files/gramps/gramps_rename.py Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3.12
import argparse
import sys
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Rename Gramps document files by appending extensions from a list."
)
parser.add_argument("directory", type=Path, help="Directory containing hashed files")
parser.add_argument("names_file", type=Path, help="Text file with target names")
return parser.parse_args()
def read_names(path: Path) -> list[str]:
if not path.is_file():
raise FileNotFoundError(f"Names file not found: {path}")
names = []
for line in path.read_text(encoding="utf-8").splitlines():
name = line.strip()
if name:
names.append(name)
return names
def rename_files(directory: Path, names: list[str]) -> None:
if not directory.is_dir():
raise NotADirectoryError(f"Directory not found: {directory}")
for name in names:
hash_part, dot, _ = name.partition(".")
if not dot:
print(f"Skipping invalid entry (missing extension): {name}", file=sys.stderr)
continue
source = directory / hash_part
target = directory / name
if target.exists():
print(f"Target already exists, skipping: {target}", file=sys.stderr)
continue
if not source.exists():
print(f"Source not found, skipping: {source}", file=sys.stderr)
continue
source.rename(target)
print(f"Renamed {source.name} -> {target.name}")
def main() -> None:
args = parse_args()
try:
names = read_names(args.names_file)
rename_files(args.directory, names)
except Exception as exc: # noqa: BLE001
print(str(exc), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,13 @@
services:
homepage_app:
# noinspection ComposeUnknownValues
image: "{{ registry_homepage_nginx_image }}"
container_name: homepage_app
restart: unless-stopped
networks:
- "web_proxy_network"
networks:
web_proxy_network:
external: true

View File

@@ -1,14 +0,0 @@
services:
homepage_app:
image: "{{ registry_homepage_web_image }}"
container_name: homepage_app
restart: unless-stopped
ports:
- "127.0.0.1:{{ homepage_port }}:80"
networks:
- "{{ web_proxy_network }}"
networks:
{{ web_proxy_network }}:
external: true

View File

@@ -32,7 +32,7 @@ def main():
sorted_files = sorted(files)
# Identify files to delete
to_delete = sorted_files[: -args.keep] if args.keep > 0 else sorted_files.copy()
to_delete = sorted_files[:-args.keep] if args.keep > 0 else sorted_files.copy()
# Delete files and print results
for filename in to_delete:

10
files/memos/backup.sh.j2 Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "{{ app_name }}: backup data with gobackups"
(cd "{{ base_dir }}" && gobackup perform --config "{{ gobackup_config }}")
echo "{{ app_name }}: done."

View File

@@ -0,0 +1,23 @@
# See versions: https://github.com/gramps-project/gramps-web/pkgs/container/grampsweb
services:
memos_app:
image: neosmemo/memos:0.25.3
container_name: memos_app
restart: unless-stopped
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
networks:
- "web_proxy_network"
volumes:
- "{{ data_dir }}:/var/opt/memos"
environment:
- MEMOS_MODE=prod
- MEMOS_PORT=5230
- MEMOS_STORAGE_TYPE=local
- MEMOS_STORAGE_PATH=assets/{uuid}
- MEMOS_MAX_FILE_SIZE=52428800
networks:
web_proxy_network:
external: true

View File

@@ -0,0 +1,16 @@
# https://gobackup.github.io/configuration
models:
memos:
compress_with:
type: 'tgz'
storages:
local:
type: 'local'
path: '{{ backups_dir }}'
keep: 3
databases:
users:
type: sqlite
path: "{{ (data_dir, 'memos_prod.db') | path_join }}"

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="miniflux_postgres_${TIMESTAMP}.sql.gz"
echo "miniflux: backing up postgresql database"
docker compose --file "{{ base_dir }}/docker-compose.yml" exec \
miniflux_postgres \
pg_dump \
-U "{{ miniflux_postgres_user }}" \
"{{ miniflux_postgres_database }}" \
| gzip > "{{ postgres_backups_dir }}/${BACKUP_FILE}"
echo "miniflux: PostgreSQL backup saved to {{ postgres_backups_dir }}/${BACKUP_FILE}"
echo "miniflux: removing old backups"
# Keep only the 3 most recent backups
keep-files.py "{{ postgres_backups_dir }}" --keep 3
echo "miniflux: backup completed successfully."

View File

@@ -0,0 +1,63 @@
# See sample https://miniflux.app/docs/docker.html#docker-compose
# See env https://miniflux.app/docs/configuration.html
services:
miniflux_app:
image: miniflux/miniflux:2.2.10
container_name: miniflux_app
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
depends_on:
miniflux_postgres:
condition: service_healthy
restart: 'unless-stopped'
networks:
- "miniflux_network"
- "web_proxy_network"
volumes:
- "{{ secrets_dir }}:/secrets:ro"
environment:
- DATABASE_URL_FILE=/secrets/miniflux_database_url
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- ADMIN_USERNAME_FILE=/secrets/miniflux_admin_user
- ADMIN_PASSWORD_FILE=/secrets/miniflux_admin_password
- BASE_URL=https://miniflux.vakhrushev.me
- DISABLE_LOCAL_AUTH=1
- OAUTH2_OIDC_DISCOVERY_ENDPOINT=https://auth.vakhrushev.me
- OAUTH2_CLIENT_ID_FILE=/secrets/miniflux_oidc_client_id
- OAUTH2_CLIENT_SECRET_FILE=/secrets/miniflux_oidc_client_secret
- OAUTH2_OIDC_PROVIDER_NAME=Authelia
- OAUTH2_PROVIDER=oidc
- OAUTH2_REDIRECT_URL=https://miniflux.vakhrushev.me/oauth2/oidc/callback
- OAUTH2_USER_CREATION=1
- METRICS_COLLECTOR=1
- METRICS_ALLOWED_NETWORKS=0.0.0.0/0
miniflux_postgres:
image: postgres:16.3-bookworm
container_name: miniflux_postgres
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: 'unless-stopped'
environment:
- POSTGRES_USER={{ miniflux_postgres_user }}
- POSTGRES_PASSWORD_FILE=/secrets/miniflux_postgres_password
- POSTGRES_DB={{ miniflux_postgres_database }}
networks:
- "miniflux_network"
- "monitoring_network"
volumes:
- "/etc/passwd:/etc/passwd:ro"
- "{{ secrets_dir }}:/secrets:ro"
- "{{ postgres_data_dir }}:/var/lib/postgresql/data"
healthcheck:
test: ["CMD", "pg_isready", "--username={{ miniflux_postgres_user }}", "--dbname={{ miniflux_postgres_database }}"]
interval: 10s
start_period: 30s
networks:
miniflux_network:
driver: bridge
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -0,0 +1,43 @@
services:
netdata:
image: netdata/netdata:v2.8.4
container_name: netdata
restart: unless-stopped
cap_add:
- SYS_PTRACE
- SYS_ADMIN
security_opt:
- apparmor:unconfined
networks:
- "web_proxy_network"
- "monitoring_network"
volumes:
- "{{ config_dir }}:/etc/netdata"
- "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata"
- "{{ (data_dir, 'cache') | path_join }}:/var/cache/netdata"
# Netdata system volumes
- "/:/host/root:ro,rslave"
- "/etc/group:/host/etc/group:ro"
- "/etc/hostname:/host/etc/hostname:ro"
- "/etc/localtime:/etc/localtime:ro"
- "/etc/os-release:/host/etc/os-release:ro"
- "/etc/passwd:/host/etc/passwd:ro"
- "/proc:/host/proc:ro"
- "/run/dbus:/run/dbus:ro"
- "/sys:/host/sys:ro"
- "/var/log:/host/var/log:ro"
- "/var/run:/host/var/run:ro"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
environment:
PGID: "{{ netdata_docker_group_output.stdout | default(999) }}"
NETDATA_EXTRA_DEB_PACKAGES: "fail2ban"
networks:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -0,0 +1,3 @@
jobs:
- name: fail2ban
update_every: 60 # Collect Fail2Ban jails statistics every N seconds

View File

@@ -0,0 +1,9 @@
update_every: 60
jobs:
- name: outline_db
dsn: 'postgresql://netdata:{{ netdata_postgres_password }}@outline_postgres:5432/outline'
- name: miniflux_db
dsn: 'postgresql://netdata:{{ netdata_postgres_password }}@miniflux_postgres:5432/miniflux'

View File

@@ -0,0 +1,24 @@
update_every: 15
jobs:
- name: caddyproxy
url: http://caddyproxy:2019/metrics
selector:
allow:
- "caddy_http_*"
- name: authelia
url: http://authelia_app:9959/metrics
selector:
allow:
- "authelia_*"
- name: miniflux
url: http://miniflux_app:8080/metrics
selector:
allow:
- "miniflux_*"
- name: transcriber
url: http://transcriber_app:8080/metrics

View File

@@ -0,0 +1,702 @@
# netdata configuration
#
# You can download the latest version of this file, using:
#
# wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf
# or
# curl -o /etc/netdata/netdata.conf http://localhost:19999/netdata.conf
#
# You can uncomment and change any of the options below.
# The value shown in the commented settings, is the default value.
#
# global netdata configuration
[global]
# run as user = netdata
# host access prefix = /host
# pthread stack size = 8MiB
# cpu cores = 2
# libuv worker threads = 16
# profile = standalone
# hostname = rivendell-v2
# glibc malloc arena max for plugins = 1
# glibc malloc arena max for netdata = 1
# crash reports = all
# timezone = Etc/UTC
# OOM score = 0
# process scheduling policy = keep
# is ephemeral node = no
# has unstable connection = no
[db]
#| >>> [db].update every <<<
#| datatype: duration (seconds), default value: 1s
update every = 10s
# enable replication = yes
# replication period = 1d
# replication step = 1h
# replication threads = 1
# replication prefetch = 10
# db = dbengine
# memory deduplication (ksm) = auto
# cleanup orphan hosts after = 1h
# cleanup ephemeral hosts after = off
# cleanup obsolete charts after = 1h
# gap when lost iterations above = 1
# dbengine page type = gorilla
# dbengine page cache size = 32MiB
# dbengine extent cache size = off
# dbengine enable journal integrity check = no
# dbengine use all ram for caches = no
# dbengine out of memory protection = 391.49MiB
# dbengine use direct io = yes
# dbengine journal v2 unmount time = 2m
# dbengine pages per extent = 109
# storage tiers = 3
# dbengine tier backfill = new
# dbengine tier 1 update every iterations = 60
# dbengine tier 2 update every iterations = 60
# dbengine tier 0 retention size = 1024MiB
# dbengine tier 0 retention time = 14d
# dbengine tier 1 retention size = 1024MiB
# dbengine tier 1 retention time = 3mo
# dbengine tier 2 retention size = 1024MiB
# dbengine tier 2 retention time = 2y
# extreme cardinality protection = yes
# extreme cardinality keep instances = 1000
# extreme cardinality min ephemerality = 50
[directories]
# config = /etc/netdata
# stock config = /usr/lib/netdata/conf.d
# log = /var/log/netdata
# web = /usr/share/netdata/web
# cache = /var/cache/netdata
# lib = /var/lib/netdata
# cloud.d = /var/lib/netdata/cloud.d
# plugins = "/usr/libexec/netdata/plugins.d" "/etc/netdata/custom-plugins.d"
# registry = /var/lib/netdata/registry
# home = /etc/netdata
# stock health config = /usr/lib/netdata/conf.d/health.d
# health config = /etc/netdata/health.d
[logs]
# facility = daemon
# logs flood protection period = 1m
# logs to trigger flood protection = 1000
# level = info
# debug = /var/log/netdata/debug.log
# daemon = /var/log/netdata/daemon.log
# collector = /var/log/netdata/collector.log
# access = /var/log/netdata/access.log
# health = /var/log/netdata/health.log
# debug flags = 0x0000000000000000
[environment variables]
# PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# PYTHONPATH =
# TZ = :/etc/localtime
[cloud]
# conversation log = no
# scope = full
# query threads = 6
# proxy = env
[ml]
# enabled = auto
# training window = 6h
# min training window = 15m
# max training vectors = 1440
# max samples to smooth = 3
# train every = 3h
# number of models per dimension = 18
# delete models older than = 7d
# num samples to diff = 1
# num samples to lag = 5
# maximum number of k-means iterations = 1000
# dimension anomaly score threshold = 0.99000
# host anomaly rate threshold = 1.00000
# anomaly detection grouping method = average
# anomaly detection grouping duration = 5m
# num training threads = 1
# flush models batch size = 256
# dimension anomaly rate suppression window = 15m
# dimension anomaly rate suppression threshold = 450
# enable statistics charts = yes
# hosts to skip from training = !*
# charts to skip from training = netdata.*
# stream anomaly detection charts = yes
[health]
# silencers file = /var/lib/netdata/health.silencers.json
# enabled = yes
# enable stock health configuration = yes
# use summary for notifications = yes
# default repeat warning = off
# default repeat critical = off
# in memory max health log entries = 1000
# health log retention = 5d
# script to execute on alarm = /usr/libexec/netdata/plugins.d/alarm-notify.sh
# enabled alarms = *
# run at least every = 10s
# postpone alarms during hibernation for = 1m
[web]
#| >>> [web].default port <<<
#| migrated from: [global].default port
# default port = 19999
# ssl key = /etc/netdata/ssl/key.pem
# ssl certificate = /etc/netdata/ssl/cert.pem
# tls version = 1.3
# tls ciphers = none
# ses max tg_des_window = 15
# des max tg_des_window = 15
# mode = static-threaded
# listen backlog = 4096
# bind to = *
# bearer token protection = no
# disconnect idle clients after = 1m
# timeout for first request = 1m
# accept a streaming request every = off
# respect do not track policy = no
# x-frame-options response header =
# allow connections from = localhost *
# allow connections by dns = heuristic
# allow dashboard from = localhost *
# allow dashboard by dns = heuristic
# allow badges from = *
# allow badges by dns = heuristic
# allow streaming from = *
# allow streaming by dns = heuristic
# allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* UNKNOWN
# allow netdata.conf by dns = no
# allow management from = localhost
# allow management by dns = heuristic
# enable gzip compression = yes
# gzip compression strategy = default
# gzip compression level = 3
# ssl skip certificate verification = no
# web server threads = 6
# web server max sockets = 131072
[registry]
# enabled = no
# registry db file = /var/lib/netdata/registry/registry.db
# registry log file = /var/lib/netdata/registry/registry-log.db
# registry save db every new entries = 1000000
# registry expire idle persons = 1y
# registry domain =
# registry to announce = https://registry.my-netdata.io
# registry hostname = rivendell-v2
# verify browser cookies support = yes
# enable cookies SameSite and Secure = yes
# max URL length = 1024
# max URL name length = 50
# netdata management api key file = /var/lib/netdata/netdata.api.key
# allow from = *
# allow by dns = heuristic
[pulse]
# extended = no
# update every = 10s
[plugins]
#| >>> [plugins].perf <<<
#| datatype: yes or no, default value: yes
perf = no
#| >>> [plugins].python.d <<<
#| datatype: yes or no, default value: yes
python.d = no
#| >>> [plugins].charts.d <<<
#| datatype: yes or no, default value: yes
charts.d = no
#| >>> [plugins].otel <<<
#| datatype: yes or no, default value: yes
otel = no
#| >>> [plugins].statsd <<<
#| datatype: yes or no, default value: yes
statsd = no
# idlejitter = yes
# netdata pulse = yes
# profile = no
# tc = yes
# diskspace = yes
# proc = yes
# cgroups = yes
# timex = yes
# enable running new plugins = yes
# check for new plugins every = 1m
# slabinfo = no
# freeipmi = no
# debugfs = yes
# ioping = yes
# network-viewer = yes
# apps = yes
# go.d = yes
# systemd-units = yes
# systemd-journal = yes
[statsd]
# update every (flushInterval) = 10s
# udp messages to process at once = 10
# create private charts for metrics matching = *
# max private charts hard limit = 1000
# set charts as obsolete after = off
# decimal detail = 1000
# disconnect idle tcp clients after = 10m
# private charts hidden = no
# histograms and timers percentile (percentThreshold) = 95.00000
# dictionaries max unique dimensions = 200
# add dimension for number of events received = no
# gaps on gauges (deleteGauges) = no
# gaps on counters (deleteCounters) = no
# gaps on meters (deleteMeters) = no
# gaps on sets (deleteSets) = no
# gaps on histograms (deleteHistograms) = no
# gaps on timers (deleteTimers) = no
# gaps on dictionaries (deleteDictionaries) = no
# statsd server max TCP sockets = 131072
[plugin:idlejitter]
# loop time = 20ms
[plugin:timex]
# update every = 10s
# clock synchronization state = yes
# time offset = yes
[plugin:proc]
# /proc/net/dev = yes
# /proc/pagetypeinfo = no
# /proc/stat = yes
# /proc/uptime = yes
# /proc/loadavg = yes
# /proc/sys/fs/file-nr = yes
# /proc/sys/kernel/random/entropy_avail = yes
# /run/reboot_required = yes
# /proc/pressure = yes
# /proc/interrupts = yes
# /proc/softirqs = yes
# /proc/vmstat = yes
# /proc/meminfo = yes
# /sys/kernel/mm/ksm = yes
# /sys/block/zram = yes
# /sys/devices/system/edac/mc = yes
# /sys/devices/pci/aer = yes
# /sys/devices/system/node = yes
# /proc/net/wireless = yes
# /proc/net/sockstat = yes
# /proc/net/sockstat6 = yes
# /proc/net/netstat = yes
# /proc/net/sctp/snmp = yes
# /proc/net/softnet_stat = yes
# /proc/net/ip_vs/stats = yes
# /sys/class/infiniband = yes
# /proc/net/stat/conntrack = yes
# /proc/net/stat/synproxy = yes
# /proc/diskstats = yes
# /proc/mdstat = yes
# /proc/net/rpc/nfsd = yes
# /proc/net/rpc/nfs = yes
# /proc/spl/kstat/zfs/arcstats = yes
# /sys/fs/btrfs = yes
# ipc = yes
# /sys/class/power_supply = yes
# /sys/class/drm = yes
[plugin:cgroups]
#| >>> [plugin:cgroups].update every <<<
#| datatype: duration (seconds), default value: 10s
update every = 20s
#| >>> [plugin:cgroups].check for new cgroups every <<<
#| datatype: duration (seconds), default value: 10s
check for new cgroups every = 20s
# use unified cgroups = auto
# max cgroups to allow = 1000
# max cgroups depth to monitor = 0
# enable by default cgroups matching = !*/init.scope !/system.slice/run-*.scope *user.slice/docker-* !*user.slice* *.scope !/machine.slice/*/.control !/machine.slice/*/payload* !/machine.slice/*/supervisor /machine.slice/*.service */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* !*/vcpu* !*/emulator !*.mount !*.partition !*.service !*.service/udev !*.socket !*.slice !*.swap !*.user !/ !/docker !*/libvirt !/lxc !/lxc/*/* !/lxc.monitor* !/lxc.pivot !/lxc.payload !*lxcfs.service/.control !/machine !/qemu !/system !/systemd !/user *
# enable by default cgroups names matching = *
# search for cgroups in subpaths matching = !*/init.scope !*-qemu !*.libvirt-qemu !/init.scope !/system !/systemd !/user !/lxc/*/* !/lxc.monitor !/lxc.payload/*/* !/lxc.payload.* *
# script to get cgroup names = /usr/libexec/netdata/plugins.d/cgroup-name.sh
# script to get cgroup network interfaces = /usr/libexec/netdata/plugins.d/cgroup-network
# run script to rename cgroups matching = !/ !*.mount !*.socket !*.partition /machine.slice/*.service !*.service !*.slice !*.swap !*.user !init.scope !*.scope/vcpu* !*.scope/emulator *.scope *docker* *lxc* *qemu* */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* *.libvirt-qemu *
# cgroups to match as systemd services = !/system.slice/*/*.service /system.slice/*.service
[plugin:proc:diskspace]
#| >>> [plugin:proc:diskspace].update every <<<
#| datatype: duration (seconds), default value: 10s
update every = 1m
# remove charts of unmounted disks = yes
# check for new mount points every = 15s
# exclude space metrics on paths = /dev /dev/shm /proc/* /sys/* /var/run/user/* /run/lock /run/user/* /snap/* /var/lib/docker/* /var/lib/containers/storage/* /run/credentials/* /run/containerd/* /rpool /rpool/*
# exclude space metrics on filesystems = *gvfs *gluster* *s3fs *ipfs *davfs2 *httpfs *sshfs *gdfs *moosefs fusectl autofs cgroup cgroup2 hugetlbfs devtmpfs fuse.lxcfs
# exclude inode metrics on filesystems = msdosfs msdos vfat overlayfs aufs* *unionfs
# space usage for all disks = auto
# inodes usage for all disks = auto
[plugin:tc]
# script to run to get tc values = /usr/libexec/netdata/plugins.d/tc-qos-helper.sh
[plugin:go.d]
# update every = 10s
# command options =
[plugin:apps]
# update every = 10s
# command options =
[plugin:systemd-journal]
# update every = 10s
# command options =
[plugin:network-viewer]
# update every = 10s
# command options =
[plugin:debugfs]
# update every = 10s
# command options =
[plugin:ioping]
# update every = 10s
# command options =
[plugin:proc:/proc/net/dev]
# compressed packets for all interfaces = no
# disable by default interfaces matching = lo fireqos* *-ifb fwpr* fwbr* fwln* ifb4*
[plugin:proc:/proc/stat]
# cpu utilization = yes
# per cpu core utilization = no
# cpu interrupts = yes
# context switches = yes
# processes started = yes
# processes running = yes
# keep per core files open = yes
# keep cpuidle files open = yes
# core_throttle_count = auto
# package_throttle_count = no
# cpu frequency = yes
# cpu idle states = no
# core_throttle_count filename to monitor = /host/sys/devices/system/cpu/%s/thermal_throttle/core_throttle_count
# package_throttle_count filename to monitor = /host/sys/devices/system/cpu/%s/thermal_throttle/package_throttle_count
# scaling_cur_freq filename to monitor = /host/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq
# time_in_state filename to monitor = /host/sys/devices/system/cpu/%s/cpufreq/stats/time_in_state
# schedstat filename to monitor = /host/proc/schedstat
# cpuidle name filename to monitor = /host/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/name
# cpuidle time filename to monitor = /host/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/time
# filename to monitor = /host/proc/stat
[plugin:proc:/proc/uptime]
# filename to monitor = /host/proc/uptime
[plugin:proc:/proc/loadavg]
# filename to monitor = /host/proc/loadavg
# enable load average = yes
# enable total processes = yes
[plugin:proc:/proc/sys/fs/file-nr]
# filename to monitor = /host/proc/sys/fs/file-nr
[plugin:proc:/proc/sys/kernel/random/entropy_avail]
# filename to monitor = /host/proc/sys/kernel/random/entropy_avail
[plugin:proc:/proc/pressure]
# base path of pressure metrics = /proc/pressure
# enable cpu some pressure = yes
# enable cpu full pressure = no
# enable memory some pressure = yes
# enable memory full pressure = yes
# enable io some pressure = yes
# enable io full pressure = yes
# enable irq some pressure = no
# enable irq full pressure = yes
[plugin:proc:/proc/interrupts]
# interrupts per core = no
# filename to monitor = /host/proc/interrupts
[plugin:proc:/proc/softirqs]
# interrupts per core = no
# filename to monitor = /host/proc/softirqs
[plugin:proc:/proc/vmstat]
# filename to monitor = /host/proc/vmstat
# swap i/o = auto
# disk i/o = yes
# memory page faults = yes
# out of memory kills = yes
# system-wide numa metric summary = auto
# transparent huge pages = auto
# zswap i/o = auto
# memory ballooning = auto
# kernel same memory = auto
[plugin:proc:/sys/devices/system/node]
# directory to monitor = /host/sys/devices/system/node
# enable per-node numa metrics = auto
[plugin:proc:/proc/meminfo]
# system ram = yes
# system swap = auto
# hardware corrupted ECC = auto
# committed memory = yes
# writeback memory = yes
# kernel memory = yes
# slab memory = yes
# hugepages = auto
# transparent hugepages = auto
# memory reclaiming = yes
# high low memory = yes
# cma memory = auto
# direct maps = yes
# filename to monitor = /host/proc/meminfo
[plugin:proc:/sys/kernel/mm/ksm]
# /sys/kernel/mm/ksm/pages_shared = /host/sys/kernel/mm/ksm/pages_shared
# /sys/kernel/mm/ksm/pages_sharing = /host/sys/kernel/mm/ksm/pages_sharing
# /sys/kernel/mm/ksm/pages_unshared = /host/sys/kernel/mm/ksm/pages_unshared
# /sys/kernel/mm/ksm/pages_volatile = /host/sys/kernel/mm/ksm/pages_volatile
[plugin:proc:/sys/devices/system/edac/mc]
# directory to monitor = /host/sys/devices/system/edac/mc
[plugin:proc:/sys/class/pci/aer]
# enable root ports = no
# enable pci slots = no
[plugin:proc:/proc/net/wireless]
# filename to monitor = /host/proc/net/wireless
# status for all interfaces = auto
# quality for all interfaces = auto
# discarded packets for all interfaces = auto
# missed beacon for all interface = auto
[plugin:proc:/proc/net/sockstat]
# ipv4 sockets = auto
# ipv4 TCP sockets = auto
# ipv4 TCP memory = auto
# ipv4 UDP sockets = auto
# ipv4 UDP memory = auto
# ipv4 UDPLITE sockets = auto
# ipv4 RAW sockets = auto
# ipv4 FRAG sockets = auto
# ipv4 FRAG memory = auto
# update constants every = 1m
# filename to monitor = /host/proc/net/sockstat
[plugin:proc:/proc/net/sockstat6]
# ipv6 TCP sockets = auto
# ipv6 UDP sockets = auto
# ipv6 UDPLITE sockets = auto
# ipv6 RAW sockets = auto
# ipv6 FRAG sockets = auto
# filename to monitor = /host/proc/net/sockstat6
[plugin:proc:/proc/net/netstat]
# bandwidth = auto
# input errors = auto
# multicast bandwidth = auto
# broadcast bandwidth = auto
# multicast packets = auto
# broadcast packets = auto
# ECN packets = auto
# TCP reorders = auto
# TCP SYN cookies = auto
# TCP out-of-order queue = auto
# TCP connection aborts = auto
# TCP memory pressures = auto
# TCP SYN queue = auto
# TCP accept queue = auto
# filename to monitor = /host/proc/net/netstat
[plugin:proc:/proc/net/snmp]
# ipv4 packets = auto
# ipv4 fragments sent = auto
# ipv4 fragments assembly = auto
# ipv4 errors = auto
# ipv4 TCP connections = auto
# ipv4 TCP packets = auto
# ipv4 TCP errors = auto
# ipv4 TCP opens = auto
# ipv4 TCP handshake issues = auto
# ipv4 UDP packets = auto
# ipv4 UDP errors = auto
# ipv4 ICMP packets = auto
# ipv4 ICMP messages = auto
# ipv4 UDPLite packets = auto
# filename to monitor = /host/proc/net/snmp
[plugin:proc:/proc/net/snmp6]
# ipv6 packets = auto
# ipv6 fragments sent = auto
# ipv6 fragments assembly = auto
# ipv6 errors = auto
# ipv6 UDP packets = auto
# ipv6 UDP errors = auto
# ipv6 UDPlite packets = auto
# ipv6 UDPlite errors = auto
# bandwidth = auto
# multicast bandwidth = auto
# broadcast bandwidth = auto
# multicast packets = auto
# icmp = auto
# icmp redirects = auto
# icmp errors = auto
# icmp echos = auto
# icmp group membership = auto
# icmp router = auto
# icmp neighbor = auto
# icmp mldv2 = auto
# icmp types = auto
# ect = auto
# filename to monitor = /host/proc/net/snmp6
[plugin:proc:/proc/net/sctp/snmp]
# established associations = auto
# association transitions = auto
# fragmentation = auto
# packets = auto
# packet errors = auto
# chunk types = auto
# filename to monitor = /host/proc/net/sctp/snmp
[plugin:proc:/proc/net/softnet_stat]
# softnet_stat per core = no
# filename to monitor = /host/proc/net/softnet_stat
[plugin:proc:/proc/net/ip_vs_stats]
# IPVS bandwidth = yes
# IPVS connections = yes
# IPVS packets = yes
# filename to monitor = /host/proc/net/ip_vs_stats
[plugin:proc:/sys/class/infiniband]
# dirname to monitor = /host/sys/class/infiniband
# bandwidth counters = yes
# packets counters = yes
# errors counters = yes
# hardware packets counters = auto
# hardware errors counters = auto
# monitor only active ports = auto
# disable by default interfaces matching =
# refresh ports state every = 30s
[plugin:proc:/proc/net/stat/nf_conntrack]
# filename to monitor = /host/proc/net/stat/nf_conntrack
# netfilter new connections = no
# netfilter connection changes = no
# netfilter connection expectations = no
# netfilter connection searches = no
# netfilter errors = no
# netfilter connections = yes
[plugin:proc:/proc/sys/net/netfilter/nf_conntrack_max]
# filename to monitor = /host/proc/sys/net/netfilter/nf_conntrack_max
# read every seconds = 10
[plugin:proc:/proc/sys/net/netfilter/nf_conntrack_count]
# filename to monitor = /host/proc/sys/net/netfilter/nf_conntrack_count
[plugin:proc:/proc/net/stat/synproxy]
# SYNPROXY cookies = auto
# SYNPROXY SYN received = auto
# SYNPROXY connections reopened = auto
# filename to monitor = /host/proc/net/stat/synproxy
[plugin:proc:/proc/diskstats]
# enable new disks detected at runtime = yes
# performance metrics for physical disks = auto
# performance metrics for virtual disks = auto
# performance metrics for partitions = no
# bandwidth for all disks = auto
# operations for all disks = auto
# merged operations for all disks = auto
# i/o time for all disks = auto
# queued operations for all disks = auto
# utilization percentage for all disks = auto
# extended operations for all disks = auto
# backlog for all disks = auto
# bcache for all disks = auto
# bcache priority stats update every = off
# remove charts of removed disks = yes
# path to get block device = /host/sys/block/%s
# path to get block device bcache = /host/sys/block/%s/bcache
# path to get virtual block device = /host/sys/devices/virtual/block/%s
# path to get block device infos = /host/sys/dev/block/%lu:%lu/%s
# path to device mapper = /host/dev/mapper
# path to /dev/disk = /host/dev/disk
# path to /sys/block = /host/sys/block
# path to /dev/disk/by-label = /host/dev/disk/by-label
# path to /dev/disk/by-id = /host/dev/disk/by-id
# path to /dev/vx/dsk = /host/dev/vx/dsk
# name disks by id = no
# preferred disk ids = *
# exclude disks = loop* ram*
# filename to monitor = /host/proc/diskstats
# performance metrics for disks with major 253 = yes
[plugin:proc:/proc/mdstat]
# faulty devices = yes
# nonredundant arrays availability = yes
# mismatch count = auto
# disk stats = yes
# operation status = yes
# make charts obsolete = yes
# filename to monitor = /host/proc/mdstat
# mismatch_cnt filename to monitor = /host/sys/block/%s/md/mismatch_cnt
[plugin:proc:/proc/net/rpc/nfsd]
# filename to monitor = /host/proc/net/rpc/nfsd
[plugin:proc:/proc/net/rpc/nfs]
# filename to monitor = /host/proc/net/rpc/nfs
[plugin:proc:/proc/spl/kstat/zfs/arcstats]
# filename to monitor = /host/proc/spl/kstat/zfs/arcstats
[plugin:proc:/sys/fs/btrfs]
# path to monitor = /host/sys/fs/btrfs
# check for btrfs changes every = 1m
# physical disks allocation = auto
# data allocation = auto
# metadata allocation = auto
# system allocation = auto
# commit stats = auto
# error stats = auto
[plugin:proc:ipc]
# message queues = yes
# semaphore totals = yes
# shared memory totals = yes
# msg filename to monitor = /host/proc/sysvipc/msg
# shm filename to monitor = /host/proc/sysvipc/shm
# max dimensions in memory allowed = 50
[plugin:proc:/sys/class/power_supply]
# battery capacity = yes
# battery power = yes
# battery charge = no
# battery energy = no
# power supply voltage = no
# keep files open = auto
# directory to monitor = /host/sys/class/power_supply
[plugin:proc:/sys/class/drm]
# directory to monitor = /host/sys/class/drm
[plugin:systemd-units]
# update every = 10s
# command options =

View File

@@ -3,24 +3,22 @@ services:
# See sample https://github.com/outline/outline/blob/main/.env.sample
outline_app:
image: outlinewiki/outline:0.83.0
image: outlinewiki/outline:1.1.0
container_name: outline_app
restart: unless-stopped
depends_on:
- outline_postgres
- outline_redis
ports:
- "127.0.0.1:{{ outline_port }}:3000"
networks:
- "outline_network"
- "{{ web_proxy_network }}"
- "web_proxy_network"
environment:
NODE_ENV: 'production'
URL: 'https://outline.vakhrushev.me'
FORCE_HTTPS: 'true'
SECRET_KEY: '{{ outline_secret_key }}'
UTILS_SECRET: '{{ outline_utils_secret }}'
DATABASE_URL: 'postgres://{{ outline_postgres_user }}:{{ outline_postgres_password }}@outline_postgres:5432/{{ outline_postgres_database }}'
DATABASE_URL: 'postgres://{{ outline_postgres_user }}:{{ outline_postgres_password }}@outline_postgres:5432/{{ outline_postgres_database }}' # yamllint disable-line rule:line-length
PGSSLMODE: 'disable'
REDIS_URL: 'redis://outline_redis:6379'
@@ -54,28 +52,37 @@ services:
SMTP_SECURE: 'false'
outline_redis:
image: valkey/valkey:8.1.1-alpine
image: valkey/valkey:9.0-alpine
container_name: outline_redis
restart: unless-stopped
networks:
- "outline_network"
- "monitoring_network"
outline_postgres:
image: postgres:16.3-bookworm
container_name: outline_postgres
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: unless-stopped
volumes:
- {{ postgres_data_dir }}:/var/lib/postgresql/data
networks:
- "outline_network"
- "/etc/passwd:/etc/passwd:ro"
- "{{ postgres_data_dir }}:/var/lib/postgresql/data"
environment:
POSTGRES_USER: '{{ outline_postgres_user }}'
POSTGRES_PASSWORD: '{{ outline_postgres_password }}'
POSTGRES_DB: '{{ outline_postgres_database }}'
networks:
- "outline_network"
- "monitoring_network"
healthcheck:
test: ["CMD", "pg_isready", "--username={{ outline_postgres_user }}", "--dbname={{ outline_postgres_database }}"]
interval: 10s
start_period: 30s
networks:
outline_network:
driver: bridge
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -0,0 +1,12 @@
services:
rssbridge_app:
image: rssbridge/rss-bridge:2025-08-05
container_name: rssbridge_app
restart: unless-stopped
networks:
- "web_proxy_network"
networks:
web_proxy_network:
external: true

View File

@@ -0,0 +1,44 @@
$ANSIBLE_VAULT;1.1;AES256
33396537353265633634336630353330653337623861373731613734663938633837613437366537
3439383366633266623463366530626662346338393165630a663539313066663061353635666366
61393437393131333166626165306563366661353338363138633239666566313330363331666537
3763356535396334380a386362383436363732353234333033613133383264643934306432313335
34646164323664636532663835306230386633316539373564383163346663376666633564326134
30666135626637343963383766383836653135633739636261353666303666633566346562643962
63376165636434343066306539653637343736323437653465656436323533636237643333326438
35626239323530643066363533323039393237333338316135313838643464306161646635313062
36386565626435373333393566393831366538363864313737306565343162316536353539333864
63376264643566613266373665666363366662643262616634333132386535383731396462633430
32343738343039616139343833366661303430383766376139636434616565356161396433643035
37363165383935373937346464343738643430333764336264373931616332393964346566636638
39303434343461326464623363323937396663376335316237373166306134636432376435663033
34346436623435626363636237373965633139343661623135633764303862353465306235666563
66653764666635636462636434663264646665383236343166643133613966366334653030653262
38326437313939616332636638323033346139343732653933356239306132613665376163646164
30316663643666633334653133613764396165646533636534613931663138666366316235396466
61313964396264626339306135376635633133366433303033633363396132303938363638346333
66326466326134313535393831343262363862663065323135643630316431336531373833316363
64376338653366353031333836643137333736363534363164306331313337353663653961623665
64626562366637336637353433303261303964633236356162363139396339396136393237643935
34316266326561663834353762343766363933313463313263393063343562613933393361653861
38363635323231666438366536626435373365323733663139666534636564623666356436346539
63326436386436356636633637373738343032353664323736653939346234643165313461643833
35666439613136396264313033336539313537613238393262306365656238396464373936616538
64316365616464386638313331653030346330393665353539393834346135643434363736323135
37663433326439356663633162616435313061353662373766633731636439636266666466613363
39343930386534376330663230623832643933336235636166626534366664366562356165373764
63343432323864366162376263656565646661633536666336643030363039616666343063386165
37343238303034313832393538313632396261316232376635633732656663396631323261363433
38373738363833323934353739643538376237316535623035383965613965636337646537326537
64663837643632666334393634323264613139353332306263613165383733386662366333316139
63373839346265366166333331353231663763306163323063613138323835313831303666306561
39316666343761303464333535336361333462623363633333383363303134336139356436666165
62616364373030613837353939363636653537373965613531636130383266643637333233316137
39353866366239643265366162663031346439663234363935353138323739393337313835313062
33373263326565383735366364316461323930336437623834356132346633636364313732383661
66346634613762613037386238656334616430633037343066623463313035646339313638653137
65643166316664626236633332326136303235623934306462643636373437373630346435633835
66346364393236393563623032306631396561623263653236393939313333373635303365316638
66373037333565323733656331636337336665363038353635383531386366633632363031623430
31356461663438653736316464363231303938653932613561633139316361633461626361383132
396436303634613135383839396566393135

View File

@@ -0,0 +1,24 @@
services:
transcriber_app:
# noinspection ComposeUnknownValues
image: "{{ registry_transcriber_image }}"
container_name: transcriber_app
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: unless-stopped
volumes:
- "{{ config_file }}:/config/config.toml:ro"
- "{{ data_dir }}:/data"
networks:
- "web_proxy_network"
- "monitoring_network"
environment:
- "USER_UID={{ user_create_result.uid }}"
- "USER_GID={{ user_create_result.group }}"
command: ./transcriber --config=/config/config.toml
networks:
web_proxy_network:
external: true
monitoring_network:
external: true

10
files/wakapi/backup.sh.j2 Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "{{ app_name }}: backup data with gobackups"
(cd "{{ base_dir }}" && gobackup perform --config "{{ gobackup_config }}")
echo "{{ app_name }}: done."

View File

@@ -0,0 +1,36 @@
# See versions: https://github.com/gramps-project/gramps-web/pkgs/container/grampsweb
services:
wakapi_app:
image: ghcr.io/muety/wakapi:2.16.1
container_name: wakapi_app
restart: unless-stopped
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
networks:
- "web_proxy_network"
volumes:
- "{{ data_dir }}:/data"
environment:
WAKAPI_PUBLIC_URL: "https://wakapi.vakhrushev.me"
WAKAPI_PASSWORD_SALT: "{{ wakapi_password_salt }}"
WAKAPI_ALLOW_SIGNUP: "false"
WAKAPI_DISABLE_FRONTPAGE: "true"
WAKAPI_COOKIE_MAX_AGE: 31536000
# OIDC
# WAKAPI_OIDC_PROVIDER_NAME: "authelia"
# WAKAPI_OIDC_PROVIDER_CLIENT_ID: "{{ wakapi_oidc_client_id }}"
# WAKAPI_OIDC_PROVIDER_CLIENT_SECRET: "{{ wakapi_oidc_client_secret }}"
# WAKAPI_OIDC_PROVIDER_ENDPOINT: "https://auth.vakhrushev.me/.well-known/openid-configuration"
# Mail
WAKAPI_MAIL_SENDER: "Wakapi <wakapi@vakhrushev.me>"
WAKAPI_MAIL_PROVIDER: "smtp"
WAKAPI_MAIL_SMTP_HOST: "{{ postbox_host }}"
WAKAPI_MAIL_SMTP_PORT: "{{ postbox_port }}"
WAKAPI_MAIL_SMTP_USER: "{{ postbox_user }}"
WAKAPI_MAIL_SMTP_PASS: "{{ postbox_pass }}"
WAKAPI_MAIL_SMTP_TLS: "false"
networks:
web_proxy_network:
external: true

View File

@@ -0,0 +1,16 @@
# https://gobackup.github.io/configuration
models:
gramps:
compress_with:
type: 'tgz'
storages:
local:
type: 'local'
path: '{{ backups_dir }}'
keep: 3
databases:
wakapi:
type: sqlite
path: "{{ (data_dir, 'wakapi.db') | path_join }}"

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "{{ app_name }}: backup data with gobackups"
(cd "{{ base_dir }}" && gobackup perform --config "{{ gobackup_config }}")
echo "{{ app_name }}: done."

View File

@@ -0,0 +1,109 @@
x-common-env: &cenv
MEILI_URL: http://wanderer_search:7700
MEILI_MASTER_KEY: "{{ wanderer_melli_master_key }}"
services:
wanderer_search:
container_name: wanderer_search
image: getmeili/meilisearch:v1.20.0
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
environment:
<<: *cenv
MEILI_NO_ANALYTICS: "true"
ports:
- "127.0.0.1:7700:7700"
networks:
- wanderer_network
volumes:
- ./data/ms_data:/meili_data
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:7700/health || exit 1
interval: 15s
retries: 10
start_period: 20s
timeout: 10s
wanderer_db:
container_name: wanderer_db
image: "flomp/wanderer-db:{{ wanderer_version }}"
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
depends_on:
wanderer_search:
condition: service_healthy
environment:
<<: *cenv
POCKETBASE_ENCRYPTION_KEY: "{{ wanderer_pocketbase_enc_key }}"
ORIGIN: "{{ wanderer_origin }}"
ports:
- "127.0.0.1:8090:8090"
networks:
- wanderer_network
- web_proxy_network
restart: unless-stopped
volumes:
- ./data/pb_data:/pb_data
healthcheck:
test: wget --spider -q http://localhost:8090/health || exit 1
interval: 15s
retries: 10
start_period: 20s
timeout: 10s
wanderer_web:
container_name: wanderer_web
image: "flomp/wanderer-web:{{ wanderer_version }}"
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
depends_on:
wanderer_search:
condition: service_healthy
wanderer_db:
condition: service_healthy
environment:
<<: *cenv
ORIGIN: "{{ wanderer_origin }}"
BODY_SIZE_LIMIT: Infinity
PUBLIC_POCKETBASE_URL: http://wanderer_db:8090
PUBLIC_DISABLE_SIGNUP: "true"
UPLOAD_FOLDER: /app/uploads
UPLOAD_USER:
UPLOAD_PASSWORD:
PUBLIC_VALHALLA_URL: https://valhalla1.openstreetmap.de
PUBLIC_NOMINATIM_URL: https://nominatim.openstreetmap.org
volumes:
- ./data/uploads:/app/uploads
# - ./data/about.md:/app/build/client/md/about.md
ports:
- "127.0.0.1:3000:3000"
networks:
- wanderer_network
- web_proxy_network
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:3000/ || exit 1
interval: 15s
retries: 10
start_period: 20s
timeout: 10s
# valhalla:
# image: ghcr.io/gis-ops/docker-valhalla/valhalla:latest
# ports:
# - "8002:8002"
# volumes:
# - ./data/valhalla:/custom_files
# environment:
# - tile_urls=https://download.geofabrik.de/europe/germany/bayern/oberbayern-latest.osm.pbf
# - use_tiles_ignore_pbf=True
# - force_rebuild=False
# - force_rebuild_elevation=False
# - build_elevation=True
# - build_admins=True
# - build_time_zones=True
networks:
wanderer_network:
driver: bridge
web_proxy_network:
external: true

View File

@@ -0,0 +1,32 @@
# https://gobackup.github.io/configuration
models:
application:
compress_with:
type: 'tgz'
storages:
local:
type: 'local'
path: '{{ backups_dir }}'
keep: 3
# databases:
# users:
# type: sqlite
# path: "{{ (data_dir, 'gramps_users/users.sqlite') | path_join }}"
# search_index:
# type: sqlite
# path: "{{ (data_dir, 'gramps_index/search_index.db') | path_join }}"
# sqlite:
# type: sqlite
# path: "{{ (data_dir, 'gramps_db/59a0f3d6-1c3d-4410-8c1d-1c9c6689659f/sqlite.db') | path_join }}"
# undo:
# type: sqlite
# path: "{{ (data_dir, 'gramps_db/59a0f3d6-1c3d-4410-8c1d-1c9c6689659f/undo.db') | path_join }}"
archive:
includes:
- "{{ data_dir }}"
# excludes:
# - "{{ (data_dir, 'gramps_cache') | path_join }}"
# - "{{ (data_dir, 'gramps_thumb_cache') | path_join }}"
# - "{{ (data_dir, 'gramps_tmp') | path_join }}"

25
lefthook.yml Normal file
View File

@@ -0,0 +1,25 @@
# Refer for explanation to following link:
# https://lefthook.dev/configuration/
glob_matcher: doublestar
templates:
av-hooks-dir: "/home/av/projects/private/git-hooks"
pre-commit:
jobs:
- name: "gitleaks"
run: "gitleaks git --staged"
- name: "check secret files"
run: "python3 {av-hooks-dir}/pre-commit/check-secrets-encrypted-with-ansible-vault.py"
- name: "format python"
glob: "**/*.py"
run: "black --quiet {staged_files}"
stage_fixed: true
- name: "mypy"
glob: "**/*.py"
run: "mypy {staged_files}"

View File

@@ -0,0 +1,48 @@
---
- name: 'Configure netdata'
ansible.builtin.import_playbook: playbook-netdata.yml
#
- name: 'Configure dozzle'
ansible.builtin.import_playbook: playbook-dozzle.yml
- name: 'Configure gitea'
ansible.builtin.import_playbook: playbook-gitea.yml
- name: 'Configure gramps'
ansible.builtin.import_playbook: playbook-gramps.yml
- name: 'Configure memos'
ansible.builtin.import_playbook: playbook-memos.yml
- name: 'Configure miniflux'
ansible.builtin.import_playbook: playbook-miniflux.yml
- name: 'Configure outline'
ansible.builtin.import_playbook: playbook-outline.yml
- name: 'Configure rssbridge'
ansible.builtin.import_playbook: playbook-rssbridge.yml
- name: 'Configure wakapi'
ansible.builtin.import_playbook: playbook-wakapi.yml
- name: 'Configure wanderer'
ansible.builtin.import_playbook: playbook-wanderer.yml
#
- name: 'Configure homepage'
ansible.builtin.import_playbook: playbook-homepage.yml
- name: 'Configure transcriber'
ansible.builtin.import_playbook: playbook-transcriber.yml
#
- name: 'Configure authelia'
ansible.builtin.import_playbook: playbook-authelia.yml
- name: 'Configure caddy proxy'
ansible.builtin.import_playbook: playbook-caddyproxy.yml

12
playbook-all-setup.yml Normal file
View File

@@ -0,0 +1,12 @@
---
- name: 'Configure system'
ansible.builtin.import_playbook: playbook-system.yml
- name: 'Configure docker'
ansible.builtin.import_playbook: playbook-docker.yml
- name: 'Configure eget applications'
ansible.builtin.import_playbook: playbook-eget.yml
- name: 'Configure backups'
ansible.builtin.import_playbook: playbook-backups.yml

View File

@@ -3,14 +3,19 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
- files/authelia/secrets.yml
vars:
app_name: "authelia"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1011
app_owner_gid: 1012
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
tasks:
- name: "Create user and environment"
@@ -18,6 +23,8 @@
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
@@ -28,22 +35,46 @@
group: "{{ app_user }}"
mode: "0700"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ backups_dir }}"
- name: "Copy configuration files"
- name: "Copy users file"
ansible.builtin.copy:
src: "files/{{ app_name }}/{{ item }}"
dest: "{{ (config_dir, item) | path_join }}"
src: "files/{{ app_name }}/users.secrets.yml"
dest: "{{ (config_dir, 'users.yml') | path_join }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
loop:
- "configuration.yml"
- "users.yml"
- name: "Copy configuration file"
ansible.builtin.template:
src: "files/{{ app_name }}/configuration.template.yml"
dest: "{{ (config_dir, 'configuration.yml') | path_join }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
- name: "Copy gobackup config"
ansible.builtin.template:
src: "files/{{ app_name }}/gobackup.template.yml"
dest: "{{ gobackup_config }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "files/{{ app_name }}/backup.template.sh"
dest: "{{ (base_dir, 'backup.sh') | path_join }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -54,8 +85,12 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app
- name: "Restart application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "restarted"
tags:
- run-app

View File

@@ -3,14 +3,40 @@
hosts: all
vars_files:
- vars/vars.yml
- vars/secrets.yml
vars:
backup_config_dir: "/etc/backup"
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
tasks:
- name: "Create backup config directory"
ansible.builtin.file:
path: "{{ backup_config_dir }}"
state: "directory"
owner: root
group: root
mode: "0755"
- name: "Create backup config file"
ansible.builtin.template:
src: "files/backups/config.template.toml"
dest: "{{ backup_config_file }}"
owner: root
group: root
mode: "0640"
- name: "Allow user to run the backup script without a password"
ansible.builtin.lineinfile:
path: /etc/sudoers
state: present
line: "{{ primary_user }} ALL=(ALL) NOPASSWD: {{ backup_all_script }}"
validate: /usr/sbin/visudo -cf %s # ВАЖНО: проверка синтаксиса перед сохранением
create: no # Файл уже должен существовать
- name: "Copy restic shell script"
ansible.builtin.template:
src: "files/backups/restic-shell.sh.j2"
@@ -20,8 +46,8 @@
mode: "0700"
- name: "Copy backup all script"
ansible.builtin.template:
src: "files/backups/backup-all.sh.j2"
ansible.builtin.copy:
src: "files/backups/backup-all.py"
dest: "{{ backup_all_script }}"
owner: root
group: root

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
app_name: "caddyproxy"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1010
app_owner_gid: 1011
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
@@ -23,8 +24,9 @@
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups:
- "docker"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
ansible.builtin.file:
@@ -34,6 +36,7 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ caddy_file_dir }}"
@@ -59,9 +62,20 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app
- name: "Reload caddy"
community.docker.docker_compose_v2_exec:
project_src: '{{ base_dir }}'
service: "{{ service_name }}"
command: caddy reload --config /etc/caddy/Caddyfile
# - name: "Reload caddy"
# community.docker.docker_compose_v2_exec:
# project_src: '{{ base_dir }}'
# service: "{{ service_name }}"
# command: caddy reload --config /etc/caddy/Caddyfile
# tags:
# - run-app
- name: "Restart application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "restarted"
tags:
- run-app

View File

@@ -3,13 +3,12 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
tasks:
- name: "Install python docker lib from pip"
ansible.builtin.pip:
name: docker
# - name: "Install python docker lib from pip"
# ansible.builtin.pip:
# name: docker
- name: "Install docker"
ansible.builtin.import_role:
@@ -21,13 +20,21 @@
- "docker-{{ docker_edition }}-cli"
- "docker-{{ docker_edition }}-rootless-extras"
docker_users:
- major
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- "{{ primary_user }}"
- name: Create a network for web proxy
community.docker.docker_network:
name: "{{ web_proxy_network }}"
name: "web_proxy_network"
driver: "bridge"
- name: Create a network for monitoring
community.docker.docker_network:
name: "monitoring_network"
driver: "bridge"
- name: "Schedule docker image prune"
ansible.builtin.cron:
name: "docker image prune"
minute: "0"
hour: "3"
job: "/usr/bin/docker image prune -af"

49
playbook-dozzle.yml Normal file
View File

@@ -0,0 +1,49 @@
---
- name: "Configure dozzle application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "dozzle"
app_user: "{{ app_name }}"
app_owner_uid: 1016
app_owner_gid: 1017
base_dir: "{{ (application_dir, app_name) | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,8 +3,7 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
# See: https://github.com/zyedidia/eget/releases
@@ -22,25 +21,42 @@
- name: "Install rclone"
ansible.builtin.command:
cmd: "{{ eget_bin_path }} rclone/rclone --quiet --upgrade-only --to {{ eget_install_dir }} --asset zip --tag v1.69.2"
changed_when: false
- name: "Install btop"
ansible.builtin.command:
cmd: "{{ eget_bin_path }} aristocratos/btop --quiet --upgrade-only --to {{ eget_install_dir }} --tag v1.4.2"
cmd: >
{{ eget_bin_path }} rclone/rclone --quiet --upgrade-only --to {{ eget_install_dir }} --asset zip
--tag v1.72.0
changed_when: false
- name: "Install restic"
ansible.builtin.command:
cmd: "{{ eget_bin_path }} restic/restic --quiet --upgrade-only --to {{ eget_install_dir }} --tag v0.18.0"
cmd: >
{{ eget_bin_path }} restic/restic --quiet --upgrade-only --to {{ eget_install_dir }}
--tag v0.18.1
changed_when: false
- name: "Install btop"
ansible.builtin.command:
cmd: >
{{ eget_bin_path }} aristocratos/btop --quiet --upgrade-only --to {{ eget_install_dir }}
--tag v1.4.5
changed_when: false
- name: "Install gobackup"
ansible.builtin.command:
cmd: "{{ eget_bin_path }} gobackup/gobackup --quiet --upgrade-only --to {{ eget_install_dir }} --tag v2.14.0"
cmd: >
{{ eget_bin_path }} gobackup/gobackup --quiet --upgrade-only --to {{ eget_install_dir }}
--tag v2.17.0
changed_when: false
- name: "Install task"
ansible.builtin.command:
cmd: "{{ eget_bin_path }} go-task/task --quiet --upgrade-only --to {{ eget_install_dir }} --asset tar.gz --tag v3.43.3"
cmd: >
{{ eget_bin_path }} go-task/task --quiet --upgrade-only --to {{ eget_install_dir }} --asset tar.gz
--tag v3.45.5
changed_when: false
- name: 'Install dust'
ansible.builtin.command:
cmd: >
{{ bin_prefix }}/eget bootandy/dust --quiet --upgrade-only --to {{ bin_prefix }} --asset gnu
--tag v1.2.3
changed_when: false

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
app_name: "gitea"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1005
app_owner_gid: 1006
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
@@ -19,10 +20,9 @@
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups:
- "docker"
owner_ssh_keys:
- "{{ lookup('file', 'files/av_id_rsa.pub') }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
ansible.builtin.file:
@@ -32,6 +32,7 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
@@ -56,3 +57,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,14 +3,17 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
app_name: "gramps"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1009
app_owner_gid: 1010
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
media_dir: "{{ (base_dir, 'media') | path_join }}"
cache_dir: "{{ (base_dir, 'cache') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
@@ -20,10 +23,9 @@
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups:
- "docker"
owner_ssh_keys:
- "{{ lookup('file', 'files/av_id_rsa.pub') }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
@@ -33,12 +35,15 @@
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ media_dir }}"
- "{{ cache_dir }}"
- "{{ backups_dir }}"
- name: "Copy gobackup config"
ansible.builtin.template:
src: "./files/{{ app_name }}/gobackup.yml.j2"
src: "./files/{{ app_name }}/gobackup.template.yml"
dest: "{{ gobackup_config }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -46,15 +51,36 @@
- name: "Copy backup script"
ansible.builtin.template:
src: "files/{{ app_name }}/backup.sh.j2"
src: "files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Create backup targets file"
ansible.builtin.lineinfile:
path: "{{ base_dir }}/backup-targets"
line: "{{ item }}"
create: true
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ data_dir }}"
- "{{ media_dir }}"
- "{{ backups_dir }}"
- name: "Copy rename script"
ansible.builtin.copy:
src: "files/{{ app_name }}/gramps_rename.py"
dest: "{{ base_dir }}/gramps_rename.py"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -65,3 +91,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -0,0 +1,20 @@
---
- name: "Upload local homepage images to registry"
hosts: all
gather_facts: false
vars_files:
- vars/secrets.yml
- vars/homepage.yml
- vars/homepage.images.yml
tasks:
- name: "Push web service image to remote registry"
community.docker.docker_image:
state: present
source: local
name: "{{ homepage_nginx_image }}"
repository: "{{ registry_homepage_nginx_image }}"
push: true
delegate_to: 127.0.0.1

View File

@@ -1,60 +1,39 @@
---
# Play 1: Setup environment for the application
- name: "Setup environment for homepage application"
- name: "Setup and deploy homepage service"
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
- vars/homepage.yml
tags:
- setup
- vars/homepage.images.yml
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups:
- "docker"
owner_ssh_keys:
- "{{ lookup('file', 'files/av_id_rsa.pub') }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
# Play 2: Deploy the application
- name: "Deploy homepage application"
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/homepage.yml
tags:
- deploy
tasks:
- name: "Check is web service image passed"
ansible.builtin.assert:
that:
- "homepage_web_image is defined"
fail_msg: 'You must pass variable "homepage_web_image"'
- name: "Create full image name with container registry"
ansible.builtin.set_fact:
registry_homepage_web_image: "{{ (docker_registry_prefix, homepage_web_image) | path_join }}"
- name: "Push web service image to remote registry"
community.docker.docker_image:
state: present
source: local
name: "{{ homepage_web_image }}"
repository: "{{ registry_homepage_web_image }}"
push: true
delegate_to: 127.0.0.1
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -65,3 +44,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

82
playbook-memos.yml Normal file
View File

@@ -0,0 +1,82 @@
---
- name: "Configure memos application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "memos"
app_user: "{{ app_name }}"
app_owner_uid: 1019
app_owner_gid: 1020
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
- name: "Copy gobackup config"
ansible.builtin.template:
src: "./files/{{ app_name }}/gobackup.yml.j2"
dest: "{{ gobackup_config }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "files/{{ app_name }}/backup.sh.j2"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Create backup targets file"
ansible.builtin.lineinfile:
path: "{{ base_dir }}/backup-targets"
line: "{{ item }}"
create: true
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ data_dir }}"
- "{{ backups_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

81
playbook-miniflux.yml Normal file
View File

@@ -0,0 +1,81 @@
---
- name: "Configure miniflux application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "miniflux"
app_user: "{{ app_name }}"
app_owner_uid: 1013
app_owner_gid: 1014
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
secrets_dir: "{{ (base_dir, 'secrets') | path_join }}"
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ secrets_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}"
- name: "Copy secrets"
ansible.builtin.import_role:
name: secrets
vars:
secrets_dest: "{{ secrets_dir }}"
secrets_user: "{{ app_user }}"
secrets_group: "{{ app_user }}"
secrets_vars:
- "miniflux_database_url"
- "miniflux_admin_user"
- "miniflux_admin_password"
- "miniflux_oidc_client_id"
- "miniflux_oidc_client_secret"
- "miniflux_postgres_password"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "./files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
recreate: "always"
remove_orphans: true
tags:
- run-app

View File

@@ -3,15 +3,78 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
netdata_version: "v2.4.0"
netdata_image: "netdata/netdata:{{ netdata_version }}"
netdata_exposed_port: "{{ netdata_port }}"
app_name: "netdata"
app_user: "{{ app_name }}"
app_owner_uid: 1012
app_owner_gid: 1013
base_dir: "{{ (application_dir, app_name) | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ config_go_d_dir }}"
- name: "Copy netdata config file"
ansible.builtin.template:
src: "files/{{ app_name }}/netdata.template.conf"
dest: "{{ config_dir }}/netdata.conf"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Find all go.d plugin config files"
ansible.builtin.find:
paths: "files/{{ app_name }}/go.d"
file_type: file
delegate_to: localhost
register: go_d_source_files
- name: "Template all go.d plugin config files"
ansible.builtin.template:
src: "{{ item.path }}"
dest: "{{ config_go_d_dir }}/{{ item.path | basename }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
loop: "{{ go_d_source_files.files }}"
- name: "Find existing go.d config files on server"
ansible.builtin.find:
paths: "{{ config_go_d_dir }}"
file_type: file
register: go_d_existing_files
- name: "Remove go.d config files that don't exist in source"
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ go_d_existing_files.files }}"
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
- name: "Grab docker group id."
ansible.builtin.shell:
cmd: |
@@ -21,31 +84,25 @@
register: netdata_docker_group_output
changed_when: netdata_docker_group_output.rc != 0
- name: "Create NetData container from {{ netdata_image }}"
community.docker.docker_container:
name: netdata
image: "{{ netdata_image }}"
image_name_mismatch: "recreate"
restart_policy: "unless-stopped"
published_ports:
- "127.0.0.1:{{ netdata_exposed_port }}:19999"
networks:
- name: "{{ web_proxy_network }}"
volumes:
- "/:/host/root:ro,rslave"
- "/etc/group:/host/etc/group:ro"
- "/etc/localtime:/etc/localtime:ro"
- "/etc/os-release:/host/etc/os-release:ro"
- "/etc/passwd:/host/etc/passwd:ro"
- "/proc:/host/proc:ro"
- "/run/dbus:/run/dbus:ro"
- "/sys:/host/sys:ro"
- "/var/log:/host/var/log:ro"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
capabilities:
- "SYS_PTRACE"
- "SYS_ADMIN"
security_opts:
- "apparmor:unconfined"
env:
PGID: "{{ netdata_docker_group_output.stdout | default(999) }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app
- name: "Restart application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "restarted"
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
app_name: "outline"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1007
app_owner_gid: 1008
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}"
@@ -20,10 +21,9 @@
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups:
- "docker"
owner_ssh_keys:
- "{{ lookup('file', 'files/av_id_rsa.pub') }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal directories"
ansible.builtin.file:
@@ -33,11 +33,14 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -45,7 +48,7 @@
- name: "Copy backup script"
ansible.builtin.template:
src: "./files/{{ app_name }}/backup.sh.j2"
src: "./files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -56,3 +59,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,8 +3,7 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
user_name: "<put-name-here>"

49
playbook-rssbridge.yml Normal file
View File

@@ -0,0 +1,49 @@
---
- name: "Configure rssbridge application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "rssbridge"
app_user: "{{ app_name }}"
app_owner_uid: 1014
app_owner_gid: 1015
base_dir: "{{ (application_dir, app_name) | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,8 +3,7 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
vars:
apt_packages:
@@ -25,21 +24,13 @@
name: "{{ apt_packages }}"
update_cache: true
- name: "Configure timezone"
ansible.builtin.import_role:
name: yatesr.timezone
vars:
timezone: UTC
tags:
- skip_ansible_lint
- name: "Configure security settings"
ansible.builtin.import_role:
name: geerlingguy.security
vars:
security_ssh_permit_root_login: "yes"
security_autoupdate_enabled: "no"
security_fail2ban_enabled: "yes"
security_fail2ban_enabled: true
- name: "Copy keep files script"
ansible.builtin.copy:
@@ -48,3 +39,20 @@
owner: root
group: root
mode: "0755"
- name: 'Create directory for mount'
ansible.builtin.file:
path: '/mnt/applications'
state: 'directory'
mode: '0755'
tags:
- mount-storage
- name: 'Mount external storages'
ansible.posix.mount:
path: '/mnt/applications'
src: 'UUID=3942bffd-8328-4536-8e88-07926fb17d17'
fstype: ext4
state: mounted
tags:
- mount-storage

View File

@@ -0,0 +1,20 @@
---
- name: "Upload local transcriber images to registry"
hosts: all
gather_facts: false
vars_files:
- vars/secrets.yml
- vars/transcriber.yml
- vars/transcriber.images.yml
tasks:
- name: "Push web service image to remote registry"
community.docker.docker_image:
state: present
source: local
name: "{{ transcriber_image }}"
repository: "{{ registry_transcriber_image }}"
push: true
delegate_to: 127.0.0.1

59
playbook-transcriber.yml Normal file
View File

@@ -0,0 +1,59 @@
---
- name: "Deploy transcriber application"
hosts: all
vars_files:
- vars/secrets.yml
- vars/transcriber.yml
- vars/transcriber.images.yml
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ config_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
- name: "Copy configuration files (templates)"
ansible.builtin.copy:
src: "files/{{ app_name }}/config.secrets.toml"
dest: "{{ config_file }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,8 +3,7 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/vars.yml
- vars/secrets.yml
tasks:
- name: Perform an upgrade of packages
@@ -25,3 +24,18 @@
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: true
- name: Check if Docker is available
ansible.builtin.stat:
path: /usr/bin/docker
register: docker_exists
- name: Clean up unnecessary Docker data
ansible.builtin.command:
cmd: docker system prune --all --force
register: docker_prune_result
when: docker_exists.stat.exists
failed_when:
- docker_prune_result.rc is defined
- docker_prune_result.rc != 0
changed_when: "'Total reclaimed space' in docker_prune_result.stdout"

70
playbook-wakapi.yml Normal file
View File

@@ -0,0 +1,70 @@
---
- name: "Configure wakapi application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "wakapi"
app_user: "{{ app_name }}"
app_owner_uid: 1015
app_owner_gid: 1016
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
- name: "Copy gobackup config"
ansible.builtin.template:
src: "./files/{{ app_name }}/gobackup.yml.j2"
dest: "{{ gobackup_config }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "files/{{ app_name }}/backup.sh.j2"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

92
playbook-wanderer.yml Normal file
View File

@@ -0,0 +1,92 @@
---
- name: "Configure gramps application"
hosts: all
vars_files:
- vars/secrets.yml
vars:
app_name: "wanderer"
app_user: "{{ app_name }}"
app_owner_uid: 1018
app_owner_gid: 1019
base_dir: "{{ (application_dir, app_name) | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
wanderer_version: "v0.18.3"
wanderer_origin: "https://wanderer.vakhrushev.me"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ (data_dir, 'pb_data') | path_join }}"
- "{{ (data_dir, 'uploads') | path_join }}"
- "{{ (data_dir, 'ms_data') | path_join }}"
- "{{ backups_dir }}"
- name: "Copy gobackup config"
ansible.builtin.template:
src: "./files/{{ app_name }}/gobackup.template.yml"
dest: "{{ gobackup_config }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
# - name: "Copy backup script"
# ansible.builtin.template:
# src: "files/{{ app_name }}/backup.template.sh"
# dest: "{{ base_dir }}/backup.sh"
# owner: "{{ app_user }}"
# group: "{{ app_user }}"
# mode: "0750"
- name: "Disable backup script"
ansible.builtin.file:
dest: "{{ base_dir }}/backup.sh"
state: absent
- name: "Create backup targets file"
ansible.builtin.lineinfile:
path: "{{ base_dir }}/backup-targets"
line: "{{ item }}"
create: true
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ data_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,7 @@
version: 1.2.2
- src: geerlingguy.security
version: 2.4.0
version: 3.0.0
- src: geerlingguy.docker
version: 7.4.3
version: 7.4.7

View File

@@ -1,7 +1,7 @@
---
# defaults file for eget
eget_version: "1.3.4"
eget_download_url: "https://github.com/zyedidia/eget/releases/download/v{{ eget_version }}/eget-{{ eget_version }}-linux_amd64.tar.gz"
eget_download_url: "https://github.com/zyedidia/eget/releases/download/v{{ eget_version }}/eget-{{ eget_version }}-linux_amd64.tar.gz" # yamllint disable-line rule:line-length
eget_install_path: "/usr/bin/eget"
eget_download_dest: '/tmp/{{ eget_download_url | split("/") | last }}'

View File

@@ -1,6 +1,8 @@
---
owner_name: ""
owner_uid: 0
owner_group: "{{ owner_name }}"
owner_gid: "{{ owner_uid }}"
owner_extra_groups: []
owner_ssh_keys: []
owner_env: {}

View File

@@ -4,9 +4,15 @@
msg: You must set owner name.
when: not owner_name
- name: 'Check app requirements for user "{{ owner_name }}".'
ansible.builtin.fail:
msg: You must set owner uid.
when: not owner_uid
- name: 'Create group "{{ owner_group }}".'
ansible.builtin.group:
name: "{{ owner_group }}"
gid: "{{ owner_gid }}"
state: present
- name: 'Create user "{{ owner_name }}".'
@@ -14,6 +20,7 @@
name: "{{ owner_name }}"
group: "{{ owner_group }}"
groups: "{{ owner_extra_groups }}"
uid: "{{ owner_uid }}"
shell: /bin/bash
register: user_create_result

55
roles/secrets/README.md Normal file
View File

@@ -0,0 +1,55 @@
# Secrets Role
Ansible роль для сохранения секретов на удаленной машине.
## Описание
Роль позволяет безопасно сохранять секреты на удаленной машине в виде файлов с ограниченными правами доступа. Поддерживает сохранение переменных как файлов и копирование существующих файлов.
## Параметры
### Обязательные параметры
- `secrets_dest` - директория, куда будут сохранены секреты в виде файлов
- `secrets_user` - имя пользователя, который будет владеть директорией и файлами
- `secrets_group` - группа, которая будет владеть директорией и файлами (по умолчанию равна `secrets_user`)
### Опциональные параметры
- `secrets_vars` - список переменных, из которых будут извлечены секреты (по умолчанию: `[]`)
- `secrets_files` - список файлов, которые будут скопированы "как есть" (по умолчанию: `[]`)
- `secrets_dir_mode` - права доступа для директории (по умолчанию: `0750`)
- `secrets_file_mode` - права доступа для файлов с секретами (по умолчанию: `0400`)
## Функциональность
1. Создает директорию `secrets_dest` если она не существует
2. Сохраняет каждую переменную из `secrets_vars` как файл с таким же именем
3. Копирует файлы из `secrets_files` в директорию назначения
4. Устанавливает права доступа `0400` для всех файлов с секретами
5. Назначает указанного пользователя и группу владельцами директории и файлов
## Пример использования
```yaml
- name: Save application secrets
include_role:
name: secrets
vars:
secrets_dest: /opt/myapp/secrets
secrets_user: myapp
secrets_group: myapp
secrets_vars:
- database_password
- api_key
- jwt_secret
secrets_files:
- /path/to/ssl/certificate.pem
- /path/to/ssl/private.key
```
## Безопасность
- Все файлы с секретами создаются с правами `0400` (только чтение для владельца)
- Директория создается с правами `0750` (полный доступ для владельца, чтение и выполнение для группы)
- Используется `no_log: true` для предотвращения вывода секретов в логи Ansible

View File

@@ -0,0 +1,9 @@
---
# Default variables for secrets role
secrets_dest: ""
secrets_user: ""
secrets_group: "{{ secrets_user }}"
secrets_vars: []
secrets_files: []
secrets_dir_mode: "0750"
secrets_file_mode: "0400"

View File

@@ -0,0 +1,22 @@
---
galaxy_info:
author: 'Anton Vakhrushev'
description: Ansible role for saving secrets on remote machine
license: MIT
min_ansible_version: '2.9'
platforms:
- name: Ubuntu
versions:
- all
- name: Debian
versions:
- all
- name: EL
versions:
- all
galaxy_tags:
- secrets
- security
- files
dependencies: []

View File

@@ -0,0 +1,46 @@
---
# tasks file for secrets role
- name: "Validate secrets_dest parameter"
ansible.builtin.fail:
msg: "secrets_dest is required but not defined"
when: secrets_dest is not defined or secrets_dest == ""
- name: "Validate secrets_user parameter"
ansible.builtin.fail:
msg: "secrets_user is required but not defined"
when: secrets_user is not defined or secrets_user == ""
- name: "Validate secrets_group parameter"
ansible.builtin.fail:
msg: "secrets_group is required but not defined"
when: secrets_group is not defined or secrets_group == ""
- name: "Create secrets destination directory"
ansible.builtin.file:
path: "{{ secrets_dest }}"
state: directory
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_dir_mode }}"
- name: "Save variables as secret files"
ansible.builtin.copy:
content: "{{ lookup('vars', item) }}"
dest: "{{ secrets_dest }}/{{ item }}"
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_file_mode }}"
loop: "{{ secrets_vars }}"
when: secrets_vars | length > 0
no_log: true
- name: "Copy secret files"
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ secrets_dest }}/{{ item | basename }}"
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_file_mode }}"
loop: "{{ secrets_files }}"
when: secrets_files | length > 0

View File

@@ -1,11 +1,35 @@
#!/usr/bin/env python3
"""
SMTP Password Generator for Yandex Cloud Postbox
Этот скрипт конвертирует Secret Access Key в SMTP пароль для использования
с Yandex Cloud Postbox сервисом. Скрипт реализует алгоритм AWS4 подписи
для генерации корректного SMTP пароля.
Yandex Cloud Postbox использует AWS-совместимый API, и для SMTP аутентификации
требуется специальный пароль, который генерируется из Secret Access Key
с использованием определенного алгоритма подписи.
Примеры использования:
python3 smtp-convert-secret-key-to-password.py "YourSecretAccessKey123"
Требования:
- Python 3.x
- Стандартные библиотеки: hmac, hashlib, base64, argparse, sys
Автор: Yandex.Cloud Team
Ссылка: https://yandex.cloud/ru/docs/postbox/operations/send-email
"""
import hmac
import hashlib
import base64
import argparse
import sys
# These values are required to calculate the signature. Do not change them.
DATE = "20230926"
SERVICE = "postbox"

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
SMTP Test Email Sender for Yandex Cloud Postbox
Скрипт для отправки тестового email через Yandex Cloud Postbox SMTP сервер.
Используется для проверки корректности настроек SMTP аутентификации.
Примеры использования:
python3 smtp-send-test-email.py --login "your-login" --password "smtp-password" --to "recipient@example.com"
"""
import smtplib
import argparse
import sys
from email.message import EmailMessage
def send_test_email(login, password, to_email):
"""Отправляет тестовое email через Yandex Cloud Postbox SMTP"""
# initialize connection to our email server
smtp = smtplib.SMTP("postbox.cloud.yandex.net", port="587")
smtp.set_debuglevel(2)
smtp.ehlo() # send the extended hello to our server
smtp.starttls() # tell server we want to communicate with TLS encryption
smtp.login(login, password) # login to our email server
message = EmailMessage()
message.set_content("Hello, world! This is a test email from Yandex Cloud Postbox.")
message.add_header("From", "service@vakhrushev.me")
message.add_header("To", to_email)
message.add_header("Subject", "Test Email from Yandex Cloud Postbox")
# send our email message
smtp.send_message(message)
smtp.quit() # finally, don't forget to close the connection
print(f"Test email successfully sent to {to_email}")
def main():
parser = argparse.ArgumentParser(
description="Send a test email via Yandex Cloud Postbox SMTP server."
)
parser.add_argument(
"--login", required=True, help="SMTP login (usually your email address)"
)
parser.add_argument(
"--password",
required=True,
help="SMTP password (generated using smtp-convert-secret-key-to-password.py)",
)
parser.add_argument("--to", required=True, help="Recipient email address")
args = parser.parse_args()
try:
send_test_email(args.login, args.password, args.to)
except Exception as e:
print(f"Error sending email: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

2
vars/homepage.images.yml Normal file
View File

@@ -0,0 +1,2 @@
---
homepage_nginx_image: "homepage-nginx:f797e17-1761204003"

View File

@@ -1,7 +1,10 @@
---
app_name: "homepage"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1008
app_owner_gid: 1009
base_dir: "{{ (application_dir, app_name) | path_join }}"
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
homepage_web_image: "{{ homepage_web_image | default(omit) }}"
# Registry images
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"

View File

@@ -1,7 +0,0 @@
---
base_port: 41080
homepage_port: "{{ base_port + 3 }}"
netdata_port: "{{ base_port + 4 }}"
gitea_port: "{{ base_port + 8 }}"
outline_port: "{{ base_port + 10 }}"
gramps_port: "{{ base_port + 12 }}"

162
vars/secrets.yml Normal file
View File

@@ -0,0 +1,162 @@
$ANSIBLE_VAULT;1.1;AES256
66333736306664316131646233393639356436323832386638353237393761386631303639396432
6266616434663638306637356435393564303633613332640a663366336135373061396239653065
32343339313734346261363461633735383538613033656138383835653661633334316533613738
3730656661393466370a653863353234323739346630323534333334306432646636383664313865
39666133623032393638633239653665376336626638303334626164376663626666393439346661
32363364363066653933313135666566346138353163333639353062336331623938353131646334
32326466323030643364356530653862633332363338333633393730663536353363663165303838
66643331366231613733316139333233393636626162643861346437613062643133353035353566
38323062653135393364376265346361613163346337636634383932666137636133363864643264
39646634363037383662376166633335636365303734646465623833313766656235333835396263
63326237306337396465343065313263663466613336303934373337623364613839323931623934
35353665363339646365353139643032366164653133616562303930306264343633343439373830
66356635343363363361313633353134313837653163346537656465623832633561323833663763
32323537303931343934376165663466356466333533643237653038373866626439356334663539
36613336326633306331323763393230653236306337353139353135316566336661623464316237
37363930666539373837336466333265333866633733363032323862333239643764333766366134
33396330613463633261626334393262343031663131313737623233323535303965333864366631
65333535343062393434396361313433386463613630353765663363633431393831316233313162
39656333306265386437663863343638323262663734623830336562636366333534373735313932
61653739616635643661306165363364316262386434643033646430366235303334326236373262
34396261653934633337646338333364326237663739623965313039303134393238653634616431
39376539323238353033323333643337613935616335336562626361383164616432633331313536
63373633633966373237393131663064613332316536373163303163653638343264323934393365
62356636643434646465313734363936346435663237393064663138643135303237633965393965
31353564616136366237666139646561363364353738633862346262616637663362333137386133
63383662363866306366326232643462376532313632336662666666386562356436616236663430
37636661656237646431616331633833306266313434326461353561643835646236346539323337
30303065313165306233646135363766613931383433313165366662636137326162633363306431
31626564323566313036363666386338366462613164663735363333313830306238323365363737
65333137363631623139333964316464376662643661643038326162643630323938626332633130
62626334666131373731343432303461656537303062396638326134356166663936663364656139
32333864653231636235663165323936626135313838663866373132376437656236363235353533
39346532626666656233343433383434613238653833626436643566653462346330316565366564
33623831643265623966326638656462306161636366303266333734396433653861393363333332
61616463613931393835613463353039646164383435373330626134376339623861396266623430
30316365356337356531363263623362633332313536373333653964356534623861613232653932
35613064336131313632656663303631363664366163343362643365663932356138376235313466
30353333376461623435363931356665306333623736313562333836336563616137633863346635
37356137306361383134663535626130646135363661353438343961653766333538353330346236
64326639383864396336343062663965343964396162386266363639643962666431336237303864
66663861346139363335663362333032613637336266323439366566373136643133383361323061
37633134323933613665366665383962373935646137616139353661336665613661623834303465
30323264333137636261393535376438363134663734313662383533313130623365386335323662
63366139643238613632326165313835363964336237383936333737646239363365623030666364
64656432653164643565376666373262333839353139666561623731343234326637316636333765
63306331343538386433376566326239376232363434343838653864393562383063663566333263
35326333326462316134383139303534343263646530363266663933353834353138303435646339
33643361363062373735663430346636333431363736373463326439353437356530373935633962
63333031376163656565663536366230333731613833396266383465333461386161373337323863
36313032613534346230636566383930656330656133376431376462656536386263343831393862
32363365623061653837303736636664663361663862656562393661623330386435646336373531
35363037356638653831386261646235613337363066343632653632306631633138633235356139
39663936333262643061646330316538373862353030626632396336393030643239316634343730
35363564373865376533333466666439646633313932656665383930623531633038316363636332
37363466643835353132333532646163316636303662646234613038333334626365623964653235
36393739353164306666313537633538383934363330373235353262616165623132613330303735
65323362666263653937376562633833653264613439303236373466646362316462386632373038
35353462633730666430623638626439626364616335643964623933663233376433353233633235
34616233636335323365646538326639383033643832323139633064616635353331376230663738
35316665616230633335366233363332353432633937653335316662306166383337633262633634
35303766653733376362623436383663663437393461643266376530613533383038373035356234
32343962623930336566363164616361386134376362383138653963366339326431653832393664
66633566343666326339323536343738396532623735343034646130386332323466346365383135
33373732363833316138343938633262373066613930343162663163323331373466366631626237
34623062353139353734653366313363323634346564663861613233366537663732363134643638
65623964396665303765343430623636376637666364363835346462613763306135326463353631
33333834333464383437336531616662643461666138383435636530373761646564626438313433
64643037626637343433366631343266373436343864396663353039333231646462333932353436
33643837616639666363353237313137353133346636383231623634326335386537363030613537
33306333363638643533373237633833663333393566656466313832383636663031663433636663
37313034353734343966613934663530643537366562623137373331623632653466333839376331
61623866633937666365633763613138346365393934383163623730373134373531616138363733
65616230303337396561666462653866333438353463316235303331643834653165363033626537
37386636653337363666393163393031333461383331613965303262616530363133373732313362
33343033373963656333363064303035666663646536373764323833653037316231316430333634
65383236663035326636336535636462323438363165633437333337613439326433626466396365
38393362633266663962646565336539333239346133323434646632643537613435643434623631
63636536393936343165393966663438343261333966363639323462663566613437393838323036
33393163323034336533333632646230343138336333353236376136383664646466626666356234
61653466363933333331613539636431393934393235376433326665643263663638306463393837
34396339383536636461366230383938386339303334393038343239363361666565336237326465
63623231663861303436353533663661656431646165646662383065386362636633333631643335
38373464373432626538616234623638303734626237393566326463633765316365653837303433
34376438323439633237313733343836343733373930643138636333366166353666373966323231
63613466306365386137336538613837613264633735393937343166303736396162303230623430
33363233663761333063363134393935333530343133303138373934666236393732396330643438
61343231666363323834346132376135303339313766383365363837313163393636333563376436
64333832393361626663376161343033383763373537396264646239303736333263303739326436
31393665363734323364643032393031313135623563383639316163616535323938343765356264
35616433396236396662393930646431643063323163343366313030383766323733333865616235
65623737663763373363613030326665613333636366393464383139626462346333323162333537
32386635636532346539623534346364623532656435653638643962353836653330316638633661
38363330656161393766383237663737663835646565363966373563663832313263633238316165
64643536336232396436636261303866376132366261373132333631646166393764636130326335
39613037633239626666396262386533343764623233626335373139663937613433356134633330
32656138383563343734616536303265306661386362343939626332623763393136323138653937
39646537366461353364616538663361663465626634356338323166373837323339343061323261
33393765653034643166313639323962306265383336663265623832393038386334643661336663
66643330306239383736636330393266346537326436663432653031303765393930633761343363
61353163323830656538373366323230353830633534326133613861356132623938663565393130
63333764613761663162633035353036663736656138623234313833326337316438323835313465
31653136356638623965363266636461646465313065666137313931623063303337393963353437
39633166326662303834303261353965363235626535643564613064306636366461666263383833
66363966636538373435323365636630383561336533356631646563646536383333393864613164
38393638623263666239623835613933393564323132373630623734363334663735633438663137
35613563356161316632323562626639633931643762643232636136653731323337333236326363
36633338386230626665373666643831626132323866653430393531336466386463663865393962
37363633343632653963306531393537633762313565303631643064363531363839643363356139
33666162306331393237333966643735643731633331373839356637636462633836633762653039
32633630303437633130613366386633346637623266663361656365633737346432343263646536
35376433386464383533323231353830666363363437623033643238363438303565616137316461
64636233643365663666656431613966396561366236383363303135613433626466366437396465
37303562633336636635363133626238643430663132626331316437626532356237383730626665
38373633323531346366313631376231353966626637636262333936343066336639396262646436
31616165626466623066336138653966323334326236663439643561663863323130653631643231
31396335333066326166383337353033376335376664653162396466346638653531346239353936
33663862323135623432333036336162353061363537363463366435643461356562323935653962
62333130616466353032316462356233393037326438303064656235323966306535346132663232
63393238646534316433643638646536313934666361323061306561396132306431633932313031
31356430313463613331363064363265646361353430646563396238326330323038666264653935
63306635323439326665306135643730386239356661303232353737383532353363303163313437
38623064383035373239623966336563626130636335383833383366343130393939316535333466
65353062396632373330663365373761656161653837396666376531376663313466393230646461
62306465363166663962316634663763626666306631363731633834366433363630383833393361
36393932326138643432666437373463343061313135613163343034373464356666333134366664
66343461393733343130636266623661323066623332633930326361626436366237323664336637
61316135613330663835353139613039613264383838313337373432623133363735333732376566
38633763336438663737653736326466646237313130623934396531306430363461653962336132
64313437323766613632376439336234393165646338396334373037323237633737393866303231
33646338333532613432393234316235653466633639306465363062656335353034666665623631
66386437616462633336636566393537323831313566326637386466616331396464653438396562
33343132626463393364386133323163393336313065316433613133663961633033613232343337
37313334386533366465353461633930643662326235366139306335656163313864636161623239
38336238663630313836363739623334633130616433393536303431333735643565326265613561
30613762396162633964336165666137626333643735323330646266666563313935623230643262
30383635663035653437333339303730346366333765353739663231643433353764363966353435
63663466343864363033646663376261363562636630643038613365333936653165633733623134
36626135313330303463336535663235323536613661353332653139336165613261316638666563
63346131306536353630363236613864393935333431333864333464353664366134313861633463
34336364663030376264663961396235643438653730396661643032623762623965623737326430
34333236316335373863663232356136333431666233353138353139313466343762356632396561
37356162303636343833316432353831373532326436363538333466333636306666626465666333
36653065363538653266643831326234356534303133343564646564323762653238303161336131
35396235343837333331396535646564656433343766323765333465356134323536636639623962
65633233376136316334653335623666313666376661326362663338633862643963353536616563
66323939643332663665306536356566383164356561303935313432373264353738643131653831
31346231633037616139373330316231363938386536303432386638323139326132663539663738
64636231663538333932363332663137323764346336633336363965616535373030663363363861
65363632653831353761306231663562623732353433656637353966303033636666613739306230
64363465623031386431663565623966643836383532626134366363656265646563343635383538
32346166363832626664613335383731616135376635336266326531663731373633303131373965
66663666333739373033616363313132643938656335353164343764383933636265656665663433
65386235636531653236333465316331353938323331623733623731303462306566333365383134
31356337343732613764633731306335366136346633393265666331636465323566656337323838
37393031316630623466393138636666303065623430656336386430363962336539616262633030
62616365623765366236303539353166306630393362386430363736313438346266366336323839
34376462376633366463636339646337623965633663363862393261373535636230613532386464
34393564383130306138366564366666646132363732653036353135656132656339353932383530
61636234393165326631303731633062333735306635303566373838393164306538303664313266
33346337613836636337316638323631396235643363333836323135303738366235326465376630
3363

View File

@@ -0,0 +1,2 @@
---
transcriber_image: "transcriber:2fc5a56-1761210134"

17
vars/transcriber.yml Normal file
View File

@@ -0,0 +1,17 @@
---
app_name: "transcriber"
app_user: "{{ app_name }}"
app_owner_uid: 1017
app_owner_gid: 1018
base_dir: "{{ (application_dir, app_name) | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
# Registry images
registry_transcriber_image: "{{ (docker_registry_prefix, transcriber_image) | path_join }}"

View File

@@ -1,118 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
33323766373637333861393930366564333561663231636433373131353535386464643763396633
6261626463656534366434613865336366323036643731650a306466306261303831356534343133
36386134346137333762646363393532666239386339393131326164366363386531613935356634
6362633333613839380a323666643935313437613161623338373734323438613062623831613565
64666231316139653463666234633262393430633231373564623862376566636134626333393034
61343461383739626130353931306536353739353165383737656133383738363235376331636135
64626561386362646334663232313132343534353533313663313836373435316634336539313562
66333732366666663163343263636664326663623135633338616638353532623832356464366137
32383165396562613162373430356331396631323630396263393637373566393935333632386465
38623135316164333563633262666361663234383935343735393637323834356561336633373333
31633333323132393339336365393433663266373363666339643632353232363762326633363661
66373861376561303038636135393132353639386530383338386237383433663334623533636162
32396562653031396463393862633062346463643466353865393933373533323661623039626538
30343438326462626137643835643466643236373836383665396362346135333466623264633033
66626430646632346139663431643930646361363262366436353861643838663764643363306633
30303462316666646261343439336131336234326236356666323336356131386263303333633863
66623834356263623766386431336165653034623264623263626536626333363534323064323037
34393066663664353837346333663162623864383730323566646534336537623162303339653235
34343262373364313361316539653661656435343532353237313465653832313366383338333639
61386230626534386438386262396464656533303630306432376133636132376635613939343838
36393539343431326132616561366466663966386537633537386138623937343932653937623939
39663366306635346233323830643066386565616461346132613835343264353662613233353231
63343066663731663630343034363632663964323831336532663831663065326462613264343665
32386339313563363730303231643166326635323137393134323732353936626438663161333338
64626237656132653364663661363466613861306430333039343030616631663735383836303436
65383334386337626633643135353830306639353162306463313036313263613330316534376266
33326463623539393531303033356237613331663830663636323630383666333964626130333966
31343731356234396165633330613362363339653837633038616330366635363863333332656133
38636238383664653566613737373731313264633632656536656234653766613036343134386636
38643735613762383530363732303738613062336363383038313337326266376461393663633333
30316433613338373362333333383538376631383335346536366338663438313736313564663531
64313331623238313438636563313662623231346439393066386361353236306265343865323066
66383463396134633731643332383064663761653430313935646236386162313366643937633938
62656461363635366139343463653435363161393862353034616562363237636638346565636362
37616365636338616430313638656562613162346163356238306532396333306564633232383535
38613561363664653333626561373661376232363930643934333566313662613835376163616461
36656164656563333662616339663333353466636266633130386334643338346332326334376630
66373031383637353562653837316561373338306438333534376165356663613330316661383137
34386634626663326135383434383762656165393065323136326432613533626338396161623631
31633565373065333834363363363430633162376334343763393639653862363537313432383037
33646333386237306662313066343635386636323964366463386434653263353539393732303134
33383735363331386431303537666139383539326237313535353466393436633061313761386534
65333936613665616139343965366239306132636131393964656136363232656630626234653962
62623363323331383035393266343563383730386166353464323837633063366536376631313166
35613236326232396239626238396161386232363261376366303637326239383330663736633766
63363562383464326138663738383464343131343636363735303866373937653839663264396431
63373232343366356339626466336262383538626366653738313564303034373537383863363166
36363039666534333237353535323438356135303730336233323636396539666436306661613236
62653966653965383233376537666562643736646261636336633661653931636536663765373330
33366536663536613536626466363165396461616231316663653739373233323763373631333161
31383337653634343465616566353665363163316534646132636133373633623866353236313931
66323939633737353037643365653531313131346366316234656464396131333939373362646361
65396230646535336638623066393734623964663933653930386438383237666636303135616334
38366333663334363733386237313233373664373534363930396237323837376332656364666261
31323866666564653861386464343335396663646234386362333562396564636434376261376531
66613437666131346262383861383139396665623264363639336664303863343665636239373331
66333361393539313535656532383032653130633565356534353034613036336566636237333164
31633831646433313930323665373165663639393662383561363133636134383237646235613035
65666438303236366435613562636133383365653630343563303036373033346135343064383433
34356666313264303763663665333761376539346462656635356537616532663339373532376362
64636233306665353066306632363737653034636535363533666464366264383963313937313833
65643064646463313763343366373066663130623866333432366135353132356231366436333733
37373535306134663239313035303832333438363233646239656266346132656364383361313066
32386161326336356331323330326139333938393739383362386239316237346137666231633164
37383638363864303131626461393936616332646239623434376161353736656231633233313964
38383136626337383339303835363130346236323131366564333836303564633735623530663934
35626430303631613565383266363163343563323232653838323365613131663163646432653332
35376335666463646661623864343639393035643636623963343936623963316339623864386538
33373737306437383861633131656131616461333432616235346335316162386534316263303639
37646538356163336636353864623830336532366139306532356133616165336461323737383062
32666164353237363237363232633431376437343832363838386264623965663131326534383461
63343764323231333036663733303634383030383836653462303336656337313662306564373132
35353930343734646633613131623230363662316434373337333434326230383364626165643066
66343461303732663938303966656434653737366338623966373538313936636363353733613931
34323764613131636632653066663533356636363636373064326536376163393231313763363632
37353964323763386233623438386636353930396562653863643161343439636437366535393434
66303763373537323039336430343564663666313131643036623064363837613937336161323562
65363238373462613730653363333566636361303264633461356432313830353238633766396566
38393333653038643836373331646332633364666630613762623163353434326165303038653635
30646231663636653637386138643233666136636438613234333535666161643035663863656466
32353166353165666161663830643331393466333031613233303262623863623861333431313436
30313361613832336532653537653632623633653838393736646539633738363263653165623430
65306366333936323262663365643734356332336430383961363863396134326430303563346462
34303363626435383534653963353636393330373839356438336139616662616462633764363034
65346530636530383163663136386363336366663230316264346664386534633837653436323630
34363666663764313463393635393863623865353035616433356263653664363838666164396234
30333730643162633832303533623531326561373935346234646433363335646562613066343062
36336234653333643830653062343831633033323561313566613866326135316338656133333239
65363664623261636239616439613361346433313133333831616532343130383833353332633331
34383437666335663266303161636162626663303263366261653966623761396235636264346666
35366431643535373462616235313034353864366662643837353163353763636564663862316332
65323133373539313531326137356362356334386332336438373763363836623863393239633030
66386637303430623836636235653138633033346166326335383934643433363563303939666133
62333232376335356564363135656339333337303565356565376337326632346130643631616132
64633037326562393236616236663434623939646535336634646637316632306138383834363032
65383336343533663638616535613638303236613563316635383238376636306266643037333432
62363864613230306335656562356130613966313263646566373864356135653830393431323664
38313234386630653834386531393862386533643339666365626466353332373539653736653333
35396265393736393939633663613965356263343234303437393862356161363832336334303764
65393136656630366262616264613464613436613938646433363864643838663835393638313731
32343835396361626366636362636630633330356263316265326632666434386365356234646131
38316338636339613138623232363361663430303136323734376135366431336364643063303566
35366263633433333537373733313830313433633162333166346435363539353862313464316263
63653263373133633237653633326363656330373838306432333936656564323231376235663839
63353331636531366138646539323565316362656365303139616364393264643130383137383361
61343935373138393632663031633865663733353064343232376565363831356263306532363866
38323366316432366632656237656564396636656331653334323763663035313735343361383833
65333961663766636635353137666239323636373761653864396433393866336432366435336537
35333935636537376363663166316630613331643231653038356362323039643731363436356362
33306131303432343432323566653561303036616631383737366338393761333737653635633463
61633235313831353337323333343062343865343235313538303034346233376466316437343834
38643338653166613262326230646364343362303731316431316432663530623436346665336137
63666230323035653734353431616364363733313033376532323633386630356665633732656662
35633366376237616536386631386433333561623832386236393062663033643439396535373866
64313663643734376632363661653433376464653933636333626530613964613234666530663063
36643335396561336161656562303333323866663466323437303731376561643666336439666563
6637