Compare commits
26 Commits
6bfb362b20
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
bc6fff68bb
|
|||
|
512f31b350
|
|||
|
d25a28c611
|
|||
|
472c7a984f
|
|||
|
df3a37e610
|
|||
|
8efab2002f
|
|||
|
6edb72077a
|
|||
|
07eacad003
|
|||
|
3b1736534d
|
|||
|
4d92b3bd3e
|
|||
|
27834c6711
|
|||
|
89f46566c8
|
|||
|
7f1809b4ca
|
|||
|
6784381833
|
|||
|
7c42acf893
|
|||
|
452f7973a9
|
|||
|
303aefb75f
|
|||
|
22307d81c9
|
|||
|
cc811f954d
|
|||
|
f17c4ac227
|
|||
|
25d20df5a9
|
|||
|
7e1a8e2e99
|
|||
|
b90b87caa1
|
|||
|
75ce60d8a0
|
|||
|
0aa34efd00
|
|||
|
b7a18f1296
|
@@ -11,7 +11,7 @@ Ansible-проект для автоматизации личного серве
|
||||
- `vars/*.yml` — переменные приложений и образов, `vars/secrets.yml` — зашифрованные секреты (vault).
|
||||
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`), галактические роли в `galaxy.roles/`.
|
||||
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
||||
- `templates/` — общие шаблоны (например `env.j2`).
|
||||
- `templates/` — общие шаблоны (например `env.template`).
|
||||
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
||||
- `.gitea/workflows/lint.yml` — CI: yamllint + ansible-lint.
|
||||
- `lefthook.yml` — pre-commit хуки (ruff, mypy, yamllint, ansible-lint, gitleaks, проверка vault).
|
||||
@@ -68,11 +68,13 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- `playbook-rssbridge.yml` — RSS-агрегатор.
|
||||
- `playbook-netdata.yml` — мониторинг.
|
||||
- `playbook-dozzle.yml` — просмотр Docker-логов.
|
||||
- `playbook-goaccess.yml` — аналитика веб-логов Caddy в реальном времени.
|
||||
- `playbook-gramps.yml` — генеалогия.
|
||||
- `playbook-calibre.yml` — управление электронными книгами.
|
||||
- `playbook-transcriber.yml` — транскрибация (образ из Yandex Registry).
|
||||
- `playbook-wanderer.yml` — пешие маршруты.
|
||||
- `playbook-remembos.yml` — интервальное повторение.
|
||||
- `playbook-tuwunel.yml` — Matrix-сервер (Tuwunel) с federation-делегацией на apex-домен.
|
||||
|
||||
### Агрегатные и служебные
|
||||
|
||||
@@ -91,8 +93,8 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
|
||||
## Шаблоны и переменные
|
||||
|
||||
- Суффиксы шаблонов: `.template.yml`, `.yml.j2`, `.template.sh` — рендерятся Ansible модулем `template`.
|
||||
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `*.images.yml`).
|
||||
- Суффиксы шаблонов: `.template.yml`, `.template.sh`, `.template.cfg`, `.template.conf`, `.template.toml`, `.template` (для файлов без естественного расширения) — рендерятся Ansible модулем `template`. Расширение оригинального формата сохраняется после `.template.` ради подсветки синтаксиса в редакторе.
|
||||
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `vars/transcriber.images.yml`).
|
||||
- Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др.
|
||||
- Каждое приложение: `app_name`, `app_user`, `app_owner_uid`, `app_owner_gid`, `base_dir`, `data_dir`.
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ services:
|
||||
ports:
|
||||
- "127.0.0.1:{{ apprise_external_port }}:8000"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
web_proxy_network:
|
||||
aliases:
|
||||
- "apprise"
|
||||
volumes:
|
||||
- "{{ config_dir }}:/config"
|
||||
environment:
|
||||
|
||||
@@ -731,6 +731,10 @@ access_control:
|
||||
subject: 'group:admins'
|
||||
policy: 'two_factor'
|
||||
|
||||
- domain: 'goaccess.vakhrushev.me'
|
||||
subject: 'group:admins'
|
||||
policy: 'two_factor'
|
||||
|
||||
- domain: 'wanderbase.vakhrushev.me'
|
||||
subject: 'group:admins'
|
||||
policy: 'two_factor'
|
||||
|
||||
@@ -2,7 +2,7 @@ services:
|
||||
|
||||
authelia_app:
|
||||
container_name: 'authelia_app'
|
||||
image: 'docker.io/authelia/authelia:4.39.16'
|
||||
image: 'docker.io/authelia/authelia:4.39.19'
|
||||
user: '{{ owner_create_result.uid }}:{{ owner_create_result.group }}'
|
||||
restart: 'unless-stopped'
|
||||
networks:
|
||||
|
||||
+236
-182
@@ -11,6 +11,7 @@ import sys
|
||||
import subprocess
|
||||
import logging
|
||||
import pwd
|
||||
import time
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
@@ -43,16 +44,38 @@ logger = logging.getLogger(__name__)
|
||||
@dataclass
|
||||
class Config:
|
||||
host_name: str
|
||||
roots: List[Path]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Application:
|
||||
path: Path
|
||||
owner: str
|
||||
backup_script: Optional[Path]
|
||||
backup_targets: List[Path]
|
||||
|
||||
|
||||
@dataclass
|
||||
class StorageRunResult:
|
||||
name: str
|
||||
success: bool
|
||||
duration: float
|
||||
|
||||
|
||||
def format_duration(seconds: float) -> str:
|
||||
if seconds < 60:
|
||||
return f"{seconds:.1f}s"
|
||||
minutes = int(seconds // 60)
|
||||
secs = int(seconds % 60)
|
||||
if minutes < 60:
|
||||
return f"{minutes}m{secs:02d}s"
|
||||
hours = minutes // 60
|
||||
minutes = minutes % 60
|
||||
return f"{hours}h{minutes:02d}m{secs:02d}s"
|
||||
|
||||
|
||||
class Storage(ABC):
|
||||
name: str
|
||||
|
||||
def backup(self, backup_dirs: List[str]) -> bool:
|
||||
"""Backup directories"""
|
||||
raise NotImplementedError()
|
||||
@@ -65,19 +88,15 @@ class ResticStorage(Storage):
|
||||
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,
|
||||
]
|
||||
):
|
||||
env_raw = params.get("env") or {}
|
||||
if not isinstance(env_raw, dict):
|
||||
raise ValueError(
|
||||
f"'env' must be a table for storage backend ResticStorage: '{self.name}'"
|
||||
)
|
||||
self.env: Dict[str, str] = {str(k): str(v) for k, v in env_raw.items()}
|
||||
|
||||
if not self.restic_repository or not self.restic_password:
|
||||
raise ValueError(
|
||||
f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
|
||||
)
|
||||
@@ -93,19 +112,13 @@ class ResticStorage(Storage):
|
||||
return False
|
||||
|
||||
def __backup_internal(self, backup_dirs: List[str]) -> bool:
|
||||
logger.info("Starting restic backup")
|
||||
logger.info("Starting restic backup for storage '%s'", self.name)
|
||||
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,
|
||||
}
|
||||
)
|
||||
env["RESTIC_REPOSITORY"] = self.restic_repository
|
||||
env["RESTIC_PASSWORD"] = self.restic_password
|
||||
env.update(self.env)
|
||||
|
||||
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
|
||||
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
|
||||
@@ -188,24 +201,13 @@ class AppriseNotifier(Notifier):
|
||||
)
|
||||
|
||||
|
||||
class BackupManager:
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
roots: List[Path],
|
||||
storages: List[Storage],
|
||||
notifiers: List[Notifier],
|
||||
):
|
||||
self.errors: List[str] = []
|
||||
class ApplicationFinder:
|
||||
def __init__(self, roots: List[Path]):
|
||||
self.roots = roots
|
||||
self.warnings: List[str] = []
|
||||
self.successful_backups: List[str] = []
|
||||
self.config = config
|
||||
self.roots: List[Path] = roots
|
||||
self.storages = storages
|
||||
self.notifiers = notifiers
|
||||
|
||||
def find_applications(self) -> List[Application]:
|
||||
"""Get all application directories and their owners."""
|
||||
"""Discover all applications with their backup scripts and targets."""
|
||||
applications: List[Application] = []
|
||||
source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
|
||||
|
||||
@@ -216,32 +218,182 @@ class BackupManager:
|
||||
try:
|
||||
stat_info = app_dir.stat()
|
||||
owner = pwd.getpwuid(stat_info.st_uid).pw_name
|
||||
applications.append(Application(path=app_dir, owner=owner))
|
||||
backup_script = self._find_backup_script(app_dir)
|
||||
backup_targets = self._find_backup_targets(app_dir)
|
||||
applications.append(
|
||||
Application(
|
||||
path=app_dir,
|
||||
owner=owner,
|
||||
backup_script=backup_script,
|
||||
backup_targets=backup_targets,
|
||||
)
|
||||
)
|
||||
except (KeyError, OSError) as e:
|
||||
logger.warning(f"Could not get owner for {app_dir}: {e}")
|
||||
|
||||
applications.sort(key=lambda app: app.path.name)
|
||||
return applications
|
||||
|
||||
def find_backup_script(self, app_dir: str) -> Optional[str]:
|
||||
"""Find backup script in user's home directory"""
|
||||
possible_scripts = [
|
||||
os.path.join(app_dir, "backup.sh"),
|
||||
os.path.join(app_dir, "backup"),
|
||||
]
|
||||
|
||||
for script_path in possible_scripts:
|
||||
if os.path.exists(script_path):
|
||||
# Check if file is executable
|
||||
def _find_backup_script(self, app_dir: Path) -> Optional[Path]:
|
||||
"""Find executable backup script in application directory."""
|
||||
for name in ("backup.sh", "backup"):
|
||||
script_path = app_dir / name
|
||||
if script_path.exists():
|
||||
if os.access(script_path, os.X_OK):
|
||||
return script_path
|
||||
else:
|
||||
logger.warning(
|
||||
f"Backup script {script_path} exists but is not executable"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def run_app_backup(self, script_path: str, app_dir: str, username: str) -> bool:
|
||||
def _find_backup_targets(self, app_dir: Path) -> List[Path]:
|
||||
"""Resolve backup target directories for an application."""
|
||||
targets_file = app_dir / BACKUP_TARGETS_FILE
|
||||
resolved_targets: List[Path] = []
|
||||
|
||||
if targets_file.exists():
|
||||
for target_line in self._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:
|
||||
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)
|
||||
|
||||
return resolved_targets
|
||||
|
||||
def _parse_targets_file(self, 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
|
||||
|
||||
|
||||
class BackupManager:
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
storages: List[Storage],
|
||||
notifiers: List[Notifier],
|
||||
):
|
||||
self.errors: List[str] = []
|
||||
self.warnings: List[str] = []
|
||||
self.successful_backups: List[str] = []
|
||||
self.config = config
|
||||
self.storages = storages
|
||||
self.notifiers = notifiers
|
||||
self.archive_duration: float = 0.0
|
||||
self.storage_results: List[StorageRunResult] = []
|
||||
|
||||
def run_backup_process(self, applications: List[Application]) -> bool:
|
||||
"""Main backup process"""
|
||||
logger.info("Starting backup process")
|
||||
logger.info(f"Found {len(applications)} application directories")
|
||||
|
||||
archive_start = time.monotonic()
|
||||
# Process each user's backup
|
||||
for app in applications:
|
||||
app_dir = str(app.path)
|
||||
username = app.owner
|
||||
logger.info(f"Processing backup for app: {app_dir} (user {username})")
|
||||
|
||||
if app.backup_script is None:
|
||||
warning_msg = (
|
||||
f"No backup script found for app: {app_dir} (user {username})"
|
||||
)
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
continue
|
||||
|
||||
self._run_app_backup(str(app.backup_script), app_dir, username)
|
||||
self.archive_duration = time.monotonic() - archive_start
|
||||
logger.info(
|
||||
"Archive phase finished in %s", format_duration(self.archive_duration)
|
||||
)
|
||||
|
||||
# Collect backup directories from applications
|
||||
backup_dirs: List[str] = []
|
||||
for app in applications:
|
||||
for target in app.backup_targets:
|
||||
target_str = str(target)
|
||||
if target_str not in backup_dirs:
|
||||
backup_dirs.append(target_str)
|
||||
logger.info(f"Found backup directories: {backup_dirs}")
|
||||
|
||||
overall_success = True
|
||||
|
||||
# Each storage is processed independently: a failure in one storage
|
||||
# must not prevent the others from being attempted.
|
||||
for storage in self.storages:
|
||||
storage_start = time.monotonic()
|
||||
try:
|
||||
backup_result = storage.backup(backup_dirs)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error(
|
||||
"Storage '%s' raised an unexpected error: %s", storage.name, exc
|
||||
)
|
||||
backup_result = False
|
||||
storage_duration = time.monotonic() - storage_start
|
||||
self.storage_results.append(
|
||||
StorageRunResult(
|
||||
name=storage.name,
|
||||
success=backup_result,
|
||||
duration=storage_duration,
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
"Storage '%s' finished in %s (success=%s)",
|
||||
storage.name,
|
||||
format_duration(storage_duration),
|
||||
backup_result,
|
||||
)
|
||||
if not backup_result:
|
||||
self.errors.append(f"Storage '{storage.name}' backup failed")
|
||||
|
||||
# Determine overall success
|
||||
overall_success = overall_success and backup_result
|
||||
|
||||
# Send notification
|
||||
self._send_notification(overall_success)
|
||||
|
||||
logger.info("Backup process completed")
|
||||
|
||||
if self.errors:
|
||||
logger.error(f"Backup completed with {len(self.errors)} errors")
|
||||
return False
|
||||
elif self.warnings:
|
||||
logger.warning(f"Backup completed with {len(self.warnings)} warnings")
|
||||
return True
|
||||
else:
|
||||
logger.info("Backup completed successfully")
|
||||
return True
|
||||
|
||||
def _run_app_backup(self, script_path: str, app_dir: str, username: str) -> bool:
|
||||
"""Run backup script as the specified user"""
|
||||
try:
|
||||
logger.info(f"Running backup script {script_path} (user {username})")
|
||||
@@ -280,88 +432,40 @@ class BackupManager:
|
||||
self.errors.append(f"App {username}: {error_msg}")
|
||||
return False
|
||||
|
||||
def get_backup_directories(self) -> List[str]:
|
||||
"""Collect backup targets according to backup-targets rules"""
|
||||
backup_dirs: List[str] = []
|
||||
applications = self.find_applications()
|
||||
|
||||
def parse_targets_file(targets_file: Path) -> List[str]:
|
||||
"""Parse backup-targets file, skipping comments and empty lines."""
|
||||
targets: List[str] = []
|
||||
try:
|
||||
for raw_line in targets_file.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
targets.append(line)
|
||||
except OSError as e:
|
||||
warning_msg = f"Could not read backup targets file {targets_file}: {e}"
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
return targets
|
||||
|
||||
for app in applications:
|
||||
app_dir = app.path
|
||||
targets_file = app_dir / BACKUP_TARGETS_FILE
|
||||
resolved_targets: List[Path] = []
|
||||
|
||||
if targets_file.exists():
|
||||
# Read custom targets defined by the application.
|
||||
for target_line in parse_targets_file(targets_file):
|
||||
target_path = Path(target_line)
|
||||
if not target_path.is_absolute():
|
||||
target_path = (app_dir / target_path).resolve()
|
||||
else:
|
||||
target_path = target_path.resolve()
|
||||
if target_path.exists():
|
||||
resolved_targets.append(target_path)
|
||||
else:
|
||||
warning_msg = (
|
||||
f"Backup target does not exist for {app_dir}: {target_path}"
|
||||
)
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
else:
|
||||
# Fallback to default backups directory when no list is provided.
|
||||
default_target = (app_dir / BACKUP_DEFAULT_DIR).resolve()
|
||||
if default_target.exists():
|
||||
resolved_targets.append(default_target)
|
||||
else:
|
||||
warning_msg = f"Default backup path does not exist for {app_dir}: {default_target}"
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
|
||||
for target in resolved_targets:
|
||||
target_str = str(target)
|
||||
if target_str not in backup_dirs:
|
||||
backup_dirs.append(target_str)
|
||||
|
||||
return backup_dirs
|
||||
|
||||
def send_notification(self, success: bool) -> None:
|
||||
def _send_notification(self, success: bool) -> None:
|
||||
"""Send notification to Notifiers"""
|
||||
|
||||
host = self.config.host_name
|
||||
|
||||
if success and not self.errors:
|
||||
title = f"{host}: бекап успешно завершен"
|
||||
message = f"<b>{host}</b>: бекап успешно завершен!"
|
||||
message = f"<p><b>{host}</b>: бекап успешно завершен!</p>"
|
||||
if self.successful_backups:
|
||||
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
|
||||
items = "".join(f"<li>{b}</li>" for b in self.successful_backups)
|
||||
message += f"<p>Успешные бекапы:</p><ul>{items}</ul>"
|
||||
else:
|
||||
title = f"{host}: бекап завершен с ошибками"
|
||||
message = f"<b>{host}</b>: бекап завершен с ошибками!"
|
||||
title = f"{host}: бекап завершен с ошибками ({len(self.errors)})"
|
||||
message = f"<p><b>{host}</b>: бекап завершен с ошибками!</p>"
|
||||
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
|
||||
)
|
||||
items = "".join(f"<li>{b}</li>" for b in self.successful_backups)
|
||||
message += f"<p>✅ Успешные бекапы:</p><ul>{items}</ul>"
|
||||
|
||||
if self.warnings:
|
||||
message += "\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
|
||||
items = "".join(f"<li>{w}</li>" for w in self.warnings)
|
||||
message += f"<p>⚠️ Предупреждения:</p><ul>{items}</ul>"
|
||||
|
||||
if self.errors:
|
||||
message += "\n\n❌ Ошибки:\n" + "\n".join(self.errors)
|
||||
items = "".join(f"<li>{e}</li>" for e in self.errors)
|
||||
message += f"<p>❌ Ошибки:</p><ul>{items}</ul>"
|
||||
|
||||
message += f"<p>⏱ Время архивации: {format_duration(self.archive_duration)}</p>"
|
||||
if self.storage_results:
|
||||
items = "".join(
|
||||
f"<li>{'✅' if r.success else '❌'} {r.name}: {format_duration(r.duration)}</li>"
|
||||
for r in self.storage_results
|
||||
)
|
||||
message += f"<p>⏱ Время записи в хранилища:</p><ul>{items}</ul>"
|
||||
|
||||
for notificator in self.notifiers:
|
||||
try:
|
||||
@@ -369,64 +473,10 @@ class BackupManager:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send notification: {str(e)}")
|
||||
|
||||
def run_backup_process(self) -> bool:
|
||||
"""Main backup process"""
|
||||
logger.info("Starting backup process")
|
||||
|
||||
# Get all home directories
|
||||
applications = self.find_applications()
|
||||
logger.info(f"Found {len(applications)} application directories")
|
||||
|
||||
# Process each user's backup
|
||||
for app in applications:
|
||||
app_dir = str(app.path)
|
||||
username = app.owner
|
||||
logger.info(f"Processing backup for app: {app_dir} (user {username})")
|
||||
|
||||
# Find backup script
|
||||
backup_script = self.find_backup_script(app_dir)
|
||||
|
||||
if backup_script is None:
|
||||
warning_msg = (
|
||||
f"No backup script found for app: {app_dir} (user {username})"
|
||||
)
|
||||
logger.warning(warning_msg)
|
||||
self.warnings.append(warning_msg)
|
||||
continue
|
||||
|
||||
self.run_app_backup(backup_script, app_dir, username)
|
||||
|
||||
# Get backup directories
|
||||
backup_dirs = self.get_backup_directories()
|
||||
logger.info(f"Found backup directories: {backup_dirs}")
|
||||
|
||||
overall_success = True
|
||||
|
||||
for storage in self.storages:
|
||||
backup_result = storage.backup(backup_dirs)
|
||||
if not backup_result:
|
||||
self.errors.append("Restic backup failed")
|
||||
|
||||
# Determine overall success
|
||||
overall_success = overall_success and backup_result
|
||||
|
||||
# Send notification
|
||||
self.send_notification(overall_success)
|
||||
|
||||
logger.info("Backup process completed")
|
||||
|
||||
if self.errors:
|
||||
logger.error(f"Backup completed with {len(self.errors)} errors")
|
||||
return False
|
||||
elif self.warnings:
|
||||
logger.warning(f"Backup completed with {len(self.warnings)} warnings")
|
||||
return True
|
||||
else:
|
||||
logger.info("Backup completed successfully")
|
||||
return True
|
||||
|
||||
|
||||
def initialize(config_path: Path) -> BackupManager:
|
||||
def initialize(
|
||||
config_path: Path,
|
||||
) -> tuple[ApplicationFinder, BackupManager]:
|
||||
try:
|
||||
with config_path.open("rb") as config_file:
|
||||
raw_config = tomllib.load(config_file)
|
||||
@@ -463,17 +513,21 @@ def initialize(config_path: Path) -> BackupManager:
|
||||
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
|
||||
config = Config(host_name=host_name)
|
||||
app_finder = ApplicationFinder(roots)
|
||||
backup_manager = BackupManager(
|
||||
config=config, storages=storages, notifiers=notifiers
|
||||
)
|
||||
|
||||
return app_finder, backup_manager
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
backup_manager = initialize(CONFIG_PATH)
|
||||
success = backup_manager.run_backup_process()
|
||||
app_finder, backup_manager = initialize(CONFIG_PATH)
|
||||
applications = app_finder.find_applications()
|
||||
backup_manager.warnings.extend(app_finder.warnings)
|
||||
success = backup_manager.run_backup_process(applications)
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
|
||||
@@ -8,9 +8,19 @@ roots = [
|
||||
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 }}"
|
||||
|
||||
[storage.yandex_cloud_s3.env]
|
||||
AWS_ACCESS_KEY_ID = "{{ restic_s3_access_key }}"
|
||||
AWS_SECRET_ACCESS_KEY = "{{ restic_s3_access_secret }}"
|
||||
AWS_DEFAULT_REGION = "{{ restic_s3_region }}"
|
||||
|
||||
[storage.pr86keedav]
|
||||
type = "restic"
|
||||
restic_repository = "{{ restic_pr86keedav_repository }}"
|
||||
restic_password = "{{ restic_pr86keedav_password }}"
|
||||
|
||||
[storage.pr86keedav.env]
|
||||
RCLONE_CONFIG = "{{ rclone_config_file }}"
|
||||
|
||||
[notifier.apprise]
|
||||
type = "apprise"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[pr86keedav]
|
||||
type = webdav
|
||||
url = {{ rclone_pr86keedav_url }}
|
||||
vendor = other
|
||||
user = {{ rclone_pr86keedav_user }}
|
||||
pass = {{ rclone_pr86keedav_password }}
|
||||
@@ -12,26 +12,74 @@
|
||||
metrics
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Snippets
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Shared access log for all sites; consumed by GoAccess.
|
||||
# Mode 644 lets read-only consumers (goaccess and ad-hoc host-side tail)
|
||||
# read the file; lumberjack would otherwise default to 0600.
|
||||
(access_log) {
|
||||
log {
|
||||
output file /var/log/caddy/access.log {
|
||||
mode 644
|
||||
roll_size 100mib
|
||||
roll_keep 10
|
||||
roll_keep_for 720h
|
||||
}
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Applications
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
# Matrix federation delegation: tells other servers/clients that the
|
||||
# homeserver for vakhrushev.me lives at matrix.vakhrushev.me.
|
||||
# https://spec.matrix.org/latest/server-server-api/#server-discovery
|
||||
handle /.well-known/matrix/server {
|
||||
header Content-Type application/json
|
||||
header Access-Control-Allow-Origin *
|
||||
respond `{"m.server": "matrix.vakhrushev.me:443"}`
|
||||
}
|
||||
|
||||
handle /.well-known/matrix/client {
|
||||
header Content-Type application/json
|
||||
header Access-Control-Allow-Origin *
|
||||
respond `{"m.homeserver": {"base_url": "https://matrix.vakhrushev.me"}}`
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy {
|
||||
to homepage_app:80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matrix.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to homepage_app:80
|
||||
to tuwunel_app:6167
|
||||
}
|
||||
}
|
||||
|
||||
auth.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy authelia_app:9091
|
||||
}
|
||||
|
||||
status.vakhrushev.me, :29999 {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
@@ -43,6 +91,7 @@ status.vakhrushev.me, :29999 {
|
||||
|
||||
git.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to gitea_app:3000
|
||||
@@ -51,6 +100,7 @@ git.vakhrushev.me {
|
||||
|
||||
outline.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to outline_app:3000
|
||||
@@ -59,6 +109,7 @@ outline.vakhrushev.me {
|
||||
|
||||
gramps.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to gramps_app:5000
|
||||
@@ -67,6 +118,7 @@ gramps.vakhrushev.me {
|
||||
|
||||
miniflux.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to miniflux_app:8080
|
||||
@@ -75,6 +127,7 @@ miniflux.vakhrushev.me {
|
||||
|
||||
wakapi.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to wakapi_app:3000
|
||||
@@ -83,6 +136,7 @@ wakapi.vakhrushev.me {
|
||||
|
||||
wanderer.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to wanderer_web:3000
|
||||
@@ -91,6 +145,7 @@ wanderer.vakhrushev.me {
|
||||
|
||||
memos.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to memos_app:5230
|
||||
@@ -99,6 +154,7 @@ memos.vakhrushev.me {
|
||||
|
||||
remembos.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
@@ -112,6 +168,7 @@ remembos.vakhrushev.me {
|
||||
|
||||
calibre.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
reverse_proxy {
|
||||
to calibre_web_app:8083
|
||||
@@ -120,6 +177,7 @@ calibre.vakhrushev.me {
|
||||
|
||||
wanderbase.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
@@ -133,6 +191,7 @@ wanderbase.vakhrushev.me {
|
||||
|
||||
rssbridge.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
@@ -146,6 +205,7 @@ rssbridge.vakhrushev.me {
|
||||
|
||||
dozzle.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
@@ -155,3 +215,21 @@ dozzle.vakhrushev.me {
|
||||
reverse_proxy dozzle_app:8080
|
||||
}
|
||||
|
||||
goaccess.vakhrushev.me {
|
||||
tls anwinged@ya.ru
|
||||
import access_log
|
||||
|
||||
forward_auth authelia_app:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||
}
|
||||
|
||||
@websocket {
|
||||
header Connection *Upgrade*
|
||||
header Upgrade websocket
|
||||
}
|
||||
reverse_proxy @websocket goaccess_processor:7890
|
||||
|
||||
reverse_proxy goaccess_app:8080
|
||||
}
|
||||
|
||||
+7
-6
@@ -1,19 +1,20 @@
|
||||
services:
|
||||
|
||||
{{ service_name }}:
|
||||
caddyproxy:
|
||||
image: caddy:2.11.2
|
||||
restart: unless-stopped
|
||||
container_name: {{ service_name }}
|
||||
container_name: "caddyproxy"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_ADMIN
|
||||
volumes:
|
||||
- {{ caddy_file_dir }}:/etc/caddy
|
||||
- {{ data_dir }}:/data
|
||||
- {{ config_dir }}:/config
|
||||
- "{{ caddy_file_dir }}:/etc/caddy"
|
||||
- "{{ data_dir }}:/data"
|
||||
- "{{ config_dir }}:/config"
|
||||
- "{{ caddy_logs_dir }}:/var/log/caddy"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
dozzle_app:
|
||||
image: amir20/dozzle:v10.2.1
|
||||
image: amir20/dozzle:v10.5.0
|
||||
container_name: dozzle_app
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
services:
|
||||
|
||||
gitea_app:
|
||||
image: gitea/gitea:1.25.5
|
||||
image: gitea/gitea:1.26.1
|
||||
restart: unless-stopped
|
||||
container_name: gitea_app
|
||||
ports:
|
||||
- "2222:22"
|
||||
volumes:
|
||||
- {{ data_dir }}:/data
|
||||
- {{ backups_dir }}:/backups
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- "{{ data_dir }}:/data"
|
||||
- "{{ backups_dir }}:/backups"
|
||||
- "/etc/timezone:/etc/timezone:ro"
|
||||
- "/etc/localtime:/etc/localtime:ro"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
environment:
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM allinurl/goaccess:1.10.2
|
||||
|
||||
RUN apk add --no-cache jq
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod 0755 /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -0,0 +1,41 @@
|
||||
services:
|
||||
|
||||
goaccess_processor:
|
||||
build: .
|
||||
image: local/goaccess-jq:1.10.2
|
||||
container_name: goaccess_processor
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
user: "{{ app_owner_uid }}:{{ app_owner_gid }}"
|
||||
command:
|
||||
- --log-format=COMBINED
|
||||
- --enable-panel=VIRTUAL_HOSTS
|
||||
- --real-time-html
|
||||
- --port=7890
|
||||
- --ws-url=wss://goaccess.vakhrushev.me:443
|
||||
- --output=/srv/report/index.html
|
||||
- --persist
|
||||
- --restore
|
||||
- --db-path=/srv/db
|
||||
- --no-global-config
|
||||
volumes:
|
||||
- "{{ caddy_logs_dir }}:/srv/logs:ro"
|
||||
- "{{ db_dir }}:/srv/db"
|
||||
- "{{ report_dir }}:/srv/report"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
|
||||
goaccess_app:
|
||||
image: caddy:2.11.2
|
||||
container_name: goaccess_app
|
||||
restart: unless-stopped
|
||||
user: "{{ app_owner_uid }}:{{ app_owner_gid }}"
|
||||
command: caddy file-server --listen :8080 --root /srv --browse
|
||||
volumes:
|
||||
- "{{ report_dir }}:/srv:ro"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
|
||||
networks:
|
||||
web_proxy_network:
|
||||
external: true
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Tail Caddy's JSON access log, transform each entry into Apache CLF
|
||||
# Combined with the virtual host glued to the request URI, and feed
|
||||
# the stream straight into goaccess via stdin. Result: every line in
|
||||
# the Requests panel renders as `host.example.com/path`.
|
||||
|
||||
set -eu
|
||||
|
||||
ACCESS_LOG="/srv/logs/access.log"
|
||||
|
||||
JQ_FILTER='
|
||||
"\(.request.remote_ip // "-") - - " +
|
||||
"[\((.ts // 0) | gmtime | strftime("%d/%b/%Y:%H:%M:%S +0000"))] " +
|
||||
"\"\(.request.method) \(.request.host)\(.request.uri) \(.request.proto)\" " +
|
||||
"\(.status) \(.size) " +
|
||||
"\"\(.request.headers.Referer[0]? // "-")\" " +
|
||||
"\"\(.request.headers["User-Agent"][0]? // "-")\""
|
||||
'
|
||||
|
||||
tail -F -n +1 "$ACCESS_LOG" \
|
||||
| jq --unbuffered -rc "$JQ_FILTER" \
|
||||
| exec goaccess - "$@"
|
||||
@@ -3,7 +3,7 @@
|
||||
services:
|
||||
|
||||
gramps_app: &gramps_app
|
||||
image: ghcr.io/gramps-project/grampsweb:26.4.0
|
||||
image: ghcr.io/gramps-project/grampsweb:26.4.1
|
||||
container_name: gramps_app
|
||||
depends_on:
|
||||
- gramps_redis
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
services:
|
||||
|
||||
memos_app:
|
||||
image: neosmemo/memos:0.26.2
|
||||
image: neosmemo/memos:0.28.0
|
||||
container_name: memos_app
|
||||
restart: unless-stopped
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
netdata:
|
||||
image: netdata/netdata:v2.9.0
|
||||
image: netdata/netdata:v2.10.3
|
||||
container_name: netdata
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Resource alerts for a low-spec home server.
|
||||
# Overrides stock alerts where thresholds differ; baseline RAM use is ~80%, so stock 80/90% would fire constantly.
|
||||
|
||||
# RAM: warn at >92%, crit at >95% — by then less than ~200 MB free.
|
||||
alarm: ram_in_use
|
||||
on: system.ram
|
||||
class: Utilization
|
||||
type: System
|
||||
component: Memory
|
||||
calc: $used * 100 / ($used + $cached + $free + $buffers)
|
||||
units: %
|
||||
every: 10s
|
||||
warn: $this > 92
|
||||
crit: $this > 95
|
||||
delay: down 5m multiplier 1.5 max 1h
|
||||
summary: System memory utilization
|
||||
info: System memory utilization (used / total, excluding reclaimable cache)
|
||||
to: sysadmin
|
||||
|
||||
# CPU: replace stock 10min_cpu_usage with two windowed alerts.
|
||||
alarm: 10min_cpu_usage
|
||||
on: system.cpu
|
||||
enabled: no
|
||||
|
||||
alarm: cpu_warn_30m
|
||||
on: system.cpu
|
||||
class: Utilization
|
||||
type: System
|
||||
component: CPU
|
||||
lookup: average -30m unaligned of user,system,softirq,irq,guest,guest_nice,nice
|
||||
units: %
|
||||
every: 1m
|
||||
warn: $this > 80
|
||||
delay: down 30m multiplier 1.5 max 2h
|
||||
summary: Sustained CPU load (30m avg)
|
||||
info: Average CPU utilization over the last 30 minutes
|
||||
to: sysadmin
|
||||
|
||||
alarm: cpu_crit_15m
|
||||
on: system.cpu
|
||||
class: Utilization
|
||||
type: System
|
||||
component: CPU
|
||||
lookup: average -15m unaligned of user,system,softirq,irq,guest,guest_nice,nice
|
||||
units: %
|
||||
every: 1m
|
||||
crit: $this > 95
|
||||
delay: down 30m multiplier 1.5 max 2h
|
||||
summary: High CPU load (15m avg)
|
||||
info: Average CPU utilization over the last 15 minutes
|
||||
to: sysadmin
|
||||
|
||||
# Disk: warn at >75%, crit at >90% on every mounted filesystem.
|
||||
template: disk_space_usage
|
||||
on: disk.space
|
||||
class: Utilization
|
||||
type: System
|
||||
component: Disk
|
||||
calc: $used * 100 / ($avail + $used)
|
||||
units: %
|
||||
every: 1m
|
||||
warn: $this > 75
|
||||
crit: $this > 90
|
||||
delay: down 15m multiplier 1.5 max 1h
|
||||
summary: Disk space utilization
|
||||
info: Disk space utilization on ${label:mount_point}
|
||||
to: sysadmin
|
||||
@@ -0,0 +1,45 @@
|
||||
# Override stock health_alarm_notify.conf — route every alert to apprise.
|
||||
# Stock conf is sourced first; this only sets what differs.
|
||||
|
||||
SEND_EMAIL="NO"
|
||||
SEND_CUSTOM="YES"
|
||||
DEFAULT_RECIPIENT_CUSTOM="server"
|
||||
|
||||
role_recipients_custom[sysadmin]="server"
|
||||
role_recipients_custom[domainadmin]="server"
|
||||
role_recipients_custom[dba]="server"
|
||||
role_recipients_custom[webmaster]="server"
|
||||
role_recipients_custom[proxyadmin]="server"
|
||||
role_recipients_custom[silent]=""
|
||||
|
||||
custom_sender() {
|
||||
local apprise_url="http://apprise:8000/notify/${1}/"
|
||||
|
||||
local notif_type="info"
|
||||
case "${status}" in
|
||||
CRITICAL) notif_type="failure" ;;
|
||||
WARNING) notif_type="warning" ;;
|
||||
CLEAR) notif_type="success" ;;
|
||||
esac
|
||||
|
||||
local title="[${status}] ${name} on ${host}"
|
||||
local body="${status_message}: ${alarm}
|
||||
Chart: ${chart}
|
||||
Value: ${value} ${units}
|
||||
Info: ${info}
|
||||
Raised for: ${raised_for}"
|
||||
|
||||
local httpcode
|
||||
httpcode=$(docurl -X POST \
|
||||
--data-urlencode "title=${title}" \
|
||||
--data-urlencode "body=${body}" \
|
||||
--data-urlencode "type=${notif_type}" \
|
||||
"${apprise_url}")
|
||||
|
||||
if [ "${httpcode}" = "200" ]; then
|
||||
info "sent custom notification for ${name} on ${host}"
|
||||
return 0
|
||||
fi
|
||||
error "failed to send notification for ${name} on ${host} (HTTP ${httpcode})"
|
||||
return 1
|
||||
}
|
||||
@@ -3,7 +3,7 @@ services:
|
||||
# See sample https://github.com/outline/outline/blob/main/.env.sample
|
||||
|
||||
outline_app:
|
||||
image: outlinewiki/outline:1.6.1
|
||||
image: outlinewiki/outline:1.7.1
|
||||
container_name: outline_app
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
remembos_app:
|
||||
image: "{{ yc_container_registry_repository }}/remembos:v0.1.5"
|
||||
image: "{{ yc_container_registry_repository }}/remembos:v0.2.0"
|
||||
container_name: remembos_app
|
||||
restart: unless-stopped
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
services:
|
||||
|
||||
|
||||
rssbridge_app:
|
||||
image: rssbridge/rss-bridge:2025-08-05
|
||||
container_name: rssbridge_app
|
||||
@@ -0,0 +1,36 @@
|
||||
# See versions: https://github.com/matrix-construct/tuwunel/releases
|
||||
# Configuration reference: https://github.com/matrix-construct/tuwunel/blob/main/tuwunel-example.toml
|
||||
|
||||
services:
|
||||
|
||||
tuwunel_app:
|
||||
image: jevolk/tuwunel:v1.6.1
|
||||
container_name: tuwunel_app
|
||||
restart: unless-stopped
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
volumes:
|
||||
- "{{ data_dir }}:/var/lib/tuwunel"
|
||||
environment:
|
||||
TUWUNEL_SERVER_NAME: "{{ tuwunel_server_name }}"
|
||||
TUWUNEL_DATABASE_PATH: "/var/lib/tuwunel"
|
||||
TUWUNEL_ADDRESS: "0.0.0.0"
|
||||
TUWUNEL_PORT: "6167"
|
||||
TUWUNEL_MAX_REQUEST_SIZE: "20000000"
|
||||
|
||||
TUWUNEL_ALLOW_REGISTRATION: "false"
|
||||
TUWUNEL_ALLOW_FEDERATION: "true"
|
||||
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: "false"
|
||||
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
|
||||
# Well-known delegation values returned to clients/servers that query tuwunel directly.
|
||||
# The canonical delegation is served by Caddy on {{ tuwunel_server_name }} (see Caddyfile).
|
||||
TUWUNEL_WELL_KNOWN_SERVER: "{{ tuwunel_well_known_server }}"
|
||||
TUWUNEL_WELL_KNOWN_CLIENT: "{{ tuwunel_well_known_client }}"
|
||||
|
||||
TUWUNEL_LOG: "info"
|
||||
|
||||
networks:
|
||||
web_proxy_network:
|
||||
external: true
|
||||
@@ -7,6 +7,9 @@
|
||||
- name: 'Configure dozzle'
|
||||
ansible.builtin.import_playbook: playbook-dozzle.yml
|
||||
|
||||
- name: 'Configure goaccess'
|
||||
ansible.builtin.import_playbook: playbook-goaccess.yml
|
||||
|
||||
- name: 'Configure gitea'
|
||||
ansible.builtin.import_playbook: playbook-gitea.yml
|
||||
|
||||
@@ -40,6 +43,9 @@
|
||||
- name: 'Configure apprise'
|
||||
ansible.builtin.import_playbook: playbook-apprise.yml
|
||||
|
||||
- name: 'Configure tuwunel'
|
||||
ansible.builtin.import_playbook: playbook-tuwunel.yml
|
||||
|
||||
#
|
||||
|
||||
- name: 'Configure homepage'
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
- name: "Copy apprise config"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/server.cfg.j2"
|
||||
src: "./files/{{ app_name }}/server.template.cfg"
|
||||
dest: "{{ config_dir }}/server.cfg"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
|
||||
+20
-1
@@ -10,6 +10,9 @@
|
||||
backup_config_dir: "/etc/backup"
|
||||
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
||||
|
||||
rclone_config_dir: "/etc/rclone"
|
||||
rclone_config_file: "{{ (rclone_config_dir, 'rclone.conf') | path_join }}"
|
||||
|
||||
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
|
||||
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
|
||||
|
||||
@@ -22,6 +25,22 @@
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: "Create rclone config directory"
|
||||
ansible.builtin.file:
|
||||
path: "{{ rclone_config_dir }}"
|
||||
state: "directory"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: "Create rclone config file"
|
||||
ansible.builtin.template:
|
||||
src: "files/backups/rclone.template.conf"
|
||||
dest: "{{ rclone_config_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
|
||||
- name: "Create backup config file"
|
||||
ansible.builtin.template:
|
||||
src: "files/backups/config.template.toml"
|
||||
@@ -40,7 +59,7 @@
|
||||
|
||||
- name: "Copy restic shell script"
|
||||
ansible.builtin.template:
|
||||
src: "files/backups/restic-shell.sh.j2"
|
||||
src: "files/backups/restic-shell.template.sh"
|
||||
dest: "{{ restic_shell_script }}"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
+32
-2
@@ -4,6 +4,7 @@
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "caddyproxy"
|
||||
@@ -41,9 +42,38 @@
|
||||
- "{{ config_dir }}"
|
||||
- "{{ caddy_file_dir }}"
|
||||
|
||||
# Shared HTTP access log directory: caddy writes here, other
|
||||
# containers (goaccess, etc.) mount it read-only. Dir mode 0755
|
||||
# so anyone can list/read; the file mode itself comes from the
|
||||
# `mode 644` option in the Caddyfile log snippet.
|
||||
- name: "Create shared caddy logs directory"
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_logs_dir }}"
|
||||
state: "directory"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: "Find pre-existing caddy log files"
|
||||
ansible.builtin.find:
|
||||
paths: "{{ caddy_logs_dir }}"
|
||||
file_type: "file"
|
||||
register: caddy_log_files
|
||||
|
||||
# Lumberjack created earlier files with 0600 before we set `mode`
|
||||
# in the Caddyfile; relax them so existing rotated archives stay
|
||||
# readable to consumers.
|
||||
- name: "Relax mode on pre-existing caddy log files"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
mode: "0644"
|
||||
loop: "{{ caddy_log_files.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: "Copy caddy file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/Caddyfile.j2"
|
||||
src: "./files/{{ app_name }}/Caddyfile.template"
|
||||
dest: "{{ (caddy_file_dir, 'Caddyfile') | path_join }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -51,7 +81,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 }}"
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
{{ eget_bin_path }} rclone/rclone --quiet --upgrade-only --to {{ eget_install_dir }} --asset zip
|
||||
--tag v1.73.2
|
||||
--tag v1.73.4
|
||||
changed_when: false
|
||||
|
||||
- name: "Install restic"
|
||||
|
||||
+2
-2
@@ -38,7 +38,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 }}"
|
||||
@@ -46,7 +46,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 }}"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
- name: "Configure goaccess application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "goaccess"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1106
|
||||
app_owner_gid: 1106
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
|
||||
db_dir: "{{ (base_dir, 'db') | path_join }}"
|
||||
report_dir: "{{ (base_dir, 'report') | path_join }}"
|
||||
|
||||
tasks:
|
||||
- name: "Create user and environment"
|
||||
ansible.builtin.import_role:
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create internal application directories"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: "directory"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0770"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ db_dir }}"
|
||||
- "{{ report_dir }}"
|
||||
|
||||
# Earlier runs left root-owned files inside db/report (the
|
||||
# containers used to start as root). Recurse-chown realigns them
|
||||
# so the now-non-root processor can rewrite/restore them.
|
||||
- name: "Realign ownership of generated artefacts"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: "directory"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
recurse: true
|
||||
loop:
|
||||
- "{{ db_dir }}"
|
||||
- "{{ report_dir }}"
|
||||
|
||||
- name: "Ensure caddy access log exists before goaccess starts"
|
||||
ansible.builtin.copy:
|
||||
content: ""
|
||||
dest: "{{ (caddy_logs_dir, 'access.log') | path_join }}"
|
||||
force: false
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0644"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
dest: "{{ base_dir }}/docker-compose.yml"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy Dockerfile and entrypoint for the local jq-enabled goaccess image"
|
||||
ansible.builtin.copy:
|
||||
src: "./files/{{ app_name }}/{{ item.name }}"
|
||||
dest: "{{ (base_dir, item.name) | path_join }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- {name: "Dockerfile", mode: "0640"}
|
||||
- {name: "entrypoint.sh", mode: "0750"}
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
build: "always"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
@@ -6,7 +6,6 @@
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/homepage.yml
|
||||
- vars/homepage.images.yml
|
||||
|
||||
tasks:
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/homepage.yml
|
||||
- vars/homepage.images.yml
|
||||
|
||||
tasks:
|
||||
- name: "Create user and environment"
|
||||
@@ -44,5 +43,6 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
pull: "always"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@
|
||||
|
||||
- name: "Copy gobackup config"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/gobackup.yml.j2"
|
||||
src: "./files/{{ app_name }}/gobackup.template.yml"
|
||||
dest: "{{ gobackup_config }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -47,7 +47,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 }}"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
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 }}"
|
||||
config_health_d_dir: "{{ (config_dir, 'health.d') | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
|
||||
tasks:
|
||||
@@ -37,6 +38,7 @@
|
||||
- "{{ data_dir }}"
|
||||
- "{{ config_dir }}"
|
||||
- "{{ config_go_d_dir }}"
|
||||
- "{{ config_health_d_dir }}"
|
||||
|
||||
- name: "Copy netdata config file"
|
||||
ansible.builtin.template:
|
||||
@@ -75,6 +77,43 @@
|
||||
loop: "{{ go_d_existing_files.files }}"
|
||||
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
|
||||
|
||||
- name: "Find all health.d config files"
|
||||
ansible.builtin.find:
|
||||
paths: "files/{{ app_name }}/health.d"
|
||||
file_type: file
|
||||
delegate_to: localhost
|
||||
register: health_d_source_files
|
||||
|
||||
- name: "Template all health.d config files"
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.path }}"
|
||||
dest: "{{ config_health_d_dir }}/{{ item.path | basename }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
loop: "{{ health_d_source_files.files }}"
|
||||
|
||||
- name: "Find existing health.d config files on server"
|
||||
ansible.builtin.find:
|
||||
paths: "{{ config_health_d_dir }}"
|
||||
file_type: file
|
||||
register: health_d_existing_files
|
||||
|
||||
- name: "Remove health.d config files that don't exist in source"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ health_d_existing_files.files }}"
|
||||
when: (item.path | basename) not in (health_d_source_files.files | map(attribute='path') | map('basename') | list)
|
||||
|
||||
- name: "Copy health alarm notify config"
|
||||
ansible.builtin.template:
|
||||
src: "files/{{ app_name }}/health_alarm_notify.template.conf"
|
||||
dest: "{{ config_dir }}/health_alarm_notify.conf"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Grab docker group id."
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
|
||||
@@ -34,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 }}"
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
- name: "Configure tuwunel matrix server"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
|
||||
vars:
|
||||
app_name: "tuwunel"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1105
|
||||
app_owner_gid: 1105
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
|
||||
tuwunel_server_name: "vakhrushev.me"
|
||||
tuwunel_well_known_server: "matrix.vakhrushev.me:443"
|
||||
tuwunel_well_known_client: "https://matrix.vakhrushev.me"
|
||||
|
||||
tasks:
|
||||
- name: "Create user and environment"
|
||||
ansible.builtin.import_role:
|
||||
name: owner
|
||||
vars:
|
||||
owner_name: "{{ app_user }}"
|
||||
owner_uid: "{{ app_owner_uid }}"
|
||||
owner_gid: "{{ app_owner_gid }}"
|
||||
owner_extra_groups: ["docker"]
|
||||
|
||||
- name: "Create application internal directories"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: "directory"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
|
||||
- name: "Disable backup script"
|
||||
ansible.builtin.file:
|
||||
dest: "{{ base_dir }}/backup.sh"
|
||||
state: absent
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
dest: "{{ base_dir }}/docker-compose.yml"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
+3
-3
@@ -39,7 +39,7 @@
|
||||
|
||||
- name: "Copy gobackup config"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/gobackup.yml.j2"
|
||||
src: "./files/{{ app_name }}/gobackup.template.yml"
|
||||
dest: "{{ gobackup_config }}"
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
@@ -47,7 +47,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 }}"
|
||||
@@ -55,7 +55,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 }}"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
- name: 'Set up environment variables for user "{{ owner_name }}".'
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
src: env.template
|
||||
dest: "/home/{{ owner_name }}/.env"
|
||||
owner: "{{ owner_name }}"
|
||||
group: "{{ owner_group }}"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
homepage_nginx_image: "homepage-nginx:531c1cd-1772885069"
|
||||
@@ -6,5 +6,7 @@ app_owner_gid: 1009
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
||||
|
||||
homepage_nginx_image: "homepage-nginx:latest"
|
||||
|
||||
# Registry images
|
||||
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"
|
||||
|
||||
+174
-157
@@ -1,158 +1,175 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
39303531663233366261396235326638633861383835306130636239313035646134666236356333
|
||||
3766646337396365636134653837373566333866333361360a323535626465383230366564336635
|
||||
63363031383731383238636330343337333563626462386461313066636130386565623061303733
|
||||
6237316539363566640a613562613834383735313237363364613932323037633262636530336464
|
||||
30343764383036646138336364376566616231373864383538326666616365396432336239623161
|
||||
65613831633838383135333163346531366337663966343764353335616236393134393635363062
|
||||
61303166363136613734323937346461393133656162353231306532646535343233653135613961
|
||||
31323033616666643062333337323736333730303939613761643661663036336464663161323663
|
||||
31646663353865663032306332646364313230363765393666643661366465373734396262323264
|
||||
35303137643435326130656264313164636461343163613230346362373035373538653432303232
|
||||
64343935383531343434323135373766386362376262363165636265376634336132363061376132
|
||||
33623762376337393937663738646238646365373766363839623239366261326465386463613139
|
||||
35366166383965333639656333393034323930656132666535383661643630613266663063323437
|
||||
64613865306465633833323462323366303164366563316236373531313637666533313136326132
|
||||
62316364326430386336656564643434383934323037623436636633633834613236366364346133
|
||||
38323034313735336434633632643965326231313961383465623737303932376366653264643364
|
||||
61373032333463643262656364386138376532386537353730326131303238366634363262336632
|
||||
65333436393739656532656263353037346166653661366661626235366132643633646238356462
|
||||
37313230383031356535613766316136333239623239373932663536393964396638663561623365
|
||||
62616561333734646335393664356638623362346230623931643865636165643736646562323231
|
||||
37306665343135616362646237613933383862363664383666346532626332343065623432303434
|
||||
33316631376430353566326434323164303834346663356265393066653033663536623062323839
|
||||
39653130633463313465386432303234396562323334326631313564616633383866616564643537
|
||||
63396262633336343066336335656136386661623762373262323062303761653139323537303465
|
||||
39386436613432326539626536396631323766316566636135356132316465356635636262303237
|
||||
31313864646563326166663163646563333133353162313339333431653233353861653730313836
|
||||
66626566643266623739346536653537316263343865376436313364616335313135343432653537
|
||||
65643733353934633030643236613134343336383033653465346461306235653631336637626437
|
||||
34366666616330356133363337323734323733643034303236326438343766643331656331323961
|
||||
30333034306461653134636565633938646535653037373733306336303037326261613238616339
|
||||
39623961363233326533633236653437363766666263623933643036623465353934616262383065
|
||||
61663566323766326663653731346537636262356364623338353562323832616661313634643565
|
||||
37313537653535326233376137356663333266323063643361313734326662636565653330623965
|
||||
63343961613034656461396639373266373666633732336231343836353234336563393531633735
|
||||
38616239333930666634303362366364353338613437353331353030363064333237333161393433
|
||||
32336131373935303033646161366138316338623334383434383131313236316539343937653961
|
||||
62626333653531343238646430623635633534373333613837353964313336663831396439633133
|
||||
31353162623938386366343738346433373032386434373165363131386663383432623532323363
|
||||
62356630373566616436613136616238353163363634373530326262383164396433313564633737
|
||||
38623432653337383961633365316366313664663132353766306636623035373830646566646432
|
||||
65663038666232383566613534626466323765346464363461363335393032613363306464333738
|
||||
35303535376335306266393666643063343831396533336562666661613934303062376237393637
|
||||
30373563623935373733623864316539383830333663616534373230333132396232323266646538
|
||||
66353539646530353832333065633266663664326566306262303966623662383635373362386535
|
||||
39343333663138663438376338316536626662663930316532626232613231613063396539363364
|
||||
31393966653966363831383261336336666662326630616364613563626434656334616539373262
|
||||
61336533363532613335333365356634336334646266363762346362326533613734393136616639
|
||||
65643139306464626662373565363033356439643933646138363537623563633966666130383732
|
||||
64383337653361653731323965326566313132656563643264326130623334333237623632313137
|
||||
65643234323464616537383637333766373161313862356364376466343231613039333539306561
|
||||
30333032663761616237353962343266656330623765663039383864376236373838626361363961
|
||||
32363430636337346134326337633166383236633937306435333838313636356437643361363063
|
||||
33393337633939306365336130653761353461393531663734633831646261373832336532333136
|
||||
65373666343636613737336466613933356130653965663036373136336231616263373966356666
|
||||
34643838653532616134663530623963633563333531323939393264393839636263356230313966
|
||||
66363666643537346130643930373730373062303836373632376533383433366432323633343863
|
||||
37326162336262633635666131303039643561663939633430613936666431646633353762306234
|
||||
35303130353138626462646431616161643930653165636531333861623066653265343239623936
|
||||
35303238373562386463373266653631373266333961633430663931343262313234376266303931
|
||||
32306362626563373133376161313633343438653438383433656533623861313365373831623463
|
||||
34613362646432643631323232313236366666386334306237656535623465363134326566303535
|
||||
39313836396231653131376538316263633463336263373839663464356531383266313439326262
|
||||
38356638333863303066383330653135643130613933643236383164623935373138323662616134
|
||||
63306562646366393865623462313633323136363461623461373136303665343839366233633238
|
||||
63363833356131643364653338666461323365396634373030663939646163353532663365393834
|
||||
63336230373134633136626137623939333566316238623337623033333939353835653463663033
|
||||
61623937623335613236323233633661363565383232633239393137323533633938353537643733
|
||||
31386530653837653763333237653763633962396237666662343534346661653730623664626638
|
||||
64373963383065333138656231393466366333666631383334326164353638323032383430656165
|
||||
30356534376261346233346630303863343739663035383637396337313638313436653138626261
|
||||
38626233346639376534313563373563306266646336633861343466613761306438306532333138
|
||||
64656365363634393733623965396237353164383031336634316634313334616338376165666233
|
||||
65623835376431336162663839373264313534626564356561646635316537656637623430326635
|
||||
62353661636534646130613134306563646233323131396363356663623865626164393865623931
|
||||
30343534306361316536653633353162653965666437353361666662663732616433613838633733
|
||||
61386564306266376361343335333132313235633535313164373734623934623930353137396331
|
||||
61333562616139373437393638363865653961626330653738306564393939383262346630663162
|
||||
39633961343132663464636637373335303834363663353266616332393335626535646335633138
|
||||
31326630366437323931656566656462366433393235353735346538323933613639636539353063
|
||||
63336436616333323238386537343362616634653266656536323235633366326561613365393230
|
||||
39623034303239316663326466393032616561656534653533333565646661663936366231356361
|
||||
33306130313630313333313137356639316430623636386464623566353338653763663532323630
|
||||
61623165316132666238383935306362306230353764373639643064396163363434313830323539
|
||||
65323036656461613137316264663032616562353235313466613436316233316462663062656234
|
||||
61313163623937663734366231363938393932623531343533306532616235336262366534393962
|
||||
64376436626466646631363262303938636538306431366335616363333738623235626263336264
|
||||
61626135393336663730303362383039646161653461666439313237653262326538376466366233
|
||||
34316430356335626334613736386337333634336366393031336433306537303538306663323665
|
||||
61373462643263636164656339633432656337646533653732393262633662653330326437353836
|
||||
38613964663261316161343166333734386134346137363437343739373132643962323563623736
|
||||
35666135313165313564356162623430393934313834376538613635396465373936393063303038
|
||||
31623633633065393766653830393735306438306563323139303835623537323032633438333935
|
||||
30653363313963366661323865656531613836666533656537346330646531373864333638306138
|
||||
66376534363734353664393163353933353861356433656233653862353666323039623166303065
|
||||
35356539616432613435333831663834623162326336613663343833613961613361643639613239
|
||||
38346230666362376364333664343438336361393430646437653161663235326533383962303833
|
||||
63636135656265333230333863626566373536663439306134363165313332316562336362333666
|
||||
35653361383666666136393431326438613233356261663639376634643537313435616433663165
|
||||
64386662653733376337613339343163346664386465663835643534383132316562346639303030
|
||||
33383736323639643334633733396535326131663230643364636436326333316335333333626634
|
||||
62306137636563306132613239306236383030646163343133353538356537316436373266303032
|
||||
39343735663634653963303161383434363632333433373335313438383734653036656434343165
|
||||
34363233373238623166396634303038346663633066643466383831373664376139313161616137
|
||||
65386263653766323330363736396334656230636332383166666663633538616165306262383630
|
||||
35346434626432353536383136353761376139373865356663656333623636623166303863643463
|
||||
34623830636164353362396532346538373139643730633065326238663938653536643437383333
|
||||
35303336656264303733333832356138306464393934343738373932643935346130356532356266
|
||||
30353736356430313965376537303033666665646339353235373030383763643231616332616162
|
||||
38306333383562353830303435323362386435616439313561623032383464373431393033313035
|
||||
61363832383030636635626463616430323063313566633966396130633935633935373135353134
|
||||
39663038653338656161336138623662326536306264616265366435373861656230623938623361
|
||||
64623064356264383462376636333665613739663138663961396462626137646561326162616563
|
||||
33626239663230366664646631656636383931653938663964313235663264373337353531336664
|
||||
66626464306161353731366563326132613063626432343839623734373364336363313864666465
|
||||
30333966383733303036303536333234376233363962646430646630383739346638383464373633
|
||||
61343731663865646537306362316261623736376161613963306664316462643334616138343435
|
||||
38373035396230376534653438346432303361393330363131633537656635656563323730383931
|
||||
63336438623035663834343237656131656664343636383137373063623565313731336661663065
|
||||
66366663326233353936323361306361623064656637373835616431386137323762313766613134
|
||||
34393332623733313239383634396333363030323065623138383035656563326138343063383265
|
||||
33346433623639386361383866333736353934303464616334643837356432666138386634396632
|
||||
37346335333734343034333664326536396335666334323633623437663339313931313132336663
|
||||
63346563306430643632363266353965663338366136366666393235323863333762376630313630
|
||||
37653961336366656638393233393362313961656437336233333734306364383632313432303434
|
||||
63346138653130643432393062663330333161306664613933333838626163646230336463326162
|
||||
35663334666534643132396639383932613764663362623634326331656262323763363061643239
|
||||
66666134353135303734373238626433306164363338386237306465353566656235346664386433
|
||||
65623833353931626634333631396237373366303163336562626337353939383463366461643534
|
||||
66663432313134343436313362396566313462316662353336363733346339353965656463613863
|
||||
39353635363436363861386464303565613663636365383732666261353334653965646262326231
|
||||
63316661363231626164343463353661376561313766303531316134396362613337363836623532
|
||||
32356235356463353564316334303864323637623137353130356139633831346430303262646539
|
||||
66643733396432643532373162363735346563343133663064613565666234313230356139663062
|
||||
34646661306461336339643239333536393861646130613633653366616434633032363566633465
|
||||
66653332336464653238373165656163636538653966656230643732653665363437653665663162
|
||||
65636533303038386537336632636339613030386466346633636338386433653434633435656639
|
||||
62383765393966316539646661306666393762623836353730663263663331396337373837643138
|
||||
34343166393061653036316336306564386530616339626465393332316237306534663965376631
|
||||
37653563643931353436323264306236393561366465383730653135333161656634343565343965
|
||||
32323839626562626335323431636135616230343833386130383138356663393735626165386231
|
||||
63396461373231623534393665613339656165353665616232613831333561656530623036663339
|
||||
38373564373966363366626439343539343236373665383863363861323539373237613463333835
|
||||
39656530613562643666333933633236373666316135303339303438333064363433396631313635
|
||||
32346461636366333737656637623161373763656164366136323136313739353937333764316333
|
||||
62633565316335393530306630623935663865366133366331346665353666343434323862383933
|
||||
61623139643163626139346566323339323363363330636336313865383431316238363738623237
|
||||
37303333323661333734356261303736326365333163626434383162306266646136353537376131
|
||||
31633636363335653037323733333036346637613536363739393836613937383062386334623736
|
||||
34326463643739653163366136636266333662646630313830633134376231303162356537336364
|
||||
65313836383135323063316435353038333833643365323364623235323931373166363532376538
|
||||
37356131313630623438343364356430626232353839653937366138363865653762366134646438
|
||||
39373263313961376334316334343336363237356232623364373439623033336635656361366438
|
||||
37333435333464663039613363623938653465373864373339653662613333633165386364373839
|
||||
63623033326637303666303833393034373033633032373837336463303166613365373330333239
|
||||
61306535656334633235363865333432326531376638366332356163376637356635393065643463
|
||||
30666438323766663564373633666565636338333930353965663365313062636466653232666632
|
||||
6163
|
||||
30613937343031343632383733623435366535373231316163393436363636656462326262383565
|
||||
3032663665323131626263356531633934326639636231620a363635376263333438336331343366
|
||||
36386337323165333861633062656433313062343764636138663533333639316336306230653732
|
||||
3331336137616263630a306135333566646434663231383138363966386661643836626561376338
|
||||
33636362323937386664646630383062613535666431393634316337626564613733313861386238
|
||||
39316263666662633066633836366236346431313531656339613566303962656165396662326563
|
||||
65623036333932393739646162353836646562643866396263386232633933326538316637656365
|
||||
38656562383861613030306635613236646235613436316635386531656666363738396461313263
|
||||
30653934366537303133613962653137633131323431396266646339376339623034373963666438
|
||||
66383464303431353962323032316533613138383831343036383230303931326433396333623935
|
||||
37396432376637373135666236333332383262323931616432343665653836626265376632643765
|
||||
33303835393863333334653664613337343063313362363136383234666335636565383237656639
|
||||
34323839613765626231303230616661626530633530333165373535663139643339656438396237
|
||||
36656134643636643733363336343739616532666130393863666665393138383261353730626565
|
||||
38306133343463306462656534326431623238336562653433316233383861303032393437316336
|
||||
32353338613639653735393239333235633565636563313933333763323339656237326162316465
|
||||
66313034306263343462376632303539656533353265336366613338326439323732623438626162
|
||||
39393937613836656236383030343436303632363330313734333665643365666138633034323462
|
||||
66376333386237383666623434636662363338626538353933636632646236393630343739636666
|
||||
37666531633839363365633863646530396432613166313035353638313463373338313139616133
|
||||
62383234356665333132613664383931316238353863306538343831363233303862383737373939
|
||||
37303430303766343366633536643139363366663734326162366434333165613033653666383337
|
||||
62626538316463343466613065326666396266643661656164376336336532666134613663623163
|
||||
64316335633839356231393130343938613334393737666663363662356466326235666561653239
|
||||
39386635616165633063383032666366383861333038373636613663613461316433633562623664
|
||||
65373536663230356632663133356639323838653431333836376330316162633261333934363335
|
||||
34383937343063303835626435316534356239316230326566383036646237336238623036323161
|
||||
62326264636130323965313866616631663039623431363139363462663435323866393437373566
|
||||
37353463353731303434303435353061633531663464656336306439373238633038343237313133
|
||||
34333463626261333038363438343034373335346332316430376436656331626664376664323037
|
||||
32393631663434383265326231353035356333343739386132326435653438306136373237396539
|
||||
33613462623562343966343933363037326234323836363636313938666534333337646139326533
|
||||
61633666623936646366643336333339303633643230393465623031643963643635313264353236
|
||||
65313631663430336262326463663938386630363464386230383766376363373235366438393635
|
||||
65313232383334666263626662646264393565326164613364313138303638653333653963316561
|
||||
34346464653637376433356335663930396432386238366132393562393162353235393438633533
|
||||
62656533316431666463633530653832356263653030326366663932306662613465643638313633
|
||||
62396562616463313066343832316238386234343537346436623039643132393562303130613331
|
||||
38653261353132633036623138643338366534396237613333333765653436363032616235373035
|
||||
35313966623531373636363638383862333935353931653861663966643531383335653739356565
|
||||
31373937396234616135653765643131666530383030343064366531336135366265633232653433
|
||||
65396566626232633831343734353432633462343336616135373861303836613463393736306133
|
||||
64663531643630326432376235386433623365373163366663623632333531623863623663643434
|
||||
36646134623665633531643732663137613862343666613139336231646564363266343935653263
|
||||
66653366666635666535636637626134363633336233613732656166373063333237323465616434
|
||||
39623238636235333866666536346430373735323530633133663937636366663530326465386161
|
||||
39346466386133656633373438333133303566363233626238366133636333656462373065613863
|
||||
65363439383163323332383931663833303234326132343462333835323664363461656566393065
|
||||
38646261323336316239363465343238643132306235613031626438323838653066376561626661
|
||||
34356530666665323230646436633935343861323638656638323163306236393865366630636236
|
||||
62386161646131623738333664636361396239643666323837646332383538623734386531313664
|
||||
65313632343365393130643137353735666565663030383231616231313237323866386336316361
|
||||
66656165643261653464316639613635323531306362353164373531326461666437303434346233
|
||||
31383864346233313633353065343236633636386138323761666662373564623234613965323131
|
||||
36313861316563333262306434663265313237626631396561303236343330633738356666633663
|
||||
61313663336237653361383963333764336137396666613634313036373564353564643334623363
|
||||
62353531306532323664376363383938646536393339346666656339393230613362666337663861
|
||||
37633633653463343430666634643863383438633933343839663865616136363538643061343437
|
||||
34613037353835613866303230303162396531626663616164343263633261363335313936666339
|
||||
63383533616530356262363838636466333038656339316364626263383731313464313734613630
|
||||
62646266666136616632636161363631623362346230643134663664396565323932343462383661
|
||||
38303663653262333236613833396237663834333139316666343065396137306562613265343863
|
||||
65323065663862636230636664623132306231366462346432343030376236346465663831623537
|
||||
63633231333165613731626137656539366131633364623661616136616434306563656139346137
|
||||
63313032343161623235306230633361666163623061333738383135636664623438323238663631
|
||||
37613964643931323432353431306564393639386437666539376238643065343738313265373661
|
||||
61303764646463326632653335323432646436353765633862623838386337623464333839643833
|
||||
39383961666234363638323735636231623962666461373435633631323530643237656464396465
|
||||
66623431393461613634373237646636333965396435663563363161626666356638366462373261
|
||||
37633238323135666136623663653665303832656437663536383236313334313461353032663933
|
||||
63643164363664663939613635373362376162336262653332663936313737396130366330656532
|
||||
31653463383132643262613839613962663836376463343661393736633633396164643264653431
|
||||
30663732303236653165386537653432656266363239373030333630353661666636303730373937
|
||||
65363237366333376133306437376534636133356238326461333762326563386265363636323831
|
||||
39343665386262336265383865343563343832623766656534306661326462333561373835366631
|
||||
39636361623831623533353962633363393531313530363833613962616331653565633733303964
|
||||
32393433303938323566646264323761633035653231353761643261663839313665663434643834
|
||||
65356432393431336235306437643861653437643362363839623634333835376636623664616139
|
||||
66376562633232636431626436653161333137633466313433663433383230636337653535643430
|
||||
61613032656135323765613837626266313632353661346636643866613138303930346563623738
|
||||
35613831623565353432336338373465303437623234313736353661353430656661366365373230
|
||||
33646134356661616164303865623464306339653439613365626261323237623135346537393535
|
||||
62393465343134626333333462316331656134383362383031353863316632393061333933336362
|
||||
36326662363833303436663166383365346433323866346462663261333330656666663162383564
|
||||
35336438643064313833393638323864343237616163383033313966303262326135323335353931
|
||||
66333938393264323533353231303935346661653835386262306133393065356535643835663665
|
||||
38363930356530366135313734306464623739376438613430373634396339393864396264303135
|
||||
61356333636236326566386264353930626564636438616265353939383733663837313233356363
|
||||
63643835393437336366313030303864306536666638623430356263336234646462383666316431
|
||||
36313464346266646438383762313138376338323537386635636561656662306533316362396162
|
||||
38326165633532623933376165643861323735353831363264376162316561613038633961333337
|
||||
30646461636332623466643033633764333330353832616365376633643263336131313733653139
|
||||
39646239366261366465333962643565636430393464613866613038333636393362383636343534
|
||||
61323830616234633364346131336630393965373730343464366166376232346464636263323639
|
||||
63336464623733363139366665336131653163613833383261376138373032666663356637383832
|
||||
32313130633363346435383638616236633761616166663339316437353938636636613530383836
|
||||
64623661366130656439306266343435396334383564353466663339383862313733313931383463
|
||||
64323237656361383262343735366562623965356636343963363966616333313333646233373464
|
||||
33383939386262663730316333616663636161356463396362643237356532386162363131626461
|
||||
37323965313063623463356133626531393339336535303562343530316663613639646531323136
|
||||
30353732646237623264653963373863363965326338666264306562373932393333633639396131
|
||||
34303764396330326165636264313532393961303038623031336631653831323337306261333630
|
||||
37333964376533636132303335653935343932373330373632626235356437636165623436383036
|
||||
38313565373561393834316532333930356135623439373161643063643738353031353565396330
|
||||
37656162346433326638353439613666336534336562623633643230636134383931653538616665
|
||||
32393432383265613237323138386361353934373965306462393666616532653563626232643035
|
||||
61643732376434633537633663633130313437656166333239633533393334373163333566343430
|
||||
38633165353637306237316436663235633162353132646562353638333038663636323465633632
|
||||
34643037623634643534663366633133363030323966313065353333633636646636306565333238
|
||||
39336662626138306464613461343762316533656433626165323764616535623539336439396663
|
||||
63393365626235613063613934306132333162646237316364306637346136623061363236383765
|
||||
37353138363337346530626563366136333635663863313038643537366237633362343136396664
|
||||
61623237353433333238633163636565386134356565303763336238636366316330666339383365
|
||||
30323235356633656362353738393234616435663333613364316539636430623262643162313337
|
||||
36323466303832336530336566343731306362333862663537613339663562623739343636613162
|
||||
36343563373665376565366266343461643562636630623166626165636337613931653338633862
|
||||
65393138353661656265666335343263333063653430326532663839383433643966363639643636
|
||||
61376365363538636235666235623638376334363265626136313536353637386564303936636263
|
||||
32306239306339656238393864666135613663366332666135663461353366313833376430376263
|
||||
62613163303964333735396338373737653837666435656130376435376434356462383264636561
|
||||
34353563316132336663316166663832383939333634316562383634383838336531313731613666
|
||||
62643231636266353935343539366465376139643834306261623738313432306133653461383738
|
||||
39396332373364353833626661333634346131396337636235653431616336393666373231383030
|
||||
32306466613136346265653038636537646330643337663863383562323638616661333037323232
|
||||
35613138363330353533643064613366343339343032373737306364353135353334336666663732
|
||||
36343963613636376561666266623537316432666161326331383761323437383738373762643937
|
||||
30623737643239326261343939663065643265653363633661376265626637643336613635393335
|
||||
65373565333936333431656331633039323135336236656337343532643939386338663239393065
|
||||
65666536333732646235633762633032393463663334616165333834653938346230316236353839
|
||||
34396362386265646261373561636230363962663433303535373035346334353932643365383763
|
||||
32333239613961346466356562376663613062373162666264633636323833323263333765616563
|
||||
39646530343962353362363634336336323463623137646531373362353832343335366461646535
|
||||
38653735316536396438613866326438363036653833366636626130323437623366373833366165
|
||||
30303636666263323062343931306435363961643838636163366433376436303231316338613034
|
||||
37393631363632383461373566306365306631396335633432383939336332626237653462393136
|
||||
34316636643464363634366535333463326533333564633163363062666463343731396231656234
|
||||
39346333303465363037313063373366373439306333636465636366666437326362626264653033
|
||||
64613062343538303931646630373565663530336133633032366331626536353237336235633636
|
||||
63366639366439386530303966323563323862383865356630313636333333393464653762626634
|
||||
65366231613661313233626239303035323666346236636362393036353839333636343434646266
|
||||
65653039353966616361363335346565383863616161316134383365616636333732653233383261
|
||||
65616664343830353861616666616237313532363334653430313437313535666436383338396363
|
||||
33313436363061306431366332373936633034393733646137636338336431333033343532613531
|
||||
32613839393232646565663931303530376432376337613762346230646366613935383234313666
|
||||
61643339353933336434666466623133336637343534303737366162316561366632333335663233
|
||||
34643036326630306632353438643666623939393033646238353261386231626634303266303530
|
||||
64643436653234616332623835333165626135613465346162393335353133356233666536313632
|
||||
65616135666533343839666132623639343565303436623162383738353633613864356535646365
|
||||
32373337393936393830666365383462333437373539666633386361373135333163393334303235
|
||||
33663631386566356366666132616265373533373561616564343538303432346562356234336663
|
||||
32623866396434326264636539323132613239343938353739376539383139313833376563623434
|
||||
62636334326234313230666662396561393130396137306437393334323561356435343866386636
|
||||
32656337656439653830653365313031326562643437376538316561653963643232353434313538
|
||||
64373638616133666463393462643465306565646136643862363162643638343565316139626539
|
||||
64643939383936313035323936656438313039376635383733633032613165343130663930323166
|
||||
37343261333332663863366533386335373962323163616564376434636361356438393035656533
|
||||
30383139323931306232353664636662313036643431663536353035356139643761613235663837
|
||||
37613133363433356536316466343237613131386536356234343135323861396130663464323236
|
||||
63636563633031396465663563366263373938373531336239323138653531386535643332653736
|
||||
61396432356161643663623130656632633862333861656464613432623732656465376236313437
|
||||
65386630633036636663303633636134343739366562643062343030383138653466326636366266
|
||||
32633239343039633636313837643432333238366533393061646237626130303934356438633936
|
||||
38366265656365333338363431643432633463313438633361333764653637623964363732303737
|
||||
61343137653930353361653364656233343166633162313964306531383834356237343031396137
|
||||
34366135623530366164646532643636346233353563333031343931643037613463613639356238
|
||||
36306235336562333935643035313934366339623365616661616461653832336137336464393662
|
||||
38363433646139646633353162616661323433636531393339643562373538616430363061366330
|
||||
35333138613136323865346462653761666534343538313033663835653631363631623532663133
|
||||
64383135326333626438363066633366316364643332623030653230353861633837646362626333
|
||||
38363236616265313638626263316164323563616237653465353031353734333032323761393761
|
||||
31333331643161396338653330653537353634306139656536363665643437633433666236356334
|
||||
64386566623836306666653766626465646664303231613062663862613565393364303233333636
|
||||
61633631643437373235636133333832646463366633353939383834373362633539333766303661
|
||||
3132333365333061633665366432346636646564313437333061
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
apprise_external_port: 8000
|
||||
apprise_external_url: "http://127.0.0.1:{{ apprise_external_port }}"
|
||||
|
||||
# Shared HTTP access log written by caddyproxy and consumed by analytics
|
||||
# tools (goaccess and so on). Lives under the system log path so it is
|
||||
# decoupled from any individual application's data directory.
|
||||
caddy_logs_dir: "/var/log/caddy"
|
||||
|
||||
Reference in New Issue
Block a user