Compare commits
55 Commits
6882d61f8e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
e5c1e19e5e
|
|||
|
8439ab3693
|
|||
|
dcb09e349b
|
|||
|
a6781b4f64
|
|||
|
a31a07bd16
|
|||
|
54a951b96a
|
|||
|
e1379bc480
|
|||
|
037e0cab9b
|
|||
|
2655869814
|
|||
|
0e96b5030d
|
|||
|
a217c79e7d
|
|||
|
6a16ebf084
|
|||
|
2617aa2bd2
|
|||
|
b686e4da4d
|
|||
|
439c239ac8
|
|||
|
acf599f905
|
|||
|
eae4f5e27b
|
|||
|
4fbe9bd5de
|
|||
|
dcc4970b20
|
|||
|
2eac1362b5
|
|||
|
e3d8479397
|
|||
|
91c5eab236
|
|||
|
ca7f089fe6
|
|||
|
479e256b1e
|
|||
|
11e5b5752e
|
|||
|
392938d0fb
|
|||
|
2cc059104e
|
|||
|
d09a26b73a
|
|||
|
097676f569
|
|||
|
e878661cb3
|
|||
|
cb50c1c515
|
|||
|
33de71a087
|
|||
|
fbd5fa5faa
|
|||
|
faf7d58f78
|
|||
|
0a75378bbc
|
|||
|
bdd74bdf2e
|
|||
|
78bee84061
|
|||
|
7b81858af6
|
|||
|
08fda17561
|
|||
|
841bd38807
|
|||
|
fb1fd711c2
|
|||
|
ecf714eda7
|
|||
|
81f693938e
|
|||
|
10d67861a0
|
|||
|
3f5befb44d
|
|||
|
1b75ddaef2
|
|||
|
7d6ef77e64
|
|||
|
ae7c20a7aa
|
|||
|
67df03efca
|
|||
|
48bb8c9d33
|
|||
|
5b53cb30ac
|
|||
|
f2bc221663
|
|||
|
b41a50006b
|
|||
|
c2ea2cdb39
|
|||
|
7e67409393
|
4
.crushignore
Normal file
4
.crushignore
Normal file
@@ -0,0 +1,4 @@
|
||||
ansible-vault-password-file
|
||||
|
||||
*secrets.yml
|
||||
*secrets.toml
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@
|
||||
/ansible-vault-password-file
|
||||
/temp
|
||||
*.retry
|
||||
|
||||
__pycache__
|
||||
|
||||
69
AGENTS.md
Normal file
69
AGENTS.md
Normal 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.
|
||||
10
files/authelia/backup.template.sh
Normal file
10
files/authelia/backup.template.sh
Normal 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."
|
||||
@@ -1026,7 +1026,7 @@ storage:
|
||||
##
|
||||
local:
|
||||
## Path to the SQLite3 Database.
|
||||
path: '/config/authelia_storage.sqlite3'
|
||||
path: '/data/authelia_storage.sqlite3'
|
||||
|
||||
##
|
||||
## MySQL / MariaDB (Storage Provider)
|
||||
|
||||
@@ -10,9 +10,10 @@ services:
|
||||
- "monitoring_network"
|
||||
volumes:
|
||||
- "{{ config_dir }}:/config"
|
||||
- "{{ data_dir }}:/data"
|
||||
|
||||
authelia_redis:
|
||||
image: valkey/valkey:9-alpine
|
||||
image: valkey/valkey:9.0-alpine
|
||||
container_name: authelia_redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
16
files/authelia/gobackup.template.yml
Normal file
16
files/authelia/gobackup.template.yml
Normal 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 }}"
|
||||
@@ -4,18 +4,28 @@ Backup script for all applications
|
||||
Automatically discovers and runs backup scripts for all users,
|
||||
then creates restic backups and sends notifications.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import logging
|
||||
import pwd
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
import requests
|
||||
import configparser
|
||||
import itertools
|
||||
import tomllib
|
||||
|
||||
# Default config path
|
||||
CONFIG_PATH = Path("/etc/backup/config.toml")
|
||||
|
||||
# File name to store directories and files to back up
|
||||
BACKUP_TARGETS_FILE = "backup-targets"
|
||||
|
||||
# Default directory fo backups (relative to app dir)
|
||||
# Used when backup-targets file not exists
|
||||
BACKUP_DEFAULT_DIR = "backups"
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
@@ -28,46 +38,193 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read("/etc/backup/config.ini")
|
||||
|
||||
RESTIC_REPOSITORY = config.get("restic", "RESTIC_REPOSITORY")
|
||||
RESTIC_PASSWORD = config.get("restic", "RESTIC_PASSWORD")
|
||||
AWS_ACCESS_KEY_ID = config.get("restic", "AWS_ACCESS_KEY_ID")
|
||||
AWS_SECRET_ACCESS_KEY = config.get("restic", "AWS_SECRET_ACCESS_KEY")
|
||||
AWS_DEFAULT_REGION = config.get("restic", "AWS_DEFAULT_REGION")
|
||||
TELEGRAM_BOT_TOKEN = config.get("telegram", "TELEGRAM_BOT_TOKEN")
|
||||
TELEGRAM_CHAT_ID = config.get("telegram", "TELEGRAM_CHAT_ID")
|
||||
NOTIFICATIONS_NAME = config.get("telegram", "NOTIFICATIONS_NAME")
|
||||
@dataclass
|
||||
class Config:
|
||||
host_name: str
|
||||
roots: List[Path]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Application:
|
||||
path: Path
|
||||
owner: str
|
||||
|
||||
|
||||
class Storage(ABC):
|
||||
def backup(self, backup_dirs: List[str]) -> bool:
|
||||
"""Backup directories"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ResticStorage(Storage):
|
||||
TYPE_NAME = "restic"
|
||||
|
||||
def __init__(self, name: str, params: Dict[str, Any]):
|
||||
self.name = name
|
||||
self.restic_repository = str(params.get("restic_repository", ""))
|
||||
self.restic_password = str(params.get("restic_password", ""))
|
||||
self.aws_access_key_id = str(params.get("aws_access_key_id", ""))
|
||||
self.aws_secret_access_key = str(params.get("aws_secret_access_key", ""))
|
||||
self.aws_default_region = str(params.get("aws_default_region", ""))
|
||||
|
||||
if not all(
|
||||
[
|
||||
self.restic_repository,
|
||||
self.restic_password,
|
||||
self.aws_access_key_id,
|
||||
self.aws_secret_access_key,
|
||||
self.aws_default_region,
|
||||
]
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
|
||||
)
|
||||
|
||||
def backup(self, backup_dirs: List[str]) -> bool:
|
||||
if not backup_dirs:
|
||||
logger.warning("No backup directories found")
|
||||
return True
|
||||
try:
|
||||
return self.__backup_internal(backup_dirs)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Restic backup process failed: %s", exc)
|
||||
return False
|
||||
|
||||
def __backup_internal(self, backup_dirs: List[str]) -> bool:
|
||||
logger.info("Starting restic backup")
|
||||
logger.info("Destination: %s", self.restic_repository)
|
||||
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"RESTIC_REPOSITORY": self.restic_repository,
|
||||
"RESTIC_PASSWORD": self.restic_password,
|
||||
"AWS_ACCESS_KEY_ID": self.aws_access_key_id,
|
||||
"AWS_SECRET_ACCESS_KEY": self.aws_secret_access_key,
|
||||
"AWS_DEFAULT_REGION": self.aws_default_region,
|
||||
}
|
||||
)
|
||||
|
||||
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
|
||||
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error("Restic backup failed: %s", result.stderr)
|
||||
return False
|
||||
|
||||
logger.info("Restic backup completed successfully")
|
||||
|
||||
check_cmd = ["restic", "check"]
|
||||
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error("Restic check failed: %s", result.stderr)
|
||||
return False
|
||||
|
||||
logger.info("Restic check completed successfully")
|
||||
|
||||
forget_cmd = [
|
||||
"restic",
|
||||
"forget",
|
||||
"--compact",
|
||||
"--prune",
|
||||
"--keep-daily",
|
||||
"90",
|
||||
"--keep-monthly",
|
||||
"36",
|
||||
]
|
||||
result = subprocess.run(forget_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error("Restic forget/prune failed: %s", result.stderr)
|
||||
return False
|
||||
|
||||
logger.info("Restic forget/prune completed successfully")
|
||||
|
||||
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error("Final restic check failed: %s", result.stderr)
|
||||
return False
|
||||
|
||||
logger.info("Final restic check completed successfully")
|
||||
return True
|
||||
|
||||
|
||||
class Notifier(ABC):
|
||||
def send(self, html_message: str):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class TelegramNotifier(Notifier):
|
||||
TYPE_NAME = "telegram"
|
||||
|
||||
def __init__(self, name: str, params: Dict[str, Any]):
|
||||
self.name = name
|
||||
self.telegram_bot_token = str(params.get("telegram_bot_token", ""))
|
||||
self.telegram_chat_id = str(params.get("telegram_chat_id", ""))
|
||||
if not all(
|
||||
[
|
||||
self.telegram_bot_token,
|
||||
self.telegram_chat_id,
|
||||
]
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing notification configuration values for backend {name}"
|
||||
)
|
||||
|
||||
def send(self, html_message: str):
|
||||
url = f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage"
|
||||
data = {
|
||||
"chat_id": self.telegram_chat_id,
|
||||
"parse_mode": "HTML",
|
||||
"text": html_message,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=data, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info("Telegram notification sent successfully")
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
|
||||
)
|
||||
|
||||
|
||||
class BackupManager:
|
||||
def __init__(self):
|
||||
self.errors = []
|
||||
self.warnings = []
|
||||
self.successful_backups = []
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
roots: List[Path],
|
||||
storages: List[Storage],
|
||||
notifiers: List[Notifier],
|
||||
):
|
||||
self.errors: List[str] = []
|
||||
self.warnings: List[str] = []
|
||||
self.successful_backups: List[str] = []
|
||||
self.config = config
|
||||
self.roots: List[Path] = roots
|
||||
self.storages = storages
|
||||
self.notifiers = notifiers
|
||||
|
||||
def get_application_directories(self) -> List[Tuple[str, str]]:
|
||||
"""Get all home directories and their owners"""
|
||||
app_dirs = []
|
||||
applications_path = Path("/mnt/applications")
|
||||
home_path = Path("/home")
|
||||
|
||||
source_dirs = itertools.chain(applications_path.iterdir(), home_path.iterdir())
|
||||
def find_applications(self) -> List[Application]:
|
||||
"""Get all application directories and their owners."""
|
||||
applications: List[Application] = []
|
||||
source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
|
||||
|
||||
for app_dir in source_dirs:
|
||||
if app_dir == "lost+found":
|
||||
if "lost+found" in str(app_dir):
|
||||
continue
|
||||
if app_dir.is_dir():
|
||||
try:
|
||||
# Get the owner of the directory
|
||||
stat_info = app_dir.stat()
|
||||
owner = pwd.getpwuid(stat_info.st_uid).pw_name
|
||||
app_dirs.append((str(app_dir), owner))
|
||||
applications.append(Application(path=app_dir, owner=owner))
|
||||
except (KeyError, OSError) as e:
|
||||
logger.warning(f"Could not get owner for {app_dir}: {e}")
|
||||
|
||||
return app_dirs
|
||||
return applications
|
||||
|
||||
def find_backup_script(self, app_dir: str) -> Optional[str]:
|
||||
"""Find backup script in user's home directory"""
|
||||
@@ -128,113 +285,72 @@ class BackupManager:
|
||||
return False
|
||||
|
||||
def get_backup_directories(self) -> List[str]:
|
||||
"""Get all backup directories that exist"""
|
||||
backup_dirs = []
|
||||
app_dirs = self.get_application_directories()
|
||||
"""Collect backup targets according to backup-targets rules"""
|
||||
backup_dirs: List[str] = []
|
||||
applications = self.find_applications()
|
||||
|
||||
for app_dir, _ in app_dirs:
|
||||
backup_path = os.path.join(app_dir, "backups")
|
||||
if os.path.exists(backup_path) and os.path.isdir(backup_path):
|
||||
backup_dirs.append(backup_path)
|
||||
def parse_targets_file(targets_file: Path) -> List[str]:
|
||||
"""Parse backup-targets file, skipping comments and empty lines."""
|
||||
targets: List[str] = []
|
||||
try:
|
||||
for raw_line in targets_file.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
targets.append(line)
|
||||
except OSError as e:
|
||||
warning_msg = f"Could not read backup targets file {targets_file}: {e}"
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
return targets
|
||||
|
||||
for app in applications:
|
||||
app_dir = app.path
|
||||
targets_file = app_dir / BACKUP_TARGETS_FILE
|
||||
resolved_targets: List[Path] = []
|
||||
|
||||
if targets_file.exists():
|
||||
# Read custom targets defined by the application.
|
||||
for target_line in parse_targets_file(targets_file):
|
||||
target_path = Path(target_line)
|
||||
if not target_path.is_absolute():
|
||||
target_path = (app_dir / target_path).resolve()
|
||||
else:
|
||||
target_path = target_path.resolve()
|
||||
if target_path.exists():
|
||||
resolved_targets.append(target_path)
|
||||
else:
|
||||
warning_msg = (
|
||||
f"Backup target does not exist for {app_dir}: {target_path}"
|
||||
)
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
else:
|
||||
# Fallback to default backups directory when no list is provided.
|
||||
default_target = (app_dir / BACKUP_DEFAULT_DIR).resolve()
|
||||
if default_target.exists():
|
||||
resolved_targets.append(default_target)
|
||||
else:
|
||||
warning_msg = f"Default backup path does not exist for {app_dir}: {default_target}"
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
|
||||
for target in resolved_targets:
|
||||
target_str = str(target)
|
||||
if target_str not in backup_dirs:
|
||||
backup_dirs.append(target_str)
|
||||
|
||||
return backup_dirs
|
||||
|
||||
def run_restic_backup(self, backup_dirs: List[str]) -> bool:
|
||||
"""Run restic backup for all backup directories"""
|
||||
if not backup_dirs:
|
||||
logger.warning("No backup directories found")
|
||||
return True
|
||||
def send_notification(self, success: bool) -> None:
|
||||
"""Send notification to Notifiers"""
|
||||
|
||||
try:
|
||||
logger.info("Starting restic backup")
|
||||
logger.info("Destination: %s", RESTIC_REPOSITORY)
|
||||
|
||||
# Set environment variables for restic
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"RESTIC_REPOSITORY": RESTIC_REPOSITORY,
|
||||
"RESTIC_PASSWORD": RESTIC_PASSWORD,
|
||||
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
|
||||
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
|
||||
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
|
||||
}
|
||||
)
|
||||
|
||||
# Run backup
|
||||
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
|
||||
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = f"Restic backup failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
self.errors.append(f"Restic backup: {error_msg}")
|
||||
return False
|
||||
|
||||
logger.info("Restic backup completed successfully")
|
||||
|
||||
# Run check
|
||||
check_cmd = ["restic", "check"]
|
||||
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = f"Restic check failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
self.errors.append(f"Restic check: {error_msg}")
|
||||
return False
|
||||
|
||||
logger.info("Restic check completed successfully")
|
||||
|
||||
# Run forget and prune
|
||||
forget_cmd = [
|
||||
"restic",
|
||||
"forget",
|
||||
"--compact",
|
||||
"--prune",
|
||||
"--keep-daily",
|
||||
"90",
|
||||
"--keep-monthly",
|
||||
"36",
|
||||
]
|
||||
result = subprocess.run(forget_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = f"Restic forget/prune failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
self.errors.append(f"Restic forget/prune: {error_msg}")
|
||||
return False
|
||||
|
||||
logger.info("Restic forget/prune completed successfully")
|
||||
|
||||
# Final check
|
||||
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = f"Final restic check failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
self.errors.append(f"Final restic check: {error_msg}")
|
||||
return False
|
||||
|
||||
logger.info("Final restic check completed successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Restic backup process failed: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
self.errors.append(f"Restic: {error_msg}")
|
||||
return False
|
||||
|
||||
def send_telegram_notification(self, success: bool) -> None:
|
||||
"""Send notification to Telegram"""
|
||||
try:
|
||||
if success and not self.errors:
|
||||
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап успешно завершен!"
|
||||
message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
|
||||
)
|
||||
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
|
||||
else:
|
||||
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап завершен с ошибками!"
|
||||
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
|
||||
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
@@ -247,31 +363,24 @@ class BackupManager:
|
||||
if self.errors:
|
||||
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
|
||||
|
||||
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
data = {"chat_id": TELEGRAM_CHAT_ID, "parse_mode": "HTML", "text": message}
|
||||
|
||||
response = requests.post(url, data=data, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info("Telegram notification sent successfully")
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
|
||||
)
|
||||
|
||||
for notificator in self.notifiers:
|
||||
try:
|
||||
notificator.send(message)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram notification: {str(e)}")
|
||||
logger.error(f"Failed to send notification: {str(e)}")
|
||||
|
||||
def run_backup_process(self) -> bool:
|
||||
"""Main backup process"""
|
||||
logger.info("Starting backup process")
|
||||
|
||||
# Get all home directories
|
||||
app_dirs = self.get_application_directories()
|
||||
logger.info(f"Found {len(app_dirs)} application directories")
|
||||
applications = self.find_applications()
|
||||
logger.info(f"Found {len(applications)} application directories")
|
||||
|
||||
# Process each user's backup
|
||||
for app_dir, username in app_dirs:
|
||||
for app in applications:
|
||||
app_dir = str(app.path)
|
||||
username = app.owner
|
||||
logger.info(f"Processing backup for app: {app_dir} (user {username})")
|
||||
|
||||
# Find backup script
|
||||
@@ -291,14 +400,18 @@ class BackupManager:
|
||||
backup_dirs = self.get_backup_directories()
|
||||
logger.info(f"Found backup directories: {backup_dirs}")
|
||||
|
||||
# Run restic backup
|
||||
restic_success = self.run_restic_backup(backup_dirs)
|
||||
overall_success = True
|
||||
|
||||
for storage in self.storages:
|
||||
backup_result = storage.backup(backup_dirs)
|
||||
if not backup_result:
|
||||
self.errors.append("Restic backup failed")
|
||||
|
||||
# Determine overall success
|
||||
overall_success = restic_success and len(self.errors) == 0
|
||||
overall_success = overall_success and backup_result
|
||||
|
||||
# Send notification
|
||||
self.send_telegram_notification(overall_success)
|
||||
self.send_notification(overall_success)
|
||||
|
||||
logger.info("Backup process completed")
|
||||
|
||||
@@ -313,9 +426,53 @@ class BackupManager:
|
||||
return True
|
||||
|
||||
|
||||
def initialize(config_path: Path) -> BackupManager:
|
||||
try:
|
||||
with config_path.open("rb") as config_file:
|
||||
raw_config = tomllib.load(config_file)
|
||||
except OSError as e:
|
||||
logger.error(f"Failed to read config file {config_path}: {e}")
|
||||
raise
|
||||
|
||||
host_name = str(raw_config.get("host_name", "unknown"))
|
||||
|
||||
roots_raw = raw_config.get("roots") or []
|
||||
if not isinstance(roots_raw, list) or not roots_raw:
|
||||
raise ValueError("roots must be a non-empty list of paths in config.toml")
|
||||
roots = [Path(root) for root in roots_raw]
|
||||
|
||||
storage_raw = raw_config.get("storage") or {}
|
||||
storages: List[Storage] = []
|
||||
for name, params in storage_raw.items():
|
||||
if not isinstance(params, dict):
|
||||
raise ValueError(f"Storage config for {name} must be a table")
|
||||
storage_type = params.get("type", "")
|
||||
if storage_type == ResticStorage.TYPE_NAME:
|
||||
storages.append(ResticStorage(name, params))
|
||||
if not storages:
|
||||
raise ValueError("At least one storage backend must be configured")
|
||||
|
||||
notifications_raw = raw_config.get("notifier") or {}
|
||||
notifiers: List[Notifier] = []
|
||||
for name, params in notifications_raw.items():
|
||||
if not isinstance(params, dict):
|
||||
raise ValueError(f"Notificator config for {name} must be a table")
|
||||
notifier_type = params.get("type", "")
|
||||
if notifier_type == TelegramNotifier.TYPE_NAME:
|
||||
notifiers.append(TelegramNotifier(name, params))
|
||||
if not notifiers:
|
||||
raise ValueError("At least one notification backend must be configured")
|
||||
|
||||
config = Config(host_name=host_name, roots=roots)
|
||||
|
||||
return BackupManager(
|
||||
config=config, roots=roots, storages=storages, notifiers=notifiers
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
backup_manager = BackupManager()
|
||||
backup_manager = initialize(CONFIG_PATH)
|
||||
success = backup_manager.run_backup_process()
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
|
||||
@@ -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 }}
|
||||
18
files/backups/config.template.toml
Normal file
18
files/backups/config.template.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
host_name = "{{ host_name }}"
|
||||
|
||||
roots = [
|
||||
"{{ application_dir }}"
|
||||
]
|
||||
|
||||
[storage.yandex_cloud_s3]
|
||||
type = "restic"
|
||||
restic_repository = "{{ restic_repository }}"
|
||||
restic_password = "{{ restic_password }}"
|
||||
aws_access_key_id = "{{ restic_s3_access_key }}"
|
||||
aws_secret_access_key = "{{ restic_s3_access_secret }}"
|
||||
aws_default_region = "{{ restic_s3_region }}"
|
||||
|
||||
[notifier.server_notifications_channel]
|
||||
type = "telegram"
|
||||
telegram_bot_token = "{{ notifications_tg_bot_token }}"
|
||||
telegram_chat_id = "{{ notifications_tg_chat_id }}"
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
dozzle_app:
|
||||
image: amir20/dozzle:v8.14.8
|
||||
image: amir20/dozzle:v8.14.11
|
||||
container_name: dozzle_app
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
gitea_app:
|
||||
image: gitea/gitea:1.25.2
|
||||
image: gitea/gitea:1.25.3
|
||||
restart: unless-stopped
|
||||
container_name: gitea_app
|
||||
ports:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
services:
|
||||
|
||||
gramps_app: &gramps_app
|
||||
image: ghcr.io/gramps-project/grampsweb:25.11.2
|
||||
image: ghcr.io/gramps-project/grampsweb:25.12.0
|
||||
container_name: gramps_app
|
||||
depends_on:
|
||||
- gramps_redis
|
||||
@@ -15,10 +15,10 @@ services:
|
||||
- "{{ (data_dir, 'gramps_db') | path_join }}:/root/.gramps/grampsdb" # persist Gramps database
|
||||
- "{{ (data_dir, 'gramps_users') | path_join }}:/app/users" # persist user database
|
||||
- "{{ (data_dir, 'gramps_index') | path_join }}:/app/indexdir" # persist search index
|
||||
- "{{ (data_dir, 'gramps_thumb_cache') | path_join }}:/app/thumbnail_cache" # persist thumbnails
|
||||
- "{{ (data_dir, 'gramps_cache') | path_join }}:/app/cache" # persist export and report caches
|
||||
- "{{ (data_dir, 'gramps_secret') | path_join }}:/app/secret" # persist flask secret
|
||||
- "{{ (data_dir, 'gramps_media') | path_join }}:/app/media" # persist media files
|
||||
- "{{ (cache_dir, 'gramps_thumb_cache') | path_join }}:/app/thumbnail_cache" # persist thumbnails
|
||||
- "{{ (cache_dir, 'gramps_cache') | path_join }}:/app/cache" # persist export and report caches
|
||||
- "{{ media_dir }}:/app/media" # persist media files
|
||||
environment:
|
||||
GRAMPSWEB_TREE: "Gramps" # will create a new tree if not exists
|
||||
GRAMPSWEB_SECRET_KEY: "{{ gramps_secret_key }}"
|
||||
@@ -37,12 +37,8 @@ services:
|
||||
GRAMPSWEB_EMAIL_USE_TLS: "false"
|
||||
GRAMPSWEB_DEFAULT_FROM_EMAIL: "gramps@vakhrushev.me"
|
||||
|
||||
# media storage at s3
|
||||
GRAMPSWEB_MEDIA_BASE_DIR: "s3://av-gramps-media-storage"
|
||||
AWS_ENDPOINT_URL: "{{ gramps_s3_endpoint }}"
|
||||
AWS_ACCESS_KEY_ID: "{{ gramps_s3_access_key_id }}"
|
||||
AWS_SECRET_ACCESS_KEY: "{{ gramps_s3_secret_access_key }}"
|
||||
AWS_DEFAULT_REGION: "{{ gramps_s3_region }}"
|
||||
# media storage
|
||||
GRAMPSWEB_MEDIA_BASE_DIR: "/app/media"
|
||||
|
||||
gramps_celery:
|
||||
<<: *gramps_app # YAML merge key copying the entire grampsweb service config
|
||||
@@ -53,10 +49,10 @@ services:
|
||||
ports: []
|
||||
networks:
|
||||
- "gramps_network"
|
||||
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
|
||||
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=1
|
||||
|
||||
gramps_redis:
|
||||
image: valkey/valkey:8.1.1-alpine
|
||||
image: valkey/valkey:9.0-alpine
|
||||
container_name: gramps_redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
@@ -23,10 +23,3 @@ models:
|
||||
undo:
|
||||
type: sqlite
|
||||
path: "{{ (data_dir, 'gramps_db/59a0f3d6-1c3d-4410-8c1d-1c9c6689659f/undo.db') | path_join }}"
|
||||
archive:
|
||||
includes:
|
||||
- "{{ data_dir }}"
|
||||
excludes:
|
||||
- "{{ (data_dir, 'gramps_cache') | path_join }}"
|
||||
- "{{ (data_dir, 'gramps_thumb_cache') | path_join }}"
|
||||
- "{{ (data_dir, 'gramps_tmp') | path_join }}"
|
||||
|
||||
65
files/gramps/gramps_rename.py
Executable file
65
files/gramps/gramps_rename.py
Executable 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()
|
||||
@@ -3,7 +3,7 @@
|
||||
services:
|
||||
|
||||
memos_app:
|
||||
image: neosmemo/memos:0.25.2
|
||||
image: neosmemo/memos:0.25.3
|
||||
container_name: memos_app
|
||||
restart: unless-stopped
|
||||
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
models:
|
||||
|
||||
gramps:
|
||||
memos:
|
||||
compress_with:
|
||||
type: 'tgz'
|
||||
storages:
|
||||
@@ -14,8 +14,3 @@ models:
|
||||
users:
|
||||
type: sqlite
|
||||
path: "{{ (data_dir, 'memos_prod.db') | path_join }}"
|
||||
archive:
|
||||
includes:
|
||||
- "{{ data_dir }}"
|
||||
excludes:
|
||||
- "{{ (data_dir, '.thumbnail_cache') | path_join }}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
netdata:
|
||||
image: netdata/netdata:v2.7.3
|
||||
image: netdata/netdata:v2.8.4
|
||||
container_name: netdata
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
@@ -16,12 +16,16 @@ services:
|
||||
- "{{ config_dir }}:/etc/netdata"
|
||||
- "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata"
|
||||
- "{{ (data_dir, 'cache') | path_join }}:/var/cache/netdata"
|
||||
|
||||
# Netdata system volumes
|
||||
- "/:/host/root:ro,rslave"
|
||||
|
||||
- "/etc/group:/host/etc/group:ro"
|
||||
- "/etc/hostname:/host/etc/hostname:ro"
|
||||
- "/etc/localtime:/etc/localtime:ro"
|
||||
- "/etc/os-release:/host/etc/os-release:ro"
|
||||
- "/etc/passwd:/host/etc/passwd:ro"
|
||||
|
||||
- "/proc:/host/proc:ro"
|
||||
- "/run/dbus:/run/dbus:ro"
|
||||
- "/sys:/host/sys:ro"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
jobs:
|
||||
- name: fail2ban
|
||||
update_every: 15 # Collect Fail2Ban jails statistics every 15 seconds
|
||||
update_every: 60 # Collect Fail2Ban jails statistics every N seconds
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
update_every: 15
|
||||
update_every: 60
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# cpu cores = 2
|
||||
# libuv worker threads = 16
|
||||
# profile = standalone
|
||||
hostname = {{ host_name }}
|
||||
# hostname = rivendell-v2
|
||||
# glibc malloc arena max for plugins = 1
|
||||
# glibc malloc arena max for netdata = 1
|
||||
# crash reports = all
|
||||
@@ -30,12 +30,15 @@
|
||||
# has unstable connection = no
|
||||
|
||||
[db]
|
||||
#| >>> [db].update every <<<
|
||||
#| datatype: duration (seconds), default value: 1s
|
||||
update every = 10s
|
||||
|
||||
# enable replication = yes
|
||||
# replication period = 1d
|
||||
# replication step = 1h
|
||||
# replication threads = 1
|
||||
# replication prefetch = 10
|
||||
# update every = 1s
|
||||
# db = dbengine
|
||||
# memory deduplication (ksm) = auto
|
||||
# cleanup orphan hosts after = 1h
|
||||
@@ -47,7 +50,7 @@
|
||||
# dbengine extent cache size = off
|
||||
# dbengine enable journal integrity check = no
|
||||
# dbengine use all ram for caches = no
|
||||
# dbengine out of memory protection = 391.99MiB
|
||||
# dbengine out of memory protection = 391.49MiB
|
||||
# dbengine use direct io = yes
|
||||
# dbengine journal v2 unmount time = 2m
|
||||
# dbengine pages per extent = 109
|
||||
@@ -96,9 +99,6 @@
|
||||
# PYTHONPATH =
|
||||
# TZ = :/etc/localtime
|
||||
|
||||
[host labels]
|
||||
# name = value
|
||||
|
||||
[cloud]
|
||||
# conversation log = no
|
||||
# scope = full
|
||||
@@ -107,15 +107,15 @@
|
||||
|
||||
[ml]
|
||||
# enabled = auto
|
||||
# maximum num samples to train = 21600
|
||||
# minimum num samples to train = 900
|
||||
# training window = 6h
|
||||
# min training window = 15m
|
||||
# max training vectors = 1440
|
||||
# max samples to smooth = 3
|
||||
# train every = 3h
|
||||
# number of models per dimension = 18
|
||||
# delete models older than = 7d
|
||||
# num samples to diff = 1
|
||||
# num samples to smooth = 3
|
||||
# num samples to lag = 5
|
||||
# random sampling ratio = 0.20000
|
||||
# maximum number of k-means iterations = 1000
|
||||
# dimension anomaly score threshold = 0.99000
|
||||
# host anomaly rate threshold = 1.00000
|
||||
@@ -181,7 +181,7 @@
|
||||
# gzip compression level = 3
|
||||
# ssl skip certificate verification = no
|
||||
# web server threads = 6
|
||||
# web server max sockets = 262144
|
||||
# web server max sockets = 131072
|
||||
|
||||
[registry]
|
||||
# enabled = no
|
||||
@@ -191,7 +191,7 @@
|
||||
# registry expire idle persons = 1y
|
||||
# registry domain =
|
||||
# registry to announce = https://registry.my-netdata.io
|
||||
# registry hostname = 7171b7f9fc69
|
||||
# registry hostname = rivendell-v2
|
||||
# verify browser cookies support = yes
|
||||
# enable cookies SameSite and Secure = yes
|
||||
# max URL length = 1024
|
||||
@@ -202,9 +202,29 @@
|
||||
|
||||
[pulse]
|
||||
# extended = no
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
|
||||
[plugins]
|
||||
#| >>> [plugins].perf <<<
|
||||
#| datatype: yes or no, default value: yes
|
||||
perf = no
|
||||
|
||||
#| >>> [plugins].python.d <<<
|
||||
#| datatype: yes or no, default value: yes
|
||||
python.d = no
|
||||
|
||||
#| >>> [plugins].charts.d <<<
|
||||
#| datatype: yes or no, default value: yes
|
||||
charts.d = no
|
||||
|
||||
#| >>> [plugins].otel <<<
|
||||
#| datatype: yes or no, default value: yes
|
||||
otel = no
|
||||
|
||||
#| >>> [plugins].statsd <<<
|
||||
#| datatype: yes or no, default value: yes
|
||||
statsd = no
|
||||
|
||||
# idlejitter = yes
|
||||
# netdata pulse = yes
|
||||
# profile = no
|
||||
@@ -213,23 +233,20 @@
|
||||
# proc = yes
|
||||
# cgroups = yes
|
||||
# timex = yes
|
||||
# statsd = yes
|
||||
# enable running new plugins = yes
|
||||
# check for new plugins every = 1m
|
||||
# slabinfo = no
|
||||
# freeipmi = no
|
||||
# python.d = yes
|
||||
# go.d = yes
|
||||
# apps = yes
|
||||
# systemd-journal = yes
|
||||
# network-viewer = yes
|
||||
# charts.d = yes
|
||||
# debugfs = yes
|
||||
# perf = yes
|
||||
# ioping = yes
|
||||
# network-viewer = yes
|
||||
# apps = yes
|
||||
# go.d = yes
|
||||
# systemd-units = yes
|
||||
# systemd-journal = yes
|
||||
|
||||
[statsd]
|
||||
# update every (flushInterval) = 1s
|
||||
# update every (flushInterval) = 10s
|
||||
# udp messages to process at once = 10
|
||||
# create private charts for metrics matching = *
|
||||
# max private charts hard limit = 1000
|
||||
@@ -247,10 +264,7 @@
|
||||
# gaps on histograms (deleteHistograms) = no
|
||||
# gaps on timers (deleteTimers) = no
|
||||
# gaps on dictionaries (deleteDictionaries) = no
|
||||
# statsd server max TCP sockets = 262144
|
||||
# listen backlog = 4096
|
||||
# default port = 8125
|
||||
# bind to = udp:localhost tcp:localhost
|
||||
# statsd server max TCP sockets = 131072
|
||||
|
||||
[plugin:idlejitter]
|
||||
# loop time = 20ms
|
||||
@@ -300,8 +314,14 @@
|
||||
# /sys/class/drm = yes
|
||||
|
||||
[plugin:cgroups]
|
||||
# update every = 1s
|
||||
# check for new cgroups every = 10s
|
||||
#| >>> [plugin:cgroups].update every <<<
|
||||
#| datatype: duration (seconds), default value: 10s
|
||||
update every = 20s
|
||||
|
||||
#| >>> [plugin:cgroups].check for new cgroups every <<<
|
||||
#| datatype: duration (seconds), default value: 10s
|
||||
check for new cgroups every = 20s
|
||||
|
||||
# use unified cgroups = auto
|
||||
# max cgroups to allow = 1000
|
||||
# max cgroups depth to monitor = 0
|
||||
@@ -314,8 +334,11 @@
|
||||
# cgroups to match as systemd services = !/system.slice/*/*.service /system.slice/*.service
|
||||
|
||||
[plugin:proc:diskspace]
|
||||
#| >>> [plugin:proc:diskspace].update every <<<
|
||||
#| datatype: duration (seconds), default value: 10s
|
||||
update every = 1m
|
||||
|
||||
# remove charts of unmounted disks = yes
|
||||
# update every = 1s
|
||||
# check for new mount points every = 15s
|
||||
# exclude space metrics on paths = /dev /dev/shm /proc/* /sys/* /var/run/user/* /run/lock /run/user/* /snap/* /var/lib/docker/* /var/lib/containers/storage/* /run/credentials/* /run/containerd/* /rpool /rpool/*
|
||||
# exclude space metrics on filesystems = *gvfs *gluster* *s3fs *ipfs *davfs2 *httpfs *sshfs *gdfs *moosefs fusectl autofs cgroup cgroup2 hugetlbfs devtmpfs fuse.lxcfs
|
||||
@@ -326,40 +349,28 @@
|
||||
[plugin:tc]
|
||||
# script to run to get tc values = /usr/libexec/netdata/plugins.d/tc-qos-helper.sh
|
||||
|
||||
[plugin:python.d]
|
||||
# update every = 1s
|
||||
# command options =
|
||||
|
||||
[plugin:go.d]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:apps]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:systemd-journal]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:network-viewer]
|
||||
# update every = 1s
|
||||
# command options =
|
||||
|
||||
[plugin:charts.d]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:debugfs]
|
||||
# update every = 1s
|
||||
# command options =
|
||||
|
||||
[plugin:perf]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:ioping]
|
||||
# update every = 1s
|
||||
# update every = 10s
|
||||
# command options =
|
||||
|
||||
[plugin:proc:/proc/net/dev]
|
||||
@@ -635,7 +646,7 @@
|
||||
# preferred disk ids = *
|
||||
# exclude disks = loop* ram*
|
||||
# filename to monitor = /host/proc/diskstats
|
||||
# performance metrics for disks with major 252 = yes
|
||||
# performance metrics for disks with major 253 = yes
|
||||
|
||||
[plugin:proc:/proc/mdstat]
|
||||
# faulty devices = yes
|
||||
@@ -685,3 +696,7 @@
|
||||
|
||||
[plugin:proc:/sys/class/drm]
|
||||
# directory to monitor = /host/sys/class/drm
|
||||
|
||||
[plugin:systemd-units]
|
||||
# update every = 10s
|
||||
# command options =
|
||||
@@ -5,15 +5,16 @@ services:
|
||||
outline_app:
|
||||
image: outlinewiki/outline:1.1.0
|
||||
container_name: outline_app
|
||||
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- outline_postgres
|
||||
- outline_redis
|
||||
ports:
|
||||
- "127.0.0.1:{{ outline_port }}:3000"
|
||||
networks:
|
||||
- "outline_network"
|
||||
- "web_proxy_network"
|
||||
volumes:
|
||||
- "{{ media_dir }}:/var/lib/outline/data"
|
||||
environment:
|
||||
NODE_ENV: 'production'
|
||||
URL: 'https://outline.vakhrushev.me'
|
||||
@@ -24,16 +25,8 @@ services:
|
||||
PGSSLMODE: 'disable'
|
||||
REDIS_URL: 'redis://outline_redis:6379'
|
||||
|
||||
FILE_STORAGE: 's3'
|
||||
FILE_STORAGE_UPLOAD_MAX_SIZE: '262144000'
|
||||
AWS_ACCESS_KEY_ID: '{{ outline_s3_access_key }}'
|
||||
AWS_SECRET_ACCESS_KEY: '{{ outline_s3_secret_key }}'
|
||||
AWS_REGION: '{{ outline_s3_region }}'
|
||||
AWS_S3_ACCELERATE_URL: ''
|
||||
AWS_S3_UPLOAD_BUCKET_URL: '{{ outline_s3_url }}'
|
||||
AWS_S3_UPLOAD_BUCKET_NAME: '{{ outline_s3_bucket }}'
|
||||
AWS_S3_FORCE_PATH_STYLE: 'true'
|
||||
AWS_S3_ACL: 'private'
|
||||
FILE_STORAGE: 'local'
|
||||
FILE_STORAGE_UPLOAD_MAX_SIZE: '262144000' # 250 MB
|
||||
|
||||
OIDC_CLIENT_ID: '{{ outline_oidc_client_id | replace("$", "$$") }}'
|
||||
OIDC_CLIENT_SECRET: '{{ outline_oidc_client_secret | replace("$", "$$") }}'
|
||||
@@ -54,7 +47,7 @@ services:
|
||||
SMTP_SECURE: 'false'
|
||||
|
||||
outline_redis:
|
||||
image: valkey/valkey:8.1.1-alpine
|
||||
image: valkey/valkey:9.0-alpine
|
||||
container_name: outline_redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -64,8 +57,10 @@ services:
|
||||
outline_postgres:
|
||||
image: postgres:16.3-bookworm
|
||||
container_name: outline_postgres
|
||||
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "/etc/passwd:/etc/passwd:ro"
|
||||
- "{{ postgres_data_dir }}:/var/lib/postgresql/data"
|
||||
environment:
|
||||
POSTGRES_USER: '{{ outline_postgres_user }}'
|
||||
@@ -74,6 +69,10 @@ services:
|
||||
networks:
|
||||
- "outline_network"
|
||||
- "monitoring_network"
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "--username={{ outline_postgres_user }}", "--dbname={{ outline_postgres_database }}"]
|
||||
interval: 10s
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
outline_network:
|
||||
|
||||
11
lefthook.yml
11
lefthook.yml
@@ -1,6 +1,8 @@
|
||||
# Refer for explanation to following link:
|
||||
# https://lefthook.dev/configuration/
|
||||
|
||||
glob_matcher: doublestar
|
||||
|
||||
templates:
|
||||
av-hooks-dir: "/home/av/projects/private/git-hooks"
|
||||
|
||||
@@ -12,3 +14,12 @@ pre-commit:
|
||||
|
||||
- name: "check secret files"
|
||||
run: "python3 {av-hooks-dir}/pre-commit/check-secrets-encrypted-with-ansible-vault.py"
|
||||
|
||||
- name: "format python"
|
||||
glob: "**/*.py"
|
||||
run: "black --quiet {staged_files}"
|
||||
stage_fixed: true
|
||||
|
||||
- name: "mypy"
|
||||
glob: "**/*.py"
|
||||
run: "mypy {staged_files}"
|
||||
|
||||
48
playbook-all-applications.yml
Normal file
48
playbook-all-applications.yml
Normal 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
12
playbook-all-setup.yml
Normal 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
|
||||
@@ -3,15 +3,19 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
- files/authelia/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "authelia"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1011
|
||||
app_owner_gid: 1012
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
config_dir: "{{ (base_dir, 'config') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
|
||||
|
||||
tasks:
|
||||
- name: "Create user and environment"
|
||||
@@ -19,6 +23,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -30,7 +36,9 @@
|
||||
mode: "0700"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ config_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Copy users file"
|
||||
ansible.builtin.copy:
|
||||
@@ -40,7 +48,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: "Copy configuration files (templates)"
|
||||
- name: "Copy configuration file"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/configuration.template.yml"
|
||||
dest: "{{ (config_dir, 'configuration.yml') | path_join }}"
|
||||
@@ -48,6 +56,22 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: "Copy gobackup config"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/gobackup.template.yml"
|
||||
dest: "{{ gobackup_config }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy backup script"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/backup.template.sh"
|
||||
dest: "{{ (base_dir, 'backup.sh') | path_join }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
@@ -61,8 +85,12 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
vars:
|
||||
backup_config_dir: "/etc/backup"
|
||||
backup_config_file: "{{ (backup_config_dir, 'config.ini') | path_join }}"
|
||||
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
||||
|
||||
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
|
||||
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
- name: "Create backup config file"
|
||||
ansible.builtin.template:
|
||||
src: "files/backups/config.template.ini"
|
||||
src: "files/backups/config.template.toml"
|
||||
dest: "{{ backup_config_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "caddyproxy"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1010
|
||||
app_owner_gid: 1011
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
@@ -23,6 +24,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -59,14 +62,20 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# - name: "Reload caddy"
|
||||
# community.docker.docker_compose_v2_exec:
|
||||
# project_src: '{{ base_dir }}'
|
||||
# service: "{{ service_name }}"
|
||||
# command: caddy reload --config /etc/caddy/Caddyfile
|
||||
# tags:
|
||||
# - run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
tasks:
|
||||
- name: "Install python docker lib from pip"
|
||||
ansible.builtin.pip:
|
||||
name: docker
|
||||
# - name: "Install python docker lib from pip"
|
||||
# ansible.builtin.pip:
|
||||
# name: docker
|
||||
|
||||
- name: "Install docker"
|
||||
ansible.builtin.import_role:
|
||||
@@ -32,3 +31,10 @@
|
||||
community.docker.docker_network:
|
||||
name: "monitoring_network"
|
||||
driver: "bridge"
|
||||
|
||||
- name: "Schedule docker image prune"
|
||||
ansible.builtin.cron:
|
||||
name: "docker image prune"
|
||||
minute: "0"
|
||||
hour: "3"
|
||||
job: "/usr/bin/docker image prune -af"
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "dozzle"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1016
|
||||
app_owner_gid: 1017
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
|
||||
tasks:
|
||||
@@ -17,6 +18,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -31,7 +34,7 @@
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
dest: "{{ base_dir }}/docker-compose.yml"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -42,3 +45,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
# See: https://github.com/zyedidia/eget/releases
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "gitea"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1005
|
||||
app_owner_gid: 1006
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
@@ -19,6 +20,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -54,3 +57,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "gramps"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1009
|
||||
app_owner_gid: 1010
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
media_dir: "{{ (base_dir, 'media') | path_join }}"
|
||||
cache_dir: "{{ (base_dir, 'cache') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
|
||||
|
||||
@@ -20,6 +23,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
@@ -32,6 +37,8 @@
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ media_dir }}"
|
||||
- "{{ cache_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Copy gobackup config"
|
||||
@@ -50,6 +57,27 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
- "{{ media_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Copy rename script"
|
||||
ansible.builtin.copy:
|
||||
src: "files/{{ app_name }}/gramps_rename.py"
|
||||
dest: "{{ base_dir }}/gramps_rename.py"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
@@ -63,3 +91,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
gather_facts: false
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
- vars/homepage.yml
|
||||
- vars/homepage.images.yml
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
- vars/homepage.yml
|
||||
- vars/homepage.images.yml
|
||||
@@ -14,8 +13,20 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: "directory"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
|
||||
- name: "Login to yandex docker registry."
|
||||
ansible.builtin.script:
|
||||
cmd: "files/yandex-docker-registry-auth.sh"
|
||||
@@ -33,3 +44,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "memos"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1019
|
||||
app_owner_gid: 1020
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
|
||||
@@ -20,6 +21,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
@@ -30,6 +33,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
@@ -49,6 +53,18 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
@@ -62,3 +78,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "miniflux"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1013
|
||||
app_owner_gid: 1014
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
secrets_dir: "{{ (base_dir, 'secrets') | path_join }}"
|
||||
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
|
||||
@@ -21,6 +22,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal directories"
|
||||
@@ -31,6 +34,10 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0770"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ secrets_dir }}"
|
||||
- "{{ postgres_data_dir }}"
|
||||
- "{{ postgres_backups_dir }}"
|
||||
|
||||
- name: "Copy secrets"
|
||||
@@ -50,7 +57,7 @@
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
dest: "{{ base_dir }}/docker-compose.yml"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -58,7 +65,7 @@
|
||||
|
||||
- name: "Copy backup script"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/backup.sh.j2"
|
||||
src: "./files/{{ app_name }}/backup.template.sh"
|
||||
dest: "{{ base_dir }}/backup.sh"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -70,3 +77,5 @@
|
||||
state: "present"
|
||||
recreate: "always"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "netdata"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1012
|
||||
app_owner_gid: 1013
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
config_dir: "{{ (base_dir, 'config') | path_join }}"
|
||||
config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}"
|
||||
@@ -20,6 +21,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -37,7 +40,7 @@
|
||||
|
||||
- name: "Copy netdata config file"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/netdata.conf.j2"
|
||||
src: "files/{{ app_name }}/netdata.template.conf"
|
||||
dest: "{{ config_dir }}/netdata.conf"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -94,8 +97,12 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,16 +3,22 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "outline"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1007
|
||||
app_owner_gid: 1008
|
||||
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
|
||||
postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}"
|
||||
media_dir: "{{ (base_dir, 'media') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
|
||||
postgres_data_dir: "{{ (data_dir, 'postgres') | path_join }}"
|
||||
postgres_backups_dir: "{{ (backups_dir, 'postgres') | path_join }}"
|
||||
|
||||
|
||||
tasks:
|
||||
- name: "Create user and environment"
|
||||
@@ -20,6 +26,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal directories"
|
||||
@@ -30,8 +38,33 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0770"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ media_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
- "{{ postgres_data_dir }}"
|
||||
- "{{ postgres_backups_dir }}"
|
||||
|
||||
- name: "Copy backup script"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/backup.template.sh"
|
||||
dest: "{{ base_dir }}/backup.sh"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ media_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
@@ -40,16 +73,10 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy backup script"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/backup.sh.j2"
|
||||
dest: "{{ base_dir }}/backup.sh"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "rssbridge"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1014
|
||||
app_owner_gid: 1015
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
|
||||
tasks:
|
||||
@@ -17,6 +18,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
@@ -42,3 +45,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
@@ -40,3 +39,20 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: 'Create directory for mount'
|
||||
ansible.builtin.file:
|
||||
path: '/mnt/applications'
|
||||
state: 'directory'
|
||||
mode: '0755'
|
||||
tags:
|
||||
- mount-storage
|
||||
|
||||
- name: 'Mount external storages'
|
||||
ansible.posix.mount:
|
||||
path: '/mnt/applications'
|
||||
src: 'UUID=3942bffd-8328-4536-8e88-07926fb17d17'
|
||||
fstype: ext4
|
||||
state: mounted
|
||||
tags:
|
||||
- mount-storage
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
gather_facts: false
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
- vars/transcriber.yml
|
||||
- vars/transcriber.images.yml
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
- vars/transcriber.yml
|
||||
- vars/transcriber.images.yml
|
||||
@@ -14,6 +13,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
@@ -24,6 +25,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ config_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
@@ -53,3 +55,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
tasks:
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "wakapi"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1015
|
||||
app_owner_gid: 1016
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
|
||||
@@ -20,6 +21,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
@@ -30,6 +33,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
@@ -62,3 +66,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/ports.yml
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "wanderer"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1018
|
||||
app_owner_gid: 1019
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
|
||||
@@ -23,6 +24,8 @@
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
@@ -33,6 +36,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ (data_dir, 'pb_data') | path_join }}"
|
||||
- "{{ (data_dir, 'uploads') | path_join }}"
|
||||
@@ -47,13 +51,29 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy backup script"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/backup.template.sh"
|
||||
# - name: "Copy backup script"
|
||||
# ansible.builtin.template:
|
||||
# src: "files/{{ app_name }}/backup.template.sh"
|
||||
# dest: "{{ base_dir }}/backup.sh"
|
||||
# owner: "{{ app_user }}"
|
||||
# group: "{{ app_user }}"
|
||||
# mode: "0750"
|
||||
|
||||
- name: "Disable backup script"
|
||||
ansible.builtin.file:
|
||||
dest: "{{ base_dir }}/backup.sh"
|
||||
state: absent
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -68,3 +88,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
owner_name: ""
|
||||
owner_uid: 0
|
||||
owner_group: "{{ owner_name }}"
|
||||
owner_gid: "{{ owner_uid }}"
|
||||
owner_extra_groups: []
|
||||
owner_ssh_keys: []
|
||||
owner_env: {}
|
||||
|
||||
@@ -4,9 +4,15 @@
|
||||
msg: You must set owner name.
|
||||
when: not owner_name
|
||||
|
||||
- name: 'Check app requirements for user "{{ owner_name }}".'
|
||||
ansible.builtin.fail:
|
||||
msg: You must set owner uid.
|
||||
when: not owner_uid
|
||||
|
||||
- name: 'Create group "{{ owner_group }}".'
|
||||
ansible.builtin.group:
|
||||
name: "{{ owner_group }}"
|
||||
gid: "{{ owner_gid }}"
|
||||
state: present
|
||||
|
||||
- name: 'Create user "{{ owner_name }}".'
|
||||
@@ -14,6 +20,7 @@
|
||||
name: "{{ owner_name }}"
|
||||
group: "{{ owner_group }}"
|
||||
groups: "{{ owner_extra_groups }}"
|
||||
uid: "{{ owner_uid }}"
|
||||
shell: /bin/bash
|
||||
register: user_create_result
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
app_name: "homepage"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1008
|
||||
app_owner_gid: 1009
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
||||
|
||||
# Registry images
|
||||
|
||||
@@ -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 }}"
|
||||
321
vars/secrets.yml
321
vars/secrets.yml
@@ -1,161 +1,162 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
62373337383866623263326333653134323532613565633430346266396463376439633833656138
|
||||
3162643934363462303436653264323862353161633963360a633861646264336133613134336336
|
||||
39333431643666323730356163626236333430313136626134333539333330646237333339306238
|
||||
3936353539653730340a613564656330663336336132356431386264666662333864663064396332
|
||||
64393163346538396366366333623431333533666563346163613633393635646531383739323435
|
||||
36636533383563383265383439323539396531623466323435303164643038653734623133343739
|
||||
34636430643030323031303434346636623764663239653535323035396634613036386230323032
|
||||
62623131643639393836346664393734653931346630666539656661626566353730333635393261
|
||||
35386339363732386137643937373730303765643234326433336366376135656637376637323862
|
||||
35323230333736366238333734386136383534393661383037346235643561386336393438633965
|
||||
34646535363835383134353232313839663763303037653766346138363531613162616539353531
|
||||
65663832633239613236626366623733323365303534616463333435393763656162343764323138
|
||||
37613530636331316665376634623233653935653933613930333130313563313061333435353639
|
||||
30636361393562653764613065316563666134336333343433336335353138656239326166636533
|
||||
31386539646636333438643366336666623764656335343066616666343737356266356335373733
|
||||
31653234643234313462333138353639323362363834353162663064363938353134313232623533
|
||||
37363732343935623132303664653436313730316534623630326362376233393233373531316162
|
||||
63326537316266613339376239376561613136633830633837393035363337343034643230303630
|
||||
61343039333839313164616536306465646262626332383665666139626534613938303739643566
|
||||
63343731333835666466626136633761366536366230623364356632653837633934303739623466
|
||||
39396535313963643535353236373338383431356331393232303336623962646366316132343263
|
||||
34613162323861643632393736383836376161643036323165393837623734386663383436393062
|
||||
32383130613063643036656365646332363865646662346431393062323961343039343935643965
|
||||
62343033646632333337613164353263353834623430336334356666323761353964306133393439
|
||||
61323438346662623435356537623064373832346338613733613061623164623466363561623466
|
||||
34393430313838623435663935333638323231663138346539653139663437643066663465666231
|
||||
31303232363863383839623037636333626166326135393439316662306435306130353639386563
|
||||
65316638376534326363306562353037653765333834663361353864383265643466396265336264
|
||||
32353865633131373061323637623736353162366131633161643963623432623133336237366164
|
||||
63393465656464646635376438363563663461626438336339386564316463343230323964633430
|
||||
37343563656462333638303638356235326635376138626333613636623830363366343435373464
|
||||
34623465646437663735333036366566303962356266666363366132643764383638386230643635
|
||||
30353838633863313037616165666265393930326333613761366566633230373761313162363135
|
||||
64386461343263646331333565396662636133356361616563396563626232313962303862353338
|
||||
36383238633134383865663434343062636465623631363461303439666565373339616136613336
|
||||
32663732333263353666323766333338396532643134316362613930396134363430313363616663
|
||||
34313766303433333936343936386138313939373734316463376531623562633834626561643134
|
||||
64356530643637313066656363653165396562616363373637373261316633643331653833623137
|
||||
33626162366636633932343135353961366631633866343564613834353237326132643963353466
|
||||
65633033616238313532666366313566373261643531333830306232373439323737313330303662
|
||||
36613164633264366133623566653034363739633435646634383832343138303931626636373037
|
||||
36326464353336383964643439393864666238623939393134326134613938303466343536613933
|
||||
63623961656435656363363365633137383563373265636564613861666331623739623532666632
|
||||
62646432643432393565396135613364653263386562386262396262333430656335313337646539
|
||||
62353335303836376433616639613531623432626434306162666134656637616562356635363931
|
||||
31303063313634336335336233353265346563646530376639663032326239393363626136346466
|
||||
38616630666432363866316137653730623066373263656331313665643439646636653362393130
|
||||
36666130616435626135306639303766656430316536376431303337316631316462623564666237
|
||||
34303262663231303437323266643262363434313232346432363030306661333536353732643761
|
||||
37333962393436323463633762393135326264323239373838613032353938366238393138393332
|
||||
34643162303335666330613263386438653835396135626166636435306238643463623061646432
|
||||
30393564643665303764356334363966623939323138336332646437343265343662333836326566
|
||||
64663464313730353162306534663731376266373062343534633230663635623535626533613636
|
||||
65313961326665333234336663303236393232373334623532333863313766613162616666393732
|
||||
33313937326164396639663835653461333033616463373034643830356361656234393330393233
|
||||
64313934653133636630323331353262646335363666366339303630303761393262363761663530
|
||||
66373638336336386432613939343835636463303838383239333361616538643538633738333834
|
||||
66356234383437336234663964613531306165616636616235386363623738616631646164313538
|
||||
30313034346264373033633431636331336562316538666639653462313438383038373034663036
|
||||
30643361323230313761633439326236663531316339303765663032653835643735386536333062
|
||||
63623338613364343137663136666234376264626663323565343762343834633330306463353135
|
||||
35643330373139653839313739376134323831383538666532633561323365363534633063653136
|
||||
32303661303831373633306164393533303336373766643463383635313035376261363461363464
|
||||
62313539646333633838303635326335326330383261326330333533396364393931643138366562
|
||||
38343961316432396230616337313164626631616330343861306438396230383466356133643161
|
||||
36353364303739326265306430653036633134343166383430663264643466653430663936353462
|
||||
32386330626330393734343938623664386430323438333965346536396639613365663461616162
|
||||
66633534363737366334633239613663356437366234306437363733376237343132353133326661
|
||||
63633362356361653264363435303030313962323462313137656532306537616330613532663665
|
||||
64376131343937636138333332353464633831323361623263323766333034386535313136613831
|
||||
62363662616661643563326465653664393032666162303364643234303362333164653738616636
|
||||
34613766316565333136663831396166643163333334366461616333376365383530396638313931
|
||||
35356264623739663935316165376363633338313164613766663137323261303935613230316661
|
||||
66376231333539386139353537356163396639663965666334643739333434393134313230356131
|
||||
65393331393836343962343235356138366634633563613838333139666565363831346166396630
|
||||
30663630376231646665386137326361336132313535336263653338343430613830346363303239
|
||||
36623539326161666564633435303439313763356536643737386432633363636162623232656137
|
||||
66346665623433333131323339636538663363303035633866656139393566313037613439366630
|
||||
32653465656664343130653937373436336236626566386638393136393536356331356637646461
|
||||
64336265326438616439336432333432656336393866666666323132323165383039323761313230
|
||||
33386162376563653638656563616232323132643534626537323161623661303937386138343034
|
||||
31316230646166316263323262623638393336333739326638316264313835653734373232633934
|
||||
64376330363035343830353337386632373863346438326332643962303061306138383539363061
|
||||
62386636366430643835336131663139306138653030383963383034326466393263663230356634
|
||||
63376437306135396137383633376636633464663365303165643131316339313131303836306136
|
||||
66633163623162636362636138326530373364316234306565323635303166636132383330316662
|
||||
65353839333434646166346662366565623061396330666166636263393962373031623632623036
|
||||
30316537616362636634316166396265633165383232383136643235666330313931326138633135
|
||||
66376662376230646336343432353634383737623835323038613235323761383830663339363432
|
||||
64613734343662306230396165636366356139303539313537356631343837323864653736336539
|
||||
64346135376633326634623839643565623339313361626532313566666263333232316562363537
|
||||
63633732313336336637313931303037626433646233353361633332343333623639386131663962
|
||||
33363731656538353134363436383066333564336138633462386331626130363937643166303161
|
||||
62303266666238626562666432383934393435643836333235626531663334343331613766303237
|
||||
62613936616136663064353762363666303464633562353061393364303563313831636432326237
|
||||
30356464653063663535333037393364363164636334653136323065663162323835336462396433
|
||||
39626532383433363835303862613534303535386131343361363664653831656463316437333035
|
||||
62333636303236336163363935643361663864623739316461353737383034306261353637363233
|
||||
32616533333933633730356365666531336135376430613462343662343930663332613961393433
|
||||
65633865346366396134633962303665633766313432383165363636616335323465303162623639
|
||||
30356232636165303264366239323932633338613065383537383235373030633736663637383135
|
||||
31393365336362326366393861313032303430663231303030653539643263353035316564393130
|
||||
31643031363537343936313538393837323634613630326239616435313732396330646562376332
|
||||
33643765326538376537376237366263396465323064393830383262386336363838333433373530
|
||||
34656466333862313336633361343034663238336139356663653835313838363835613034633830
|
||||
62356535613135663435653566353233623635646235313264353731666432313763666638653739
|
||||
31393865326632303937373036646432626665356435363762386566653937626237386333656237
|
||||
37633664326330323665616633666462636139663565633036643935363534626263376137386137
|
||||
63666162346663366366633565346531623162656534303366373965363131303638343134366438
|
||||
32373364316565303039386536363833643738623465373566356134386363323066393931646163
|
||||
31316235653965343936346366393838313836396563346165623630363835313738663565393032
|
||||
30316233313235353165373561316534333065376466356239373964663963376138643930353263
|
||||
30326363353434373230383765653933313135373363613439643432343338616564336264326439
|
||||
39373939373765613531306533363362656164313731383530313462366537633866373238623032
|
||||
30313438306466343337326631346466346535623139363961626239343861306138343861326566
|
||||
62323931353637646231303361633162636537373838663235653966663235653236613535623135
|
||||
32353038616532376636633064353739633064343639333530643562663436633431616237333062
|
||||
39663061623533393932303537343562663962623734336631376532613731373864373531623937
|
||||
64636162613066613734313433383330323764393265336239303266356533656166303238623732
|
||||
33313531393934383639303163633766323961316532386161343665336431333138316438613362
|
||||
32323634633062373535656566303130643366366265623063346532666566386263666436633837
|
||||
31343361383735343236316337613762336338353461313965663438616538396530366234313135
|
||||
61393337626430313537663738303462356133336564353865663532656534333538306131343930
|
||||
61313961363164653566663930346332353737333432333239343264653134386361623761663331
|
||||
32646265336533613234326231623466373130376139643763373833316637613136616631333538
|
||||
39393934343131323535363136393566353632383935366239323661623434323431656435373137
|
||||
36323139303335656139666437316238383932633965663861363363316337383938633262303830
|
||||
62626339626638333430653531653039316235393436623237633764383038306235326637313530
|
||||
31656132306333343830396531363936656537306233616564633733663762663731333362316165
|
||||
62636331356636386632376338396463396164396334633831313733396136656637303031373330
|
||||
37663432666263666461386437323536636439366664323730633065396332326464363634386361
|
||||
62666564343562643433363437633431333062333330346666666432333335643763323361333832
|
||||
30636134343362656562636663363864616439643364333135386438643132616436653739393161
|
||||
39303966393261626536646137633935653932613339393764633632313136383366303863333832
|
||||
61343062336263323436333633343833383864376464626262363933663664643733626632623234
|
||||
61313936633335363361663930613436613233653962646135303961616161333561313434383736
|
||||
37323030623832323935313334376362376439646238393030333963376536326531363832353161
|
||||
61666263376465356564333462376131393434623036653634343030326538373564303637396466
|
||||
35326630616435306361366136363864636437316366333463386534366366663838653734653633
|
||||
66646132323165396335316231613932316333366564653538313834383461323561313231643533
|
||||
35316433383837643561653433656239613764366337323064663262663333306166383935316135
|
||||
64326131656239666562376538613735353235663336346633333864333637636230646237376435
|
||||
38666534303038656237386563336136666333383764633564313330353634626336313435323233
|
||||
33373632633265396537623065333461656130313063643063366236336363633564613366353932
|
||||
32623832653639323966616634616139653837626165626437636538363937396234373933383366
|
||||
64386630636235666464396631316434386138313864343230313462616236626134313738383933
|
||||
64626161636662323133326466636334666339353565613263656334643838373530353263646539
|
||||
33353565323939336164363131346438376637653835343839333833373461353030383734643863
|
||||
39376565643437376434666539613364363836343238353365313564613164356664353363623534
|
||||
62383066643762363134343765643862353038646639303562626239653630653963633432306635
|
||||
35303561306134653566626434323336386239363439623731333761616366313032663664656432
|
||||
66363932656564343565346436316438383534613239373337323766346131343364666331393461
|
||||
38616266663239623763626364653639353435643133353034336632343235626235313565303931
|
||||
34653438383365353335633061616331633539343636313439303738636239633934393362353739
|
||||
63613761636465393564363039363235663830373936376236623330313265306630643065396639
|
||||
61393535616532396232613562316436303066303732386262396664346334393439643738396537
|
||||
32613834306466623066653765333165356231633065376462343236323730343936396562393263
|
||||
62663434316466666532313036316539393237653835623432663237376332326138643038643065
|
||||
34326565653561353465373939616564653833333161303430623339323062333136616664666236
|
||||
30366330666330333362
|
||||
35643431306561396237633439336230333563316364353462393534646565653163396362393566
|
||||
6636306532353662343437646631363766376166333934340a363062323332386261643832323762
|
||||
33303235646434636636366532626331653865666465643561633863663938313535623162306334
|
||||
3332616236326436650a383636393434303165363964383966633138326361373134306563653363
|
||||
37376465643762383761653132636336663762656136303730616463386134346436383539653661
|
||||
35623933346565633030373730316331633236623561313166376335636464333439643637623338
|
||||
32393433653339656135343766393565366530636132346339306339353438396337303534336266
|
||||
34303635316363343031363263626434386139633762353038666266643338343631303530313661
|
||||
61373239663739666535636466393937333437316334306631336334303961396134323062666339
|
||||
62303437623131626130363838353736386463663734356530333061353031643861636437323431
|
||||
62633036316639306366643633333366386132343337313832343133313235626162323266306165
|
||||
39326434326235373536633464313234623464373465653061383834363266663435303461633834
|
||||
36663031323634326436303138623262396332363934646337353166373163326132626437636138
|
||||
38623864666434363939663234623732623962323836323532393031396638343737373764303239
|
||||
31316639653066373166326432653930666630393061366461663337333936326239376165633064
|
||||
32356530333338303636366234336135353130646134643432613965366430326164393563383135
|
||||
63653237646237323832613363643732383436646539396239333632353566386135633961316132
|
||||
62313630313065376465346338663661343466366364363761616363376535373031383736616136
|
||||
37343634626366356261363030616330396263623965306166386238363039663461396431303733
|
||||
61626164353466323762666236376536356364653565333266363730653539626531663639333864
|
||||
32366333336234376635616361626635616265376361316439653230646537303031343564653631
|
||||
63663237353766643766343130613739386636663534336536353936353561616538316535333064
|
||||
62343835643636633831366265313939313433326239633130303863303136363732653766303663
|
||||
33643231363761336463623339323831363034636532346237373236656266303063633365393636
|
||||
36663066336233643236393837323762393435303665303661356237666237646464326536623731
|
||||
64396431323561353832333834336232396366663439643161626338653930633665346566303435
|
||||
66636466313936316530353537643637323833653561616339353465323466333332363436613461
|
||||
38653837373637343532343662366435616366313831623239343363366131633062303036333135
|
||||
36356136376236383431613266663164616135343062666136353430626663653631313064326131
|
||||
63353066313433653838613934323833373237356230376363376132306663656461653264383866
|
||||
37333863633536346538366665346666346364633733366434393135353664336330623865343132
|
||||
31396534643633333134363465323063313435333236646630613066353232613039393235366630
|
||||
36646664366339613535616437376437326561636130333063626637316431626330386137306162
|
||||
65393533306439613032623061373331333762643336633863333061393734313335386633666338
|
||||
31316637643031353462363363626662373462633065653738613733633933623432343535316438
|
||||
31356530306263303566636535363465356562633439386365316461323339393339643133323032
|
||||
32616662633565346564643737386266623836623830663265323439313762636437336436346430
|
||||
65353266373766363166633363636231623031363337626631626466616637616630383632333565
|
||||
35303737316531306237613832366565376364313435376266306263623139663964336635633030
|
||||
39306662616233646136336237646139326239653435653436323066333332626266646564616631
|
||||
31653433663539333633663433643261363065663030373265646533323832323366376230346266
|
||||
65616633363938333037656663393334363564626136333162383162663437653565663532646366
|
||||
31343063306434663231396464646232353335636138393038633737323339303033373539363739
|
||||
64306163343534363131646135396234346166313039323833656636343235656238623333626534
|
||||
30653264656166326562343534313063326332336330623530646563313262396233386335316538
|
||||
64316363613865313236633437646139353433386234326537633961356133353361333833636339
|
||||
65646232336137626130326631626436623933353333383438306237326132303838353833376432
|
||||
31653936363131623535386331353066323462613238636130376562376361643166363332343537
|
||||
63346366306161373666306536396164323437613066363537366562666130653638356564383235
|
||||
66303838616266623335363338376535303665643363326662376166643865383039306236393730
|
||||
34303130386631636636393736616237633933346136303530373033383739653531326437656463
|
||||
39326239616163313463636232343036626435366261643030386530393763356133663863373264
|
||||
62316164386464366663373635393365363161336530313734343633346563396666616262396563
|
||||
37383638613930366635623962366662343232303635636330383534653861336264396132653538
|
||||
65306539613333306339643063303063333165653734323333353461356536663661613463633632
|
||||
61613530653734643761616636386238633535643961633765313863323961313362323265373132
|
||||
65346530653962613534343062333039663837623130666261636135343663643036356261643333
|
||||
64306430343136616438636231623236316264623533313439643434343333623030376338663365
|
||||
36393932613161316137386136343939383035326231323561636337343833613635643261633165
|
||||
38316233323862666439633833646235623031336638353834323633333562346463333436316437
|
||||
36303237353138353763346166626465663137323062643633323762313137343661323839353231
|
||||
38643137383739393866616364386262363733316133313934366165353761643164303439643534
|
||||
37633636323535373964323939346339633138386239333466363239393430613139623466643531
|
||||
66323662363834653166363835343363303066306262623432353165313239323130616235316264
|
||||
65376532623530353935346238346261613062633439333630643962343233326234316130616538
|
||||
64353932613038353866383263333561623632663863373231636132653365613836363730636263
|
||||
65626666653962653266343134343638666437373532333464633163333861386632356562346637
|
||||
62303433383464373165316561646633636130636434633430623031633364643336643835613030
|
||||
64653937656262623536353764343661326632303964393862343032343330653534353530326238
|
||||
34613236336339653364333565313862613531643763633166363734316363643534323838333531
|
||||
36353137653535393862393534303366366464613035376439383134336534366263393661646564
|
||||
39616233323638653337333461636465353935613236646430623163656334376463323135323761
|
||||
36353064646532323738363837663238653766366634363232323933363033656332626436363166
|
||||
36376366336236626265373530646664633335623035646662663261353165313634646463316535
|
||||
38633862623433613937666162343936393337336537623663306439373830326434356366323664
|
||||
61646337396562373535353834393665643037626539303932663831306535623733626239323939
|
||||
61616165643439653235346566346631323035306437323339326665363232333338616133666366
|
||||
61396136353432376263333961353738343463663937313136336536623539383835663361353232
|
||||
38343737383361613930326265346562356136313539393464623935626566343137346430356261
|
||||
62633230396665316533313831346236303265303232363935313138323537373933633232343732
|
||||
35346363663938306562663433396138393861343332373934303864303963623739316239303264
|
||||
62393634346231393534633861386364336639366663353065333930616335323438383537616666
|
||||
63373836363839336634316338643935346634626437326431363132636335326238353930663662
|
||||
62383464366263336430633338646263626565393030646230306230353837313965623430636138
|
||||
38393334623661363532663464363163383365323736386134393634333332353263353763393636
|
||||
35353535303030356665303262333334353630613832323733393638613035343833356662353565
|
||||
64373334626565653631613863323866336261393533333639326266343435376666646131313535
|
||||
39343734326162623831343730303734643830626338653232633834373565643133393562653336
|
||||
35373432316161623061373238333932366161663137616337613632366263373830663464396537
|
||||
37303363386336663663303161343963633834623564643661653332623434626466376637323631
|
||||
64653265623861363837346462613164373264313766633034353531666561623732353838643639
|
||||
37646262646362386331663334666131336335313833313463373463313439353934313733303763
|
||||
65626339363732326565366464633230303933386438373539633338363439393630316164376565
|
||||
63333132613739663461346361613761396161323863616263333638376437613139353733613963
|
||||
32663062393435323061353738316132363438393762316666626231386138366639313566646461
|
||||
36306664343433653836316436303736343436343762366133373066313232333834363536343462
|
||||
30303866646131663530313463343261633261663932323562636135346664323761343765333730
|
||||
39663231636465336361393062666135316534656637306236323137653939383565643637623866
|
||||
33633536366163346466303864313732303036306665326664353064366134326539333831663632
|
||||
64343533323166623661326536393437623432336435366435396165333431366532653936396636
|
||||
30653263306461656230663731336266363832616539373333306238363834333838393063383432
|
||||
61343238373861303932653162663330333964623834336235336430376562313963613134353834
|
||||
63666537343837336531633536633038666232343165336663356639636537306333303434383231
|
||||
64306633383338646362633238386263336632393562623330343532373839663238383965643739
|
||||
39313363383331363339616431653434366131303666623263326662636161666635643162663730
|
||||
62356136643364666164383932376536623266373535613230336165396163383830346265393738
|
||||
30636139353336343630316132366365306234313064636537646262666463646437376336366562
|
||||
31663731666664313339353331626534626462663431633530653365613762363666373532383762
|
||||
34323866356239383332373632616464666230373339386235393838376333333464613038373337
|
||||
33373231386263623664353231626331366565356662623934316632653763363739653862356265
|
||||
39653539616232356333326463373666656133303161356630393839336638316338646238623138
|
||||
61343833613130376562656465343438653861653431313762313062656563643665356664646465
|
||||
33343430663066616536393935366132336666616164356463323663373930653266653238663336
|
||||
35616263613461356333633430653661376239663363653434346437656161623961333761633663
|
||||
35643031323063666163623639313864313937386339376235653033366136373333653239653238
|
||||
36376636653766366632613262326336333430353534326464653931663366383039643466303739
|
||||
66393065323965383962633266623033643330333437383262373461363539626333653433356436
|
||||
64383738653735386630346264643462353336313830376139353166666330303638386639623039
|
||||
34303137623936663636303864623932343766323235653934303263363164326534653137613863
|
||||
39353739633466376136343938303634643565613537646166326536303739316536376162353838
|
||||
61326235373733623465313530393831623438343530616339326561386239646562343130376336
|
||||
63373430313332613561623636393338353933613161656264646366633665373336343530303635
|
||||
35336561336334626333343030366264356264343062623531363331313466666136666332376265
|
||||
62363862623635376631336235353531343931663131333861616566356438396664633962646236
|
||||
61343063383238376261656465386635623263386430656132393631623330326462326166643631
|
||||
34333238663531366232393764626665376465386336653133643335656161343261616532386135
|
||||
63333931646435373630323931396564636131616234346130393566663131353835323833666338
|
||||
63653332383130666164376534656430366664356339323463393338353066663537313336653864
|
||||
66323361383737323930663732376261323238653537623335636638363966343264346537383264
|
||||
64663334366538326365663266613662356132646230363461643862643337633531333233363134
|
||||
66313565313733326363623935363535336166623761346162663964396266613932356462376664
|
||||
63313339333035333234353765383735303830653162346565303465363762623465326431353431
|
||||
65616335313733353863626561613236616339393038383562313263376633363765306262396230
|
||||
38373963306637316534613964376538666536383235393663616236653338373034363765636135
|
||||
65666239356563363538666162313038633861353732386630633930616439323838666536383165
|
||||
34353761613738616361613862656236623633356364383436383435383632303635343233356435
|
||||
35353466636461376436643965653837633461643430386565353530323333313964376233666665
|
||||
63353838323363666435393634643237626663356331653461303936366533343435373732613861
|
||||
33393231636235613234383737633430343461616261373265313163363730653237346563393835
|
||||
38646566313730633866636162393434316365396663333938656235613635613161363938653531
|
||||
65316233643561623837633362383232313664313737643231363838616434666635343466653435
|
||||
62653765303832353761346563363338656431313439346234333432363535626461336163616330
|
||||
33306633313764616533666263633634306633633261663637633133356430313231323331353535
|
||||
31316663373063376665373732366365386464393032313335376463353435313133376161336263
|
||||
33363835363062646536343833383232386133666137313539663534653138333236626438333866
|
||||
39316433626133666636376532366332346134646233383737386337356464363263343334363663
|
||||
32363538343566666263396265636261643764383736643663386333623266363535333766353037
|
||||
33333864636539636165373433343532326431316138666164353262643236623237636539646636
|
||||
30356361663863646366303238643335626434353832306338346231383764616566626163313931
|
||||
64663663613363396661396564383531653231663735653535393437356339353466353737366266
|
||||
37353739646632393837353032636331316338393564643261383536383536323036333238633237
|
||||
38643735663339316263633964336137303939303531626633646236656430373132316432343436
|
||||
66383135663130323462373934656666653837336332626137303931303263613038646235623631
|
||||
39383936393665316561373637333935643565656433316462333832323034323533316232656164
|
||||
63656630356363336231326364656531623839316236616266363037303138306537376131616134
|
||||
31323132613533663664376136376437623837303835613331623339623531653563386464306339
|
||||
62346465396362303262356239326636666435343131333566653661613463363461633631383030
|
||||
63303738613735313262656362383432356236613339646462393836633861303562663262333561
|
||||
62323562656461663764336462353230653537383038323931353831643731343837323234643565
|
||||
36386531613931623036636332663561663438333364616232626461333639326564313335376134
|
||||
3935
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
app_name: "transcriber"
|
||||
app_user: "{{ app_name }}"
|
||||
base_dir: "/home/{{ app_user }}"
|
||||
app_owner_uid: 1017
|
||||
app_owner_gid: 1018
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
|
||||
config_dir: "{{ (base_dir, 'config') | path_join }}"
|
||||
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
|
||||
|
||||
Reference in New Issue
Block a user