Compare commits
2 Commits
fe024b3b12
...
10e1e8187b
| Author | SHA1 | Date | |
|---|---|---|---|
|
10e1e8187b
|
|||
|
1ce168655d
|
@@ -8,6 +8,146 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Шаг 6 — `vars/vars.yml` загружается во всех плейбуках (2026-05-22, выполнено)
|
||||||
|
|
||||||
|
Сегодняшний коммит `8378f0e` («Migration: expose some public vars»)
|
||||||
|
вынес общие переменные (`application_dir`, `host_name`, `primary_user`,
|
||||||
|
`primary_user_uid`, `primary_user_gid`, `bin_prefix`,
|
||||||
|
`apprise_external_port`, `apprise_external_url`, `caddy_logs_dir`) из
|
||||||
|
vault в `vars/vars.yml`. Но большая часть плейбуков загружала только
|
||||||
|
`vars/secrets.yml` — на текущем сервере они работали лишь потому, что
|
||||||
|
inventory дублирует `application_dir` как override. На чистом
|
||||||
|
Timeweb-инвентаре без override они бы упали с undefined.
|
||||||
|
|
||||||
|
Прошёлся по всем плейбукам, добавил `- vars/vars.yml` сразу после
|
||||||
|
`- vars/secrets.yml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
playbook-authelia.yml playbook-netdata.yml
|
||||||
|
playbook-calibre.yml playbook-outline.yml
|
||||||
|
playbook-docker.yml playbook-remembos.yml
|
||||||
|
playbook-dozzle.yml playbook-rssbridge.yml
|
||||||
|
playbook-eget.yml playbook-transcriber.yml
|
||||||
|
playbook-gitea.yml playbook-transcriber-registry.yml
|
||||||
|
playbook-gramps.yml playbook-tuwunel.yml
|
||||||
|
playbook-homepage.yml playbook-ufw.yml
|
||||||
|
playbook-homepage-registry.yml playbook-upgrade.yml
|
||||||
|
playbook-memos.yml playbook-wakapi.yml
|
||||||
|
playbook-miniflux.yml playbook-wanderer.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
(21 файл — все «обычные» плейбуки, которые ещё не подключали vars.yml.)
|
||||||
|
|
||||||
|
Aggregator'ы `playbook-all-applications.yml` и `playbook-all-setup.yml`
|
||||||
|
не трогал — у них нет собственных `vars_files`, они используют
|
||||||
|
`import_playbook`, каждый импортируемый плейбук уже сам подключает
|
||||||
|
`vars.yml`.
|
||||||
|
|
||||||
|
`yamllint` чист. Идемпотентность проверить отдельным прогоном.
|
||||||
|
|
||||||
|
Проверить прогоном `inv pl -- all-applications` (или хотя бы
|
||||||
|
`inv pl -- gitea outline miniflux`) на текущем сервере — diff
|
||||||
|
ожидается пустой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 5 — переезд default application_dir на /srv (2026-05-22, выполнено)
|
||||||
|
|
||||||
|
`/mnt` по FHS — место для точек монтирования внешних дисков; на
|
||||||
|
системном диске Timeweb (фаза 1) это семантически неверно. Поменяли
|
||||||
|
дефолт на `/srv/applications` (FHS: «data for services provided by
|
||||||
|
this system»), для текущего YC-сервера сделали override в инвентаре.
|
||||||
|
|
||||||
|
Изменения:
|
||||||
|
|
||||||
|
- `vars/vars.yml` — `application_dir: "/srv/applications"`
|
||||||
|
(комментарий обновлён).
|
||||||
|
- `production.yml` — у хоста `server` добавлен override
|
||||||
|
`application_dir: "/mnt/applications"`.
|
||||||
|
- `playbook-system.yml` — добавлен `vars/vars.yml` в `vars_files`,
|
||||||
|
захардкоженный `/mnt/applications` в задачах
|
||||||
|
`Create directory for mount` и `Mount external storages` заменён
|
||||||
|
на `{{ application_dir }}`.
|
||||||
|
- `playbook-remove-user-and-app.yml` — то же самое (`vars/vars.yml`
|
||||||
|
в `vars_files` + `{{ (application_dir, user_name) | path_join }}`).
|
||||||
|
- `tasks.py` — новый helper `_application_dir()` читает значение
|
||||||
|
сначала из inventory (override), затем из `vars/vars.yml`. `login_as_app`
|
||||||
|
больше не содержит `/mnt/applications`.
|
||||||
|
|
||||||
|
Что остаётся хардкодом — только `/mnt/applications` в `production.yml`
|
||||||
|
как override, и это правильно.
|
||||||
|
|
||||||
|
На Timeweb-инвентаре (когда появится) можно либо не задавать
|
||||||
|
`application_dir` вовсе (применится дефолт `/srv/applications`), либо
|
||||||
|
задать явно — для читаемости.
|
||||||
|
|
||||||
|
Проверить прогоном `inv pl -- system` на текущем сервере (Yandex
|
||||||
|
Cloud) — ничего не должно поменяться, потому что inventory override
|
||||||
|
возвращает `/mnt/applications` и mount всё ещё включён. Diff ожидается
|
||||||
|
пустой.
|
||||||
|
|
||||||
|
### Восстановление restic-снапшотов после смены путей
|
||||||
|
|
||||||
|
Старые снапшоты записаны с путями `/mnt/applications/<app>`. На
|
||||||
|
Timeweb данные должны лежать в `/srv/applications/<app>`. У restic
|
||||||
|
нет встроенного «remap path» при restore, поэтому делается в два
|
||||||
|
шага: восстановить во временный каталог, затем `rsync` на новое
|
||||||
|
место с сохранением uid/gid (приложения уже созданы playbook'ом с
|
||||||
|
теми же uid/gid, см. шаг про подготовку target).
|
||||||
|
|
||||||
|
Пример — восстановить gitea на Timeweb-машине:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo /usr/local/sbin/restic-shell.sh
|
||||||
|
|
||||||
|
# Распакуем нужную поддиректорию во временный каталог
|
||||||
|
restic restore latest \
|
||||||
|
--target /tmp/restic-restore \
|
||||||
|
--include /mnt/applications/gitea
|
||||||
|
|
||||||
|
# Перенесём данные на новый путь, сохранив владельца/группу/ACL/xattr
|
||||||
|
sudo rsync -aAX --info=progress2 \
|
||||||
|
/tmp/restic-restore/mnt/applications/gitea/ \
|
||||||
|
/srv/applications/gitea/
|
||||||
|
|
||||||
|
sudo rm -rf /tmp/restic-restore
|
||||||
|
```
|
||||||
|
|
||||||
|
Несколько приложений за один проход:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic restore latest \
|
||||||
|
--target /tmp/restic-restore \
|
||||||
|
--include /mnt/applications/gitea \
|
||||||
|
--include /mnt/applications/outline \
|
||||||
|
--include /mnt/applications/miniflux
|
||||||
|
|
||||||
|
for app in gitea outline miniflux; do
|
||||||
|
sudo rsync -aAX --info=progress2 \
|
||||||
|
"/tmp/restic-restore/mnt/applications/$app/" \
|
||||||
|
"/srv/applications/$app/"
|
||||||
|
done
|
||||||
|
sudo rm -rf /tmp/restic-restore
|
||||||
|
```
|
||||||
|
|
||||||
|
Альтернатива через `restic mount` (если не хочется промежуточной
|
||||||
|
копии — данные мапятся как FUSE-FS):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /mnt/restic-snapshots
|
||||||
|
restic mount /mnt/restic-snapshots &
|
||||||
|
sudo rsync -aAX \
|
||||||
|
/mnt/restic-snapshots/snapshots/latest/mnt/applications/gitea/ \
|
||||||
|
/srv/applications/gitea/
|
||||||
|
sudo fusermount -u /mnt/restic-snapshots
|
||||||
|
```
|
||||||
|
|
||||||
|
После переезда новые снапшоты будут записываться уже с путями
|
||||||
|
`/srv/applications/<app>` — никаких трюков для текущих бэкапов не
|
||||||
|
нужно.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Шаг 4 — условное монтирование внешнего диска (2026-05-22, выполнено)
|
## Шаг 4 — условное монтирование внешнего диска (2026-05-22, выполнено)
|
||||||
|
|
||||||
Задача `Mount external storages` в `playbook-system.yml` теперь
|
Задача `Mount external storages` в `playbook-system.yml` теперь
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
- files/authelia/secrets.yml
|
- files/authelia/secrets.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "calibre"
|
app_name: "calibre"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
# - name: "Install python docker lib from pip"
|
# - name: "Install python docker lib from pip"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "dozzle"
|
app_name: "dozzle"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
# See: https://github.com/zyedidia/eget/releases
|
# See: https://github.com/zyedidia/eget/releases
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "gitea"
|
app_name: "gitea"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "gramps"
|
app_name: "gramps"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
- vars/homepage.yml
|
- vars/homepage.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
- vars/homepage.yml
|
- vars/homepage.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "memos"
|
app_name: "memos"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "miniflux"
|
app_name: "miniflux"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "netdata"
|
app_name: "netdata"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "outline"
|
app_name: "outline"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "remembos"
|
app_name: "remembos"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
user_name: "<put-name-here>"
|
user_name: "<put-name-here>"
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
- name: "Remove application dir"
|
- name: "Remove application dir"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "/mnt/applications/{{ user_name }}"
|
path: "{{ (application_dir, user_name) | path_join }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: "Remove home dir"
|
- name: "Remove home dir"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "rssbridge"
|
app_name: "rssbridge"
|
||||||
|
|||||||
+4
-3
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
apt_packages:
|
apt_packages:
|
||||||
@@ -40,9 +41,9 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: 'Create directory for mount'
|
- name: 'Create directory for applications'
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: '/mnt/applications'
|
path: '{{ application_dir }}'
|
||||||
state: 'directory'
|
state: 'directory'
|
||||||
mode: '0755'
|
mode: '0755'
|
||||||
tags:
|
tags:
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
|
|
||||||
- name: 'Mount external storages'
|
- name: 'Mount external storages'
|
||||||
ansible.posix.mount:
|
ansible.posix.mount:
|
||||||
path: '/mnt/applications'
|
path: '{{ application_dir }}'
|
||||||
src: 'UUID=3942bffd-8328-4536-8e88-07926fb17d17'
|
src: 'UUID=3942bffd-8328-4536-8e88-07926fb17d17'
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
state: mounted
|
state: mounted
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
- vars/transcriber.yml
|
- vars/transcriber.yml
|
||||||
- vars/transcriber.images.yml
|
- vars/transcriber.images.yml
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
- vars/transcriber.yml
|
- vars/transcriber.yml
|
||||||
- vars/transcriber.images.yml
|
- vars/transcriber.images.yml
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "tuwunel"
|
app_name: "tuwunel"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: "Ensure UFW is installed"
|
- name: "Ensure UFW is installed"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Perform an upgrade of packages
|
- name: Perform an upgrade of packages
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "wakapi"
|
app_name: "wakapi"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
- vars/secrets.yml
|
||||||
|
- vars/vars.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "wanderer"
|
app_name: "wanderer"
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ ungrouped:
|
|||||||
ansible_host: "158.160.46.255"
|
ansible_host: "158.160.46.255"
|
||||||
ansible_user: "major"
|
ansible_user: "major"
|
||||||
ansible_become: true
|
ansible_become: true
|
||||||
|
application_dir: "/mnt/applications"
|
||||||
mount_external_storage: true
|
mount_external_storage: true
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from invoke.exceptions import Exit
|
|||||||
from invoke.tasks import task
|
from invoke.tasks import task
|
||||||
|
|
||||||
HOSTS_FILE = "production.yml"
|
HOSTS_FILE = "production.yml"
|
||||||
|
VARS_FILE = "vars/vars.yml"
|
||||||
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +29,25 @@ def _remote_host() -> str:
|
|||||||
return _yq(".ungrouped.hosts.server.ansible_host")
|
return _yq(".ungrouped.hosts.server.ansible_host")
|
||||||
|
|
||||||
|
|
||||||
|
def _application_dir() -> str:
|
||||||
|
"""Чтение application_dir: сначала из inventory (override), затем из vars/vars.yml."""
|
||||||
|
inv_value = _yq('.ungrouped.hosts.server.application_dir // ""')
|
||||||
|
if inv_value:
|
||||||
|
return inv_value
|
||||||
|
result = subprocess.run(
|
||||||
|
["yq", ".application_dir", VARS_FILE],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
value = result.stdout.strip()
|
||||||
|
if not value or value == "null":
|
||||||
|
raise Exit(
|
||||||
|
f"application_dir не определён ни в inventory, ни в {VARS_FILE}", code=1
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _rest_args() -> list[str]:
|
def _rest_args() -> list[str]:
|
||||||
"""Возвращает аргументы после '--' из sys.argv"""
|
"""Возвращает аргументы после '--' из sys.argv"""
|
||||||
try:
|
try:
|
||||||
@@ -85,8 +105,9 @@ def login_as_app(ctx: Context, app: str) -> None:
|
|||||||
"""SSH и переключиться на пользователя приложения: inv login gitea"""
|
"""SSH и переключиться на пользователя приложения: inv login gitea"""
|
||||||
# sudo -i: login shell, -u: от имени пользователя
|
# sudo -i: login shell, -u: от имени пользователя
|
||||||
# bash -i: интерактивный режим (job control), -l: login (читает профиль)
|
# bash -i: интерактивный режим (job control), -l: login (читает профиль)
|
||||||
|
app_dir = f"{_application_dir()}/{app}"
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
f"""ssh {_remote_user()}@{_remote_host()} -t 'sudo -iu {app} bash -c "cd /mnt/applications/{app} && exec bash -il"'""",
|
f"""ssh {_remote_user()}@{_remote_host()} -t 'sudo -iu {app} bash -c "cd {app_dir} && exec bash -il"'""",
|
||||||
shell=True,
|
shell=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -7,8 +7,9 @@ primary_user_gid: 1001
|
|||||||
# Directory for all user binaries and scripts
|
# Directory for all user binaries and scripts
|
||||||
bin_prefix: "/usr/local/bin"
|
bin_prefix: "/usr/local/bin"
|
||||||
|
|
||||||
# External disk for application data
|
# Root directory for application data. Override in inventory if host
|
||||||
application_dir: "/mnt/applications"
|
# uses a different path (e.g. external disk mounted elsewhere).
|
||||||
|
application_dir: "/srv/applications"
|
||||||
|
|
||||||
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 }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user