Compare commits

...

69 Commits

Author SHA1 Message Date
a6781b4f64 Gramps: upgrade to 25.12.0
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 16s
2025-12-21 12:24:11 +03:00
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
64 changed files with 1459 additions and 705 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 .

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
/ansible-vault-password-file
/temp
*.retry
__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

@@ -21,11 +21,11 @@ tasks:
ssh:
cmds:
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}}
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}}
btop:
cmds:
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} -t btop
- ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} -t btop
encrypt:
cmds:
@@ -44,20 +44,20 @@ tasks:
DEST_FILE: "temp/configuration.yml"
cmds:
- >
ansible localhost
--module-name template
--args "src=files/authelia/configuration.template.yml dest={{.DEST_FILE}}"
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_DOCKER}}
validate-config --config /data/{{.DEST_FILE}}
authelia-gen-random-string:
summary: |
Generate random string.
Usage example:
Generate random string.
Usage example:
task authelia-gen-random-string LEN=64
vars:
LEN: '{{ .LEN | default 10 }}'
@@ -76,5 +76,5 @@ tasks:
format-py-files:
cmds:
- >-
docker run --rm -u {{.USER_ID}}:{{.GROUP_ID}} -v $PWD:/app -w /app pyfound/black:latest_release black .
- >-
docker run --rm -u {{.USER_ID}}:{{.GROUP_ID}} -v $PWD:/app -w /app pyfound/black:latest_release black .

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

@@ -408,12 +408,12 @@ identity_validation:
##
## The available providers are: `file`, `ldap`. You must use only one of these providers.
authentication_backend:
## Password Change Options.
password_change:
## Disable both the HTML element and the API for password change functionality.
disable: true
## Password Reset Options.
password_reset:
## Disable both the HTML element and the API for reset password functionality.
@@ -896,8 +896,8 @@ session:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
redis:
host: 'authelia_redis'
port: 6379
host: 'authelia_redis'
port: 6379
## Use a unix socket instead
# host: '/var/run/redis/redis.sock'
@@ -1026,7 +1026,7 @@ storage:
##
local:
## Path to the SQLite3 Database.
path: '/config/authelia_storage.sqlite3'
path: '/data/authelia_storage.sqlite3'
##
## MySQL / MariaDB (Storage Provider)
@@ -1292,7 +1292,8 @@ identity_providers:
## configured has the RS256 algorithm. For RSA keys (RS or PS) the minimum is a 2048 bit key.
jwks:
-
## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters.
## Key ID embedded into the JWT header for key matching.
## Must be an alphanumeric string with 7 or less characters.
## This value is automatically generated if not provided. It's recommended to not configure this.
# key_id: 'example'
@@ -1383,7 +1384,7 @@ identity_providers:
clients:
-
client_name: 'Miniflux'
client_id: '{{ oidc__miniflux__client_id }}'
client_id: '{{ oidc__miniflux__client_id }}'
client_secret: '{{ oidc__miniflux__client_secret }}'
redirect_uris:
- 'https://miniflux.vakhrushev.me/oauth2/oidc/callback'
@@ -1397,7 +1398,7 @@ identity_providers:
- 'authorization_code'
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_basic'
token_endpoint_auth_method: 'client_secret_basic'
-
client_name: 'Wakapi'

View File

