chore: можно из make устанавливать сервера
This commit is contained in:
1 parent
c4300066f5
commit
6cfb00a847
3 files changed
+136
-1
No files matched your search
@@ -4,11 +4,14 @@ PIP := $(VENV)/bin/pip
|
|||||||
WHEELS_DIR := wheels
|
WHEELS_DIR := wheels
|
||||||
REQUIREMENTS := requirements.txt
|
REQUIREMENTS := requirements.txt
|
||||||
MANAGE := cd src/ && ../$(PYTHON) manage.py
|
MANAGE := cd src/ && ../$(PYTHON) manage.py
|
||||||
|
SERVERUS := cd src/ && ../$(PYTHON) -m serverus
|
||||||
|
# доступы к серверам, настроенным вручную через serverus (папка в .gitignore)
|
||||||
|
SERVERUS_ACCESS_DIR := $(abspath extra/serverus)
|
||||||
|
|
||||||
MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help
|
MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help
|
||||||
PKG := $(filter-out $(MAKECMDGOALS_TARGETS),$(MAKECMDGOALS))
|
PKG := $(filter-out $(MAKECMDGOALS_TARGETS),$(MAKECMDGOALS))
|
||||||
|
|
||||||
.PHONY: install freeze wheels sync sync-offline clean help venv dj_makemigrations dj_migrate dj_superuser dj_run dj_test dj_startapp dj_worker tg_run
|
.PHONY: install freeze wheels sync sync-offline clean help venv dj_makemigrations dj_migrate dj_superuser dj_run dj_test dj_startapp dj_worker tg_run serverus_xui serverus_release
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "make install <pkg> [<pkg> ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/"
|
@echo "make install <pkg> [<pkg> ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/"
|
||||||
@@ -17,6 +20,8 @@ help:
|
|||||||
@echo "make freeze - перезаписать $(REQUIREMENTS) текущим состоянием окружения"
|
@echo "make freeze - перезаписать $(REQUIREMENTS) текущим состоянием окружения"
|
||||||
@echo "make wheels - собрать wheel для всех пакетов из $(REQUIREMENTS)"
|
@echo "make wheels - собрать wheel для всех пакетов из $(REQUIREMENTS)"
|
||||||
@echo "make clean - удалить $(WHEELS_DIR)/"
|
@echo "make clean - удалить $(WHEELS_DIR)/"
|
||||||
|
@echo "make serverus_xui ip=<ip> password=<root_password> - настроить свежий сервер и поставить 3x-ui, доступы в $(SERVERUS_ACCESS_DIR)/<ip>/"
|
||||||
|
@echo "make serverus_release ip=<ip> - вернуть сервер, настроенный через serverus_xui: вход по паролю на 22 порт"
|
||||||
|
|
||||||
venv:
|
venv:
|
||||||
@test -d $(VENV) || python3 -m venv $(VENV)
|
@test -d $(VENV) || python3 -m venv $(VENV)
|
||||||
@@ -75,3 +80,11 @@ dj_startapp:
|
|||||||
|
|
||||||
tg_run:
|
tg_run:
|
||||||
$(MANAGE) telegram_run
|
$(MANAGE) telegram_run
|
||||||
|
|
||||||
|
serverus_xui:
|
||||||
|
@test -n "$(ip)" -a -n "$(password)" || { echo "Укажите сервер: make serverus_xui ip=<ip> password=<root_password>"; exit 1; }
|
||||||
|
$(SERVERUS) xui $(ip) '$(password)' --out $(SERVERUS_ACCESS_DIR)/$(ip)
|
||||||
|
|
||||||
|
serverus_release:
|
||||||
|
@test -n "$(ip)" || { echo "Укажите сервер: make serverus_release ip=<ip>"; exit 1; }
|
||||||
|
$(SERVERUS) release --out $(SERVERUS_ACCESS_DIR)/$(ip)
|
||||||
@@ -72,4 +72,14 @@ server.release(generate_password())
|
|||||||
|
|
||||||
Плейбуки лежат в `playbooks/`, общие шаги в `playbooks/tasks/`. Новая операция - это плейбук и метод в `Server` или его наследнике.
|
Плейбуки лежат в `playbooks/`, общие шаги в `playbooks/tasks/`. Новая операция - это плейбук и метод в `Server` или его наследнике.
|
||||||
|
|
||||||
|
## Ручная проверка на живом сервере
|
||||||
|
|
||||||
|
```
|
||||||
|
make serverus_xui ip=<ip> password=<root_password> # bootstrap + 3x-ui
|
||||||
|
make serverus_release ip=<ip> # вернуть сервер
|
||||||
|
```
|
||||||
|
|
||||||
|
То же без Makefile: `python -m serverus xui <ip> <password> --out <dir>` и `python -m serverus release --out <dir>`.
|
||||||
|
Доступы (ключ, пароль root, порт ssh, отпечаток, панель) пишутся в `extra/serverus/<ip>/` до начала настройки, так что доступ к серверу не теряется, даже если настройка упала.
|
||||||
|
|
||||||
Тесты: `python -m unittest serverus.tests` из `src/`. Плейбуки проверены на Debian 13 в контейнере с systemd.
|
Тесты: `python -m unittest serverus.tests` из `src/`. Плейбуки проверены на Debian 13 в контейнере с systemd.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""Ручная проверка сценариев на живом сервере.
|
||||||
|
|
||||||
|
python -m serverus xui <ip> <root_password> --out <dir>
|
||||||
|
python -m serverus release --out <dir>
|
||||||
|
|
||||||
|
Доступы (ключ, пароль root, порт ssh, отпечаток, панель) пишутся в <dir>/access.json до начала настройки:
|
||||||
|
bootstrap меняет пароль и порт ssh, без этого файла доступ к серверу можно потерять.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from . import Host, Server, XuiPanel, generate_keypair, generate_password, generate_ssh_port
|
||||||
|
|
||||||
|
ACCESS_FILE = 'access.json'
|
||||||
|
KEY_FILE = 'id_ed25519'
|
||||||
|
PANEL_PORT = 12345
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog='python -m serverus')
|
||||||
|
commands = parser.add_subparsers(dest='command', required=True)
|
||||||
|
|
||||||
|
xui = commands.add_parser('xui', help='bootstrap свежего сервера и установка 3x-ui')
|
||||||
|
xui.add_argument('address')
|
||||||
|
xui.add_argument('password', help='текущий пароль root')
|
||||||
|
xui.add_argument('--out', type=Path, required=True, help='папка для доступов к серверу')
|
||||||
|
|
||||||
|
release = commands.add_parser('release', help='вернуть сервер: вход по паролю на 22 порт, новый пароль root')
|
||||||
|
release.add_argument('--out', type=Path, required=True, help='папка с доступами от команды xui')
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
return install_xui(args.address, args.password, args.out) if args.command == 'xui' else release_server(args.out)
|
||||||
|
|
||||||
|
|
||||||
|
def install_xui(address: str, password: str, out: Path) -> int:
|
||||||
|
keys = generate_keypair()
|
||||||
|
access = {
|
||||||
|
'address': address,
|
||||||
|
'ssh_port': generate_ssh_port(),
|
||||||
|
'root_password': generate_password(),
|
||||||
|
'private_key': keys.private_key,
|
||||||
|
'known_hosts': '',
|
||||||
|
'panel': {
|
||||||
|
'username': 'admin',
|
||||||
|
'password': generate_password(16),
|
||||||
|
'panel_port': PANEL_PORT,
|
||||||
|
'web_base_path': generate_password(18),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
save(out, access)
|
||||||
|
|
||||||
|
result = Server(Host(address, private_key=keys.private_key)).bootstrap(
|
||||||
|
password, keys.public_key, access['root_password'], access['ssh_port'],
|
||||||
|
)
|
||||||
|
access['known_hosts'] = result.known_hosts
|
||||||
|
save(out, access)
|
||||||
|
report('bootstrap', result)
|
||||||
|
if not result.ok:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
host = load_host(access)
|
||||||
|
result = XuiPanel(host).install(**access['panel'])
|
||||||
|
report('xui', result)
|
||||||
|
if not result.ok:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
panel = access['panel']
|
||||||
|
print(f"uname: {Server(host).facts().data.get('uname', '?')}")
|
||||||
|
print(f"panel: http(s)://{address}:{panel['panel_port']}/{panel['web_base_path']}/")
|
||||||
|
print(f"panel login: {panel['username']} / {panel['password']}")
|
||||||
|
print(f"ssh: ssh -i {out / KEY_FILE} -p {access['ssh_port']} root@{address}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def release_server(out: Path) -> int:
|
||||||
|
access = json.loads((out / ACCESS_FILE).read_text())
|
||||||
|
new_password = generate_password()
|
||||||
|
result = Server(load_host(access)).release(new_password)
|
||||||
|
report('release', result)
|
||||||
|
if not result.ok:
|
||||||
|
return 1
|
||||||
|
access.update(ssh_port=22, root_password=new_password, known_hosts='')
|
||||||
|
save(out, access)
|
||||||
|
print(f"ssh: ssh -p 22 root@{access['address']}, root password: {new_password}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def load_host(access: dict) -> Host:
|
||||||
|
return Host(
|
||||||
|
access['address'], port=access['ssh_port'], private_key=access['private_key'],
|
||||||
|
known_hosts=access['known_hosts'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def save(out: Path, access: dict):
|
||||||
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
|
for name, content in ((ACCESS_FILE, json.dumps(access, indent=2)), (KEY_FILE, access['private_key'])):
|
||||||
|
path = out / name
|
||||||
|
path.touch(mode=0o600, exist_ok=True)
|
||||||
|
path.write_text(content)
|
||||||
|
|
||||||
|
|
||||||
|
def report(step: str, result):
|
||||||
|
print(f'{step}: {"ok" if result.ok else "FAILED"}')
|
||||||
|
if result.error:
|
||||||
|
print(result.error, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in new issue
Block a user