Compare commits

..

63 Commits

Author SHA1 Message Date
e5c1e19e5e Backups: fix host name
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 16s
2025-12-21 14:18:05 +03:00
8439ab3693 Outline: migrate assets to local storage 2025-12-21 14:17:47 +03:00
dcb09e349b Secrets: update outline secrets before migration to local storage 2025-12-21 14:17:30 +03:00
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
57 changed files with 1196 additions and 543 deletions

4
.crushignore Normal file
View File

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

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
/ansible-vault-password-file
/temp
*.retry
__pycache__

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

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

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

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

View File

@@ -4,16 +4,28 @@ 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 List, Tuple, Optional
from typing import Dict, List, Optional, Any
import requests
import configparser
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(
@@ -26,51 +38,199 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
config = configparser.ConfigParser()
config.read("/etc/backup/config.ini")
RESTIC_REPOSITORY = config.get("restic", "RESTIC_REPOSITORY")
RESTIC_PASSWORD = config.get("restic", "RESTIC_PASSWORD")
AWS_ACCESS_KEY_ID = config.get("restic", "AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = config.get("restic", "AWS_SECRET_ACCESS_KEY")
AWS_DEFAULT_REGION = config.get("restic", "AWS_DEFAULT_REGION")
TELEGRAM_BOT_TOKEN = config.get("telegram", "TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID = config.get("telegram", "TELEGRAM_CHAT_ID")
NOTIFICATIONS_NAME = config.get("telegram", "NOTIFICATIONS_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):
self.errors = []
self.warnings = []
self.successful_backups = []
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 get_home_directories(self) -> List[Tuple[str, str]]:
"""Get all home directories and their owners"""
home_dirs = []
home_path = Path("/home")
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))
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():
for app_dir in source_dirs:
if "lost+found" in str(app_dir):
continue
if app_dir.is_dir():
try:
# Get the owner of the directory
stat_info = user_dir.stat()
stat_info = app_dir.stat()
owner = pwd.getpwuid(stat_info.st_uid).pw_name
home_dirs.append((str(user_dir), owner))
applications.append(Application(path=app_dir, owner=owner))
except (KeyError, OSError) as e:
logger.warning(f"Could not get owner for {user_dir}: {e}")
logger.warning(f"Could not get owner for {app_dir}: {e}")
return home_dirs
return applications
def find_backup_script(self, home_dir: str) -> Optional[str]:
def find_backup_script(self, app_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"),
os.path.join(app_dir, "backup.sh"),
os.path.join(app_dir, "backup"),
]
for script_path in possible_scripts:
@@ -85,16 +245,20 @@ class BackupManager:
return None
def run_user_backup(self, script_path: str, username: str) -> bool:
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} as user {username}")
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, capture_output=True, text=True, timeout=3600 # 1 hour timeout
cmd,
cwd=app_dir,
capture_output=True,
text=True,
timeout=3600, # 1 hour timeout
)
if result.returncode == 0:
@@ -102,132 +266,91 @@ class BackupManager:
self.successful_backups.append(username)
return True
else:
error_msg = f"Backup script for {username} failed with return code {result.returncode}"
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"User {username}: {error_msg}")
self.errors.append(f"App {username}: {error_msg}")
return False
except subprocess.TimeoutExpired:
error_msg = f"Backup script for {username} timed out"
error_msg = f"Backup script {script_path} timed out"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
self.errors.append(f"App {username}: {error_msg}")
return False
except Exception as e:
error_msg = f"Failed to run backup script for {username}: {str(e)}"
error_msg = f"Failed to run backup script {script_path}: {str(e)}"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
self.errors.append(f"App {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()
"""Collect backup targets according to backup-targets rules"""
backup_dirs: List[str] = []
applications = self.find_applications()
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)
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 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
def send_notification(self, success: bool) -> None:
"""Send notification to Notifiers"""
try:
logger.info("Starting restic backup")
logger.info("Destination: %s", RESTIC_REPOSITORY)
# 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>: бекап успешно завершен!"
message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
if self.successful_backups:
message += (
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
)
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
else:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап завершен с ошибками!"
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
if self.successful_backups:
message += (
@@ -240,59 +363,55 @@ class BackupManager:
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}"
)
for notificator in self.notifiers:
try:
notificator.send(message)
except Exception as e:
logger.error(f"Failed to send Telegram notification: {str(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
home_dirs = self.get_home_directories()
logger.info(f"Found {len(home_dirs)} home directories")
applications = self.find_applications()
logger.info(f"Found {len(applications)} application directories")
# Process each user's backup
for home_dir, username in home_dirs:
logger.info(f"Processing backup for user: {username} ({home_dir})")
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(home_dir)
backup_script = self.find_backup_script(app_dir)
if backup_script is None:
warning_msg = (
f"No backup script found for user {username} in {home_dir}"
f"No backup script found for app: {app_dir} (user {username})"
)
logger.warning(warning_msg)
self.warnings.append(warning_msg)
continue
# Run backup script
self.run_user_backup(backup_script, username)
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}")
# Run restic backup
restic_success = self.run_restic_backup(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 = restic_success and len(self.errors) == 0
overall_success = overall_success and backup_result
# Send notification
self.send_telegram_notification(overall_success)
self.send_notification(overall_success)
logger.info("Backup process completed")
@@ -307,17 +426,56 @@ class BackupManager:
return True
def main():
"""Main entry point"""
def initialize(config_path: Path) -> BackupManager:
try:
backup_manager = BackupManager()
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 success:
sys.exit(0)
else:
if not success:
sys.exit(1)
except KeyboardInterrupt:
logger.info("Backup process interrupted by user")
sys.exit(130)

View File

@@ -1,11 +0,0 @@
[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 }}
[telegram]
TELEGRAM_BOT_TOKEN={{ notifications_tg_bot_token }}
TELEGRAM_CHAT_ID={{ notifications_tg_chat_id }}
NOTIFICATIONS_NAME={{ notifications_name }}