@@ -10,9 +10,10 @@ services:
- "monitoring_network"
volumes:
- "{{ config_dir }}:/config"
- "{{ data_dir }}:/data"
authelia_redis:
image: valkey/valkey:9-alpine
image: valkey/valkey:9.0-alpine
container_name: authelia_redis
restart: unless-stopped
networks:

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 }}"

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,326 +0,0 @@
#!/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 os
import sys
import subprocess
import logging
import pwd
from pathlib import Path
from typing import List, Tuple, Optional
import requests
# 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__)
# Configuration from Ansible template variables
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 }}"
TELEGRAM_BOT_TOKEN = "{{ notifications_tg_bot_token }}"
TELEGRAM_CHAT_ID = "{{ notifications_tg_chat_id }}"
NOTIFICATIONS_NAME = "{{ notifications_name }}"
class BackupManager:
def __init__(self):
self.errors = []
self.warnings = []
self.successful_backups = []
def get_home_directories(self) -> List[Tuple[str, str]]:
"""Get all home directories and their owners"""
home_dirs = []
home_path = Path("/home")
if not home_path.exists():
logger.error("/home directory does not exist")
return home_dirs
for user_dir in home_path.iterdir():
if user_dir.is_dir():
try:
# Get the owner of the directory
stat_info = user_dir.stat()
owner = pwd.getpwuid(stat_info.st_uid).pw_name
home_dirs.append((str(user_dir), owner))
except (KeyError, OSError) as e:
logger.warning(f"Could not get owner for {user_dir}: {e}")
return home_dirs
def find_backup_script(self, home_dir: str) -> Optional[str]:
"""Find backup script in user's home directory"""
possible_scripts = [
os.path.join(home_dir, "backup.sh"),
os.path.join(home_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_user_backup(self, script_path: str, username: str) -> bool:
"""Run backup script as the specified user"""
try:
logger.info(f"Running backup script {script_path} as user {username}")
# Use su to run the script as the user
cmd = ["su", "--login", username, "--command", script_path]
result = subprocess.run(
cmd, 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 for {username} failed with return code {result.returncode}"
if result.stderr:
error_msg += f": {result.stderr}"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
except subprocess.TimeoutExpired:
error_msg = f"Backup script for {username} timed out"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
except Exception as e:
error_msg = f"Failed to run backup script for {username}: {str(e)}"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
def get_backup_directories(self) -> List[str]:
"""Get all backup directories that exist"""
backup_dirs = []
home_dirs = self.get_home_directories()
for home_dir, _ in home_dirs:
backup_path = os.path.join(home_dir, "backups")
if os.path.exists(backup_path) and os.path.isdir(backup_path):
backup_dirs.append(backup_path)
return backup_dirs
def run_restic_backup(self, backup_dirs: List[str]) -> bool:
"""Run restic backup for all backup directories"""
if not backup_dirs:
logger.warning("No backup directories found")
return True
try:
logger.info("Starting restic backup")
# Set environment variables for restic
env = os.environ.copy()
env.update(
{
"RESTIC_REPOSITORY": RESTIC_REPOSITORY,
"RESTIC_PASSWORD": RESTIC_PASSWORD,
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
}
)
# Run backup
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic backup failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic backup: {error_msg}")
return False
logger.info("Restic backup completed successfully")
# Run check
check_cmd = ["restic", "check"]
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic check: {error_msg}")
return False
logger.info("Restic check completed successfully")
# Run forget and prune
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:
error_msg = f"Restic forget/prune failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic forget/prune: {error_msg}")
return False
logger.info("Restic forget/prune completed successfully")
# Final check
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Final restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Final restic check: {error_msg}")
return False
logger.info("Final restic check completed successfully")
return True
except Exception as e:
error_msg = f"Restic backup process failed: {str(e)}"
logger.error(error_msg)
self.errors.append(f"Restic: {error_msg}")
return False
def send_telegram_notification(self, success: bool) -> None:
"""Send notification to Telegram"""
try:
if success and not self.errors:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап успешно завершен!"
if self.successful_backups:
message += (
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
)
else:
message = f"<b>{NOTIFICATIONS_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)
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
data = {"chat_id": TELEGRAM_CHAT_ID, "parse_mode": "HTML", "text": 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}"
)
except Exception as e:
logger.error(f"Failed to send Telegram notification: {str(e)}")
def run_backup_process(self) -> bool:
"""Main backup process"""
logger.info("Starting backup process")
# Get all home directories
home_dirs = self.get_home_directories()
logger.info(f"Found {len(home_dirs)} home directories")
# Process each user's backup
for home_dir, username in home_dirs:
logger.info(f"Processing backup for user: {username} ({home_dir})")
# Find backup script
backup_script = self.find_backup_script(home_dir)
if backup_script is None:
warning_msg = (
f"No backup script found for user {username} in {home_dir}"
)
logger.warning(warning_msg)
self.warnings.append(warning_msg)
continue
# Run backup script
self.run_user_backup(backup_script, username)
# Get backup directories
backup_dirs = self.get_backup_directories()
logger.info(f"Found backup directories: {backup_dirs}")
# Run restic backup
restic_success = self.run_restic_backup(backup_dirs)
# Determine overall success
overall_success = restic_success and len(self.errors) == 0
# Send notification
self.send_telegram_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 main():
"""Main entry point"""
try:
backup_manager = BackupManager()
success = backup_manager.run_backup_process()
if success:
sys.exit(0)
else:
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

@@ -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

@@ -1,7 +1,7 @@
services:
dozzle_app:
image: amir20/dozzle:v8.14.8
image: amir20/dozzle:v8.14.11
container_name: dozzle_app
restart: unless-stopped
volumes:

View File

@@ -1,7 +1,7 @@
services:
gitea_app:
image: gitea/gitea:1.25.1
image: gitea/gitea:1.25.3
restart: unless-stopped
container_name: gitea_app
ports:

View File

@@ -3,7 +3,7 @@
services:
gramps_app: &gramps_app
image: ghcr.io/gramps-project/grampsweb:25.11.0
image: ghcr.io/gramps-project/grampsweb:25.12.0
container_name: gramps_app
depends_on:
- gramps_redis
@@ -15,10 +15,10 @@ services:
- "{{ (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 }}"
@@ -36,16 +36,12 @@ services:
GRAMPSWEB_EMAIL_HOST_PASSWORD: "{{ postbox_pass }}"
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
<<: *gramps_app # YAML merge key copying the entire grampsweb service config
container_name: gramps_celery
depends_on:
- gramps_redis
@@ -53,10 +49,10 @@ 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:

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

@@ -1,4 +1,5 @@
services:
homepage_app:
# noinspection ComposeUnknownValues
image: "{{ registry_homepage_nginx_image }}"
@@ -10,4 +11,3 @@ services:
networks:
web_proxy_network:
external: true

View File

@@ -3,7 +3,7 @@
services:
memos_app:
image: neosmemo/memos:0.25.2
image: neosmemo/memos:0.25.3
container_name: memos_app
restart: unless-stopped
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"

View File

@@ -2,7 +2,7 @@
models:
gramps:
memos:
compress_with:
type: 'tgz'
storages:
@@ -14,8 +14,3 @@ models:
users:
type: sqlite
path: "{{ (data_dir, 'memos_prod.db') | path_join }}"
archive:
includes:
- "{{ data_dir }}"
excludes:
- "{{ (data_dir, '.thumbnail_cache') | path_join }}"

View File

@@ -1,8 +1,7 @@
services:
netdata:
image: netdata/netdata:v2.7.3
image: netdata/netdata:v2.8.4
container_name: netdata
restart: unless-stopped
cap_add:
@@ -17,12 +16,16 @@ services:
- "{{ config_dir }}:/etc/netdata"
- "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata"
- "{{ (data_dir, 'cache') | path_join }}:/var/cache/netdata"
# Netdata system volumes
# 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"

View File

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

View File

@@ -1,4 +1,4 @@
update_every: 15
update_every: 60
jobs:

View File

@@ -19,7 +19,7 @@
# cpu cores = 2
# libuv worker threads = 16
# profile = standalone
hostname = {{ host_name }}
# hostname = rivendell-v2
# glibc malloc arena max for plugins = 1
# glibc malloc arena max for netdata = 1
# crash reports = all
@@ -30,12 +30,15 @@
# 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
# update every = 1s
# db = dbengine
# memory deduplication (ksm) = auto
# cleanup orphan hosts after = 1h
@@ -47,7 +50,7 @@
# dbengine extent cache size = off
# dbengine enable journal integrity check = no
# dbengine use all ram for caches = no
# dbengine out of memory protection = 391.99MiB
# dbengine out of memory protection = 391.49MiB
# dbengine use direct io = yes
# dbengine journal v2 unmount time = 2m
# dbengine pages per extent = 109
@@ -93,12 +96,9 @@
[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 =
# PYTHONPATH =
# TZ = :/etc/localtime
[host labels]
# name = value
[cloud]
# conversation log = no
# scope = full
@@ -107,15 +107,15 @@
[ml]
# enabled = auto
# maximum num samples to train = 21600
# minimum num samples to train = 900
# 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 smooth = 3
# num samples to lag = 5
# random sampling ratio = 0.20000
# maximum number of k-means iterations = 1000
# dimension anomaly score threshold = 0.99000
# host anomaly rate threshold = 1.00000
@@ -163,7 +163,7 @@
# timeout for first request = 1m
# accept a streaming request every = off
# respect do not track policy = no
# x-frame-options response header =
# x-frame-options response header =
# allow connections from = localhost *
# allow connections by dns = heuristic
# allow dashboard from = localhost *
@@ -181,7 +181,7 @@
# gzip compression level = 3
# ssl skip certificate verification = no
# web server threads = 6
# web server max sockets = 262144
# web server max sockets = 131072
[registry]
# enabled = no
@@ -189,9 +189,9 @@
# 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 domain =
# registry to announce = https://registry.my-netdata.io
# registry hostname = 7171b7f9fc69
# registry hostname = rivendell-v2
# verify browser cookies support = yes
# enable cookies SameSite and Secure = yes
# max URL length = 1024
@@ -202,9 +202,29 @@
[pulse]
# extended = no
# update every = 1s
# 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
@@ -213,23 +233,20 @@
# proc = yes
# cgroups = yes
# timex = yes
# statsd = yes
# enable running new plugins = yes
# check for new plugins every = 1m
# slabinfo = no
# freeipmi = no
# python.d = yes
# go.d = yes
# apps = yes
# systemd-journal = yes
# network-viewer = yes
# charts.d = yes
# debugfs = yes
# perf = yes
# ioping = yes
# network-viewer = yes
# apps = yes
# go.d = yes
# systemd-units = yes
# systemd-journal = yes
[statsd]
# update every (flushInterval) = 1s
# update every (flushInterval) = 10s
# udp messages to process at once = 10
# create private charts for metrics matching = *
# max private charts hard limit = 1000
@@ -247,10 +264,7 @@
# gaps on histograms (deleteHistograms) = no
# gaps on timers (deleteTimers) = no
# gaps on dictionaries (deleteDictionaries) = no
# statsd server max TCP sockets = 262144
# listen backlog = 4096
# default port = 8125
# bind to = udp:localhost tcp:localhost
# statsd server max TCP sockets = 131072
[plugin:idlejitter]
# loop time = 20ms
@@ -300,22 +314,31 @@
# /sys/class/drm = yes
[plugin:cgroups]
# update every = 1s
# check for new cgroups every = 10s
#| >>> [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.* *
# 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
# 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
# update every = 1s
# 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
@@ -326,41 +349,29 @@
[plugin:tc]
# script to run to get tc values = /usr/libexec/netdata/plugins.d/tc-qos-helper.sh
[plugin:python.d]
# update every = 1s
# command options =
[plugin:go.d]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:apps]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:systemd-journal]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:network-viewer]
# update every = 1s
# command options =
[plugin:charts.d]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:debugfs]
# update every = 1s
# command options =
[plugin:perf]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:ioping]
# update every = 1s
# command options =
# update every = 10s
# command options =
[plugin:proc:/proc/net/dev]
# compressed packets for all interfaces = no
@@ -580,7 +591,7 @@
# hardware packets counters = auto
# hardware errors counters = auto
# monitor only active ports = auto
# disable by default interfaces matching =
# disable by default interfaces matching =
# refresh ports state every = 30s
[plugin:proc:/proc/net/stat/nf_conntrack]
@@ -635,7 +646,7 @@
# preferred disk ids = *
# exclude disks = loop* ram*
# filename to monitor = /host/proc/diskstats
# performance metrics for disks with major 252 = yes
# performance metrics for disks with major 253 = yes
[plugin:proc:/proc/mdstat]
# faulty devices = yes
@@ -685,3 +696,7 @@
[plugin:proc:/sys/class/drm]
# directory to monitor = /host/sys/class/drm
[plugin:systemd-units]
# update every = 10s
# command options =

