Compare commits
27 Commits
5f619eaccc
...
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
|
|||
|
6bfb362b20
|
@@ -11,7 +11,7 @@ Ansible-проект для автоматизации личного серве
|
|||||||
- `vars/*.yml` — переменные приложений и образов, `vars/secrets.yml` — зашифрованные секреты (vault).
|
- `vars/*.yml` — переменные приложений и образов, `vars/secrets.yml` — зашифрованные секреты (vault).
|
||||||
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`), галактические роли в `galaxy.roles/`.
|
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`), галактические роли в `galaxy.roles/`.
|
||||||
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
||||||
- `templates/` — общие шаблоны (например `env.j2`).
|
- `templates/` — общие шаблоны (например `env.template`).
|
||||||
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
||||||
- `.gitea/workflows/lint.yml` — CI: yamllint + ansible-lint.
|
- `.gitea/workflows/lint.yml` — CI: yamllint + ansible-lint.
|
||||||
- `lefthook.yml` — pre-commit хуки (ruff, mypy, yamllint, ansible-lint, gitleaks, проверка vault).
|
- `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-rssbridge.yml` — RSS-агрегатор.
|
||||||
- `playbook-netdata.yml` — мониторинг.
|
- `playbook-netdata.yml` — мониторинг.
|
||||||
- `playbook-dozzle.yml` — просмотр Docker-логов.
|
- `playbook-dozzle.yml` — просмотр Docker-логов.
|
||||||
|
- `playbook-goaccess.yml` — аналитика веб-логов Caddy в реальном времени.
|
||||||
- `playbook-gramps.yml` — генеалогия.
|
- `playbook-gramps.yml` — генеалогия.
|
||||||
- `playbook-calibre.yml` — управление электронными книгами.
|
- `playbook-calibre.yml` — управление электронными книгами.
|
||||||
- `playbook-transcriber.yml` — транскрибация (образ из Yandex Registry).
|
- `playbook-transcriber.yml` — транскрибация (образ из Yandex Registry).
|
||||||
- `playbook-wanderer.yml` — пешие маршруты.
|
- `playbook-wanderer.yml` — пешие маршруты.
|
||||||
- `playbook-remembos.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`.
|
- Суффиксы шаблонов: `.template.yml`, `.template.sh`, `.template.cfg`, `.template.conf`, `.template.toml`, `.template` (для файлов без естественного расширения) — рендерятся Ansible модулем `template`. Расширение оригинального формата сохраняется после `.template.` ради подсветки синтаксиса в редакторе.
|
||||||
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `*.images.yml`).
|
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `vars/transcriber.images.yml`).
|
||||||
- Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др.
|
- Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др.
|
||||||
- Каждое приложение: `app_name`, `app_user`, `app_owner_uid`, `app_owner_gid`, `base_dir`, `data_dir`.
|
- Каждое приложение: `app_name`, `app_user`, `app_owner_uid`, `app_owner_gid`, `base_dir`, `data_dir`.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:{{ apprise_external_port }}:8000"
|
- "127.0.0.1:{{ apprise_external_port }}:8000"
|
||||||
networks:
|
networks:
|
||||||
- "web_proxy_network"
|
web_proxy_network:
|
||||||
|
aliases:
|
||||||
|
- "apprise"
|
||||||
volumes:
|
volumes:
|
||||||
- "{{ config_dir }}:/config"
|
- "{{ config_dir }}:/config"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
tgram://{{ notifications_tg_bot_token }}/{{ notifications_tg_chat_id }}
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
tgram://{{ notifications_tg_bot_token }}/{{ notifications_tg_chat_id }}
|
||||||
|
mailtos://{{ postbox_user }}:{{ postbox_pass }}@{{ postbox_host }}:{{ postbox_port }}/?from=notifications@vakhrushev.me&to={{ notifications_email }}
|
||||||
@@ -731,6 +731,10 @@ access_control:
|
|||||||
subject: 'group:admins'
|
subject: 'group:admins'
|
||||||
policy: 'two_factor'
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
- domain: 'goaccess.vakhrushev.me'
|
||||||
|
subject: 'group:admins'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
- domain: 'wanderbase.vakhrushev.me'
|
- domain: 'wanderbase.vakhrushev.me'
|
||||||
subject: 'group:admins'
|
subject: 'group:admins'
|
||||||
policy: 'two_factor'
|
policy: 'two_factor'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ services:
|
|||||||
|
|
||||||
authelia_app:
|
authelia_app:
|
||||||
container_name: '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 }}'
|
user: '{{ owner_create_result.uid }}:{{ owner_create_result.group }}'
|
||||||
restart: 'unless-stopped'
|
restart: 'unless-stopped'
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
+243
-184
@@ -11,6 +11,7 @@ import sys
|
|||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
import pwd
|
import pwd
|
||||||
|
import time
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -43,16 +44,38 @@ logger = logging.getLogger(__name__)
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
host_name: str
|
host_name: str
|
||||||
roots: List[Path]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Application:
|
class Application:
|
||||||
path: Path
|
path: Path
|
||||||
owner: str
|
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):
|
class Storage(ABC):
|
||||||
|
name: str
|
||||||
|
|
||||||
def backup(self, backup_dirs: List[str]) -> bool:
|
def backup(self, backup_dirs: List[str]) -> bool:
|
||||||
"""Backup directories"""
|
"""Backup directories"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -65,19 +88,15 @@ class ResticStorage(Storage):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.restic_repository = str(params.get("restic_repository", ""))
|
self.restic_repository = str(params.get("restic_repository", ""))
|
||||||
self.restic_password = str(params.get("restic_password", ""))
|
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(
|
env_raw = params.get("env") or {}
|
||||||
[
|
if not isinstance(env_raw, dict):
|
||||||
self.restic_repository,
|
raise ValueError(
|
||||||
self.restic_password,
|
f"'env' must be a table for storage backend ResticStorage: '{self.name}'"
|
||||||
self.aws_access_key_id,
|
)
|
||||||
self.aws_secret_access_key,
|
self.env: Dict[str, str] = {str(k): str(v) for k, v in env_raw.items()}
|
||||||
self.aws_default_region,
|
|
||||||
]
|
if not self.restic_repository or not self.restic_password:
|
||||||
):
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
|
f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
|
||||||
)
|
)
|
||||||
@@ -93,19 +112,13 @@ class ResticStorage(Storage):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def __backup_internal(self, backup_dirs: List[str]) -> bool:
|
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)
|
logger.info("Destination: %s", self.restic_repository)
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.update(
|
env["RESTIC_REPOSITORY"] = self.restic_repository
|
||||||
{
|
env["RESTIC_PASSWORD"] = self.restic_password
|
||||||
"RESTIC_REPOSITORY": self.restic_repository,
|
env.update(self.env)
|
||||||
"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
|
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
|
||||||
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
|
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
|
||||||
@@ -154,7 +167,7 @@ class ResticStorage(Storage):
|
|||||||
|
|
||||||
|
|
||||||
class Notifier(ABC):
|
class Notifier(ABC):
|
||||||
def send(self, html_message: str) -> None:
|
def send(self, title: str, html_message: str) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
@@ -170,9 +183,10 @@ class AppriseNotifier(Notifier):
|
|||||||
f"Missing notification configuration values for backend {name}"
|
f"Missing notification configuration values for backend {name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def send(self, html_message: str) -> None:
|
def send(self, title: str, html_message: str) -> None:
|
||||||
url = f"{self.api_url}/notify/{self.tag}/"
|
url = f"{self.api_url}/notify/{self.tag}/"
|
||||||
payload = {
|
payload = {
|
||||||
|
"title": title,
|
||||||
"body": html_message,
|
"body": html_message,
|
||||||
"format": "html",
|
"format": "html",
|
||||||
}
|
}
|
||||||
@@ -187,24 +201,13 @@ class AppriseNotifier(Notifier):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BackupManager:
|
class ApplicationFinder:
|
||||||
def __init__(
|
def __init__(self, roots: List[Path]):
|
||||||
self,
|
self.roots = roots
|
||||||
config: Config,
|
|
||||||
roots: List[Path],
|
|
||||||
storages: List[Storage],
|
|
||||||
notifiers: List[Notifier],
|
|
||||||
):
|
|
||||||
self.errors: List[str] = []
|
|
||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = []
|
||||||
self.successful_backups: List[str] = []
|
|
||||||
self.config = config
|
|
||||||
self.roots: List[Path] = roots
|
|
||||||
self.storages = storages
|
|
||||||
self.notifiers = notifiers
|
|
||||||
|
|
||||||
def find_applications(self) -> List[Application]:
|
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] = []
|
applications: List[Application] = []
|
||||||
source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
|
source_dirs = itertools.chain(*(root.iterdir() for root in self.roots))
|
||||||
|
|
||||||
@@ -215,32 +218,182 @@ class BackupManager:
|
|||||||
try:
|
try:
|
||||||
stat_info = app_dir.stat()
|
stat_info = app_dir.stat()
|
||||||
owner = pwd.getpwuid(stat_info.st_uid).pw_name
|
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:
|
except (KeyError, OSError) as e:
|
||||||
logger.warning(f"Could not get owner for {app_dir}: {e}")
|
logger.warning(f"Could not get owner for {app_dir}: {e}")
|
||||||
|
|
||||||
|
applications.sort(key=lambda app: app.path.name)
|
||||||
return applications
|
return applications
|
||||||
|
|
||||||
def find_backup_script(self, app_dir: str) -> Optional[str]:
|
def _find_backup_script(self, app_dir: Path) -> Optional[Path]:
|
||||||
"""Find backup script in user's home directory"""
|
"""Find executable backup script in application directory."""
|
||||||
possible_scripts = [
|
for name in ("backup.sh", "backup"):
|
||||||
os.path.join(app_dir, "backup.sh"),
|
script_path = app_dir / name
|
||||||
os.path.join(app_dir, "backup"),
|
if script_path.exists():
|
||||||
]
|
|
||||||
|
|
||||||
for script_path in possible_scripts:
|
|
||||||
if os.path.exists(script_path):
|
|
||||||
# Check if file is executable
|
|
||||||
if os.access(script_path, os.X_OK):
|
if os.access(script_path, os.X_OK):
|
||||||
return script_path
|
return script_path
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Backup script {script_path} exists but is not executable"
|
f"Backup script {script_path} exists but is not executable"
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
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"""
|
"""Run backup script as the specified user"""
|
||||||
try:
|
try:
|
||||||
logger.info(f"Running backup script {script_path} (user {username})")
|
logger.info(f"Running backup script {script_path} (user {username})")
|
||||||
@@ -279,149 +432,51 @@ class BackupManager:
|
|||||||
self.errors.append(f"App {username}: {error_msg}")
|
self.errors.append(f"App {username}: {error_msg}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_backup_directories(self) -> List[str]:
|
def _send_notification(self, success: bool) -> None:
|
||||||
"""Collect backup targets according to backup-targets rules"""
|
|
||||||
backup_dirs: List[str] = []
|
|
||||||
applications = self.find_applications()
|
|
||||||
|
|
||||||
def parse_targets_file(targets_file: Path) -> List[str]:
|
|
||||||
"""Parse backup-targets file, skipping comments and empty lines."""
|
|
||||||
targets: List[str] = []
|
|
||||||
try:
|
|
||||||
for raw_line in targets_file.read_text(encoding="utf-8").splitlines():
|
|
||||||
line = raw_line.strip()
|
|
||||||
if not line or line.startswith("#"):
|
|
||||||
continue
|
|
||||||
targets.append(line)
|
|
||||||
except OSError as e:
|
|
||||||
warning_msg = f"Could not read backup targets file {targets_file}: {e}"
|
|
||||||
logger.warning(warning_msg)
|
|
||||||
self.warnings.append(warning_msg)
|
|
||||||
return targets
|
|
||||||
|
|
||||||
for app in applications:
|
|
||||||
app_dir = app.path
|
|
||||||
targets_file = app_dir / BACKUP_TARGETS_FILE
|
|
||||||
resolved_targets: List[Path] = []
|
|
||||||
|
|
||||||
if targets_file.exists():
|
|
||||||
# Read custom targets defined by the application.
|
|
||||||
for target_line in parse_targets_file(targets_file):
|
|
||||||
target_path = Path(target_line)
|
|
||||||
if not target_path.is_absolute():
|
|
||||||
target_path = (app_dir / target_path).resolve()
|
|
||||||
else:
|
|
||||||
target_path = target_path.resolve()
|
|
||||||
if target_path.exists():
|
|
||||||
resolved_targets.append(target_path)
|
|
||||||
else:
|
|
||||||
warning_msg = (
|
|
||||||
f"Backup target does not exist for {app_dir}: {target_path}"
|
|
||||||
)
|
|
||||||
logger.warning(warning_msg)
|
|
||||||
self.warnings.append(warning_msg)
|
|
||||||
else:
|
|
||||||
# Fallback to default backups directory when no list is provided.
|
|
||||||
default_target = (app_dir / BACKUP_DEFAULT_DIR).resolve()
|
|
||||||
if default_target.exists():
|
|
||||||
resolved_targets.append(default_target)
|
|
||||||
else:
|
|
||||||
warning_msg = f"Default backup path does not exist for {app_dir}: {default_target}"
|
|
||||||
logger.warning(warning_msg)
|
|
||||||
self.warnings.append(warning_msg)
|
|
||||||
|
|
||||||
for target in resolved_targets:
|
|
||||||
target_str = str(target)
|
|
||||||
if target_str not in backup_dirs:
|
|
||||||
backup_dirs.append(target_str)
|
|
||||||
|
|
||||||
return backup_dirs
|
|
||||||
|
|
||||||
def send_notification(self, success: bool) -> None:
|
|
||||||
"""Send notification to Notifiers"""
|
"""Send notification to Notifiers"""
|
||||||
|
|
||||||
|
host = self.config.host_name
|
||||||
|
|
||||||
if success and not self.errors:
|
if success and not self.errors:
|
||||||
message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
|
title = f"{host}: бекап успешно завершен"
|
||||||
|
message = f"<p><b>{host}</b>: бекап успешно завершен!</p>"
|
||||||
if self.successful_backups:
|
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:
|
else:
|
||||||
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
|
title = f"{host}: бекап завершен с ошибками ({len(self.errors)})"
|
||||||
|
message = f"<p><b>{host}</b>: бекап завершен с ошибками!</p>"
|
||||||
|
|
||||||
if self.successful_backups:
|
if self.successful_backups:
|
||||||
message += (
|
items = "".join(f"<li>{b}</li>" for b in self.successful_backups)
|
||||||
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
|
message += f"<p>✅ Успешные бекапы:</p><ul>{items}</ul>"
|
||||||
)
|
|
||||||
|
|
||||||
if self.warnings:
|
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:
|
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:
|
for notificator in self.notifiers:
|
||||||
try:
|
try:
|
||||||
notificator.send(message)
|
notificator.send(title, message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send 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
|
def initialize(
|
||||||
applications = self.find_applications()
|
config_path: Path,
|
||||||
logger.info(f"Found {len(applications)} application directories")
|
) -> tuple[ApplicationFinder, BackupManager]:
|
||||||
|
|
||||||
# Process each user's backup
|
|
||||||
for app in applications:
|
|
||||||
app_dir = str(app.path)
|
|
||||||
username = app.owner
|
|
||||||
logger.info(f"Processing backup for app: {app_dir} (user {username})")
|
|
||||||
|
|
||||||
# Find backup script
|
|
||||||
backup_script = self.find_backup_script(app_dir)
|
|
||||||
|
|
||||||
if backup_script is None:
|
|
||||||
warning_msg = (
|
|
||||||
f"No backup script found for app: {app_dir} (user {username})"
|
|
||||||
)
|
|
||||||
logger.warning(warning_msg)
|
|
||||||
self.warnings.append(warning_msg)
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.run_app_backup(backup_script, app_dir, username)
|
|
||||||
|
|
||||||
# Get backup directories
|
|
||||||
backup_dirs = self.get_backup_directories()
|
|
||||||
logger.info(f"Found backup directories: {backup_dirs}")
|
|
||||||
|
|
||||||
overall_success = True
|
|
||||||
|
|
||||||
for storage in self.storages:
|
|
||||||
backup_result = storage.backup(backup_dirs)
|
|
||||||
if not backup_result:
|
|
||||||
self.errors.append("Restic backup failed")
|
|
||||||
|
|
||||||
# Determine overall success
|
|
||||||
overall_success = overall_success and backup_result
|
|
||||||
|
|
||||||
# Send notification
|
|
||||||
self.send_notification(overall_success)
|
|
||||||
|
|
||||||
logger.info("Backup process completed")
|
|
||||||
|
|
||||||
if self.errors:
|
|
||||||
logger.error(f"Backup completed with {len(self.errors)} errors")
|
|
||||||
return False
|
|
||||||
elif self.warnings:
|
|
||||||
logger.warning(f"Backup completed with {len(self.warnings)} warnings")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.info("Backup completed successfully")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(config_path: Path) -> BackupManager:
|
|
||||||
try:
|
try:
|
||||||
with config_path.open("rb") as config_file:
|
with config_path.open("rb") as config_file:
|
||||||
raw_config = tomllib.load(config_file)
|
raw_config = tomllib.load(config_file)
|
||||||
@@ -458,17 +513,21 @@ def initialize(config_path: Path) -> BackupManager:
|
|||||||
if not notifiers:
|
if not notifiers:
|
||||||
raise ValueError("At least one notification backend must be configured")
|
raise ValueError("At least one notification backend must be configured")
|
||||||
|
|
||||||
config = Config(host_name=host_name, roots=roots)
|
config = Config(host_name=host_name)
|
||||||
|
app_finder = ApplicationFinder(roots)
|
||||||
return BackupManager(
|
backup_manager = BackupManager(
|
||||||
config=config, roots=roots, storages=storages, notifiers=notifiers
|
config=config, storages=storages, notifiers=notifiers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return app_finder, backup_manager
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
try:
|
try:
|
||||||
backup_manager = initialize(CONFIG_PATH)
|
app_finder, backup_manager = initialize(CONFIG_PATH)
|
||||||
success = backup_manager.run_backup_process()
|
applications = app_finder.find_applications()
|
||||||
|
backup_manager.warnings.extend(app_finder.warnings)
|
||||||
|
success = backup_manager.run_backup_process(applications)
|
||||||
if not success:
|
if not success:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@@ -8,9 +8,19 @@ roots = [
|
|||||||
type = "restic"
|
type = "restic"
|
||||||
restic_repository = "{{ restic_repository }}"
|
restic_repository = "{{ restic_repository }}"
|
||||||
restic_password = "{{ restic_password }}"
|
restic_password = "{{ restic_password }}"
|
||||||
aws_access_key_id = "{{ restic_s3_access_key }}"
|
|
||||||
aws_secret_access_key = "{{ restic_s3_access_secret }}"
|
[storage.yandex_cloud_s3.env]
|
||||||
aws_default_region = "{{ restic_s3_region }}"
|
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]
|
[notifier.apprise]
|
||||||
type = "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
|
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
|
# Applications
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
vakhrushev.me {
|
vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
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 {
|
reverse_proxy {
|
||||||
to homepage_app:80
|
to homepage_app:80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.vakhrushev.me {
|
||||||
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
|
reverse_proxy {
|
||||||
|
to tuwunel_app:6167
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auth.vakhrushev.me {
|
auth.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy authelia_app:9091
|
reverse_proxy authelia_app:9091
|
||||||
}
|
}
|
||||||
|
|
||||||
status.vakhrushev.me, :29999 {
|
status.vakhrushev.me, :29999 {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
@@ -43,6 +91,7 @@ status.vakhrushev.me, :29999 {
|
|||||||
|
|
||||||
git.vakhrushev.me {
|
git.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to gitea_app:3000
|
to gitea_app:3000
|
||||||
@@ -51,6 +100,7 @@ git.vakhrushev.me {
|
|||||||
|
|
||||||
outline.vakhrushev.me {
|
outline.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to outline_app:3000
|
to outline_app:3000
|
||||||
@@ -59,6 +109,7 @@ outline.vakhrushev.me {
|
|||||||
|
|
||||||
gramps.vakhrushev.me {
|
gramps.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to gramps_app:5000
|
to gramps_app:5000
|
||||||
@@ -67,6 +118,7 @@ gramps.vakhrushev.me {
|
|||||||
|
|
||||||
miniflux.vakhrushev.me {
|
miniflux.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to miniflux_app:8080
|
to miniflux_app:8080
|
||||||
@@ -75,6 +127,7 @@ miniflux.vakhrushev.me {
|
|||||||
|
|
||||||
wakapi.vakhrushev.me {
|
wakapi.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to wakapi_app:3000
|
to wakapi_app:3000
|
||||||
@@ -83,6 +136,7 @@ wakapi.vakhrushev.me {
|
|||||||
|
|
||||||
wanderer.vakhrushev.me {
|
wanderer.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to wanderer_web:3000
|
to wanderer_web:3000
|
||||||
@@ -91,6 +145,7 @@ wanderer.vakhrushev.me {
|
|||||||
|
|
||||||
memos.vakhrushev.me {
|
memos.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to memos_app:5230
|
to memos_app:5230
|
||||||
@@ -99,6 +154,7 @@ memos.vakhrushev.me {
|
|||||||
|
|
||||||
remembos.vakhrushev.me {
|
remembos.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
@@ -112,6 +168,7 @@ remembos.vakhrushev.me {
|
|||||||
|
|
||||||
calibre.vakhrushev.me {
|
calibre.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to calibre_web_app:8083
|
to calibre_web_app:8083
|
||||||
@@ -120,6 +177,7 @@ calibre.vakhrushev.me {
|
|||||||
|
|
||||||
wanderbase.vakhrushev.me {
|
wanderbase.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
@@ -133,6 +191,7 @@ wanderbase.vakhrushev.me {
|
|||||||
|
|
||||||
rssbridge.vakhrushev.me {
|
rssbridge.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
@@ -146,6 +205,7 @@ rssbridge.vakhrushev.me {
|
|||||||
|
|
||||||
dozzle.vakhrushev.me {
|
dozzle.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
|
import access_log
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
@@ -155,3 +215,21 @@ dozzle.vakhrushev.me {
|
|||||||
reverse_proxy dozzle_app:8080
|
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
|
||||||
|
}
|
||||||
|
|
||||||
+6
-5
@@ -1,9 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
{{ service_name }}:
|
caddyproxy:
|
||||||
image: caddy:2.11.2
|
image: caddy:2.11.2
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: {{ service_name }}
|
container_name: "caddyproxy"
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
@@ -11,9 +11,10 @@ services:
|
|||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
volumes:
|
volumes:
|
||||||
- {{ caddy_file_dir }}:/etc/caddy
|
- "{{ caddy_file_dir }}:/etc/caddy"
|
||||||
- {{ data_dir }}:/data
|
- "{{ data_dir }}:/data"
|
||||||
- {{ config_dir }}:/config
|
- "{{ config_dir }}:/config"
|
||||||
|
- "{{ caddy_logs_dir }}:/var/log/caddy"
|
||||||
networks:
|
networks:
|
||||||
- "web_proxy_network"
|
- "web_proxy_network"
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
dozzle_app:
|
dozzle_app:
|
||||||
image: amir20/dozzle:v10.2.1
|
image: amir20/dozzle:v10.5.0
|
||||||
container_name: dozzle_app
|
container_name: dozzle_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
gitea_app:
|
gitea_app:
|
||||||
image: gitea/gitea:1.25.5
|
image: gitea/gitea:1.26.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: gitea_app
|
container_name: gitea_app
|
||||||
ports:
|
ports:
|
||||||
- "2222:22"
|
- "2222:22"
|
||||||
volumes:
|
volumes:
|
||||||
- {{ data_dir }}:/data
|
- "{{ data_dir }}:/data"
|
||||||
- {{ backups_dir }}:/backups
|
- "{{ backups_dir }}:/backups"
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- "/etc/localtime:/etc/localtime:ro"
|
||||||
networks:
|
networks:
|
||||||
- "web_proxy_network"
|
- "web_proxy_network"
|
||||||
environment:
|
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:
|
services:
|
||||||
|
|
||||||
gramps_app: &gramps_app
|
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
|
container_name: gramps_app
|
||||||
depends_on:
|
depends_on:
|
||||||
- gramps_redis
|
- gramps_redis
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
memos_app:
|
memos_app:
|
||||||
image: neosmemo/memos:0.26.2
|
image: neosmemo/memos:0.28.0
|
||||||
container_name: memos_app
|
container_name: memos_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
netdata:
|
netdata:
|
||||||
image: netdata/netdata:v2.9.0
|
image: netdata/netdata:v2.10.3
|
||||||
container_name: netdata
|
container_name: netdata
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
cap_add:
|
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
|
# See sample https://github.com/outline/outline/blob/main/.env.sample
|
||||||
|
|
||||||
outline_app:
|
outline_app:
|
||||||
image: outlinewiki/outline:1.6.1
|
image: outlinewiki/outline:1.7.1
|
||||||
container_name: outline_app
|
container_name: outline_app
|
||||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
remembos_app:
|
remembos_app:
|
||||||
image: "{{ yc_container_registry_repository }}/remembos:v0.1.5"
|
image: "{{ yc_container_registry_repository }}/remembos:v0.2.0"
|
||||||
container_name: remembos_app
|
container_name: remembos_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||||
|
|||||||
@@ -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'
|
- name: 'Configure dozzle'
|
||||||
ansible.builtin.import_playbook: playbook-dozzle.yml
|
ansible.builtin.import_playbook: playbook-dozzle.yml
|
||||||
|
|
||||||
|
- name: 'Configure goaccess'
|
||||||
|
ansible.builtin.import_playbook: playbook-goaccess.yml
|
||||||
|
|
||||||
- name: 'Configure gitea'
|
- name: 'Configure gitea'
|
||||||
ansible.builtin.import_playbook: playbook-gitea.yml
|
ansible.builtin.import_playbook: playbook-gitea.yml
|
||||||
|
|
||||||
@@ -40,6 +43,9 @@
|
|||||||
- name: 'Configure apprise'
|
- name: 'Configure apprise'
|
||||||
ansible.builtin.import_playbook: playbook-apprise.yml
|
ansible.builtin.import_playbook: playbook-apprise.yml
|
||||||
|
|
||||||
|
- name: 'Configure tuwunel'
|
||||||
|
ansible.builtin.import_playbook: playbook-tuwunel.yml
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
- name: 'Configure homepage'
|
- name: 'Configure homepage'
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
- name: "Copy apprise config"
|
- name: "Copy apprise config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/server.cfg.j2"
|
src: "./files/{{ app_name }}/server.template.cfg"
|
||||||
dest: "{{ config_dir }}/server.cfg"
|
dest: "{{ config_dir }}/server.cfg"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
+20
-1
@@ -10,6 +10,9 @@
|
|||||||
backup_config_dir: "/etc/backup"
|
backup_config_dir: "/etc/backup"
|
||||||
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
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 }}"
|
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
|
||||||
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
|
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
|
||||||
|
|
||||||
@@ -22,6 +25,22 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
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"
|
- name: "Create backup config file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "files/backups/config.template.toml"
|
src: "files/backups/config.template.toml"
|
||||||
@@ -40,7 +59,7 @@
|
|||||||
|
|
||||||
- name: "Copy restic shell script"
|
- name: "Copy restic shell script"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "files/backups/restic-shell.sh.j2"
|
src: "files/backups/restic-shell.template.sh"
|
||||||
dest: "{{ restic_shell_script }}"
|
dest: "{{ restic_shell_script }}"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
|
|||||||
+32
-2
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "caddyproxy"
|
app_name: "caddyproxy"
|
||||||
@@ -41,9 +42,38 @@
|
|||||||
- "{{ config_dir }}"
|
- "{{ config_dir }}"
|
||||||
- "{{ caddy_file_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"
|
- name: "Copy caddy file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/Caddyfile.j2"
|
src: "./files/{{ app_name }}/Caddyfile.template"
|
||||||
dest: "{{ (caddy_file_dir, 'Caddyfile') | path_join }}"
|
dest: "{{ (caddy_file_dir, 'Caddyfile') | path_join }}"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
@@ -51,7 +81,7 @@
|
|||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||||
dest: "{{ base_dir }}/docker-compose.yml"
|
dest: "{{ base_dir }}/docker-compose.yml"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: >
|
cmd: >
|
||||||
{{ eget_bin_path }} rclone/rclone --quiet --upgrade-only --to {{ eget_install_dir }} --asset zip
|
{{ 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
|
changed_when: false
|
||||||
|
|
||||||
- name: "Install restic"
|
- name: "Install restic"
|
||||||
|
|||||||
+2
-2
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
- name: "Copy backup script"
|
- name: "Copy backup script"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "files/{{ app_name }}/backup.sh.j2"
|
src: "files/{{ app_name }}/backup.template.sh"
|
||||||
dest: "{{ base_dir }}/backup.sh"
|
dest: "{{ base_dir }}/backup.sh"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||||
dest: "{{ base_dir }}/docker-compose.yml"
|
dest: "{{ base_dir }}/docker-compose.yml"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
@@ -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_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
- vars/homepage.yml
|
- vars/homepage.yml
|
||||||
- vars/homepage.images.yml
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
- vars/homepage.yml
|
- vars/homepage.yml
|
||||||
- vars/homepage.images.yml
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: "Create user and environment"
|
- name: "Create user and environment"
|
||||||
@@ -44,5 +43,6 @@
|
|||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "present"
|
state: "present"
|
||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
|
pull: "always"
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
+2
-2
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
- name: "Copy gobackup config"
|
- name: "Copy gobackup config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/gobackup.yml.j2"
|
src: "./files/{{ app_name }}/gobackup.template.yml"
|
||||||
dest: "{{ gobackup_config }}"
|
dest: "{{ gobackup_config }}"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
- name: "Copy backup script"
|
- name: "Copy backup script"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "files/{{ app_name }}/backup.sh.j2"
|
src: "files/{{ app_name }}/backup.template.sh"
|
||||||
dest: "{{ base_dir }}/backup.sh"
|
dest: "{{ base_dir }}/backup.sh"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||||
config_dir: "{{ (base_dir, 'config') | path_join }}"
|
config_dir: "{{ (base_dir, 'config') | path_join }}"
|
||||||
config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}"
|
config_go_d_dir: "{{ (config_dir, 'go.d') | path_join }}"
|
||||||
|
config_health_d_dir: "{{ (config_dir, 'health.d') | path_join }}"
|
||||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
- "{{ data_dir }}"
|
- "{{ data_dir }}"
|
||||||
- "{{ config_dir }}"
|
- "{{ config_dir }}"
|
||||||
- "{{ config_go_d_dir }}"
|
- "{{ config_go_d_dir }}"
|
||||||
|
- "{{ config_health_d_dir }}"
|
||||||
|
|
||||||
- name: "Copy netdata config file"
|
- name: "Copy netdata config file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -75,6 +77,43 @@
|
|||||||
loop: "{{ go_d_existing_files.files }}"
|
loop: "{{ go_d_existing_files.files }}"
|
||||||
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
|
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."
|
- name: "Grab docker group id."
|
||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: |
|
cmd: |
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||||
dest: "{{ base_dir }}/docker-compose.yml"
|
dest: "{{ base_dir }}/docker-compose.yml"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
@@ -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"
|
- name: "Copy gobackup config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/gobackup.yml.j2"
|
src: "./files/{{ app_name }}/gobackup.template.yml"
|
||||||
dest: "{{ gobackup_config }}"
|
dest: "{{ gobackup_config }}"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
- name: "Copy backup script"
|
- name: "Copy backup script"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "files/{{ app_name }}/backup.sh.j2"
|
src: "files/{{ app_name }}/backup.template.sh"
|
||||||
dest: "{{ base_dir }}/backup.sh"
|
dest: "{{ base_dir }}/backup.sh"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/docker-compose.yml.j2"
|
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||||
dest: "{{ base_dir }}/docker-compose.yml"
|
dest: "{{ base_dir }}/docker-compose.yml"
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
- name: 'Set up environment variables for user "{{ owner_name }}".'
|
- name: 'Set up environment variables for user "{{ owner_name }}".'
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: env.j2
|
src: env.template
|
||||||
dest: "/home/{{ owner_name }}/.env"
|
dest: "/home/{{ owner_name }}/.env"
|
||||||
owner: "{{ owner_name }}"
|
owner: "{{ owner_name }}"
|
||||||
group: "{{ owner_group }}"
|
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 }}"
|
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
||||||
|
|
||||||
|
homepage_nginx_image: "homepage-nginx:latest"
|
||||||
|
|
||||||
# Registry images
|
# Registry images
|
||||||
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"
|
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"
|
||||||
|
|||||||
+174
-156
@@ -1,157 +1,175 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
30353464333261353263666333626634633162666239653631386263363431333734656631313930
|
30613937343031343632383733623435366535373231316163393436363636656462326262383565
|
||||||
6539363363653239656166623839636131306462633834310a646438626666653163613762313637
|
3032663665323131626263356531633934326639636231620a363635376263333438336331343366
|
||||||
33316565343539376535303135366530386333313562306161633861306237313030386366656435
|
36386337323165333861633062656433313062343764636138663533333639316336306230653732
|
||||||
3830353139613066350a666564333832393465316531353863373437646464316262396532383738
|
3331336137616263630a306135333566646434663231383138363966386661643836626561376338
|
||||||
36643034623263643038633162666265646661383461303335653935376330633033653730643235
|
33636362323937386664646630383062613535666431393634316337626564613733313861386238
|
||||||
38663938333934353131393762363762366333306439396264346565336439653239353034386138
|
39316263666662633066633836366236346431313531656339613566303962656165396662326563
|
||||||
65343832666363663731656263616263636662323038663835386437306564643265396434393832
|
65623036333932393739646162353836646562643866396263386232633933326538316637656365
|
||||||
31373231636363656463366436303433616239646331353239363966393264623866636466323839
|
38656562383861613030306635613236646235613436316635386531656666363738396461313263
|
||||||
61653037396130373763633265666663316666623961643131373564326361653038646530323964
|
30653934366537303133613962653137633131323431396266646339376339623034373963666438
|
||||||
66383738303230313533653562356535343162633231636634646237663466313031346463393965
|
66383464303431353962323032316533613138383831343036383230303931326433396333623935
|
||||||
65336164373666343932376234343133623531323163653961666432326134333865336262333336
|
37396432376637373135666236333332383262323931616432343665653836626265376632643765
|
||||||
63643038636537323039616563613861386566393364306131326463366531363063613766383965
|
33303835393863333334653664613337343063313362363136383234666335636565383237656639
|
||||||
66353237383734643564626239346662656435386166356161343031343633613331623433366465
|
34323839613765626231303230616661626530633530333165373535663139643339656438396237
|
||||||
34613237383934343636613833353933613536386637626338326537373438646139623839366263
|
36656134643636643733363336343739616532666130393863666665393138383261353730626565
|
||||||
31386137636362356339333530626565333561333634613964343133393038663033656433376165
|
38306133343463306462656534326431623238336562653433316233383861303032393437316336
|
||||||
33323633383237343433636532613965303232306262326237383233656430363030366164643561
|
32353338613639653735393239333235633565636563313933333763323339656237326162316465
|
||||||
37373930646233366439383635333133393631366265373439643635396231336263373266373338
|
66313034306263343462376632303539656533353265336366613338326439323732623438626162
|
||||||
33613432616634666531623631356162363364643437623239643536336636396132313232656233
|
39393937613836656236383030343436303632363330313734333665643365666138633034323462
|
||||||
64333032653930353430336364353335396130633634666131393166383765656362616139396265
|
66376333386237383666623434636662363338626538353933636632646236393630343739636666
|
||||||
32653563366562383930623439363539333235326661383665336564623832313839306263386232
|
37666531633839363365633863646530396432613166313035353638313463373338313139616133
|
||||||
61633734353733346266343936306364393835316635633061386661313262386630623933656436
|
62383234356665333132613664383931316238353863306538343831363233303862383737373939
|
||||||
30633662613937326162666666353339363736373139626133316233383433373665623637326634
|
37303430303766343366633536643139363366663734326162366434333165613033653666383337
|
||||||
62623165306431363931616335363339383836366363353565366136643661366638343361306438
|
62626538316463343466613065326666396266643661656164376336336532666134613663623163
|
||||||
36346265383930326461653931626537306431633766653265373266623333643437323533353233
|
64316335633839356231393130343938613334393737666663363662356466326235666561653239
|
||||||
30336437353739353861316134306361333063336233323437616630386532663934303032356238
|
39386635616165633063383032666366383861333038373636613663613461316433633562623664
|
||||||
64626335653161313533636539353665656239336237633461346535636539393161643961613464
|
65373536663230356632663133356639323838653431333836376330316162633261333934363335
|
||||||
30313366323937313265636563333261643732363161323934303463316663313033633831386536
|
34383937343063303835626435316534356239316230326566383036646237336238623036323161
|
||||||
36313733326133643663353830643936613366653766636638643737323362303537636563336339
|
62326264636130323965313866616631663039623431363139363462663435323866393437373566
|
||||||
66353930633761396666383138653339343632656232643866376337623732326635373735623362
|
37353463353731303434303435353061633531663464656336306439373238633038343237313133
|
||||||
38613831356437383865653964656637396137386463316562663464383834323962333465356562
|
34333463626261333038363438343034373335346332316430376436656331626664376664323037
|
||||||
62343864613364623639336334646462376366356138386162373763353930366663646463303736
|
32393631663434383265326231353035356333343739386132326435653438306136373237396539
|
||||||
36323165373236643139303935333266323064633230353934343538616332653265373438633564
|
33613462623562343966343933363037326234323836363636313938666534333337646139326533
|
||||||
62393332383533313564656434366435326362316666623763353563626138643732643531343238
|
61633666623936646366643336333339303633643230393465623031643963643635313264353236
|
||||||
33313031313366656363343562653763613833653536613562343465376664376562356164613936
|
65313631663430336262326463663938386630363464386230383766376363373235366438393635
|
||||||
66343763313262636532626363393562316661613130313866346165633862373136393661303936
|
65313232383334666263626662646264393565326164613364313138303638653333653963316561
|
||||||
38633562663461623133653864306139393165386265313032653231306361393830653763663062
|
34346464653637376433356335663930396432386238366132393562393162353235393438633533
|
||||||
63383430633734646134303239666532626264613332313136363238323139326237623035623964
|
62656533316431666463633530653832356263653030326366663932306662613465643638313633
|
||||||
37663435353965383535646331343761383562653735363666666335613630373865643265643336
|
62396562616463313066343832316238386234343537346436623039643132393562303130613331
|
||||||
32343664666239326337643366306531623834336463386637666564646338333433303134323564
|
38653261353132633036623138643338366534396237613333333765653436363032616235373035
|
||||||
32323866323337303262363833313838623337323232616135363963363631336637623865373437
|
35313966623531373636363638383862333935353931653861663966643531383335653739356565
|
||||||
31316539663237316162366338303763346139663435313037306338623339316637363133363965
|
31373937396234616135653765643131666530383030343064366531336135366265633232653433
|
||||||
62303439383637353333333631363234343866623763616261666239316631353434323930373066
|
65396566626232633831343734353432633462343336616135373861303836613463393736306133
|
||||||
32613334303566353830323064626537663339373130613533373739616236346337393033316638
|
64663531643630326432376235386433623365373163366663623632333531623863623663643434
|
||||||
39303462633463306634373933396233666530303465323133386435333563373936623466376334
|
36646134623665633531643732663137613862343666613139336231646564363266343935653263
|
||||||
39323131353866313263333436303831323166366165623263323730333663616665373261313232
|
66653366666635666535636637626134363633336233613732656166373063333237323465616434
|
||||||
62333361623964316236303739363430636461396264333638363835646366633633326666326363
|
39623238636235333866666536346430373735323530633133663937636366663530326465386161
|
||||||
30376339646361386337343561666230313066303263353430333436613464303965353634353831
|
39346466386133656633373438333133303566363233626238366133636333656462373065613863
|
||||||
35343866396639366539653939313939656234663665666533326630306530333863383761613161
|
65363439383163323332383931663833303234326132343462333835323664363461656566393065
|
||||||
61376433623635663636656564643135626239343663333532663366353165383463383038353232
|
38646261323336316239363465343238643132306235613031626438323838653066376561626661
|
||||||
35346437383931653166323431353766393036626136643262623263363932396437303033356131
|
34356530666665323230646436633935343861323638656638323163306236393865366630636236
|
||||||
31333066393166356665663963306366383837303036373930653462386232373563303630633636
|
62386161646131623738333664636361396239643666323837646332383538623734386531313664
|
||||||
30363466353437306232383663363161396665393739306536643236663761323366613130656330
|
65313632343365393130643137353735666565663030383231616231313237323866386336316361
|
||||||
30366661353939303030363833636238346134323738366138396336393463373563333632653262
|
66656165643261653464316639613635323531306362353164373531326461666437303434346233
|
||||||
39633333353466313339386362656538336338373565323536646333616434626137323335353563
|
31383864346233313633353065343236633636386138323761666662373564623234613965323131
|
||||||
31636564636537613033656236366362316134353733623961366333313165626637616536646361
|
36313861316563333262306434663265313237626631396561303236343330633738356666633663
|
||||||
39623561656234356663313032326231313231363963643534666136343433663730333333633434
|
61313663336237653361383963333764336137396666613634313036373564353564643334623363
|
||||||
35303363366133323330376163363662653534633431333035363837633831343861373633393930
|
62353531306532323664376363383938646536393339346666656339393230613362666337663861
|
||||||
30626635353166303962313639326239623532396435623230353166323165656261643030356131
|
37633633653463343430666634643863383438633933343839663865616136363538643061343437
|
||||||
65356632613834653063656138353261613231663465336366623564316165623932663530376631
|
34613037353835613866303230303162396531626663616164343263633261363335313936666339
|
||||||
31616137386433336135323636613665356162353139306263633833336566346339666135626433
|
63383533616530356262363838636466333038656339316364626263383731313464313734613630
|
||||||
30336361333736333334383162343336663631663333353739633036346633393763366636353161
|
62646266666136616632636161363631623362346230643134663664396565323932343462383661
|
||||||
36373566613530366632666333646563336136663034663337313032643165643966303964613234
|
38303663653262333236613833396237663834333139316666343065396137306562613265343863
|
||||||
38653332343037653464646635666634376138616636326531353865346663396534363338393465
|
65323065663862636230636664623132306231366462346432343030376236346465663831623537
|
||||||
37393962343233633739313961663531616565363337346535303962663533643866363333353430
|
63633231333165613731626137656539366131633364623661616136616434306563656139346137
|
||||||
35323864383338313837643266653937316432313834653038636561653238303164626362326432
|
63313032343161623235306230633361666163623061333738383135636664623438323238663631
|
||||||
61626638636539303135646432643539306566316464383935333535343163393832316561323764
|
37613964643931323432353431306564393639386437666539376238643065343738313265373661
|
||||||
38373239376363633237346664626137363562303134333563346434396335383235383032653637
|
61303764646463326632653335323432646436353765633862623838386337623464333839643833
|
||||||
66356138396436623734316139373964373364633937373431386432333263326563326234623936
|
39383961666234363638323735636231623962666461373435633631323530643237656464396465
|
||||||
62313830613436356337623865373432356165313930373362643037386239303937666637636332
|
66623431393461613634373237646636333965396435663563363161626666356638366462373261
|
||||||
36666239343336316538623938376538383038386662396662626137633138663766396432353061
|
37633238323135666136623663653665303832656437663536383236313334313461353032663933
|
||||||
66656665656535303535376161303562643035383961343832373137636335363534393861316332
|
63643164363664663939613635373362376162336262653332663936313737396130366330656532
|
||||||
63353831616439376663616363363763323838366363396637313931616337633832643061616662
|
31653463383132643262613839613962663836376463343661393736633633396164643264653431
|
||||||
31663738396162316538343763353330653739303963616530323064373331366433326465326532
|
30663732303236653165386537653432656266363239373030333630353661666636303730373937
|
||||||
34303437346463366362346532376263356134633838373630323831643432663265313965633964
|
65363237366333376133306437376534636133356238326461333762326563386265363636323831
|
||||||
63386361303666376162653938346236636434666564623638646161656661393462613237363935
|
39343665386262336265383865343563343832623766656534306661326462333561373835366631
|
||||||
66346661353735646261333339313638653061636130623963346235376234346138613762643566
|
39636361623831623533353962633363393531313530363833613962616331653565633733303964
|
||||||
30323831613461383766643836666633636364323139333261303632613730663730336637646233
|
32393433303938323566646264323761633035653231353761643261663839313665663434643834
|
||||||
37373733373930333735306532663930396331343037383032636461653463353862613362303332
|
65356432393431336235306437643861653437643362363839623634333835376636623664616139
|
||||||
66663361373030363031396663616638396465313438396230633835633335636664356565316664
|
66376562633232636431626436653161333137633466313433663433383230636337653535643430
|
||||||
61356632303035366638613762306636333439373534356239626439626465613837336539636532
|
61613032656135323765613837626266313632353661346636643866613138303930346563623738
|
||||||
61393564613962303938393435306238383761396339656533653265633461666435353133333164
|
35613831623565353432336338373465303437623234313736353661353430656661366365373230
|
||||||
33316630663131323634343637643435326237666134326534666563623734633634366636623731
|
33646134356661616164303865623464306339653439613365626261323237623135346537393535
|
||||||
33666536396138393137333761633139323735303039313837366635623037333037663231353338
|
62393465343134626333333462316331656134383362383031353863316632393061333933336362
|
||||||
61633962643830663038343530643330356137663665663537336564353730323565626135313662
|
36326662363833303436663166383365346433323866346462663261333330656666663162383564
|
||||||
38646430306638663431373037323061313438666433313264633732303664323634303236356364
|
35336438643064313833393638323864343237616163383033313966303262326135323335353931
|
||||||
32326131656438656661616638393462333532313737396235643830663832643435373362373533
|
66333938393264323533353231303935346661653835386262306133393065356535643835663665
|
||||||
34313638626264363561653232323263643135323666626539636435303131336533623137643735
|
38363930356530366135313734306464623739376438613430373634396339393864396264303135
|
||||||
38333734636133336431393634316566626266356165663639333461306463363637613366643433
|
61356333636236326566386264353930626564636438616265353939383733663837313233356363
|
||||||
61313031623933633731353533346264363336326534366231366239373137666430376266383836
|
63643835393437336366313030303864306536666638623430356263336234646462383666316431
|
||||||
33396365343161616238396133326433343535333032643533616439643236323561353236356536
|
36313464346266646438383762313138376338323537386635636561656662306533316362396162
|
||||||
39663534666533393638376639653161376562636333616437623937383035313032636632396232
|
38326165633532623933376165643861323735353831363264376162316561613038633961333337
|
||||||
62373961616262313936623663613837316539616330336339363436373465363662313836656662
|
30646461636332623466643033633764333330353832616365376633643263336131313733653139
|
||||||
31333231653836306362643234383631666431663431666333366563393830343837393339313533
|
39646239366261366465333962643565636430393464613866613038333636393362383636343534
|
||||||
65343034373265343737326232316162386434326661336530373639633838373766323437393937
|
61323830616234633364346131336630393965373730343464366166376232346464636263323639
|
||||||
38313962356364306165393730356339336639303731393931323130386462663139353639626338
|
63336464623733363139366665336131653163613833383261376138373032666663356637383832
|
||||||
30373162356131313737326164393164373837366632643064326635323934396239363033303938
|
32313130633363346435383638616236633761616166663339316437353938636636613530383836
|
||||||
62376539313164333365336331393735346138376362333962396364323063636539346663613631
|
64623661366130656439306266343435396334383564353466663339383862313733313931383463
|
||||||
66353062643261666538633430653036636461626161356265303037353665326461333335373562
|
64323237656361383262343735366562623965356636343963363966616333313333646233373464
|
||||||
62363237356166363139616532333530373230623865383862383362376534363162336163633133
|
33383939386262663730316333616663636161356463396362643237356532386162363131626461
|
||||||
37636564353836643963613333373837343431346538303738346462663739623932363831353534
|
37323965313063623463356133626531393339336535303562343530316663613639646531323136
|
||||||
64613934323136623761633830313335306666643765343037363665303733613639343466393963
|
30353732646237623264653963373863363965326338666264306562373932393333633639396131
|
||||||
62386361326362386535376364303237313564316661326337336434616134313464313565333136
|
34303764396330326165636264313532393961303038623031336631653831323337306261333630
|
||||||
64663436643939623438366437643034666362386233666238363434613862386333323134363566
|
37333964376533636132303335653935343932373330373632626235356437636165623436383036
|
||||||
61646661386139393731666632343066366139333638333366336137353833383931383835363934
|
38313565373561393834316532333930356135623439373161643063643738353031353565396330
|
||||||
35666563303333303932386264316134383035613035663466656465336439346137656336656434
|
37656162346433326638353439613666336534336562623633643230636134383931653538616665
|
||||||
34363438663962393434633761633336623966636465623665356433343236383862636435303038
|
32393432383265613237323138386361353934373965306462393666616532653563626232643035
|
||||||
33313661336364346562666234396462646231666162306566636461613462643034653861663439
|
61643732376434633537633663633130313437656166333239633533393334373163333566343430
|
||||||
39633361643733643533346561363232336465383633396461376239623837366135336238346332
|
38633165353637306237316436663235633162353132646562353638333038663636323465633632
|
||||||
33373762613230353537656566353466623864636161316361396338613566336132303762306538
|
34643037623634643534663366633133363030323966313065353333633636646636306565333238
|
||||||
36633663643136303262373365363063396163613464333662653430396636623335313061626561
|
39336662626138306464613461343762316533656433626165323764616535623539336439396663
|
||||||
38636165346532373732333463373436323239386432326634613762303665343636343366656162
|
63393365626235613063613934306132333162646237316364306637346136623061363236383765
|
||||||
39613035626133306139663831313236353739313930666339333133663839313932336433353531
|
37353138363337346530626563366136333635663863313038643537366237633362343136396664
|
||||||
63373966633635336632363137326533363864303466366664656532623362663265613936663636
|
61623237353433333238633163636565386134356565303763336238636366316330666339383365
|
||||||
32336265393330616437646664333464623635353662616133623264393935303530356330633834
|
30323235356633656362353738393234616435663333613364316539636430623262643162313337
|
||||||
64626161646366306134303634356138663634396231353064343661373662646132653563656162
|
36323466303832336530336566343731306362333862663537613339663562623739343636613162
|
||||||
36343864396263343561333137383238393063333634376634656231346238383862313264303263
|
36343563373665376565366266343461643562636630623166626165636337613931653338633862
|
||||||
66343766353133316337356265343361363931373362303462663935323735393330623632623237
|
65393138353661656265666335343263333063653430326532663839383433643966363639643636
|
||||||
34636565646461666431313362613862303530343537306563396236663435623164326134326530
|
61376365363538636235666235623638376334363265626136313536353637386564303936636263
|
||||||
61356566313733363035346530613934376236383136633363316263393761383532393133323939
|
32306239306339656238393864666135613663366332666135663461353366313833376430376263
|
||||||
38653731643330666261356236623039633262343031393330356565326261336230303132656332
|
62613163303964333735396338373737653837666435656130376435376434356462383264636561
|
||||||
37353464336365633633643063353237626535326537653131633535306361353165313232366664
|
34353563316132336663316166663832383939333634316562383634383838336531313731613666
|
||||||
65613966356634633134633563343036323539633036363437626466363865626333376134613731
|
62643231636266353935343539366465376139643834306261623738313432306133653461383738
|
||||||
31363434373534613239333031333564653733363732343835346435633934356236356166393538
|
39396332373364353833626661333634346131396337636235653431616336393666373231383030
|
||||||
38613432303032333830633161353938383632313137633437313131653664323934613432356635
|
32306466613136346265653038636537646330643337663863383562323638616661333037323232
|
||||||
32656661303163646335633137363734323363653330623137613438623831366135393261343833
|
35613138363330353533643064613366343339343032373737306364353135353334336666663732
|
||||||
61383839623733346434653739626365376266353430363131636665636639333037646262346465
|
36343963613636376561666266623537316432666161326331383761323437383738373762643937
|
||||||
65393934373564386335343865656161306635653833373430663064616264653135316530303338
|
30623737643239326261343939663065643265653363633661376265626637643336613635393335
|
||||||
38366434323961396638663534396530636639613338643463326432646366646163316363383832
|
65373565333936333431656331633039323135336236656337343532643939386338663239393065
|
||||||
31373331366565376439643131343631393663666434363838316435653132393034643536353332
|
65666536333732646235633762633032393463663334616165333834653938346230316236353839
|
||||||
66343530363564346335623464653862643632356638656535353666353336323033356336636134
|
34396362386265646261373561636230363962663433303535373035346334353932643365383763
|
||||||
39316165633562303239623863613633313238323862393636336464633934316266313235343736
|
32333239613961346466356562376663613062373162666264633636323833323263333765616563
|
||||||
38353435663166643566653236623039353465303431383132306135323239346135323831336363
|
39646530343962353362363634336336323463623137646531373362353832343335366461646535
|
||||||
65396136666433393334393731373437383631313139666139663239303330363031356330373035
|
38653735316536396438613866326438363036653833366636626130323437623366373833366165
|
||||||
34366264396332643163626237636132623532326636396461356566363835363163643161396130
|
30303636666263323062343931306435363961643838636163366433376436303231316338613034
|
||||||
30663637346464646333623833393534613464336432336239386238393630376435343535356464
|
37393631363632383461373566306365306631396335633432383939336332626237653462393136
|
||||||
36393539653632393430663035613539626165396463323739353263636364346662653535633261
|
34316636643464363634366535333463326533333564633163363062666463343731396231656234
|
||||||
63663461623435386231653232313539356131346534636137383562313733663031653730353533
|
39346333303465363037313063373366373439306333636465636366666437326362626264653033
|
||||||
37393233363635643339373530376130326333643764356634643161333135663436663437373136
|
64613062343538303931646630373565663530336133633032366331626536353237336235633636
|
||||||
38393163343166666237663062313236373938646263356530363136383666636536616436626139
|
63366639366439386530303966323563323862383865356630313636333333393464653762626634
|
||||||
66633038303961336537613030323639383666333930623161633061643131313235333235303561
|
65366231613661313233626239303035323666346236636362393036353839333636343434646266
|
||||||
64633631333964336164363963346163303730306334353834343061353661633436363536313839
|
65653039353966616361363335346565383863616161316134383365616636333732653233383261
|
||||||
30366535376537313065303839323939396465623734656233396535316465333436323937323232
|
65616664343830353861616666616237313532363334653430313437313535666436383338396363
|
||||||
36656662316232306330393435333836363234666161656434653033643236303864356137393834
|
33313436363061306431366332373936633034393733646137636338336431333033343532613531
|
||||||
38643234663365663037353663616439383430616665643061363531343765363034386432626564
|
32613839393232646565663931303530376432376337613762346230646366613935383234313666
|
||||||
62323630613337663562616664613138623262326134343430623661656166653135396562323364
|
61643339353933336434666466623133336637343534303737366162316561366632333335663233
|
||||||
38616165316464376563636535643839346665613465316662363566313337333666383336303064
|
34643036326630306632353438643666623939393033646238353261386231626634303266303530
|
||||||
37633130323464393831323962356633663430343761393932323266353864363062653132323639
|
64643436653234616332623835333165626135613465346162393335353133356233666536313632
|
||||||
33656337666236303764653834326230613165636437306337393132626339663161623536376138
|
65616135666533343839666132623639343565303436623162383738353633613864356535646365
|
||||||
32343063313864643164613739653038663430633466356331313638343766393166306138336239
|
32373337393936393830666365383462333437373539666633386361373135333163393334303235
|
||||||
39303938303861343331626231303333343931306136373533613466616336366261313866656631
|
33663631386566356366666132616265373533373561616564343538303432346562356234336663
|
||||||
37336566623933656433646465306237383334363264333532386537343336363933636133306263
|
32623866396434326264636539323132613239343938353739376539383139313833376563623434
|
||||||
37333033633461656236623433356438643936343861363833306333386636633463356365643730
|
62636334326234313230666662396561393130396137306437393334323561356435343866386636
|
||||||
61303931326162353638363035666333333661393262306234366165383530663231656334623133
|
32656337656439653830653365313031326562643437376538316561653963643232353434313538
|
||||||
30363436616266313062636232313435376462633734393130656239396462613039623236396330
|
64373638616133666463393462643465306565646136643862363162643638343565316139626539
|
||||||
62666336663661623239666530656664346461363436303930306338303031623461646137366432
|
64643939383936313035323936656438313039376635383733633032613165343130663930323166
|
||||||
62643332353135663935
|
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_port: 8000
|
||||||
apprise_external_url: "http://127.0.0.1:{{ apprise_external_port }}"
|
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