View File

@@ -0,0 +1,18 @@
host_name = "{{ host_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.2
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.2
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 }}"
@@ -37,12 +37,8 @@ services:
GRAMPSWEB_EMAIL_USE_TLS: "false"
GRAMPSWEB_DEFAULT_FROM_EMAIL: "gramps@vakhrushev.me"
# media storage at s3
GRAMPSWEB_MEDIA_BASE_DIR: "s3://av-gramps-media-storage"
AWS_ENDPOINT_URL: "{{ gramps_s3_endpoint }}"
AWS_ACCESS_KEY_ID: "{{ gramps_s3_access_key_id }}"
AWS_SECRET_ACCESS_KEY: "{{ gramps_s3_secret_access_key }}"
AWS_DEFAULT_REGION: "{{ gramps_s3_region }}"
# media storage
GRAMPSWEB_MEDIA_BASE_DIR: "/app/media"
gramps_celery:
<<: *gramps_app # YAML merge key copying the entire grampsweb service config
@@ -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

@@ -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,7 +1,7 @@
services:
netdata:
image: netdata/netdata:v2.7.3
image: netdata/netdata:v2.8.4
container_name: netdata
restart: unless-stopped
cap_add:
@@ -16,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
- "/:/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
@@ -96,9 +99,6 @@
# 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
@@ -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
@@ -191,7 +191,7 @@
# registry expire idle persons = 1y
# 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,8 +314,14 @@
# /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
@@ -314,8 +334,11 @@
# 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,40 +349,28 @@
[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
# update every = 10s
# command options =
[plugin:apps]
# update every = 1s
# update every = 10s
# command options =
[plugin:systemd-journal]
# update every = 1s
# update every = 10s
# command options =
[plugin:network-viewer]
# update every = 1s
# command options =
[plugin:charts.d]
# update every = 1s
# update every = 10s
# command options =
[plugin:debugfs]
# update every = 1s
# command options =
[plugin:perf]
# update every = 1s
# update every = 10s
# command options =
[plugin:ioping]
# update every = 1s
# update every = 10s
# command options =
[plugin:proc:/proc/net/dev]
@@ -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

@@ -5,15 +5,16 @@ services:
outline_app:
image: outlinewiki/outline:1.1.0
container_name: outline_app
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: unless-stopped
depends_on:
- outline_postgres
- outline_redis
ports:
- "127.0.0.1:{{ outline_port }}:3000"
networks:
- "outline_network"
- "web_proxy_network"
volumes:
- "{{ media_dir }}:/var/lib/outline/data"
environment:
NODE_ENV: 'production'
URL: 'https://outline.vakhrushev.me'
@@ -24,16 +25,8 @@ services:
PGSSLMODE: 'disable'
REDIS_URL: 'redis://outline_redis:6379'
FILE_STORAGE: 's3'
FILE_STORAGE_UPLOAD_MAX_SIZE: '262144000'
AWS_ACCESS_KEY_ID: '{{ outline_s3_access_key }}'
AWS_SECRET_ACCESS_KEY: '{{ outline_s3_secret_key }}'
AWS_REGION: '{{ outline_s3_region }}'
AWS_S3_ACCELERATE_URL: ''
AWS_S3_UPLOAD_BUCKET_URL: '{{ outline_s3_url }}'
AWS_S3_UPLOAD_BUCKET_NAME: '{{ outline_s3_bucket }}'
AWS_S3_FORCE_PATH_STYLE: 'true'
AWS_S3_ACL: 'private'
FILE_STORAGE: 'local'
FILE_STORAGE_UPLOAD_MAX_SIZE: '262144000' # 250 MB
OIDC_CLIENT_ID: '{{ outline_oidc_client_id | replace("$", "$$") }}'
OIDC_CLIENT_SECRET: '{{ outline_oidc_client_secret | replace("$", "$$") }}'
@@ -54,7 +47,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 +57,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 +69,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,6 +1,8 @@
# Refer for explanation to following link:
# https://lefthook.dev/configuration/
glob_matcher: doublestar
templates:
av-hooks-dir: "/home/av/projects/private/git-hooks"
@@ -12,3 +14,12 @@ pre-commit:
- 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

@@ -7,7 +7,7 @@
vars:
backup_config_dir: "/etc/backup"
backup_config_file: "{{ (backup_config_dir, 'config.ini') | path_join }}"
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 }}"
@@ -23,7 +23,7 @@
- name: "Create backup config file"
ansible.builtin.template:
src: "files/backups/config.template.ini"
src: "files/backups/config.template.toml"
dest: "{{ backup_config_file }}"
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,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 internal application directories"
@@ -33,6 +36,7 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}"
- "{{ caddy_file_dir }}"
@@ -58,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
@@ -54,3 +53,10 @@
{{ 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,6 +20,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,6 +32,7 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
@@ -53,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,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 application internal directories"
@@ -30,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 }}"
@@ -43,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 }}"
@@ -62,3 +91,5 @@
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -4,7 +4,6 @@
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,16 +3,22 @@
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 }}"
media_dir: "{{ (base_dir, 'media') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
postgres_data_dir: "{{ (data_dir, 'postgres') | path_join }}"
postgres_backups_dir: "{{ (backups_dir, 'postgres') | path_join }}"
tasks:
- name: "Create user and environment"
@@ -20,6 +26,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,8 +38,33 @@
group: "{{ app_user }}"
mode: "0770"
loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ media_dir }}"
- "{{ backups_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}"
- 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: "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:
- "{{ media_dir }}"
- "{{ backups_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
@@ -40,16 +73,10 @@
group: "{{ app_user }}"
mode: "0640"
- name: "Copy backup script"
ansible.builtin.template:
src: "./files/{{ app_name }}/backup.sh.j2"
dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,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