View File

@@ -9,8 +9,6 @@ services:
depends_on:
- outline_postgres
- outline_redis
ports:
- "127.0.0.1:{{ outline_port }}:3000"
networks:
- "outline_network"
- "web_proxy_network"
@@ -20,9 +18,9 @@ services:
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'
REDIS_URL: 'redis://outline_redis:6379'
FILE_STORAGE: 's3'
FILE_STORAGE_UPLOAD_MAX_SIZE: '262144000'
@@ -34,7 +32,7 @@ services:
AWS_S3_UPLOAD_BUCKET_NAME: '{{ outline_s3_bucket }}'
AWS_S3_FORCE_PATH_STYLE: 'true'
AWS_S3_ACL: 'private'
OIDC_CLIENT_ID: '{{ outline_oidc_client_id | replace("$", "$$") }}'
OIDC_CLIENT_SECRET: '{{ outline_oidc_client_secret | replace("$", "$$") }}'
OIDC_AUTH_URI: 'https://auth.vakhrushev.me/api/oidc/authorization'
@@ -54,7 +52,7 @@ 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:
@@ -64,8 +62,10 @@ services:
outline_postgres:
image: postgres:16.3-bookworm
container_name: outline_postgres
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: unless-stopped
volumes:
- "/etc/passwd:/etc/passwd:ro"
- "{{ postgres_data_dir }}:/var/lib/postgresql/data"
environment:
POSTGRES_USER: '{{ outline_postgres_user }}'
@@ -74,6 +74,10 @@ services:
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:

