Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Deployment

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: deployment-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
name: ${{ matrix.image }} / ${{ matrix.method }} / ${{ matrix.engine }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
image: ["ubuntu:24.04", "images:debian/12/cloud"]
method: [checkout, bash]
engine: [mtg, mtproxy]
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Initialize LXD
run: |
sudo snap install lxd --channel=5.21/stable || sudo snap refresh lxd --channel=5.21/stable
sudo lxd waitready
sudo lxd init --auto
- name: Install and deploy in a clean guest
run: tests/integration/deploy.sh '${{ matrix.image }}' '${{ matrix.method }}' '${{ matrix.engine }}'
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ server. [MIT licensed](LICENSE).

## Start here

On a Debian/Ubuntu control machine, install the CLI environment into `/opt/mega-tg-proxy-server`:

```sh
curl -fsSL https://raw.githubusercontent.com/andre487/MegaTgProxyServer/main/install.sh | sudo bash
```

The installer asks for confirmation through the terminal, installs dependencies, and prepares
the CLI. Configure inventory and Vault separately; it does not provision servers.
Reruns reuse the existing checkout without updating it. To select a branch or tag for
a fresh installation, use `sudo env MEGATG_REF=TAG bash` in place of `sudo bash`.

Requirements: macOS/Linux, Git, Python 3.12+, [uv](https://docs.astral.sh/uv/), OpenSSH,
and SSH access to each server. Use an existing non-root administrator with sudo,
or create one with the two-phase `bootstrap` command described in the guides.
Expand Down
20 changes: 20 additions & 0 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@

## Setup

On a Debian/Ubuntu control machine, install the CLI environment into `/opt/mega-tg-proxy-server`:

```sh
curl -fsSL https://raw.githubusercontent.com/andre487/MegaTgProxyServer/main/install.sh | sudo bash
```

The installer asks for confirmation through the terminal, installs dependencies, and prepares
the CLI. Configure inventory and Vault separately; it does not provision servers.
Reruns reuse the existing checkout without updating it. To select a branch or tag for
a fresh installation, use `sudo env MEGATG_REF=TAG bash` in place of `sudo bash`.

Install Git, Python 3.12+, uv, and OpenSSH on macOS/Linux. Targets must run Debian/Ubuntu
and already have a non-root account with public-key SSH and sudo. Set ansible_user and
tg_admin.user to that account; use `bootstrap` below for initial root access. Use --ask-become-pass
Expand Down Expand Up @@ -320,3 +331,12 @@ Use `--tags cleanup` for cleanup only; check mode never removes resources.
Set `tg_links_order` in `tg_proxies.vars` to a list of exact public domains/IPs
in the desired order. It applies to `configs`, TXT, stdout and hook exports;
`--limit` preserves relative ordering. Unlisted addresses follow in unspecified order.

Deployment integration tests run in disposable LXD guests on GitHub Actions: Ubuntu 24.04
and Debian 12, checkout and Bash installation, MTG and official MTProxy. They exercise
bootstrap, apply/verify, plan, private link export, and a second deployment with cleanup.
Run one case on a Linux host with initialized LXD:

```sh
tests/integration/deploy.sh ubuntu:24.04 bash mtg
```
20 changes: 20 additions & 0 deletions docs/ru/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@

## Подготовка

На управляющей Debian/Ubuntu можно установить окружение в `/opt/mega-tg-proxy-server`:

```sh
curl -fsSL https://raw.githubusercontent.com/andre487/MegaTgProxyServer/main/install.sh | sudo bash
```

Установщик запрашивает подтверждение через терминал, устанавливает зависимости и готовит CLI.
Inventory и пароль Vault нужно настроить отдельно; серверы автоматически не настраиваются.
Повторный запуск использует существующий checkout без обновления. Для выбора ветки или тега
при первой установке задайте `MEGATG_REF` через `sudo env MEGATG_REF=TAG bash`.

На управляющей macOS/Linux нужны Git, Python 3.12+, uv и OpenSSH. На Debian/Ubuntu
нужен непривилегированный администратор с SSH-ключом и sudo.
На новой ноде создайте его командой `bootstrap` (см. ниже).
Expand Down Expand Up @@ -326,3 +337,12 @@ Backend list и protocol key скачивает Ansible на хосте; startup
точные публичные домены/IP из ссылок в нужном порядке. Этот порядок используется
для `configs`, TXT, stdout и файла hook; `--limit` сохраняет относительный порядок.
Адреса, которых нет в списке, выводятся после перечисленных в произвольном порядке.

Интеграционные тесты развёртывания запускаются в одноразовых LXD-гостях в GitHub Actions:
Ubuntu 24.04 и Debian 12, установка из checkout и через Bash, MTG и официальный MTProxy.
Проверяются bootstrap, apply/verify, plan, приватный экспорт ссылок и повторная раскатка
с очисткой. Запуск одного сценария на Linux с настроенным LXD:

```sh
tests/integration/deploy.sh ubuntu:24.04 bash mtg
```
58 changes: 58 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Keep execution after the complete function definition for curl | bash.
main() {
set -euo pipefail
umask 077
if [[ ${1:-} == --help ]]; then
echo 'Usage: curl -fsSL https://raw.githubusercontent.com/andre487/MegaTgProxyServer/main/install.sh | sudo bash'
echo 'Interactive Debian/Ubuntu CLI installation into /opt/mega-tg-proxy-server. Requires a terminal.'
echo 'Set MEGATG_REF to select a branch or tag for a fresh checkout (default: main).'
return
fi
if [[ $# -ne 0 ]]; then
echo 'Unexpected arguments; use --help.' >&2
return 2
fi
if [[ $EUID -ne 0 ]]; then
echo 'Run as root: curl -fsSL URL | sudo bash' >&2
return 1
fi
if [[ ! -r /etc/os-release ]]; then
echo 'Only Debian and Ubuntu are supported.' >&2
return 1
fi
. /etc/os-release
case "$ID" in
debian|ubuntu) ;;
*) echo 'Only Debian and Ubuntu are supported.' >&2; return 1 ;;
esac
# stdin contains the script when piped; all prompts must read the terminal.
if ! exec </dev/tty; then
echo 'An interactive terminal is required (connect with ssh -t).' >&2
return 1
fi
local install_dir=/opt/mega-tg-proxy-server answer
echo "Install dependencies and MegaTgProxyServer into $install_dir, then prepare the CLI environment."
read -r -p 'Continue? [y/N] ' answer
[[ "$answer" == [yY] ]] || return 0
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl git python3 python3-venv openssh-client sudo
if [[ ! -e "$install_dir" ]]; then
git clone --depth 1 --branch "${MEGATG_REF:-main}" https://github.com/andre487/MegaTgProxyServer.git "$install_dir"
elif [[ ! -f "$install_dir/cli.py" ]]; then
echo "$install_dir already exists without CLI support; move it aside or update it manually." >&2
return 1
fi
cd "$install_dir"
if ! command -v uv >/dev/null 2>&1; then
python3 -m venv .bootstrap
.bootstrap/bin/pip install 'uv==0.12.5'
ln -s "$install_dir/.bootstrap/bin/uv" /usr/local/bin/uv
fi
uv sync --frozen --python 3.12
./mega-tg-proxy deps
echo "Installed in $install_dir. Configure a private inventory and Vault password as described in docs/en/README.md or docs/ru/README.md."
echo "Then run: cd $install_dir && ./mega-tg-proxy plan --limit HOST"
}

main "$@"
23 changes: 23 additions & 0 deletions tests/integration/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Runs only in disposable LXD guests; never point this at a production server.
set -euo pipefail
image=${1:?Debian/Ubuntu LXD image required}
method=${2:?checkout or bash required}
engine=${3:?mtg or mtproxy required}
case "$method:$engine" in checkout:mtg|checkout:mtproxy|bash:mtg|bash:mtproxy) ;; *) exit 2 ;; esac
instance="megatg-ci-$RANDOM"
work_dir=$(mktemp -d)
cleanup() {
sudo lxc delete --force "$instance" >/dev/null 2>&1 || true
rm -rf "$work_dir"
}
trap cleanup EXIT
sudo iptables -P FORWARD ACCEPT
sudo ip6tables -P FORWARD ACCEPT
sudo lxc launch "$image" "$instance" -c security.nesting=true -c security.privileged=true
# Copy the working tree, including uncommitted installer changes for local runs.
tar --exclude=.git --exclude=.venv --exclude=.generated --exclude=.hooks \
--exclude=.secrets --exclude=inventory-path --exclude=inventory.yml \
--exclude=__pycache__ -czf "$work_dir/source.tar.gz" .
sudo lxc file push "$work_dir/source.tar.gz" "$instance/root/source.tar.gz"
sudo lxc exec "$instance" -- bash -s -- "$method" "$engine" < tests/integration/guest.sh
68 changes: 68 additions & 0 deletions tests/integration/guest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
method=${1:?}
engine=${2:?}
export DEBIAN_FRONTEND=noninteractive
# Some CI runners disallow plain HTTP in nested guests.
sed -i 's|http://|https://|g' /etc/apt/sources.list 2>/dev/null || true
sed -i 's|http://|https://|g' /etc/apt/sources.list.d/*.sources 2>/dev/null || true
apt-get update
apt-get install -y ca-certificates git python3 python3-venv openssh-server sudo
mkdir /root/source
tar --no-same-owner -xzf /root/source.tar.gz -C /root/source
cd /root/source
git init -b main
git add .
git -c user.name=CI -c user.email=ci@example.com commit -qm 'Test working tree'
if [[ $method == bash ]]; then
# Exercise a fresh clone without fetching an unrelated main revision.
git config --global url.file:///root/source.insteadOf https://github.com/andre487/MegaTgProxyServer.git
printf 'y\n' | script -qefc 'bash /root/source/install.sh' /dev/null
cd /opt/mega-tg-proxy-server
test -x .bootstrap/bin/uv
else
python3 -m venv .bootstrap
.bootstrap/bin/pip install 'uv==0.12.5'
export PATH="$PWD/.bootstrap/bin:$PATH"
uv sync --frozen --python 3.12
./mega-tg-proxy deps
fi
ssh-keygen -q -t ed25519 -N '' -f /root/ci-key
install -d -m 0700 /root/.ssh
cat /root/ci-key.pub >> /root/.ssh/authorized_keys
chmod 0600 /root/.ssh/authorized_keys
systemctl enable --now ssh
ssh-keyscan -H 127.0.0.1 > /root/.ssh/known_hosts
export CI_ENGINE="$engine"
.bootstrap/bin/uv run --frozen python - <<'PY'
import os
from pathlib import Path
from ruamel.yaml import YAML
engine = os.environ['CI_ENGINE']
host = {
'ansible_host': '127.0.0.1', 'ansible_user': 'ci-admin',
'ansible_ssh_private_key_file': '/root/ci-key',
'ansible_python_interpreter': '/usr/bin/python3',
'tg_endpoint': 'ci.example.com', 'tg_port': 443,
'tg_engine': engine, 'tg_mode': 'faketls' if engine == 'mtg' else 'classic',
'tg_domain': 'www.google.com', 'tg_egress': 'direct',
'tg_container_name': 'mtg-proxy' if engine == 'mtg' else 'mtproto-proxy',
'tg_secret': '0123456789abcdef0123456789abcdef',
'tg_unattended_upgrades': False,
'tg_admin': {'user': 'ci-admin', 'public_key': Path('/root/ci-key.pub').read_text().strip(),
'private_key_file': '/root/ci-key', 'bootstrap_user': 'root',
'bootstrap_auth': 'key'},
}
YAML().dump({'all': {'children': {'tg_proxies': {'hosts': {'ci': host}}}}}, Path('/root/ci-inventory.yml'))
Path('/root/ci-inventory.yml').chmod(0o600)
PY
cli=(./mega-tg-proxy --inventory /root/ci-inventory.yml --limit ci --no-hooks)
"${cli[@]}" bootstrap
"${cli[@]}" apply --verify
"${cli[@]}" plan
"${cli[@]}" configs --output /root/links.txt
grep -q 'server=ci.example.com' /root/links.txt
test "$(stat -c %a /root/links.txt)" = 600
# A second deployment also exercises cleanup with an existing container.
"${cli[@]}" apply --verify
docker inspect --format '{{.State.Running}}' "$( [[ $engine == mtg ]] && echo mtg-proxy || echo mtproto-proxy )" | grep -qx true
22 changes: 22 additions & 0 deletions tests/test_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Installer entry points must work when stdin is the downloaded script."""
from pathlib import Path
import subprocess
import unittest


class InstallerTests(unittest.TestCase):
def run_installer(self, *args):
script = Path(__file__).resolve().parents[1] / 'install.sh'
return subprocess.run(['bash', '-s', '--', *args], input=script.read_text(),
text=True, capture_output=True, timeout=10)

def test_piped_help_needs_neither_root_nor_terminal(self):
result = self.run_installer('--help')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('MegaTgProxyServer/main/install.sh', result.stdout)
self.assertIn('MEGATG_REF', result.stdout)

def test_unknown_argument_fails_before_installation(self):
result = self.run_installer('--unknown')
self.assertEqual(result.returncode, 2)
self.assertIn('Unexpected arguments', result.stderr)
Loading