Compare commits

..

59 Commits

Author SHA1 Message Date
a31a07bd16 Backup: remove old config
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 16s
2025-12-21 10:13:05 +03:00
54a951b96a Backup: refactor notifications 2025-12-21 10:10:12 +03:00
e1379bc480 Backup: roots parameter 2025-12-20 21:33:21 +03:00
037e0cab9b Backup: restic backup refactoring 2025-12-20 21:31:39 +03:00
2655869814 Backup: support for multiple storages
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 19s
2025-12-20 21:19:06 +03:00
0e96b5030d Backup: refactoring 2025-12-20 21:04:54 +03:00
a217c79e7d Backup: extract restic storage into separate class 2025-12-20 21:03:32 +03:00
6a16ebf084 Backup: parse config to dataclasses
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-20 17:44:02 +03:00
2617aa2bd2 Backup: support multiple roots 2025-12-20 17:27:29 +03:00
b686e4da4d Backup: change config format to toml
With support of multiple config values
2025-12-20 17:13:35 +03:00
439c239ac8 Lefthook: fix python format hook 2025-12-20 11:55:13 +03:00
acf599f905 Lefthook: check py files with mypy
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 18s
2025-12-20 11:38:14 +03:00
eae4f5e27b Lefthook: format py files on commit 2025-12-20 11:35:54 +03:00
4fbe9bd5de Backups: skip system dir lost+found
Some checks failed
Linting / YAML Lint (push) Failing after 8s
Linting / Ansible Lint (push) Successful in 15s
2025-12-20 11:22:24 +03:00
dcc4970b20 Add owner and group to backup-targets files 2025-12-20 11:18:37 +03:00
2eac1362b5 Wanderer: backup all data with restic 2025-12-20 11:18:11 +03:00
e3d8479397 Memos: exclude media files from gobackup
Backup media files with backup-targets
2025-12-20 11:06:56 +03:00
91c5eab236 Gramps: exclude media files from gobackup
Backup media files with backup-targets
2025-12-20 11:04:50 +03:00
ca7f089fe6 Backups: use dataclass Application for app info 2025-12-20 10:48:40 +03:00
479e256b1e Backups: use constants for file names 2025-12-20 10:36:19 +03:00
11e5b5752e Backups: add backup-targets file support 2025-12-20 10:32:00 +03:00
392938d0fb Gitea: upgrade to 1.25.3
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 19s
2025-12-19 20:35:53 +03:00
2cc059104e Netdata: upgrade to 2.8.4
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 20s
2025-12-17 20:18:12 +03:00
d09a26b73a Gramps: update vars
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-16 20:36:51 +03:00
097676f569 Gramps: move assets to local storage 2025-12-16 20:34:25 +03:00
e878661cb3 Specify secret files to ignore 2025-12-16 19:43:40 +03:00
cb50c1c515 Docker: prune images every night
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 18s
2025-12-16 19:34:31 +03:00
33de71a087 Add agents.md file for ai agents 2025-12-16 19:29:52 +03:00
fbd5fa5faa Memos: upgrade to 0.25.3
Some checks failed
Linting / YAML Lint (push) Failing after 10s
Linting / Ansible Lint (push) Successful in 18s
2025-12-16 11:51:21 +03:00
faf7d58f78 Netdata: update config
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 17s
map /etc/hostname config into container
2025-12-14 21:22:00 +03:00
0a75378bbc Remove old ports config
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-14 19:25:33 +03:00
bdd74bdf2e Authelia: add backup for storage database
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 19s
2025-12-14 18:10:59 +03:00
78bee84061 Outline: add postgres health check 2025-12-14 17:58:04 +03:00
7b81858af6 Miniflux: change file names 2025-12-14 17:51:06 +03:00
08fda17561 Gramps: move cache to separate dir 2025-12-13 15:40:56 +03:00
841bd38807 Update valkey to 9.0 2025-12-13 15:35:43 +03:00
fb1fd711c2 Dozzle: upgrade to 8.14.11
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-13 14:57:41 +03:00
ecf714eda7 Gramps: reduce celery workers to 1
And update valkey to 9
2025-12-13 14:57:23 +03:00
81f693938e Netdata: upgrade to 2.8.2
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
Tune config, setup update every 10s instead of 1s
2025-12-13 14:46:15 +03:00
10d67861a0 Netdata: revert to 2.7.3
All checks were successful
Linting / YAML Lint (push) Successful in 10s
Linting / Ansible Lint (push) Successful in 17s
High cpu usage for dockerd and containerd
2025-12-13 13:29:40 +03:00
3f5befb44d Netdata: upgrade to 2.8.2
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-13 09:52:08 +03:00
1b75ddaef2 Disable python docker package 2025-12-13 09:51:49 +03:00
7d6ef77e64 Authelia: fix run-app behavior 2025-12-13 09:51:35 +03:00
ae7c20a7aa Add mount configuration 2025-12-13 09:03:29 +03:00
67df03efca Add combined application playbook
All checks were successful
Linting / YAML Lint (push) Successful in 11s
Linting / Ansible Lint (push) Successful in 21s
2025-12-13 08:46:34 +03:00
48bb8c9d33 Add combined system playbook 2025-12-13 08:38:12 +03:00
5b53cb30ac Add tag 'run-app' for application run
Useful skip run when configure app
2025-12-13 08:22:11 +03:00
f2bc221663 Outline: change postgres owner
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 16s
2025-12-11 11:26:13 +03:00
b41a50006b Exclude home dir from backups 2025-12-11 10:59:27 +03:00
c2ea2cdb39 Fix app user and group uid and gid
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 16s
Prepare for system upgrade
2025-12-11 10:52:27 +03:00
7e67409393 Applications: move to new base directory
All checks were successful
Linting / YAML Lint (push) Successful in 10s
Linting / Ansible Lint (push) Successful in 21s
2025-12-11 10:14:27 +03:00
6882d61f8e Gitea: move to new app directory
All checks were successful
Linting / YAML Lint (push) Successful in 9s
Linting / Ansible Lint (push) Successful in 17s
2025-12-07 17:53:08 +03:00
47a63202b8 Fix spaces in file 2025-12-07 17:52:48 +03:00
af289f1e28 Configure stateless apps for new storage
Some checks failed
Linting / YAML Lint (push) Failing after 9s
Linting / Ansible Lint (push) Successful in 18s
2025-12-07 17:40:32 +03:00
b08f681c92 Exclude lost+found dir from applications 2025-12-07 17:40:08 +03:00
8dfd061991 Backup apps from /mnt/applications 2025-12-07 17:16:43 +03:00
306d4bf8d0 Install dust for space usage check 2025-12-07 17:13:20 +03:00
dbd679aa8b Gramps: move to new app directory 2025-12-07 16:48:25 +03:00
47ed9c11c1 Add apps data directory (external drive) 2025-12-07 16:47:59 +03:00
57 changed files with 1162 additions and 523 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 /ansible-vault-password-file
/temp /temp
*.retry *.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: local:
## Path to the SQLite3 Database. ## Path to the SQLite3 Database.
path: '/config/authelia_storage.sqlite3' path: '/data/authelia_storage.sqlite3'
## ##
## MySQL / MariaDB (Storage Provider) ## MySQL / MariaDB (Storage Provider)

View File