View File

@@ -1,14 +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,15 +3,19 @@
hosts: all
vars_files:
- vars/ports.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"
@@ -19,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"
@@ -29,7 +35,10 @@
group: "{{ app_user }}"
mode: "0700"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ backups_dir }}"
- name: "Copy users file"
ansible.builtin.copy:
@@ -39,7 +48,7 @@
group: "{{ app_user }}"
mode: "0600"
- name: "Copy configuration files (templates)"
- name: "Copy configuration file"
ansible.builtin.template:
src: "files/{{ app_name }}/configuration.template.yml"
dest: "{{ (config_dir, 'configuration.yml') | path_join }}"
@@ -47,6 +56,22 @@
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.template.yml"
@@ -60,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

@@ -4,13 +4,39 @@
vars_files:
- vars/secrets.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.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.template.py"
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/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,14 +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/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:
@@ -32,3 +31,10 @@
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"

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "dozzle"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1016
app_owner_gid: 1017
base_dir: "{{ (application_dir, app_name) | path_join }}"
tasks:
- name: "Create user and environment"
@@ -17,11 +18,23 @@
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"
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -32,3 +45,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.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.71.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.5"
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.1"
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.15.3"
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.45.4"
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/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/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

@@ -1,10 +1,9 @@
---
- name: "Upload local homepage images to registry"
hosts: all
gather_facts: no
gather_facts: false
vars_files:
- vars/ports.yml
- vars/secrets.yml
- vars/homepage.yml
- vars/homepage.images.yml

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
- vars/homepage.yml
- vars/homepage.images.yml
@@ -14,8 +13,20 @@
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 }}"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
@@ -33,3 +44,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "memos"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"
@@ -20,6 +21,8 @@
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"
@@ -30,6 +33,7 @@
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
@@ -49,6 +53,18 @@
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"
@@ -62,3 +78,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "miniflux"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"
@@ -21,6 +22,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 directories"
@@ -31,6 +34,10 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ secrets_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}"
- name: "Copy secrets"
@@ -50,7 +57,7 @@
- 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 }}"
@@ -58,7 +65,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 }}"
@@ -70,3 +77,5 @@
state: "present"
recreate: "always"
remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "netdata"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"
@@ -20,6 +21,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"
@@ -30,13 +33,14 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ config_go_d_dir }}"
- "{{ data_dir }}"
- name: "Copy netdata config file"
ansible.builtin.template:
src: "files/{{ app_name }}/netdata.conf.j2"
src: "files/{{ app_name }}/netdata.template.conf"
dest: "{{ config_dir }}/netdata.conf"
owner: "{{ app_user }}"
group: "{{ app_user }}"
@@ -93,8 +97,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,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.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,6 +21,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 directories"
@@ -30,6 +33,9 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}"
- name: "Copy docker compose file"
@@ -42,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 }}"
@@ -53,3 +59,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "rssbridge"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
app_owner_uid: 1014
app_owner_gid: 1015
base_dir: "{{ (application_dir, app_name) | path_join }}"
tasks:
- name: "Create user and environment"
@@ -17,8 +18,20 @@
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"
@@ -32,3 +45,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
@@ -40,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