@@ -4,7 +4,6 @@
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,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
66323565663433656434373835653439643630386365303736373738353664376233343765393637
3735666334336164386538623665333634613564393962340a343336656665663435313761323139
66613838386237613936613936366533306463356331393463616436333038613036616130396664
3161656630656330340a396139313534646162643437646463336566306534346664306133353564
31663439356666313139626235393834326561323065393462663961353534646538396234393838
33653163366435623633343362343234613334376430613132336234333935663931383233643265
30343737323337386130396234376661626666646531333232353934383862326566626261366531
32313733313265373837383536313430366362393032396536383137333337656330373930666536
63316162353332393138626531663063646437656130376265653138313239383962633438613832
39366335613335313765373736316530376562383764636164613132393066643966646439323831
63663836313533336663323163666431616536326163396237343365303463323237313162666139
39353537316339313463663234373433386534323965653236613638643330613831396434386662
64383938623231376665613034383036396132633265373036386234363733383663383062323337
36326364313437333835366162383064656662333732653263326331333537326162663638663431
65626335336132653362336331393264616531303465643330386630303730643532373337316164
35316431376439303437323532386436623030656666656332613265303933366365383031313064
66623336653038376162343161376663663163643136306132313437663365616464386665613239
33626335633330663535366234373465353632623963336163313231323234666137336439353739
36626261373563316161393465323935653862323165616138636662383736326536636337366334
34646164373939633431656430363966383133373236383337633738353536326132313733303665
64643336656235656664633834623833636263333032626461353363656238393131646562643265
38343364383138646663613362303231333961336665366230633832316238646534316534343865
39366461626432636431323235313364626366396638343239646465363937636530333535633439
34393635333030353966323635343261353563303234633238313635313535343331613931626235
32636265376631623732643439333639336637663039356661386665363939623963626361636139
31323439313938336137316466373634363736666136376633633734393765313335636334336532
34393262363835326139636335323739313531336565396664346635623934616461343239623035
64386264386663393466633635366665346463323336643839656566663536333338346162353235
61373866333134333634366638383231643662343730633539396164343935623362646662306239
34383432653930353430376466623236653834653566633335336632303063616135396365653637
38386165306432306638616134393636393862323836393865636130623162616161323531613132
39623939383437353633323932376536636434323437363065653361363532653935613636383939
32343866303930373033316136373134323735363164633663626637303735653561643832393162
66616133643466376638633536353963393662383639323632393061623361383635353237613437
36373236336461316330616438633031663035326433356533333265626363393332346464363739
32313937386333616261346565633430393064333739393064383131626131626137346463343362
61646234396361313936646332633332626337663561336561633362333436346439663963633032
35666539323031626166363333646632633732616137343962383231663938633035386435633833
66393534633734346131373563613066396432613037356537613365323739336331376132356231
31313434353135666562613365636266323732303433383036646430626365623632613734343432
66333165303131306236396566373561326565343166303830623033653861356633333437316264
61633934633266656333303864623031663662383638396631646165373036643266653434383066
31313633316430613430313961656332656438653365383933366437633030313162386161356261
64613365386433376363623161323435326465633339336637653362613463353264333030373830
30353034313963323438643030666366366533326239343061646362626330353762326366323939
65303361663265326333326330656331386161656164373663303333373139663333623863313966
62326331653030336136326537653337333164366435326233366565666130326162643463623332
37663966383439633937356263646534646137363138666461383865616136646265303961363231
30636566333933323530326434663766313831623734386534356533386434346139646466366631
62613863336235633531646633326539353564383162663962346161366239653539313430666664
61656433333739653338643739373434306331376563393733613466303962633866373631373437
35343535353631353062616234643139393934346137383630316332373961313235633633646238
38316635653462643739353937666663343865643065396436393831366463316463383134306363
39303037643637393861373136643539326439396562646136616332323938653966633730653765
66663865636363336232356165313237323266353237336164326431346263616534653739656433
38643033356137363936373932323562336264653337303738306564326662363238303365646334
36306665353261633365363366366466396330656335656363326231376564393632636365353864
63393434383136393333336336336639343362666463393438653338663039353561323039616239
34633335396164366566653435313139633165383265316333373135616339303562313739336532
38373436316336343662343731323161616239306330323233646664363232633236333237323163
63386664303766326662356539666434393665323462333139663865326232343936313161626630
35393532313234333565303661613035306638313964336138303066306161653537376166383138
31343430643139643465376663303537386536333736326534313631636135633531626233376231
66313136383263316363376131313434363964643135613666643863363038356338633833356563
31396465623731646463613462323232626465613465353536636466663937613534306438363834
64306534383562373834313639303933323233356434643331386562356531393463656235656531
32323538623462366663373365613539626436376634613834356331633633643936366536356138
30613662336136393465336538313462363966666130353536353139356430383331373030303937
64616465353065366566323533363335646631386664313033386537353730336239653964333365
32336138373938313863346431613234366630356537323463323963346265363665313563393634
63363132373362343536633063626334386464386438376261363336313037326164393238613439
31356535366166366535633336376333663932666532323230656162633435333762663465623237
38363639653565383532303161313539396264666163323630393865313665343861353137636164
37313336346162643534623464623763333130303564633262366330666161393065306630373136
66363465616364623339366232303366623233616331646564303464313137633165313232663561
34653232323862386163383565643364313062353236333133356339656136633631333838623835
34376236333837366662333733633933633735613865613939306535396232363439313962313533
37636536303730616535656465316230623137623961613433303732666330666332346664383237
31343961306635313966333039353930313938303237396636383365666364626361633765376534
65643261386334616130306334613966343937373135623035663030366362623461363936336236
66353833613337333735336164383064366365346163346638663033393262316537616235373334
31323335623331366164373030656565613963306533303030363963613739363464623063626565
34366237663037626635653938323733633462666562353765633337633863306433616136363262
66313366633666373238333366316165616437623639326136613234316337656234306664363665
34363664366632376334626263373036666335613134643331326337383835613238623761303630
32666137323330346262653164623934383730613962343439333333643866613364306561396138
36353262306530376466653264633231373433356362383132653139643830386262663964653631
61346466613738633131366239356666623536363330633735383966633265663264626638653564
35663437306532636531356334343232313936373334346430343865303464366232666663326433
63666432333935383137396364636631383663323437386161616630303933633839636133643936
36363035316632373035663565653663623162373438386166326462383165343965376433613436
66656165326632323632336664663638373337616563383562376231623164353936316561373461
33386132653232626362353430636433323638633131366133393061333665346564336663323165
63623330623237666232666232303237613564666432636630393665623837356636383038353732
32316364666337646663306462346665623565353130656262633063646663343136333661333135
61303261343334313834376664616634333632656666333436303531303233633362393865626637
39343539333139326135326337643264393065356638613538626363633939343833376634643363
37363238303638363532386163626231383931316438306163663238393936336630323331643936
36623530663530343436343431633736393964336635323930626164386464646437356233393833
64633363313332353862663831383465613965396132346632626532353236666130633762346533
62616232636563626630303763366166376166623633636137353632646231376238303737636339
33393239323362616433333834626130336637636634613537323663303961393565326564306135
31356339346164626532306130343931346261336361663462613236356134303639393535643233
38373864396166633331346361386364373061306633326262306164353964633833313163343562
33376265303462373330303739353462363139623530326163663861373361373030656264373432
61653066616161613136306164373432366439316638306165643264356263643466633734346463
64383738636532353237343932383734386232613434366134323863613236316365333932653565
36633136316138373337313132306236363562346238636432336262376437306536343035346430
38303634663839663236336236323336623531323064376236356531636337343938323839633432
63663137383561633066366330303666626336613465623861366563623263656166303361353139
31353434313366353032313534613464633830643164393032623066346432666533323734663838
39313938386130396465373763303061656463396334663261353234326532653435356135346233
30333035363834376235313762636636343337363463653563323862393439613038396230316533
31323164623839323864343031386639323065663763383434663665323436383435656135346135
31633436383732303963326230616632363838633366326135376266323930636365663237343163
31306461666439613839313832303639386631333665633965396234363539313532363733633831
33643366353563626263626539363031323262383634643266356262346333376330306339386561
39636538373633396565393739333966333965353330653936633436303764633834633563373434
64383561316161623639643131633862336630633563343738616265303139633336656564633236
64613339623064636561626631313931333963313536396665366231306562646565383530633039
39656636396432643561656361666231393736383330643262376630316634303836653639383434
32666431313337393530373265643833386332656636316461303437613736383236613066333431
39343661376464663936633833613463623132383834666634383438313161623836653361353135
37313237643561303666366265616330373131313066643831383462386265376336396130613034
32386331343931323364623565326535626637343730393539363834336532323734656663346164
33643038613931396165626234363732313866323438333739356432333461646330613465633139
62316464646561646136316634313262323030373030663761376536333136326138363166383230
31316662313433376264656437383333303132643632623564623038663261633638613835316131
61643135373439336462323361353739363161653233306439333766343939386364616565643332
31383039666137653366326434633639323336656464336339363139336632353739306263393139
65646539626537613664653661613533383135626566303264363637353239346463333862306437
36346635646562343838613836303364383766626134343566356662626533336631343564373434
37336133316635396366383837363566656336636138393538393266343466613838393339353936
31393136396131363062343265353166646433653436623232356363636337353937613863653330
39643732626265636630616338633336643936623861366362366362333934373939613966656137
34396637393332306566356334326438643931616336323035353630343964373933663132653434
31626565616362313330303666633833326337366239323662663232346265373537313537306664
36316133666565623565633365666565626361396266393364316164313365663933346139666138
36383065636265333561306161333031363961353339613063303631643833346563363338373839
37383636363238613764623737376266346138386336663663653430323236323739666630653563
39386230643632633837336565363965316666613030643432383165346164633437333763613136
30323665613366653936393733303463353762336331343636376263343764666633333332316566
30383564643966653163363034616233393165393262646235326237393961663466343365646661
39663935366531303237376131646530623338326538316430616630613835363534663133616334
31613362336163313535616361636130643138386661626461306265373761653334356234613231
65613934646536336165616362616661613030353734663434616438653364393137363630333237
64623735396563623235323864626432316162663165313365613136393331353561313438346634
62626533636161663361636466353030356663356166393838366261323538343966623439336565
39373439653832386361616632313033326434326563356639363838303861613564396565633037
63636665363939346337383562353235646230366366356330313330333762656232396439373538
39646635386463326636363861653230653836376463663562653531323134663435356233353432
30633065646138653032363038633537363164613363356165393562663431643962633237363333
38353739643464623538636366616166643235316662313330383434366530633965303562616630
38376330656233326431316331636666626336303065623433613333616431363064303239323033
62623266386531363261346531653863306233316534396236393264623531633462363131326537
65393539313038653133
35643431306561396237633439336230333563316364353462393534646565653163396362393566
6636306532353662343437646631363766376166333934340a363062323332386261643832323762
33303235646434636636366532626331653865666465643561633863663938313535623162306334
3332616236326436650a383636393434303165363964383966633138326361373134306563653363
37376465643762383761653132636336663762656136303730616463386134346436383539653661
35623933346565633030373730316331633236623561313166376335636464333439643637623338
32393433653339656135343766393565366530636132346339306339353438396337303534336266
34303635316363343031363263626434386139633762353038666266643338343631303530313661
61373239663739666535636466393937333437316334306631336334303961396134323062666339
62303437623131626130363838353736386463663734356530333061353031643861636437323431
62633036316639306366643633333366386132343337313832343133313235626162323266306165
39326434326235373536633464313234623464373465653061383834363266663435303461633834
36663031323634326436303138623262396332363934646337353166373163326132626437636138
38623864666434363939663234623732623962323836323532393031396638343737373764303239
31316639653066373166326432653930666630393061366461663337333936326239376165633064
32356530333338303636366234336135353130646134643432613965366430326164393563383135
63653237646237323832613363643732383436646539396239333632353566386135633961316132
62313630313065376465346338663661343466366364363761616363376535373031383736616136
37343634626366356261363030616330396263623965306166386238363039663461396431303733
61626164353466323762666236376536356364653565333266363730653539626531663639333864
32366333336234376635616361626635616265376361316439653230646537303031343564653631
63663237353766643766343130613739386636663534336536353936353561616538316535333064
62343835643636633831366265313939313433326239633130303863303136363732653766303663
33643231363761336463623339323831363034636532346237373236656266303063633365393636
36663066336233643236393837323762393435303665303661356237666237646464326536623731
64396431323561353832333834336232396366663439643161626338653930633665346566303435
66636466313936316530353537643637323833653561616339353465323466333332363436613461
38653837373637343532343662366435616366313831623239343363366131633062303036333135
36356136376236383431613266663164616135343062666136353430626663653631313064326131
63353066313433653838613934323833373237356230376363376132306663656461653264383866
37333863633536346538366665346666346364633733366434393135353664336330623865343132
31396534643633333134363465323063313435333236646630613066353232613039393235366630
36646664366339613535616437376437326561636130333063626637316431626330386137306162
65393533306439613032623061373331333762643336633863333061393734313335386633666338
31316637643031353462363363626662373462633065653738613733633933623432343535316438
31356530306263303566636535363465356562633439386365316461323339393339643133323032
32616662633565346564643737386266623836623830663265323439313762636437336436346430
65353266373766363166633363636231623031363337626631626466616637616630383632333565
35303737316531306237613832366565376364313435376266306263623139663964336635633030
39306662616233646136336237646139326239653435653436323066333332626266646564616631
31653433663539333633663433643261363065663030373265646533323832323366376230346266
65616633363938333037656663393334363564626136333162383162663437653565663532646366
31343063306434663231396464646232353335636138393038633737323339303033373539363739
64306163343534363131646135396234346166313039323833656636343235656238623333626534
30653264656166326562343534313063326332336330623530646563313262396233386335316538
64316363613865313236633437646139353433386234326537633961356133353361333833636339
65646232336137626130326631626436623933353333383438306237326132303838353833376432
31653936363131623535386331353066323462613238636130376562376361643166363332343537
63346366306161373666306536396164323437613066363537366562666130653638356564383235
66303838616266623335363338376535303665643363326662376166643865383039306236393730
34303130386631636636393736616237633933346136303530373033383739653531326437656463
39326239616163313463636232343036626435366261643030386530393763356133663863373264
62316164386464366663373635393365363161336530313734343633346563396666616262396563
37383638613930366635623962366662343232303635636330383534653861336264396132653538
65306539613333306339643063303063333165653734323333353461356536663661613463633632
61613530653734643761616636386238633535643961633765313863323961313362323265373132
65346530653962613534343062333039663837623130666261636135343663643036356261643333
64306430343136616438636231623236316264623533313439643434343333623030376338663365
36393932613161316137386136343939383035326231323561636337343833613635643261633165
38316233323862666439633833646235623031336638353834323633333562346463333436316437
36303237353138353763346166626465663137323062643633323762313137343661323839353231
38643137383739393866616364386262363733316133313934366165353761643164303439643534
37633636323535373964323939346339633138386239333466363239393430613139623466643531
66323662363834653166363835343363303066306262623432353165313239323130616235316264
65376532623530353935346238346261613062633439333630643962343233326234316130616538
64353932613038353866383263333561623632663863373231636132653365613836363730636263
65626666653962653266343134343638666437373532333464633163333861386632356562346637
62303433383464373165316561646633636130636434633430623031633364643336643835613030
64653937656262623536353764343661326632303964393862343032343330653534353530326238
34613236336339653364333565313862613531643763633166363734316363643534323838333531
36353137653535393862393534303366366464613035376439383134336534366263393661646564
39616233323638653337333461636465353935613236646430623163656334376463323135323761
36353064646532323738363837663238653766366634363232323933363033656332626436363166
36376366336236626265373530646664633335623035646662663261353165313634646463316535
38633862623433613937666162343936393337336537623663306439373830326434356366323664
61646337396562373535353834393665643037626539303932663831306535623733626239323939
61616165643439653235346566346631323035306437323339326665363232333338616133666366
61396136353432376263333961353738343463663937313136336536623539383835663361353232
38343737383361613930326265346562356136313539393464623935626566343137346430356261
62633230396665316533313831346236303265303232363935313138323537373933633232343732
35346363663938306562663433396138393861343332373934303864303963623739316239303264
62393634346231393534633861386364336639366663353065333930616335323438383537616666
63373836363839336634316338643935346634626437326431363132636335326238353930663662
62383464366263336430633338646263626565393030646230306230353837313965623430636138
38393334623661363532663464363163383365323736386134393634333332353263353763393636
35353535303030356665303262333334353630613832323733393638613035343833356662353565
64373334626565653631613863323866336261393533333639326266343435376666646131313535
39343734326162623831343730303734643830626338653232633834373565643133393562653336
35373432316161623061373238333932366161663137616337613632366263373830663464396537
37303363386336663663303161343963633834623564643661653332623434626466376637323631
64653265623861363837346462613164373264313766633034353531666561623732353838643639
37646262646362386331663334666131336335313833313463373463313439353934313733303763
65626339363732326565366464633230303933386438373539633338363439393630316164376565
63333132613739663461346361613761396161323863616263333638376437613139353733613963
32663062393435323061353738316132363438393762316666626231386138366639313566646461
36306664343433653836316436303736343436343762366133373066313232333834363536343462
30303866646131663530313463343261633261663932323562636135346664323761343765333730
39663231636465336361393062666135316534656637306236323137653939383565643637623866
33633536366163346466303864313732303036306665326664353064366134326539333831663632
64343533323166623661326536393437623432336435366435396165333431366532653936396636
30653263306461656230663731336266363832616539373333306238363834333838393063383432
61343238373861303932653162663330333964623834336235336430376562313963613134353834
63666537343837336531633536633038666232343165336663356639636537306333303434383231
64306633383338646362633238386263336632393562623330343532373839663238383965643739
39313363383331363339616431653434366131303666623263326662636161666635643162663730
62356136643364666164383932376536623266373535613230336165396163383830346265393738
30636139353336343630316132366365306234313064636537646262666463646437376336366562
31663731666664313339353331626534626462663431633530653365613762363666373532383762
34323866356239383332373632616464666230373339386235393838376333333464613038373337
33373231386263623664353231626331366565356662623934316632653763363739653862356265
39653539616232356333326463373666656133303161356630393839336638316338646238623138
61343833613130376562656465343438653861653431313762313062656563643665356664646465
33343430663066616536393935366132336666616164356463323663373930653266653238663336
35616263613461356333633430653661376239663363653434346437656161623961333761633663
35643031323063666163623639313864313937386339376235653033366136373333653239653238
36376636653766366632613262326336333430353534326464653931663366383039643466303739
66393065323965383962633266623033643330333437383262373461363539626333653433356436
64383738653735386630346264643462353336313830376139353166666330303638386639623039
34303137623936663636303864623932343766323235653934303263363164326534653137613863
39353739633466376136343938303634643565613537646166326536303739316536376162353838
61326235373733623465313530393831623438343530616339326561386239646562343130376336
63373430313332613561623636393338353933613161656264646366633665373336343530303635
35336561336334626333343030366264356264343062623531363331313466666136666332376265
62363862623635376631336235353531343931663131333861616566356438396664633962646236
61343063383238376261656465386635623263386430656132393631623330326462326166643631
34333238663531366232393764626665376465386336653133643335656161343261616532386135
63333931646435373630323931396564636131616234346130393566663131353835323833666338
63653332383130666164376534656430366664356339323463393338353066663537313336653864
66323361383737323930663732376261323238653537623335636638363966343264346537383264
64663334366538326365663266613662356132646230363461643862643337633531333233363134
66313565313733326363623935363535336166623761346162663964396266613932356462376664
63313339333035333234353765383735303830653162346565303465363762623465326431353431
65616335313733353863626561613236616339393038383562313263376633363765306262396230
38373963306637316534613964376538666536383235393663616236653338373034363765636135
65666239356563363538666162313038633861353732386630633930616439323838666536383165
34353761613738616361613862656236623633356364383436383435383632303635343233356435
35353466636461376436643965653837633461643430386565353530323333313964376233666665
63353838323363666435393634643237626663356331653461303936366533343435373732613861
33393231636235613234383737633430343461616261373265313163363730653237346563393835
38646566313730633866636162393434316365396663333938656235613635613161363938653531
65316233643561623837633362383232313664313737643231363838616434666635343466653435
62653765303832353761346563363338656431313439346234333432363535626461336163616330
33306633313764616533666263633634306633633261663637633133356430313231323331353535
31316663373063376665373732366365386464393032313335376463353435313133376161336263
33363835363062646536343833383232386133666137313539663534653138333236626438333866
39316433626133666636376532366332346134646233383737386337356464363263343334363663
32363538343566666263396265636261643764383736643663386333623266363535333766353037
33333864636539636165373433343532326431316138666164353262643236623237636539646636
30356361663863646366303238643335626434353832306338346231383764616566626163313931
64663663613363396661396564383531653231663735653535393437356339353466353737366266
37353739646632393837353032636331316338393564643261383536383536323036333238633237
38643735663339316263633964336137303939303531626633646236656430373132316432343436
66383135663130323462373934656666653837336332626137303931303263613038646235623631
39383936393665316561373637333935643565656433316462333832323034323533316232656164
63656630356363336231326364656531623839316236616266363037303138306537376131616134
31323132613533663664376136376437623837303835613331623339623531653563386464306339
62346465396362303262356239326636666435343131333566653661613463363461633631383030
63303738613735313262656362383432356236613339646462393836633861303562663262333561
62323562656461663764336462353230653537383038323931353831643731343837323234643565
36386531613931623036636332663561663438333364616232626461333639326564313335376134
3935

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