148 lines
4.4 KiB
Python
148 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
||
"""Invoke tasks — замена Taskfile.yml"""
|
||
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
from invoke.context import Context
|
||
from invoke.exceptions import Exit
|
||
from invoke.tasks import task
|
||
|
||
HOSTS_FILE = "production.yml"
|
||
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
||
|
||
|
||
def _yq(query: str) -> str:
|
||
result = subprocess.run(
|
||
["yq", query, HOSTS_FILE], capture_output=True, text=True, check=True
|
||
)
|
||
return result.stdout.strip()
|
||
|
||
|
||
def _remote_user() -> str:
|
||
return _yq(".ungrouped.hosts.server.ansible_user")
|
||
|
||
|
||
def _remote_host() -> str:
|
||
return _yq(".ungrouped.hosts.server.ansible_host")
|
||
|
||
|
||
def _rest_args() -> list[str]:
|
||
"""Возвращает аргументы после '--' из sys.argv"""
|
||
try:
|
||
return sys.argv[sys.argv.index("--") + 1 :]
|
||
except ValueError:
|
||
return []
|
||
|
||
|
||
def _resolve_playbook(name: str) -> str:
|
||
candidates = [name, f"{name}.yml", f"playbook-{name}.yml"]
|
||
for candidate in candidates:
|
||
if os.path.isfile(candidate):
|
||
return candidate
|
||
raise Exit(
|
||
f"Плейбук для '{name}' не найден. Проверял: {', '.join(candidates)}", code=1
|
||
)
|
||
|
||
|
||
@task
|
||
def install_roles(ctx: Context) -> None:
|
||
"""Установить ansible-galaxy roles"""
|
||
ctx.run("uv run ansible-galaxy role install --role-file requirements.yml --force")
|
||
|
||
|
||
@task
|
||
def pl(ctx: Context) -> None:
|
||
"""Запустить плейбуки по имени: inv pl -- gitea miniflux"""
|
||
names = _rest_args()
|
||
if not names:
|
||
raise Exit("Укажи хотя бы один плейбук: inv pl -- <name> [name ...]", code=1)
|
||
playbooks = [_resolve_playbook(name) for name in names]
|
||
ctx.run(
|
||
f"uv run ansible-playbook -i production.yml --diff {' '.join(playbooks)}",
|
||
pty=True,
|
||
)
|
||
|
||
|
||
@task
|
||
def ssh(ctx: Context) -> None:
|
||
"""SSH на удалённый сервер"""
|
||
subprocess.run(f"ssh {_remote_user()}@{_remote_host()}", shell=True)
|
||
|
||
|
||
@task
|
||
def zj(ctx: Context) -> None:
|
||
"""Запуск zellij на удаленном сервере"""
|
||
subprocess.run(
|
||
f"ssh {_remote_user()}@{_remote_host()} -t zellij attach --create main",
|
||
shell=True,
|
||
)
|
||
|
||
|
||
@task
|
||
def btop(ctx: Context) -> None:
|
||
"""Запустить btop на удалённом сервере"""
|
||
subprocess.run(f"ssh {_remote_user()}@{_remote_host()} -t btop", shell=True)
|
||
|
||
|
||
@task
|
||
def encrypt(ctx: Context, file: str) -> None:
|
||
"""Зашифровать файлы через ansible-vault"""
|
||
ctx.run(f"uv run ansible-vault encrypt {file}")
|
||
|
||
|
||
@task
|
||
def decrypt(ctx: Context, file: str) -> None:
|
||
"""Расшифровать файлы через ansible-vault"""
|
||
ctx.run(f"uv run ansible-vault decrypt {file}")
|
||
|
||
|
||
@task
|
||
def authelia_cli(ctx: Context, args: str = "") -> None:
|
||
"""Запустить authelia CLI в docker"""
|
||
ctx.run(f"{AUTHELIA_DOCKER} {args}")
|
||
|
||
|
||
@task
|
||
def authelia_validate_config(ctx: Context) -> None:
|
||
"""Отрендерить конфиг authelia из шаблона и проверить его"""
|
||
dest = "temp/configuration.yml"
|
||
try:
|
||
ctx.run(
|
||
"uv run ansible localhost"
|
||
" --module-name template"
|
||
f' --args "src=files/authelia/configuration.template.yml dest={dest}"'
|
||
" --extra-vars @vars/secrets.yml"
|
||
" --extra-vars @files/authelia/secrets.yml"
|
||
)
|
||
ctx.run(f"{AUTHELIA_DOCKER} validate-config --config /data/{dest}")
|
||
finally:
|
||
ctx.run(f"rm -f {dest}", warn=True)
|
||
|
||
|
||
@task
|
||
def authelia_gen_random_string(ctx: Context, length: int = 10) -> None:
|
||
"""Сгенерировать случайную alphanumeric-строку"""
|
||
ctx.run(f"{AUTHELIA_DOCKER} crypto rand --length {length} --charset alphanumeric")
|
||
|
||
|
||
@task
|
||
def authelia_gen_secret_and_hash(ctx: Context, length: int = 72) -> None:
|
||
"""Сгенерировать случайный секрет и его pbkdf2-sha512 хэш"""
|
||
ctx.run(
|
||
f"{AUTHELIA_DOCKER} crypto hash generate pbkdf2"
|
||
f" --variant sha512 --random --random.length {length} --random.charset rfc3986"
|
||
)
|
||
|
||
|
||
@task
|
||
def format_py_files(ctx: Context) -> None:
|
||
"""Отформатировать Python-файлы через Black в docker"""
|
||
uid = os.getuid()
|
||
gid = os.getgid()
|
||
ctx.run(
|
||
f"docker run --rm -u {uid}:{gid} -v $PWD:/app -w /app"
|
||
" pyfound/black:latest_release black ."
|
||
)
|