@@ -1,10 +1,9 @@
---
- name: "Upload local transcriber images to registry"
hosts: all
gather_facts: no
gather_facts: false
vars_files:
- vars/ports.yml
- vars/secrets.yml
- vars/transcriber.yml
- vars/transcriber.images.yml

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
- vars/transcriber.yml
- vars/transcriber.images.yml
@@ -14,6 +13,8 @@
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"
@@ -24,6 +25,7 @@
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ config_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
@@ -53,3 +55,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
tasks:

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "wakapi"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"
@@ -20,6 +21,8 @@
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"
@@ -30,6 +33,7 @@
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
@@ -62,3 +66,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "wanderer"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"
@@ -23,6 +24,8 @@
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"
@@ -33,6 +36,7 @@
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ (data_dir, 'pb_data') | path_join }}"
- "{{ (data_dir, 'uploads') | path_join }}"
@@ -47,13 +51,29 @@
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "files/{{ app_name }}/backup.template.sh"
# - 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:
@@ -68,3 +88,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

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

View File

@@ -1,7 +1,9 @@
---
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"
# Registry images

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 }}"

View File

@@ -1,157 +1,162 @@
$ANSIBLE_VAULT;1.1;AES256
34393132353631343361373266363131613633303038633363333036386436356139616630346163
3034666435313833306265633833326631353562336266640a396332316162313462396334326439
39383731303565396132643736323562383832376630633338393362623439353366303830656662
6265383433376136660a326164316431336637376635306138366262333935646362303935353135
65666330383933613838356361643932353630656137316131663764326137346137363435396534
31366531633339373838623036633931356633306535366133396230383738353136323863653232
62393461333434623561333365636464316533313763346361343764326238383464346630633439
30666333636532333133336637643532656434346162626463316634633732326561353461373530
35333231353635616664653230306664323663306536616663646638333335383432326565313737
65616230613738313866333937386230666461313731656234613234363733303235323430653530
65303664613138333966376364616135303866666330346264303133323331343337633332366665
39643063386331643763393037393633383331333536303763363135306339623932383538326566
66376339643530616666663838323063623463333038633330313434666562383365343165316131
61313632303965306166323562656230396435343336623432313638313662643866353934636434
36306632633436306361636533613733633935656666333332633033333431323861623065353534
62356330303564633163356130663865363465623236373932663662663536656235613434353939
31666537396635366563643139363662313337313037643530313066656538663861376464353933
36363138643466376264363337646462653562663633626430316632393865313738343662393432
31306164613431626166306437613164333234376164333765353034383530636137646438383438
62366637376364373565363935353663626363303035376562323665636432666437333136353130
66663130643330306336343138636462373838633264326632343433636163353061636231313135
39363437333535313466383937366636333133653438313037346431633166306331616165343337
66613036333431313066616138666666663266393030313832653332643138626532666531336437
37343635653665333861613263343439643432396330366534393336336361653037326264353437
36663836386663303439656266383630323561363332303837373030316435303730643564356630
65623064333265303666323966353865376565373137363134643964653630313365303334383732
63666161336539313136303637373336636237353361363061613439363561393561633431343433
31336137646330366565336538646462666532376265313332313664653430303737303435376665
32346364323038303435643836316439656636353530346336303266663733646665383039623264
32613935656332643435333630666162616338333762633131656436353130353532393939376363
38303530393665393333303064353663353238643436303032306237663031323035623230623163
38363965646437643033333335363137663037306436343764373266343335316637373638306461
65646436633034313236353737363734656561313161343036353237613536623432393334666539
61346266383935373466393563636635316566323765326430366530393836663333646331383962
36616663623465356436653033313534336461313562636530306137386532366265326336333336
37323563373039383266646632633635313239666630393561353231663436643434373635353163
37626165323733323135373033323633346535646538396331653231616262313034623235373836
65343639623837353534663938313934636230363862613532623434383632383662343736363935
38333363613430646230366539333834303665386265336565316539343664663534336165303362
37363234336563353165633630313534666461303639306634656366383764326335306565303336
32356165363637313065653937303834363534643137363030383232383465306530626334666362
35626563323636323231643031393063613839353333386231623337633435303565643565363539
34303364613230366162333435623330313963636538626535303033333461363239653333633634
61633334636364633861643130633061623564656239376632666561646430303831666137656236
33646134353039656361623739623835363137663530626535336235666238626536376335643937
35633736346137323663366138393162633466633238306639383236396234343839303630616130
65623362393635633965633635303139306261373262363434303436356561373534626162626330
65613266396362373230323631323030633830366531643863363337323763333461363139626538
64316135333631373031333464623663653630613736393437303035356163323537633865616633
65616136323162326537343330643337373030393163306232303161316632636163333231613138
61316266623263366266313831343961356339313430633865303663373664373066663037646239
62626564623632343531333238656461653664623134643665323139386530383037346332653333
38623536663934656636376334613733373939396633346436333866633832663631343466653339
66666536663634633933636363313733336530333263643735626362323631663661613331313931
34313539326439623363613939616362633837346439653636353961316132396636663263333234
37333338373730643538383064623161396164393833383739396666643033313135643132613239
35633739663931626362383664643434616264663336323938363165323331656431323265306361
64363431616630613566613632623061306135643765656332356164383439313265306233393962
34623366313563663164313261316162373430613062373138376439326232376235636366663036
37376365326261336361396666373663383239383735623534343239363232636666366135343738
63323161346266346431343339346364623439346235323764663534393432326564633362376262
39363030636261356361303365316133613232363666353761653532613433326262613065393166
35343737343039323061613833623536663031323636333762306331383664303730643666663436
35383938656436613036646261373135623065383236333063343931663738383862343365653966
38616362393034396434666337396262373437623030623832376131633036656433663835636531
35623633666339656633616135366465376362306362393965663133633733346265333930643339
65326533616461633339323135376539363866383537336531343863643563616264313864383164
32356632663061333139383938366531356261386663393663623661323139386239313937393534
65323935363765396434306466653733376338363664376433666538326133626635646634333434
32313534366364323435336361626136363036653336656632653364323836613165633362353462
30383037366631643037306631663361376166353733353832363530373061383631616238656434
34636333376238363738336337613038396664393037333939353837643963633635633030383066
61303933346630333164623963363263313233366535623630636665343935333065633530383039
34383361646138653862363431336132346466633563386564393761393438323038363166336536
30663763356331626334626665316130373162326433663238613833303034653439316337653765
31353136633230323662653534386361636334336533326439353538333236363535323535643538
34626637333364653662376430613731366337623434373165303330316662376338353837343937
62353037323239633036613864633034353064636463373938396566363166613062343733626465
61386239613239396163376533663862616361623432323662386161663832386136373032353466
30346431396436656339353065393135653664333463626632373338373638643439663961633365
61636537353731333035626332383663393863373435303064653338643262383564646539666633
62373030303938316236663661316461626534303862623234646465383330353661323639383664
33306133623265373938356131643366316538643733373139333664613939656133393630306538
62323134653163336335363262653663363735323866666237336336393138336235623437326233
35313736323832313737303965636266343564613139393838376133396532656662353735313336
37633238353362323534626438393838643833616330393464353837326238366332613339656239
34303664316263326432363930646662306635303234663931323363656432623035363561336138
31383637666431333838646139623162363164633964653936336137616230646430323466373736
63323661653361306331646363666561623930653035623764366235636161386666663931613632
33306262386337613939366632373166666437376364633038633565346439616662346665613738
38623466646131373762376264386461663134373661353563353661333635313839303232666531
63663136383737396662616165633639663334623137626537373332636430653765343066306530
64383632356165316337623037616566306463353036653634303538393733366535653862636234
35336235393261303961626364313330383965353337616430313739316266323462653838356230
35393732333265323833623233666164663163346632373966646638643265633037313635396464
38333836323362303638386536663661313138666235653261343036663565363933376132653836
62646461663534653035663432363365316131623137316665666664316136353966643161383938
62303461353166623239346235643065376336313565656336656465366233393230613033643265
35363161303861393039616237316538336166613938343764613030303361663934633030616362
37623239626134373361333237623431653735393866343463313037393038396432653235653239
65326530383462353363336430366336353735303335316465376662393232336539613037346135
36323461353939626465656439313865306165353936393137343662366139336465616561633137
39623764376435386536326662626464306231373161376431653463636164613238303438363035
37323239373634373162353864343639613861343832616161336337373765343636663030346139
35376462316331346662653939353465383661393839306531396237663364346438336164343465
37653163373231326637333332313766656536396539613839316134363531666166356335373261
32333437303563323161623665343239306639333532376264363964383961633236323138323438
34636265353664346264373663393765343638386236373863353734623738646437363134656666
31303432373363633365383932366661653163366464343164336165613637336536653937376364
62643332373337326665313761316365323263316130643564313836626336333364343135393465
36306431633765343935626631333764336634343636613864623663646661333432306535333339
36376564613066306266653031396437363566373465663234623535636335626434326566393238
39366330393661303730393831633665356536336338343834373733346334343264643733393031
38373161373939636134653663396437306534663265316333346338663132616131633139336262
63616331313965613062333064326638356266373935393733633664303862373134303664383362
32613434386537643030383064383865303431316339386233336564313432343534363834303138
37393536343032343432343766633333366662316535303336346361356330373262366537636335
37366561373231636630316232656132393365336438386566333363623638363235386634656139
35666263383735626233383235396363393436633738303639633838326263396538303334646532
64343538396435663166613865376433323461646539636462356466633134333233366161376639
30303837333331383638663530343630636261323466616331313263333962353366616531353337
36343230343965333364336664313131396433626138363038336365353730393534386633346263
65663731313566643731366561353965303039303830376139623030613237333037366563393663
62643730653431666161386663643536666333396166393338326239633861663465323236356462
65663033353834306332323364306462393035626339353461373134626363376261303564653638
64633330646134666336323965633065326561386233383338343837336461333236633935316238
64643433383766343461353934616265316335386661323365393032613461376562633239303235
61323730363963663039306530653631626266343132613239353435303030666562383263623561
32383066363230316332306365303735336465316336356634346530366330643132636266356139
63316461393664613364643163613536353062313832643262376330393464343738653363333765
32386663373732623234333032353964643033643538393132306133393365326438393564356364
62626439366132393531353733613936643363393736653461666339353036333333306466363430
66346135333162363033383339373631393836376635633136323134623861663066356638396136
33623131666163633035666665313236646235626363363633343263376333333133326564366637
35383663386133326432333537653032633230353435303935333161346532663731666339373431
37656364643531663866653962306638333663366338373537323035323832656365343239613739
63366166393237613534636563333137356365333936303633656134346166396530656133356562
65326230643236633531613139363134353731363730633165636636653339363266656265623330
64633063336330396130316264656165616537616162353130316138386335656635633237313462
61633231373236303534313861613639663732633638633030333465376438623234386634616263
34643939316135303737303837336533663739316633323238613838363531636463356338343235
31346164346535643733653936303337393230616137633161646364353034346632663830613332
38366138633334633131663662613862633162626366616435636563383839303766363131643763
66336530303835613432343065353031386636353761303730663865656336623333333464386334
35373365613766396237616462386432623635643537666565353734623663663136653766643031
61346162303534346262646236663935656561353137373132643664616334623334376437396533
31393035323366353961333538643233393832616534343530333137643064366635366236386466
65626235356438343038373331313131626365386133613464653561376566313132666236666461
61336639633163373662653939613830613863363230303834373231623231333165363362303436
34653366663838343035646562393031613062643332663739643334643436316336643261333133
34623764323363626362643662313233613238653133626631353338643336663664613030386266
34633632316431353039666438633366393038633462326262396432316438316664333130343634
33383632653535636565356334333563353138313134326330613865376561663731363138376130
37353461346364343563616464623837326665656532633062366439313063336639633635643837
34323738373631623937653533366330333635323436346630633466666665323234613938636330
65333936633766343930
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

@@ -1,7 +1,9 @@
---
app_name: "transcriber"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
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 }}"