@@ -10,9 +10,10 @@ services:
- "monitoring_network" - "monitoring_network"
volumes: volumes:
- "{{ config_dir }}:/config" - "{{ config_dir }}:/config"
- "{{ data_dir }}:/data"
authelia_redis: authelia_redis:
image: valkey/valkey:9-alpine image: valkey/valkey:9.0-alpine
container_name: authelia_redis container_name: authelia_redis
restart: unless-stopped restart: unless-stopped
networks: 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, Automatically discovers and runs backup scripts for all users,
then creates restic backups and sends notifications. then creates restic backups and sends notifications.
""" """
import itertools
import os import os
import sys import sys
import subprocess import subprocess
import logging import logging
import pwd import pwd
from abc import ABC
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Optional from typing import Dict, List, Optional, Any
import requests 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 # Configure logging
logging.basicConfig( logging.basicConfig(
@@ -26,51 +38,199 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
config = configparser.ConfigParser()
config.read("/etc/backup/config.ini")
RESTIC_REPOSITORY = config.get("restic", "RESTIC_REPOSITORY") @dataclass
RESTIC_PASSWORD = config.get("restic", "RESTIC_PASSWORD") class Config:
AWS_ACCESS_KEY_ID = config.get("restic", "AWS_ACCESS_KEY_ID") host_name: str
AWS_SECRET_ACCESS_KEY = config.get("restic", "AWS_SECRET_ACCESS_KEY") roots: List[Path]
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") @dataclass
NOTIFICATIONS_NAME = config.get("telegram", "NOTIFICATIONS_NAME") 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: class BackupManager:
def __init__(self): def __init__(
self.errors = [] self,
self.warnings = [] config: Config,
self.successful_backups = [] 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]]: def find_applications(self) -> List[Application]:
"""Get all home directories and their owners""" """Get all application directories and their owners."""
home_dirs = [] applications: List[Application] = []
home_path = Path("/home") source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
if not home_path.exists(): for app_dir in source_dirs:
logger.error("/home directory does not exist") if "lost+found" in str(app_dir):
return home_dirs continue
if app_dir.is_dir():
for user_dir in home_path.iterdir():
if user_dir.is_dir():
try: try:
# Get the owner of the directory stat_info = app_dir.stat()
stat_info = user_dir.stat()
owner = pwd.getpwuid(stat_info.st_uid).pw_name 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: 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""" """Find backup script in user's home directory"""
possible_scripts = [ possible_scripts = [
os.path.join(home_dir, "backup.sh"), os.path.join(app_dir, "backup.sh"),
os.path.join(home_dir, "backup"), os.path.join(app_dir, "backup"),
] ]
for script_path in possible_scripts: for script_path in possible_scripts:
@@ -85,16 +245,20 @@ class BackupManager:
return None 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""" """Run backup script as the specified user"""
try: 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 # Use su to run the script as the user
cmd = ["su", "--login", username, "--command", script_path] cmd = ["su", "--login", username, "--command", script_path]
result = subprocess.run( 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: if result.returncode == 0:
@@ -102,197 +266,152 @@ class BackupManager:
self.successful_backups.append(username) self.successful_backups.append(username)
return True return True
else: 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: if result.stderr:
error_msg += f": {result.stderr}" error_msg += f": {result.stderr}"
logger.error(error_msg) logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}") self.errors.append(f"App {username}: {error_msg}")
return False return False
except subprocess.TimeoutExpired: 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) logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}") self.errors.append(f"App {username}: {error_msg}")
return False return False
except Exception as e: 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) logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}") self.errors.append(f"App {username}: {error_msg}")
return False return False
def get_backup_directories(self) -> List[str]: def get_backup_directories(self) -> List[str]:
"""Get all backup directories that exist""" """Collect backup targets according to backup-targets rules"""
backup_dirs = [] backup_dirs: List[str] = []
home_dirs = self.get_home_directories() applications = self.find_applications()
for home_dir, _ in home_dirs: def parse_targets_file(targets_file: Path) -> List[str]:
backup_path = os.path.join(home_dir, "backups") """Parse backup-targets file, skipping comments and empty lines."""
if os.path.exists(backup_path) and os.path.isdir(backup_path): targets: List[str] = []
backup_dirs.append(backup_path) 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 return backup_dirs
def run_restic_backup(self, backup_dirs: List[str]) -> bool: def send_notification(self, success: bool) -> None:
"""Run restic backup for all backup directories""" """Send notification to Notifiers"""
if not backup_dirs:
logger.warning("No backup directories found")
return True
try: if success and not self.errors:
logger.info("Starting restic backup") message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
logger.info("Destination: %s", RESTIC_REPOSITORY) if self.successful_backups:
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
else:
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
# Set environment variables for restic if self.successful_backups:
env = os.environ.copy() message += (
env.update( f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
{
"RESTIC_REPOSITORY": RESTIC_REPOSITORY,
"RESTIC_PASSWORD": RESTIC_PASSWORD,
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
}
)
# Run backup
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic backup failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic backup: {error_msg}")
return False
logger.info("Restic backup completed successfully")
# Run check
check_cmd = ["restic", "check"]
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic check: {error_msg}")
return False
logger.info("Restic check completed successfully")
# Run forget and prune
forget_cmd = [
"restic",
"forget",
"--compact",
"--prune",
"--keep-daily",
"90",
"--keep-monthly",
"36",
]
result = subprocess.run(forget_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic forget/prune failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic forget/prune: {error_msg}")
return False
logger.info("Restic forget/prune completed successfully")
# Final check
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Final restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Final restic check: {error_msg}")
return False
logger.info("Final restic check completed successfully")
return True
except Exception as e:
error_msg = f"Restic backup process failed: {str(e)}"
logger.error(error_msg)
self.errors.append(f"Restic: {error_msg}")
return False
def send_telegram_notification(self, success: bool) -> None:
"""Send notification to Telegram"""
try:
if success and not self.errors:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап успешно завершен!"
if self.successful_backups:
message += (
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
)
else:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап завершен с ошибками!"
if self.successful_backups:
message += (
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
)
if self.warnings:
message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
if self.errors:
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
data = {"chat_id": TELEGRAM_CHAT_ID, "parse_mode": "HTML", "text": message}
response = requests.post(url, data=data, timeout=30)
if response.status_code == 200:
logger.info("Telegram notification sent successfully")
else:
logger.error(
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
) )
except Exception as e: if self.warnings:
logger.error(f"Failed to send Telegram notification: {str(e)}") message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
if self.errors:
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
for notificator in self.notifiers:
try:
notificator.send(message)
except Exception as e:
logger.error(f"Failed to send notification: {str(e)}")
def run_backup_process(self) -> bool: def run_backup_process(self) -> bool:
"""Main backup process""" """Main backup process"""
logger.info("Starting backup process") logger.info("Starting backup process")
# Get all home directories # Get all home directories
home_dirs = self.get_home_directories() applications = self.find_applications()
logger.info(f"Found {len(home_dirs)} home directories") logger.info(f"Found {len(applications)} application directories")
# Process each user's backup # Process each user's backup
for home_dir, username in home_dirs: for app in applications:
logger.info(f"Processing backup for user: {username} ({home_dir})") app_dir = str(app.path)
username = app.owner
logger.info(f"Processing backup for app: {app_dir} (user {username})")
# Find backup script # Find backup script
backup_script = self.find_backup_script(home_dir) backup_script = self.find_backup_script(app_dir)
if backup_script is None: if backup_script is None:
warning_msg = ( 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) logger.warning(warning_msg)
self.warnings.append(warning_msg) self.warnings.append(warning_msg)
continue continue
# Run backup script self.run_app_backup(backup_script, app_dir, username)
self.run_user_backup(backup_script, username)
# Get backup directories # Get backup directories
backup_dirs = self.get_backup_directories() backup_dirs = self.get_backup_directories()
logger.info(f"Found backup directories: {backup_dirs}") logger.info(f"Found backup directories: {backup_dirs}")
# Run restic backup overall_success = True
restic_success = self.run_restic_backup(backup_dirs)
# Determine overall success for storage in self.storages:
overall_success = restic_success and len(self.errors) == 0 backup_result = storage.backup(backup_dirs)
if not backup_result:
self.errors.append("Restic backup failed")
# Determine overall success
overall_success = overall_success and backup_result
# Send notification # Send notification
self.send_telegram_notification(overall_success) self.send_notification(overall_success)
logger.info("Backup process completed") logger.info("Backup process completed")
@@ -307,17 +426,56 @@ class BackupManager:
return True return True
def main(): def initialize(config_path: Path) -> BackupManager:
"""Main entry point"""
try: 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() success = backup_manager.run_backup_process()
if not success:
if success:
sys.exit(0)
else:
sys.exit(1) sys.exit(1)
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info("Backup process interrupted by user") logger.info("Backup process interrupted by user")
sys.exit(130) 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 = "{{ notifications_name }}"
roots = [
"{{ application_dir }}"
]
[storage.yandex_cloud_s3]
type = "restic"
restic_repository = "{{ restic_repository }}"
restic_password = "{{ restic_password }}"
aws_access_key_id = "{{ restic_s3_access_key }}"
aws_secret_access_key = "{{ restic_s3_access_secret }}"
aws_default_region = "{{ restic_s3_region }}"
[notifier.server_notifications_channel]
type = "telegram"
telegram_bot_token = "{{ notifications_tg_bot_token }}"
telegram_chat_id = "{{ notifications_tg_chat_id }}"

View File

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

View File

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

View File

@@ -15,10 +15,10 @@ services:
- "{{ (data_dir, 'gramps_db') | path_join }}:/root/.gramps/grampsdb" # persist Gramps database - "{{ (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_users') | path_join }}:/app/users" # persist user database
- "{{ (data_dir, 'gramps_index') | path_join }}:/app/indexdir" # persist search index - "{{ (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_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: environment:
GRAMPSWEB_TREE: "Gramps" # will create a new tree if not exists GRAMPSWEB_TREE: "Gramps" # will create a new tree if not exists
GRAMPSWEB_SECRET_KEY: "{{ gramps_secret_key }}" GRAMPSWEB_SECRET_KEY: "{{ gramps_secret_key }}"
@@ -36,16 +36,12 @@ services:
GRAMPSWEB_EMAIL_HOST_PASSWORD: "{{ postbox_pass }}" GRAMPSWEB_EMAIL_HOST_PASSWORD: "{{ postbox_pass }}"
GRAMPSWEB_EMAIL_USE_TLS: "false" GRAMPSWEB_EMAIL_USE_TLS: "false"
GRAMPSWEB_DEFAULT_FROM_EMAIL: "gramps@vakhrushev.me" GRAMPSWEB_DEFAULT_FROM_EMAIL: "gramps@vakhrushev.me"
# media storage at s3 # media storage
GRAMPSWEB_MEDIA_BASE_DIR: "s3://av-gramps-media-storage" GRAMPSWEB_MEDIA_BASE_DIR: "/app/media"
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 }}"
gramps_celery: gramps_celery:
<<: *gramps_app # YAML merge key copying the entire grampsweb service config <<: *gramps_app # YAML merge key copying the entire grampsweb service config
container_name: gramps_celery container_name: gramps_celery
depends_on: depends_on:
- gramps_redis - gramps_redis
@@ -53,10 +49,10 @@ services:
ports: [] ports: []
networks: networks:
- "gramps_network" - "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: gramps_redis:
image: valkey/valkey:8.1.1-alpine image: valkey/valkey:9.0-alpine
container_name: gramps_redis container_name: gramps_redis
restart: unless-stopped restart: unless-stopped
networks: networks:

View File

@@ -23,10 +23,3 @@ models:
undo: undo:
type: sqlite type: sqlite
path: "{{ (data_dir, 'gramps_db/59a0f3d6-1c3d-4410-8c1d-1c9c6689659f/undo.db') | path_join }}" 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: services:
memos_app: memos_app:
image: neosmemo/memos:0.25.2 image: neosmemo/memos:0.25.3
container_name: memos_app container_name: memos_app
restart: unless-stopped restart: unless-stopped
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}" user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"

View File

@@ -2,7 +2,7 @@
models: models:
gramps: memos:
compress_with: compress_with:
type: 'tgz' type: 'tgz'
storages: storages:
@@ -14,8 +14,3 @@ models:
users: users:
type: sqlite type: sqlite
path: "{{ (data_dir, 'memos_prod.db') | path_join }}" 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: services:
netdata: netdata:
image: netdata/netdata:v2.7.3 image: netdata/netdata:v2.8.4
container_name: netdata container_name: netdata
restart: unless-stopped restart: unless-stopped
cap_add: cap_add:
@@ -16,12 +16,16 @@ services:
- "{{ config_dir }}:/etc/netdata" - "{{ config_dir }}:/etc/netdata"
- "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata" - "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata"
- "{{ (data_dir, 'cache') | path_join }}:/var/cache/netdata" - "{{ (data_dir, 'cache') | path_join }}:/var/cache/netdata"
# Netdata system volumes # Netdata system volumes
- "/:/host/root:ro,rslave" - "/:/host/root:ro,rslave"
- "/etc/group:/host/etc/group:ro" - "/etc/group:/host/etc/group:ro"
- "/etc/hostname:/host/etc/hostname:ro"
- "/etc/localtime:/etc/localtime:ro" - "/etc/localtime:/etc/localtime:ro"
- "/etc/os-release:/host/etc/os-release:ro" - "/etc/os-release:/host/etc/os-release:ro"
- "/etc/passwd:/host/etc/passwd:ro" - "/etc/passwd:/host/etc/passwd:ro"
- "/proc:/host/proc:ro" - "/proc:/host/proc:ro"
- "/run/dbus:/run/dbus:ro" - "/run/dbus:/run/dbus:ro"
- "/sys:/host/sys:ro" - "/sys:/host/sys:ro"

View File

@@ -1,3 +1,3 @@
jobs: jobs:
- name: fail2ban - 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: jobs:

View File

@@ -19,7 +19,7 @@
# cpu cores = 2 # cpu cores = 2
# libuv worker threads = 16 # libuv worker threads = 16
# profile = standalone # profile = standalone
hostname = {{ host_name }} # hostname = rivendell-v2
# glibc malloc arena max for plugins = 1 # glibc malloc arena max for plugins = 1
# glibc malloc arena max for netdata = 1 # glibc malloc arena max for netdata = 1
# crash reports = all # crash reports = all
@@ -30,12 +30,15 @@
# has unstable connection = no # has unstable connection = no
[db] [db]
#| >>> [db].update every <<<
#| datatype: duration (seconds), default value: 1s
update every = 10s
# enable replication = yes # enable replication = yes
# replication period = 1d # replication period = 1d
# replication step = 1h # replication step = 1h
# replication threads = 1 # replication threads = 1
# replication prefetch = 10 # replication prefetch = 10
# update every = 1s
# db = dbengine # db = dbengine
# memory deduplication (ksm) = auto # memory deduplication (ksm) = auto
# cleanup orphan hosts after = 1h # cleanup orphan hosts after = 1h
@@ -47,7 +50,7 @@
# dbengine extent cache size = off # dbengine extent cache size = off
# dbengine enable journal integrity check = no # dbengine enable journal integrity check = no
# dbengine use all ram for caches = 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 use direct io = yes
# dbengine journal v2 unmount time = 2m # dbengine journal v2 unmount time = 2m
# dbengine pages per extent = 109 # dbengine pages per extent = 109
@@ -93,12 +96,9 @@
[environment variables] [environment variables]
# PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin # PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# PYTHONPATH = # PYTHONPATH =
# TZ = :/etc/localtime # TZ = :/etc/localtime
[host labels]
# name = value
[cloud] [cloud]
# conversation log = no # conversation log = no
# scope = full # scope = full
@@ -107,15 +107,15 @@
[ml] [ml]
# enabled = auto # enabled = auto
# maximum num samples to train = 21600 # training window = 6h
# minimum num samples to train = 900 # min training window = 15m
# max training vectors = 1440
# max samples to smooth = 3
# train every = 3h # train every = 3h
# number of models per dimension = 18 # number of models per dimension = 18
# delete models older than = 7d # delete models older than = 7d
# num samples to diff = 1 # num samples to diff = 1
# num samples to smooth = 3
# num samples to lag = 5 # num samples to lag = 5
# random sampling ratio = 0.20000
# maximum number of k-means iterations = 1000 # maximum number of k-means iterations = 1000
# dimension anomaly score threshold = 0.99000 # dimension anomaly score threshold = 0.99000
# host anomaly rate threshold = 1.00000 # host anomaly rate threshold = 1.00000
@@ -163,7 +163,7 @@
# timeout for first request = 1m # timeout for first request = 1m
# accept a streaming request every = off # accept a streaming request every = off
# respect do not track policy = no # respect do not track policy = no
# x-frame-options response header = # x-frame-options response header =
# allow connections from = localhost * # allow connections from = localhost *
# allow connections by dns = heuristic # allow connections by dns = heuristic
# allow dashboard from = localhost * # allow dashboard from = localhost *
@@ -181,7 +181,7 @@
# gzip compression level = 3 # gzip compression level = 3
# ssl skip certificate verification = no # ssl skip certificate verification = no
# web server threads = 6 # web server threads = 6
# web server max sockets = 262144 # web server max sockets = 131072
[registry] [registry]
# enabled = no # enabled = no
@@ -189,9 +189,9 @@
# registry log file = /var/lib/netdata/registry/registry-log.db # registry log file = /var/lib/netdata/registry/registry-log.db
# registry save db every new entries = 1000000 # registry save db every new entries = 1000000
# registry expire idle persons = 1y # registry expire idle persons = 1y
# registry domain = # registry domain =
# registry to announce = https://registry.my-netdata.io # registry to announce = https://registry.my-netdata.io
# registry hostname = 7171b7f9fc69 # registry hostname = rivendell-v2
# verify browser cookies support = yes # verify browser cookies support = yes
# enable cookies SameSite and Secure = yes # enable cookies SameSite and Secure = yes
# max URL length = 1024 # max URL length = 1024
@@ -202,9 +202,29 @@
[pulse] [pulse]
# extended = no # extended = no
# update every = 1s # update every = 10s
[plugins] [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 # idlejitter = yes
# netdata pulse = yes # netdata pulse = yes
# profile = no # profile = no
@@ -213,23 +233,20 @@
# proc = yes # proc = yes
# cgroups = yes # cgroups = yes
# timex = yes # timex = yes
# statsd = yes
# enable running new plugins = yes # enable running new plugins = yes
# check for new plugins every = 1m # check for new plugins every = 1m
# slabinfo = no # slabinfo = no
# freeipmi = no # freeipmi = no
# python.d = yes
# go.d = yes
# apps = yes
# systemd-journal = yes
# network-viewer = yes
# charts.d = yes
# debugfs = yes # debugfs = yes
# perf = yes
# ioping = yes # ioping = yes
# network-viewer = yes
# apps = yes
# go.d = yes
# systemd-units = yes
# systemd-journal = yes
[statsd] [statsd]
# update every (flushInterval) = 1s # update every (flushInterval) = 10s
# udp messages to process at once = 10 # udp messages to process at once = 10
# create private charts for metrics matching = * # create private charts for metrics matching = *
# max private charts hard limit = 1000 # max private charts hard limit = 1000
@@ -247,10 +264,7 @@
# gaps on histograms (deleteHistograms) = no # gaps on histograms (deleteHistograms) = no
# gaps on timers (deleteTimers) = no # gaps on timers (deleteTimers) = no
# gaps on dictionaries (deleteDictionaries) = no # gaps on dictionaries (deleteDictionaries) = no
# statsd server max TCP sockets = 262144 # statsd server max TCP sockets = 131072
# listen backlog = 4096
# default port = 8125
# bind to = udp:localhost tcp:localhost
[plugin:idlejitter] [plugin:idlejitter]
# loop time = 20ms # loop time = 20ms
@@ -300,22 +314,31 @@
# /sys/class/drm = yes # /sys/class/drm = yes
[plugin:cgroups] [plugin:cgroups]
# update every = 1s #| >>> [plugin:cgroups].update every <<<
# check for new cgroups every = 10s #| 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 # use unified cgroups = auto
# max cgroups to allow = 1000 # max cgroups to allow = 1000
# max cgroups depth to monitor = 0 # max cgroups depth to monitor = 0
# enable by default cgroups matching = !*/init.scope !/system.slice/run-*.scope *user.slice/docker-* !*user.slice* *.scope !/machine.slice/*/.control !/machine.slice/*/payload* !/machine.slice/*/supervisor /machine.slice/*.service */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* !*/vcpu* !*/emulator !*.mount !*.partition !*.service !*.service/udev !*.socket !*.slice !*.swap !*.user !/ !/docker !*/libvirt !/lxc !/lxc/*/* !/lxc.monitor* !/lxc.pivot !/lxc.payload !*lxcfs.service/.control !/machine !/qemu !/system !/systemd !/user * # enable by default cgroups matching = !*/init.scope !/system.slice/run-*.scope *user.slice/docker-* !*user.slice* *.scope !/machine.slice/*/.control !/machine.slice/*/payload* !/machine.slice/*/supervisor /machine.slice/*.service */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* !*/vcpu* !*/emulator !*.mount !*.partition !*.service !*.service/udev !*.socket !*.slice !*.swap !*.user !/ !/docker !*/libvirt !/lxc !/lxc/*/* !/lxc.monitor* !/lxc.pivot !/lxc.payload !*lxcfs.service/.control !/machine !/qemu !/system !/systemd !/user *
# enable by default cgroups names matching = * # enable by default cgroups names matching = *
# search for cgroups in subpaths matching = !*/init.scope !*-qemu !*.libvirt-qemu !/init.scope !/system !/systemd !/user !/lxc/*/* !/lxc.monitor !/lxc.payload/*/* !/lxc.payload.* * # search for cgroups in subpaths matching = !*/init.scope !*-qemu !*.libvirt-qemu !/init.scope !/system !/systemd !/user !/lxc/*/* !/lxc.monitor !/lxc.payload/*/* !/lxc.payload.* *
# script to get cgroup names = /usr/libexec/netdata/plugins.d/cgroup-name.sh # script to get cgroup names = /usr/libexec/netdata/plugins.d/cgroup-name.sh
# script to get cgroup network interfaces = /usr/libexec/netdata/plugins.d/cgroup-network # script to get cgroup network interfaces = /usr/libexec/netdata/plugins.d/cgroup-network
# run script to rename cgroups matching = !/ !*.mount !*.socket !*.partition /machine.slice/*.service !*.service !*.slice !*.swap !*.user !init.scope !*.scope/vcpu* !*.scope/emulator *.scope *docker* *lxc* *qemu* */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* *.libvirt-qemu * # run script to rename cgroups matching = !/ !*.mount !*.socket !*.partition /machine.slice/*.service !*.service !*.slice !*.swap !*.user !init.scope !*.scope/vcpu* !*.scope/emulator *.scope *docker* *lxc* *qemu* */kubepods/pod*/* */kubepods/*/pod*/* */*-kubepods-pod*/* */*-kubepods-*-pod*/* !*kubepods* !*kubelet* *.libvirt-qemu *
# cgroups to match as systemd services = !/system.slice/*/*.service /system.slice/*.service # cgroups to match as systemd services = !/system.slice/*/*.service /system.slice/*.service
[plugin:proc:diskspace] [plugin:proc:diskspace]
#| >>> [plugin:proc:diskspace].update every <<<
#| datatype: duration (seconds), default value: 10s
update every = 1m
# remove charts of unmounted disks = yes # remove charts of unmounted disks = yes
# update every = 1s
# check for new mount points every = 15s # 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 paths = /dev /dev/shm /proc/* /sys/* /var/run/user/* /run/lock /run/user/* /snap/* /var/lib/docker/* /var/lib/containers/storage/* /run/credentials/* /run/containerd/* /rpool /rpool/*
# exclude space metrics on filesystems = *gvfs *gluster* *s3fs *ipfs *davfs2 *httpfs *sshfs *gdfs *moosefs fusectl autofs cgroup cgroup2 hugetlbfs devtmpfs fuse.lxcfs # exclude space metrics on filesystems = *gvfs *gluster* *s3fs *ipfs *davfs2 *httpfs *sshfs *gdfs *moosefs fusectl autofs cgroup cgroup2 hugetlbfs devtmpfs fuse.lxcfs
@@ -326,41 +349,29 @@
[plugin:tc] [plugin:tc]
# script to run to get tc values = /usr/libexec/netdata/plugins.d/tc-qos-helper.sh # 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] [plugin:go.d]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:apps] [plugin:apps]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:systemd-journal] [plugin:systemd-journal]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:network-viewer] [plugin:network-viewer]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:charts.d]
# update every = 1s
# command options =
[plugin:debugfs] [plugin:debugfs]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:perf]
# update every = 1s
# command options =
[plugin:ioping] [plugin:ioping]
# update every = 1s # update every = 10s
# command options = # command options =
[plugin:proc:/proc/net/dev] [plugin:proc:/proc/net/dev]
# compressed packets for all interfaces = no # compressed packets for all interfaces = no
@@ -580,7 +591,7 @@
# hardware packets counters = auto # hardware packets counters = auto
# hardware errors counters = auto # hardware errors counters = auto
# monitor only active ports = auto # monitor only active ports = auto
# disable by default interfaces matching = # disable by default interfaces matching =
# refresh ports state every = 30s # refresh ports state every = 30s
[plugin:proc:/proc/net/stat/nf_conntrack] [plugin:proc:/proc/net/stat/nf_conntrack]
@@ -635,7 +646,7 @@
# preferred disk ids = * # preferred disk ids = *
# exclude disks = loop* ram* # exclude disks = loop* ram*
# filename to monitor = /host/proc/diskstats # 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] [plugin:proc:/proc/mdstat]
# faulty devices = yes # faulty devices = yes
@@ -685,3 +696,7 @@
[plugin:proc:/sys/class/drm] [plugin:proc:/sys/class/drm]
# directory to monitor = /host/sys/class/drm # directory to monitor = /host/sys/class/drm
[plugin:systemd-units]
# update every = 10s
# command options =

View File

@@ -9,8 +9,6 @@ services:
depends_on: depends_on:
- outline_postgres - outline_postgres
- outline_redis - outline_redis
ports:
- "127.0.0.1:{{ outline_port }}:3000"
networks: networks:
- "outline_network" - "outline_network"
- "web_proxy_network" - "web_proxy_network"
@@ -54,7 +52,7 @@ services:
SMTP_SECURE: 'false' SMTP_SECURE: 'false'
outline_redis: outline_redis:
image: valkey/valkey:8.1.1-alpine image: valkey/valkey:9.0-alpine
container_name: outline_redis container_name: outline_redis
restart: unless-stopped restart: unless-stopped
networks: networks:
@@ -64,8 +62,10 @@ services:
outline_postgres: outline_postgres:
image: postgres:16.3-bookworm image: postgres:16.3-bookworm
container_name: outline_postgres container_name: outline_postgres
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- "/etc/passwd:/etc/passwd:ro"
- "{{ postgres_data_dir }}:/var/lib/postgresql/data" - "{{ postgres_data_dir }}:/var/lib/postgresql/data"
environment: environment:
POSTGRES_USER: '{{ outline_postgres_user }}' POSTGRES_USER: '{{ outline_postgres_user }}'
@@ -74,6 +74,10 @@ services:
networks: networks:
- "outline_network" - "outline_network"
- "monitoring_network" - "monitoring_network"
healthcheck:
test: ["CMD", "pg_isready", "--username={{ outline_postgres_user }}", "--dbname={{ outline_postgres_database }}"]
interval: 10s
start_period: 30s
networks: networks:
outline_network: outline_network:

View File

@@ -1,6 +1,8 @@
# Refer for explanation to following link: # Refer for explanation to following link:
# https://lefthook.dev/configuration/ # https://lefthook.dev/configuration/
glob_matcher: doublestar
templates: templates:
av-hooks-dir: "/home/av/projects/private/git-hooks" av-hooks-dir: "/home/av/projects/private/git-hooks"
@@ -12,3 +14,12 @@ pre-commit:
- name: "check secret files" - name: "check secret files"
run: "python3 {av-hooks-dir}/pre-commit/check-secrets-encrypted-with-ansible-vault.py" 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 hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
- files/authelia/secrets.yml - files/authelia/secrets.yml
vars: vars:
app_name: "authelia" app_name: "authelia"
app_user: "{{ app_name }}" 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 }}" config_dir: "{{ (base_dir, 'config') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
tasks: tasks:
- name: "Create user and environment" - name: "Create user and environment"
@@ -19,6 +23,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
@@ -29,7 +35,10 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0700" mode: "0700"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}" - "{{ config_dir }}"
- "{{ backups_dir }}"
- name: "Copy users file" - name: "Copy users file"
ansible.builtin.copy: ansible.builtin.copy:
@@ -39,7 +48,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0600" mode: "0600"
- name: "Copy configuration files (templates)" - name: "Copy configuration file"
ansible.builtin.template: ansible.builtin.template:
src: "files/{{ app_name }}/configuration.template.yml" src: "files/{{ app_name }}/configuration.template.yml"
dest: "{{ (config_dir, 'configuration.yml') | path_join }}" dest: "{{ (config_dir, 'configuration.yml') | path_join }}"
@@ -47,6 +56,22 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0600" 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" - name: "Copy docker compose file"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml" src: "./files/{{ app_name }}/docker-compose.template.yml"
@@ -60,8 +85,12 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app
- name: "Restart application with docker compose" - name: "Restart application with docker compose"
community.docker.docker_compose_v2: community.docker.docker_compose_v2:
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "restarted" state: "restarted"
tags:
- run-app

View File

@@ -7,7 +7,7 @@
vars: vars:
backup_config_dir: "/etc/backup" 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 }}" restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}" backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
@@ -23,7 +23,7 @@
- name: "Create backup config file" - name: "Create backup config file"
ansible.builtin.template: ansible.builtin.template:
src: "files/backups/config.template.ini" src: "files/backups/config.template.toml"
dest: "{{ backup_config_file }}" dest: "{{ backup_config_file }}"
owner: root owner: root
group: root group: root

View File

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

View File

@@ -3,13 +3,12 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
tasks: tasks:
- name: "Install python docker lib from pip" # - name: "Install python docker lib from pip"
ansible.builtin.pip: # ansible.builtin.pip:
name: docker # name: docker
- name: "Install docker" - name: "Install docker"
ansible.builtin.import_role: ansible.builtin.import_role:
@@ -32,3 +31,10 @@
community.docker.docker_network: community.docker.docker_network:
name: "monitoring_network" name: "monitoring_network"
driver: "bridge" 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 hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "dozzle" app_name: "dozzle"
app_user: "{{ app_name }}" 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: tasks:
- name: "Create user and environment" - name: "Create user and environment"
@@ -17,11 +18,23 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] 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" - name: "Copy docker compose file"
ansible.builtin.template: 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" dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -32,3 +45,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
# See: https://github.com/zyedidia/eget/releases # 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 {{ eget_bin_path }} go-task/task --quiet --upgrade-only --to {{ eget_install_dir }} --asset tar.gz
--tag v3.45.5 --tag v3.45.5
changed_when: false 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 hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "gitea" app_name: "gitea"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
@@ -19,6 +20,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
@@ -29,6 +32,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0770" mode: "0770"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ backups_dir }}" - "{{ backups_dir }}"
@@ -53,3 +57,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,14 +3,17 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "gramps" app_name: "gramps"
app_user: "{{ app_name }}" 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 }}" 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 }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}" gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
@@ -20,6 +23,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
@@ -30,12 +35,15 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ media_dir }}"
- "{{ cache_dir }}"
- "{{ backups_dir }}" - "{{ backups_dir }}"
- name: "Copy gobackup config" - name: "Copy gobackup config"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/gobackup.yml.j2" src: "./files/{{ app_name }}/gobackup.template.yml"
dest: "{{ gobackup_config }}" dest: "{{ gobackup_config }}"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -43,15 +51,36 @@
- name: "Copy backup script" - name: "Copy backup script"
ansible.builtin.template: ansible.builtin.template:
src: "files/{{ app_name }}/backup.sh.j2" src: "files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh" dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" 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" - name: "Copy docker compose file"
ansible.builtin.template: 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" dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -62,3 +91,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -4,7 +4,6 @@
gather_facts: false gather_facts: false
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
- vars/homepage.yml - vars/homepage.yml
- vars/homepage.images.yml - vars/homepage.images.yml

View File

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

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "memos" app_name: "memos"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}" gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
@@ -20,6 +21,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
@@ -30,6 +33,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ backups_dir }}" - "{{ backups_dir }}"
@@ -49,6 +53,18 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" 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" - name: "Copy docker compose file"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml" src: "./files/{{ app_name }}/docker-compose.template.yml"
@@ -62,3 +78,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "miniflux" app_name: "miniflux"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
secrets_dir: "{{ (base_dir, 'secrets') | path_join }}" secrets_dir: "{{ (base_dir, 'secrets') | path_join }}"
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}" postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
@@ -21,6 +22,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create internal directories" - name: "Create internal directories"
@@ -31,6 +34,10 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0770" mode: "0770"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ secrets_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}" - "{{ postgres_backups_dir }}"
- name: "Copy secrets" - name: "Copy secrets"
@@ -50,7 +57,7 @@
- name: "Copy docker compose file" - name: "Copy docker compose file"
ansible.builtin.template: 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" dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -58,7 +65,7 @@
- name: "Copy backup script" - name: "Copy backup script"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/backup.sh.j2" src: "./files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh" dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -70,3 +77,5 @@
state: "present" state: "present"
recreate: "always" recreate: "always"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "netdata" app_name: "netdata"
app_user: "{{ app_name }}" 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_dir: "{{ (base_dir, 'config') | path_join }}"
config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}" config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
@@ -20,6 +21,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
@@ -30,13 +33,14 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0770" mode: "0770"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ config_dir }}" - "{{ config_dir }}"
- "{{ config_go_d_dir }}" - "{{ config_go_d_dir }}"
- "{{ data_dir }}"
- name: "Copy netdata config file" - name: "Copy netdata config file"
ansible.builtin.template: ansible.builtin.template:
src: "files/{{ app_name }}/netdata.conf.j2" src: "files/{{ app_name }}/netdata.template.conf"
dest: "{{ config_dir }}/netdata.conf" dest: "{{ config_dir }}/netdata.conf"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -93,8 +97,12 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app
- name: "Restart application with docker compose" - name: "Restart application with docker compose"
community.docker.docker_compose_v2: community.docker.docker_compose_v2:
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "restarted" state: "restarted"
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "outline" app_name: "outline"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}" postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}" postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}"
@@ -20,6 +21,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create internal directories" - name: "Create internal directories"
@@ -30,6 +33,9 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0770" mode: "0770"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}"
- "{{ postgres_data_dir }}"
- "{{ postgres_backups_dir }}" - "{{ postgres_backups_dir }}"
- name: "Copy docker compose file" - name: "Copy docker compose file"
@@ -42,7 +48,7 @@
- name: "Copy backup script" - name: "Copy backup script"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/backup.sh.j2" src: "./files/{{ app_name }}/backup.template.sh"
dest: "{{ base_dir }}/backup.sh" dest: "{{ base_dir }}/backup.sh"
owner: "{{ app_user }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
@@ -53,3 +59,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

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

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "rssbridge" app_name: "rssbridge"
app_user: "{{ app_name }}" 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: tasks:
- name: "Create user and environment" - name: "Create user and environment"
@@ -17,8 +18,20 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] 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" - name: "Copy docker compose file"
ansible.builtin.template: ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2" src: "./files/{{ app_name }}/docker-compose.yml.j2"
@@ -32,3 +45,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,7 +3,6 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
@@ -40,3 +39,20 @@
owner: root owner: root
group: root group: root
mode: "0755" 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 gather_facts: false
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
- vars/transcriber.yml - vars/transcriber.yml
- vars/transcriber.images.yml - vars/transcriber.images.yml

View File

@@ -3,7 +3,6 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
- vars/transcriber.yml - vars/transcriber.yml
- vars/transcriber.images.yml - vars/transcriber.images.yml
@@ -14,6 +13,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
@@ -24,6 +25,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop: loop:
- "{{ base_dir }}"
- "{{ config_dir }}" - "{{ config_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ backups_dir }}" - "{{ backups_dir }}"
@@ -53,3 +55,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

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

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "wakapi" app_name: "wakapi"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}" gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
@@ -20,6 +21,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
@@ -30,6 +33,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ backups_dir }}" - "{{ backups_dir }}"
@@ -62,3 +66,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

@@ -3,13 +3,14 @@
hosts: all hosts: all
vars_files: vars_files:
- vars/ports.yml
- vars/secrets.yml - vars/secrets.yml
vars: vars:
app_name: "wanderer" app_name: "wanderer"
app_user: "{{ app_name }}" 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 }}" data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}" gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
@@ -23,6 +24,8 @@
name: owner name: owner
vars: vars:
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"] owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
@@ -33,6 +36,7 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop: loop:
- "{{ base_dir }}"
- "{{ data_dir }}" - "{{ data_dir }}"
- "{{ (data_dir, 'pb_data') | path_join }}" - "{{ (data_dir, 'pb_data') | path_join }}"
- "{{ (data_dir, 'uploads') | path_join }}" - "{{ (data_dir, 'uploads') | path_join }}"
@@ -47,13 +51,29 @@
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0640" mode: "0640"
- name: "Copy backup script" # - name: "Copy backup script"
ansible.builtin.template: # ansible.builtin.template:
src: "files/{{ app_name }}/backup.template.sh" # 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" 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 }}" owner: "{{ app_user }}"
group: "{{ app_user }}" group: "{{ app_user }}"
mode: "0750" mode: "0750"
loop:
- "{{ data_dir }}"
- name: "Copy docker compose file" - name: "Copy docker compose file"
ansible.builtin.template: ansible.builtin.template:
@@ -68,3 +88,5 @@
project_src: "{{ base_dir }}" project_src: "{{ base_dir }}"
state: "present" state: "present"
remove_orphans: true remove_orphans: true
tags:
- run-app

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
--- ---
app_name: "homepage" app_name: "homepage"
app_user: "{{ app_name }}" 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" docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
# Registry images # 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 $ANSIBLE_VAULT;1.1;AES256
66323565663433656434373835653439643630386365303736373738353664376233343765393637 66333736306664316131646233393639356436323832386638353237393761386631303639396432
3735666334336164386538623665333634613564393962340a343336656665663435313761323139 6266616434663638306637356435393564303633613332640a663366336135373061396239653065
66613838386237613936613936366533306463356331393463616436333038613036616130396664 32343339313734346261363461633735383538613033656138383835653661633334316533613738
3161656630656330340a396139313534646162643437646463336566306534346664306133353564 3730656661393466370a653863353234323739346630323534333334306432646636383664313865
31663439356666313139626235393834326561323065393462663961353534646538396234393838 39666133623032393638633239653665376336626638303334626164376663626666393439346661
33653163366435623633343362343234613334376430613132336234333935663931383233643265 32363364363066653933313135666566346138353163333639353062336331623938353131646334
30343737323337386130396234376661626666646531333232353934383862326566626261366531 32326466323030643364356530653862633332363338333633393730663536353363663165303838
32313733313265373837383536313430366362393032396536383137333337656330373930666536 66643331366231613733316139333233393636626162643861346437613062643133353035353566
63316162353332393138626531663063646437656130376265653138313239383962633438613832 38323062653135393364376265346361613163346337636634383932666137636133363864643264
39366335613335313765373736316530376562383764636164613132393066643966646439323831 39646634363037383662376166633335636365303734646465623833313766656235333835396263
63663836313533336663323163666431616536326163396237343365303463323237313162666139 63326237306337396465343065313263663466613336303934373337623364613839323931623934
39353537316339313463663234373433386534323965653236613638643330613831396434386662 35353665363339646365353139643032366164653133616562303930306264343633343439373830
64383938623231376665613034383036396132633265373036386234363733383663383062323337 66356635343363363361313633353134313837653163346537656465623832633561323833663763
36326364313437333835366162383064656662333732653263326331333537326162663638663431 32323537303931343934376165663466356466333533643237653038373866626439356334663539
65626335336132653362336331393264616531303465643330386630303730643532373337316164 36613336326633306331323763393230653236306337353139353135316566336661623464316237
35316431376439303437323532386436623030656666656332613265303933366365383031313064 37363930666539373837336466333265333866633733363032323862333239643764333766366134
66623336653038376162343161376663663163643136306132313437663365616464386665613239 33396330613463633261626334393262343031663131313737623233323535303965333864366631
33626335633330663535366234373465353632623963336163313231323234666137336439353739 65333535343062393434396361313433386463613630353765663363633431393831316233313162
36626261373563316161393465323935653862323165616138636662383736326536636337366334 39656333306265386437663863343638323262663734623830336562636366333534373735313932
34646164373939633431656430363966383133373236383337633738353536326132313733303665 61653739616635643661306165363364316262386434643033646430366235303334326236373262
64643336656235656664633834623833636263333032626461353363656238393131646562643265 34396261653934633337646338333364326237663739623965313039303134393238653634616431
38343364383138646663613362303231333961336665366230633832316238646534316534343865 39376539323238353033323333643337613935616335336562626361383164616432633331313536
39366461626432636431323235313364626366396638343239646465363937636530333535633439 63373633633966373237393131663064613332316536373163303163653638343264323934393365
34393635333030353966323635343261353563303234633238313635313535343331613931626235 62356636643434646465313734363936346435663237393064663138643135303237633965393965
32636265376631623732643439333639336637663039356661386665363939623963626361636139 31353564616136366237666139646561363364353738633862346262616637663362333137386133
31323439313938336137316466373634363736666136376633633734393765313335636334336532 63383662363866306366326232643462376532313632336662666666386562356436616236663430
34393262363835326139636335323739313531336565396664346635623934616461343239623035 37636661656237646431616331633833306266313434326461353561643835646236346539323337
64386264386663393466633635366665346463323336643839656566663536333338346162353235 30303065313165306233646135363766613931383433313165366662636137326162633363306431
61373866333134333634366638383231643662343730633539396164343935623362646662306239 31626564323566313036363666386338366462613164663735363333313830306238323365363737
34383432653930353430376466623236653834653566633335336632303063616135396365653637 65333137363631623139333964316464376662643661643038326162643630323938626332633130
38386165306432306638616134393636393862323836393865636130623162616161323531613132 62626334666131373731343432303461656537303062396638326134356166663936663364656139
39623939383437353633323932376536636434323437363065653361363532653935613636383939 32333864653231636235663165323936626135313838663866373132376437656236363235353533
32343866303930373033316136373134323735363164633663626637303735653561643832393162 39346532626666656233343433383434613238653833626436643566653462346330316565366564
66616133643466376638633536353963393662383639323632393061623361383635353237613437 33623831643265623966326638656462306161636366303266333734396433653861393363333332
36373236336461316330616438633031663035326433356533333265626363393332346464363739 61616463613931393835613463353039646164383435373330626134376339623861396266623430
32313937386333616261346565633430393064333739393064383131626131626137346463343362 30316365356337356531363263623362633332313536373333653964356534623861613232653932
61646234396361313936646332633332626337663561336561633362333436346439663963633032 35613064336131313632656663303631363664366163343362643365663932356138376235313466
35666539323031626166363333646632633732616137343962383231663938633035386435633833 30353333376461623435363931356665306333623736313562333836336563616137633863346635
66393534633734346131373563613066396432613037356537613365323739336331376132356231 37356137306361383134663535626130646135363661353438343961653766333538353330346236
31313434353135666562613365636266323732303433383036646430626365623632613734343432 64326639383864396336343062663965343964396162386266363639643962666431336237303864
66333165303131306236396566373561326565343166303830623033653861356633333437316264 66663861346139363335663362333032613637336266323439366566373136643133383361323061
61633934633266656333303864623031663662383638396631646165373036643266653434383066 37633134323933613665366665383962373935646137616139353661336665613661623834303465
31313633316430613430313961656332656438653365383933366437633030313162386161356261 30323264333137636261393535376438363134663734313662383533313130623365386335323662
64613365386433376363623161323435326465633339336637653362613463353264333030373830 63366139643238613632326165313835363964336237383936333737646239363365623030666364
30353034313963323438643030666366366533326239343061646362626330353762326366323939 64656432653164643565376666373262333839353139666561623731343234326637316636333765
65303361663265326333326330656331386161656164373663303333373139663333623863313966 63306331343538386433376566326239376232363434343838653864393562383063663566333263
62326331653030336136326537653337333164366435326233366565666130326162643463623332 35326333326462316134383139303534343263646530363266663933353834353138303435646339
37663966383439633937356263646534646137363138666461383865616136646265303961363231 33643361363062373735663430346636333431363736373463326439353437356530373935633962
30636566333933323530326434663766313831623734386534356533386434346139646466366631 63333031376163656565663536366230333731613833396266383465333461386161373337323863
62613863336235633531646633326539353564383162663962346161366239653539313430666664 36313032613534346230636566383930656330656133376431376462656536386263343831393862
61656433333739653338643739373434306331376563393733613466303962633866373631373437 32363365623061653837303736636664663361663862656562393661623330386435646336373531
35343535353631353062616234643139393934346137383630316332373961313235633633646238 35363037356638653831386261646235613337363066343632653632306631633138633235356139
38316635653462643739353937666663343865643065396436393831366463316463383134306363 39663936333262643061646330316538373862353030626632396336393030643239316634343730
39303037643637393861373136643539326439396562646136616332323938653966633730653765 35363564373865376533333466666439646633313932656665383930623531633038316363636332
66663865636363336232356165313237323266353237336164326431346263616534653739656433 37363466643835353132333532646163316636303662646234613038333334626365623964653235
38643033356137363936373932323562336264653337303738306564326662363238303365646334 36393739353164306666313537633538383934363330373235353262616165623132613330303735
36306665353261633365363366366466396330656335656363326231376564393632636365353864 65323362666263653937376562633833653264613439303236373466646362316462386632373038
63393434383136393333336336336639343362666463393438653338663039353561323039616239 35353462633730666430623638626439626364616335643964623933663233376433353233633235
34633335396164366566653435313139633165383265316333373135616339303562313739336532 34616233636335323365646538326639383033643832323139633064616635353331376230663738
38373436316336343662343731323161616239306330323233646664363232633236333237323163 35316665616230633335366233363332353432633937653335316662306166383337633262633634
63386664303766326662356539666434393665323462333139663865326232343936313161626630 35303766653733376362623436383663663437393461643266376530613533383038373035356234
35393532313234333565303661613035306638313964336138303066306161653537376166383138 32343962623930336566363164616361386134376362383138653963366339326431653832393664
31343430643139643465376663303537386536333736326534313631636135633531626233376231 66633566343666326339323536343738396532623735343034646130386332323466346365383135
66313136383263316363376131313434363964643135613666643863363038356338633833356563 33373732363833316138343938633262373066613930343162663163323331373466366631626237
31396465623731646463613462323232626465613465353536636466663937613534306438363834 34623062353139353734653366313363323634346564663861613233366537663732363134643638
64306534383562373834313639303933323233356434643331386562356531393463656235656531 65623964396665303765343430623636376637666364363835346462613763306135326463353631
32323538623462366663373365613539626436376634613834356331633633643936366536356138 33333834333464383437336531616662643461666138383435636530373761646564626438313433
30613662336136393465336538313462363966666130353536353139356430383331373030303937 64643037626637343433366631343266373436343864396663353039333231646462333932353436
64616465353065366566323533363335646631386664313033386537353730336239653964333365 33643837616639666363353237313137353133346636383231623634326335386537363030613537
32336138373938313863346431613234366630356537323463323963346265363665313563393634 33306333363638643533373237633833663333393566656466313832383636663031663433636663
63363132373362343536633063626334386464386438376261363336313037326164393238613439 37313034353734343966613934663530643537366562623137373331623632653466333839376331
31356535366166366535633336376333663932666532323230656162633435333762663465623237 61623866633937666365633763613138346365393934383163623730373134373531616138363733
38363639653565383532303161313539396264666163323630393865313665343861353137636164 65616230303337396561666462653866333438353463316235303331643834653165363033626537
37313336346162643534623464623763333130303564633262366330666161393065306630373136 37386636653337363666393163393031333461383331613965303262616530363133373732313362
66363465616364623339366232303366623233616331646564303464313137633165313232663561 33343033373963656333363064303035666663646536373764323833653037316231316430333634
34653232323862386163383565643364313062353236333133356339656136633631333838623835 65383236663035326636336535636462323438363165633437333337613439326433626466396365
34376236333837366662333733633933633735613865613939306535396232363439313962313533 38393362633266663962646565336539333239346133323434646632643537613435643434623631
37636536303730616535656465316230623137623961613433303732666330666332346664383237 63636536393936343165393966663438343261333966363639323462663566613437393838323036
31343961306635313966333039353930313938303237396636383365666364626361633765376534 33393163323034336533333632646230343138336333353236376136383664646466626666356234
65643261386334616130306334613966343937373135623035663030366362623461363936336236 61653466363933333331613539636431393934393235376433326665643263663638306463393837
66353833613337333735336164383064366365346163346638663033393262316537616235373334 34396339383536636461366230383938386339303334393038343239363361666565336237326465
31323335623331366164373030656565613963306533303030363963613739363464623063626565 63623231663861303436353533663661656431646165646662383065386362636633333631643335
34366237663037626635653938323733633462666562353765633337633863306433616136363262 38373464373432626538616234623638303734626237393566326463633765316365653837303433
66313366633666373238333366316165616437623639326136613234316337656234306664363665 34376438323439633237313733343836343733373930643138636333366166353666373966323231
34363664366632376334626263373036666335613134643331326337383835613238623761303630 63613466306365386137336538613837613264633735393937343166303736396162303230623430
32666137323330346262653164623934383730613962343439333333643866613364306561396138 33363233663761333063363134393935333530343133303138373934666236393732396330643438
36353262306530376466653264633231373433356362383132653139643830386262663964653631 61343231666363323834346132376135303339313766383365363837313163393636333563376436
61346466613738633131366239356666623536363330633735383966633265663264626638653564 64333832393361626663376161343033383763373537396264646239303736333263303739326436
35663437306532636531356334343232313936373334346430343865303464366232666663326433 31393665363734323364643032393031313135623563383639316163616535323938343765356264
63666432333935383137396364636631383663323437386161616630303933633839636133643936 35616433396236396662393930646431643063323163343366313030383766323733333865616235
36363035316632373035663565653663623162373438386166326462383165343965376433613436 65623737663763373363613030326665613333636366393464383139626462346333323162333537
66656165326632323632336664663638373337616563383562376231623164353936316561373461 32386635636532346539623534346364623532656435653638643962353836653330316638633661
33386132653232626362353430636433323638633131366133393061333665346564336663323165 38363330656161393766383237663737663835646565363966373563663832313263633238316165
63623330623237666232666232303237613564666432636630393665623837356636383038353732 64643536336232396436636261303866376132366261373132333631646166393764636130326335
32316364666337646663306462346665623565353130656262633063646663343136333661333135 39613037633239626666396262386533343764623233626335373139663937613433356134633330
61303261343334313834376664616634333632656666333436303531303233633362393865626637 32656138383563343734616536303265306661386362343939626332623763393136323138653937
39343539333139326135326337643264393065356638613538626363633939343833376634643363 39646537366461353364616538663361663465626634356338323166373837323339343061323261
37363238303638363532386163626231383931316438306163663238393936336630323331643936 33393765653034643166313639323962306265383336663265623832393038386334643661336663
36623530663530343436343431633736393964336635323930626164386464646437356233393833 66643330306239383736636330393266346537326436663432653031303765393930633761343363
64633363313332353862663831383465613965396132346632626532353236666130633762346533 61353163323830656538373366323230353830633534326133613861356132623938663565393130
62616232636563626630303763366166376166623633636137353632646231376238303737636339 63333764613761663162633035353036663736656138623234313833326337316438323835313465
33393239323362616433333834626130336637636634613537323663303961393565326564306135 31653136356638623965363266636461646465313065666137313931623063303337393963353437
31356339346164626532306130343931346261336361663462613236356134303639393535643233 39633166326662303834303261353965363235626535643564613064306636366461666263383833
38373864396166633331346361386364373061306633326262306164353964633833313163343562 66363966636538373435323365636630383561336533356631646563646536383333393864613164
33376265303462373330303739353462363139623530326163663861373361373030656264373432 38393638623263666239623835613933393564323132373630623734363334663735633438663137
61653066616161613136306164373432366439316638306165643264356263643466633734346463 35613563356161316632323562626639633931643762643232636136653731323337333236326363
64383738636532353237343932383734386232613434366134323863613236316365333932653565 36633338386230626665373666643831626132323866653430393531336466386463663865393962
36633136316138373337313132306236363562346238636432336262376437306536343035346430 37363633343632653963306531393537633762313565303631643064363531363839643363356139
38303634663839663236336236323336623531323064376236356531636337343938323839633432 33666162306331393237333966643735643731633331373839356637636462633836633762653039
63663137383561633066366330303666626336613465623861366563623263656166303361353139 32633630303437633130613366386633346637623266663361656365633737346432343263646536
31353434313366353032313534613464633830643164393032623066346432666533323734663838 35376433386464383533323231353830666363363437623033643238363438303565616137316461
39313938386130396465373763303061656463396334663261353234326532653435356135346233 64636233643365663666656431613966396561366236383363303135613433626466366437396465
30333035363834376235313762636636343337363463653563323862393439613038396230316533 37303562633336636635363133626238643430663132626331316437626532356237383730626665
31323164623839323864343031386639323065663763383434663665323436383435656135346135 38373633323531346366313631376231353966626637636262333936343066336639396262646436
31633436383732303963326230616632363838633366326135376266323930636365663237343163 31616165626466623066336138653966323334326236663439643561663863323130653631643231
31306461666439613839313832303639386631333665633965396234363539313532363733633831 31396335333066326166383337353033376335376664653162396466346638653531346239353936
33643366353563626263626539363031323262383634643266356262346333376330306339386561 33663862323135623432333036336162353061363537363463366435643461356562323935653962
39636538373633396565393739333966333965353330653936633436303764633834633563373434 62333130616466353032316462356233393037326438303064656235323966306535346132663232
64383561316161623639643131633862336630633563343738616265303139633336656564633236 63393238646534316433643638646536313934666361323061306561396132306431633932313031
64613339623064636561626631313931333963313536396665366231306562646565383530633039 31356430313463613331363064363265646361353430646563396238326330323038666264653935
39656636396432643561656361666231393736383330643262376630316634303836653639383434 63306635323439326665306135643730386239356661303232353737383532353363303163313437
32666431313337393530373265643833386332656636316461303437613736383236613066333431 38623064383035373239623966336563626130636335383833383366343130393939316535333466
39343661376464663936633833613463623132383834666634383438313161623836653361353135 65353062396632373330663365373761656161653837396666376531376663313466393230646461
37313237643561303666366265616330373131313066643831383462386265376336396130613034 62306465363166663962316634663763626666306631363731633834366433363630383833393361
32386331343931323364623565326535626637343730393539363834336532323734656663346164 36393932326138643432666437373463343061313135613163343034373464356666333134366664
33643038613931396165626234363732313866323438333739356432333461646330613465633139 66343461393733343130636266623661323066623332633930326361626436366237323664336637
62316464646561646136316634313262323030373030663761376536333136326138363166383230 61316135613330663835353139613039613264383838313337373432623133363735333732376566
31316662313433376264656437383333303132643632623564623038663261633638613835316131 38633763336438663737653736326466646237313130623934396531306430363461653962336132
61643135373439336462323361353739363161653233306439333766343939386364616565643332 64313437323766613632376439336234393165646338396334373037323237633737393866303231
31383039666137653366326434633639323336656464336339363139336632353739306263393139 33646338333532613432393234316235653466633639306465363062656335353034666665623631
65646539626537613664653661613533383135626566303264363637353239346463333862306437 66386437616462633336636566393537323831313566326637386466616331396464653438396562
36346635646562343838613836303364383766626134343566356662626533336631343564373434 33343132626463393364386133323163393336313065316433613133663961633033613232343337
37336133316635396366383837363566656336636138393538393266343466613838393339353936 37313334386533366465353461633930643662326235366139306335656163313864636161623239
31393136396131363062343265353166646433653436623232356363636337353937613863653330 38336238663630313836363739623334633130616433393536303431333735643565326265613561
39643732626265636630616338633336643936623861366362366362333934373939613966656137 30613762396162633964336165666137626333643735323330646266666563313935623230643262
34396637393332306566356334326438643931616336323035353630343964373933663132653434 30383635663035653437333339303730346366333765353739663231643433353764363966353435
31626565616362313330303666633833326337366239323662663232346265373537313537306664 63663466343864363033646663376261363562636630643038613365333936653165633733623134
36316133666565623565633365666565626361396266393364316164313365663933346139666138 36626135313330303463336535663235323536613661353332653139336165613261316638666563
36383065636265333561306161333031363961353339613063303631643833346563363338373839 63346131306536353630363236613864393935333431333864333464353664366134313861633463
37383636363238613764623737376266346138386336663663653430323236323739666630653563 34336364663030376264663961396235643438653730396661643032623762623965623737326430
39386230643632633837336565363965316666613030643432383165346164633437333763613136 34333236316335373863663232356136333431666233353138353139313466343762356632396561
30323665613366653936393733303463353762336331343636376263343764666633333332316566 37356162303636343833316432353831373532326436363538333466333636306666626465666333
30383564643966653163363034616233393165393262646235326237393961663466343365646661 36653065363538653266643831326234356534303133343564646564323762653238303161336131
39663935366531303237376131646530623338326538316430616630613835363534663133616334 35396235343837333331396535646564656433343766323765333465356134323536636639623962
31613362336163313535616361636130643138386661626461306265373761653334356234613231 65633233376136316334653335623666313666376661326362663338633862643963353536616563
65613934646536336165616362616661613030353734663434616438653364393137363630333237 66323939643332663665306536356566383164356561303935313432373264353738643131653831
64623735396563623235323864626432316162663165313365613136393331353561313438346634 31346231633037616139373330316231363938386536303432386638323139326132663539663738
62626533636161663361636466353030356663356166393838366261323538343966623439336565 64636231663538333932363332663137323764346336633336363965616535373030663363363861
39373439653832386361616632313033326434326563356639363838303861613564396565633037 65363632653831353761306231663562623732353433656637353966303033636666613739306230
63636665363939346337383562353235646230366366356330313330333762656232396439373538 64363465623031386431663565623966643836383532626134366363656265646563343635383538
39646635386463326636363861653230653836376463663562653531323134663435356233353432 32346166363832626664613335383731616135376635336266326531663731373633303131373965
30633065646138653032363038633537363164613363356165393562663431643962633237363333 66663666333739373033616363313132643938656335353164343764383933636265656665663433
38353739643464623538636366616166643235316662313330383434366530633965303562616630 65386235636531653236333465316331353938323331623733623731303462306566333365383134
38376330656233326431316331636666626336303065623433613333616431363064303239323033 31356337343732613764633731306335366136346633393265666331636465323566656337323838
62623266386531363261346531653863306233316534396236393264623531633462363131326537 37393031316630623466393138636666303065623430656336386430363962336539616262633030
65393539313038653133 62616365623765366236303539353166306630393362386430363736313438346266366336323839
34376462376633366463636339646337623965633663363862393261373535636230613532386464
34393564383130306138366564366666646132363732653036353135656132656339353932383530
61636234393165326631303731633062333735306635303566373838393164306538303664313266
33346337613836636337316638323631396235643363333836323135303738366235326465376630
3363

View File

@@ -1,7 +1,9 @@
--- ---
app_name: "transcriber" app_name: "transcriber"
app_user: "{{ app_name }}" 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_dir: "{{ (base_dir, 'config') | path_join }}"
config_file: "{{ (config_dir, 'config.toml') | path_join }}" config_file: "{{ (config_dir, 'config.toml') | path_join }}"