From f22a5688323a084339a1a796d8da31b973cc35b9 Mon Sep 17 00:00:00 2001 From: onysd Date: Fri, 24 Jul 2026 02:37:50 +0300 Subject: [PATCH] added startup script --- .gitignore | 5 + start-server.bat | 234 +++++++++++++++++++++++++++++++++++++++++++++++ start-server.sh | 213 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 452 insertions(+) create mode 100644 start-server.bat create mode 100644 start-server.sh diff --git a/.gitignore b/.gitignore index b7c5a824..f49a03ca 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,11 @@ tmp/ .gocache/ .tdesktop-e2e/ +# start-server.sh/bat generated files +.public_ip +.link_prefix +.secrets + # IDE .idea/ .vscode/ diff --git a/start-server.bat b/start-server.bat new file mode 100644 index 00000000..a65b73c6 --- /dev/null +++ b/start-server.bat @@ -0,0 +1,234 @@ +@echo off +setlocal enabledelayedexpansion +cd /d "%~dp0" + +set "ENV_EXAMPLE=.env.example" +set "ENV_FILE=.env" +set "IP_FILE=.public_ip" +set "PREFIX_FILE=.link_prefix" +set "SECRETS_FILE=.secrets" +set "COMPOSE_FILE=deploy\docker-compose.yml" + +set "NO_BUILD=false" +:parse_args +if "%~1"=="" goto done_args +if /i "%~1"=="--no-build" set "NO_BUILD=true" +shift +goto parse_args +:done_args + +echo [cfg] script started, NO_BUILD=%NO_BUILD% + +rem --- Public address (interactive) ----------------------------------------- +set "DEFAULT_IP=" +if exist "%IP_FILE%" ( + set /p DEFAULT_IP=<"%IP_FILE%" + echo [cfg] loaded saved IP: !DEFAULT_IP! +) + +if not defined DEFAULT_IP ( + echo [cfg] detecting public IP... + for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "(Invoke-WebRequest -Uri 'https://api.ipify.org' -TimeoutSec 5 -UseBasicParsing).Content" 2^>nul`) do set "DEFAULT_IP=%%i" + if defined DEFAULT_IP echo [cfg] detected IP: !DEFAULT_IP! +) + +if defined DEFAULT_IP ( + set /p "PUBLIC_IP=Public server IP/host [!DEFAULT_IP!]: " +) else ( + set /p "PUBLIC_IP=Public server IP/host: " +) +if not defined PUBLIC_IP set "PUBLIC_IP=!DEFAULT_IP!" +if not defined PUBLIC_IP ( + echo [ERROR] public IP/host is required. + pause + exit /b 1 +) +> "%IP_FILE%" echo !PUBLIC_IP! +echo [cfg] public address = !PUBLIC_IP! + +rem --- Link prefix / me_url_prefix (interactive) ---------------------------- +set "DEFAULT_PREFIX=!PUBLIC_IP!" +if exist "%PREFIX_FILE%" set /p DEFAULT_PREFIX=<"%PREFIX_FILE%" +set /p "LINK_PREFIX=Link prefix [!DEFAULT_PREFIX!]: " +if not defined LINK_PREFIX set "LINK_PREFIX=!DEFAULT_PREFIX!" +for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "$p='!LINK_PREFIX!'; $p=$p -replace '^https?://','' -replace '/+$',''; Write-Output $p"`) do set "LINK_PREFIX=%%i" +if not defined LINK_PREFIX ( + echo [ERROR] link prefix is required. + pause + exit /b 1 +) +> "%PREFIX_FILE%" echo !LINK_PREFIX! +echo [cfg] link prefix = !LINK_PREFIX! + +rem --- Secrets (generated once, cached) ------------------------------------- +set "ADMIN_TOKEN=" +set "ADMIN_PASSWORD=" +set "SESSION_KEY=" +set "SECRETS_CHANGED=" +if exist "%SECRETS_FILE%" ( + echo [cfg] loading secrets from %SECRETS_FILE% + for /f "usebackq tokens=1,* delims==" %%A in (`type "%SECRETS_FILE%"`) do ( + if "%%A"=="ADMIN_TOKEN" set "ADMIN_TOKEN=%%B" + if "%%A"=="ADMIN_PASSWORD" set "ADMIN_PASSWORD=%%B" + if "%%A"=="SESSION_KEY" set "SESSION_KEY=%%B" + ) +) +if not defined ADMIN_TOKEN ( + echo [cfg] generating ADMIN_TOKEN... + for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "$bytes=New-Object byte[] 32; [System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes); ($bytes | ForEach-Object { $_.ToString('x2') }) -join ''"`) do set "ADMIN_TOKEN=%%i" + set "SECRETS_CHANGED=1" +) +if not defined ADMIN_PASSWORD ( + echo [cfg] generating ADMIN_PASSWORD... + for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "$chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; -join (1..24 | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] })"`) do set "ADMIN_PASSWORD=%%i" + set "SECRETS_CHANGED=1" +) +if not defined SESSION_KEY ( + echo [cfg] generating SESSION_KEY... + for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "$chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; -join (1..48 | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] })"`) do set "SESSION_KEY=%%i" + set "SECRETS_CHANGED=1" +) +if defined SECRETS_CHANGED ( + > "%SECRETS_FILE%" echo ADMIN_TOKEN=!ADMIN_TOKEN! + >> "%SECRETS_FILE%" echo ADMIN_PASSWORD=!ADMIN_PASSWORD! + >> "%SECRETS_FILE%" echo SESSION_KEY=!SESSION_KEY! + echo [cfg] secrets written to %SECRETS_FILE% +) else ( + echo [cfg] secrets loaded from %SECRETS_FILE% +) + +rem --- Generate .env from .env.example -------------------------------------- +if not exist "%ENV_EXAMPLE%" ( + echo [ERROR] template file %ENV_EXAMPLE% not found + pause + exit /b 1 +) +copy /Y "%ENV_EXAMPLE%" "%ENV_FILE%" >nul +echo [cfg] copied %ENV_EXAMPLE% to %ENV_FILE% + +echo [cfg] patching .env values... +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$f='%ENV_FILE%'; " ^ + "$enc=New-Object System.Text.UTF8Encoding($false); " ^ + "$t=[System.IO.File]::ReadAllText($f,$enc); " ^ + "$map=[ordered]@{ " ^ + " 'TELESRV_ADVERTISE_IP'='!PUBLIC_IP!'; " ^ + " 'TELESRV_TURN_ADVERTISE_IP'='!PUBLIC_IP!'; " ^ + " 'TELESRV_SFU_ADVERTISE_IP'='!PUBLIC_IP!'; " ^ + " 'TELESRV_PUBLIC_BASE_URL'='https://!LINK_PREFIX!'; " ^ + " 'TELESRV_PASSKEY_RP_ID'='!LINK_PREFIX!'; " ^ + " 'TELESRV_PUBLIC_APP_SCHEME'='owpg'; " ^ + " 'TELESRV_PUBLIC_APP_NAME'='OwpenGram'; " ^ + " 'TELESRV_ADMIN_API_TOKEN'='!ADMIN_TOKEN!'; " ^ + " 'TELESRV_ADMIN_UI_PASSWORD'='!ADMIN_PASSWORD!'; " ^ + " 'TELESRV_ADMIN_SESSION_KEY'='!SESSION_KEY!'; " ^ + " 'TELESRV_ADMIN_UI_ADDR'='127.0.0.1:2600'; " ^ + " 'TELESRV_ADMIN_API_ADDR'='127.0.0.1:2399'; " ^ + " 'TELESRV_PUBLIC_LINK_WEB_ADDR'='127.0.0.1:2401' " ^ + "}; " ^ + "foreach ($k in $map.Keys) { " ^ + " $pat='(?m)^' + [regex]::Escape($k) + '=.*$'; " ^ + " $rep=$k + '=' + $map[$k]; " ^ + " if ($t -match $pat) { $t=[regex]::Replace($t,$pat,$rep) } " ^ + " else { $t+=\"`r`n\"+$rep } " ^ + "}; " ^ + "[System.IO.File]::WriteAllText($f,$t,$enc); " ^ + "Write-Output 'ok'" +if %ERRORLEVEL% neq 0 ( + echo [ERROR] failed to patch .env + pause + exit /b 1 +) +echo [cfg] .env written + +rem --- Start infrastructure (PostgreSQL + Redis) ---------------------------- +echo. +echo == [1/4] Starting infrastructure (PostgreSQL + Redis) == +docker compose -f "%COMPOSE_FILE%" up -d +if %ERRORLEVEL% neq 0 ( + echo [ERROR] docker compose failed + pause + exit /b 1 +) + +rem --- Wait for PostgreSQL -------------------------------------------------- +echo. +echo == [2/4] Waiting for PostgreSQL == +set /a "_pgw=0" +:wait_pg +docker exec telesrv-postgres pg_isready -U telesrv -d telesrv >nul 2>&1 +if not errorlevel 1 goto pg_ready +set /a "_pgw+=1" +if !_pgw! gtr 30 ( + echo [ERROR] PostgreSQL not ready after 60s + pause + exit /b 1 +) +echo [cfg] waiting for PostgreSQL... !_pgw!/30 +timeout /t 2 >nul +goto wait_pg +:pg_ready +echo [ok] PostgreSQL is ready + +rem --- Build ------------------------------------------------------------------ +echo. +echo == [3/4] Building server binaries == +if /i "%NO_BUILD%"=="true" ( + echo [cfg] skipping build (--no-build) + if not exist "bin\telesrv.exe" ( + if not exist "bin\telesrv-admin.exe" ( + echo [ERROR] no binaries found in bin\ - run without --no-build first + pause + exit /b 1 + ) + ) +) else ( + if not exist bin mkdir bin + echo [cfg] building telesrv... + go build -o bin\telesrv.exe .\cmd\telesrv + if %ERRORLEVEL% neq 0 ( + echo [ERROR] failed to build telesrv + pause + exit /b 1 + ) + echo [cfg] building telesrv-admin... + go build -o bin\telesrv-admin.exe .\cmd\telesrv-admin + if %ERRORLEVEL% neq 0 ( + echo [ERROR] failed to build telesrv-admin + pause + exit /b 1 + ) + echo [ok] binaries built +) + +rem --- Start servers ---------------------------------------------------------- +echo. +echo == [4/4] Starting telesrv + telesrv-admin == + +start "telesrv" /B bin\telesrv.exe +echo [ok] telesrv started + +start "telesrv-admin" /B bin\telesrv-admin.exe +echo [ok] telesrv-admin started + +echo. +echo ============================================ +echo OwpenGram server is running +echo ============================================ +echo. +echo MTProto: !PUBLIC_IP!:2398 +echo Admin UI: http://127.0.0.1:2600 +echo Admin API: http://127.0.0.1:2399 +echo. +echo Admin login password: !ADMIN_PASSWORD! +echo. +echo Ports to open in firewall: +echo TCP 2398 - MTProto (login / chats / media) +echo TCP 12400 - TURN/STUN control (calls) +echo UDP 12500-12999 - TURN media relay (calls) +echo UDP 12399 - SFU group calls +echo TCP 2400 - RTMP livestream ingest +echo. +echo ============================================ + +exit /b 0 diff --git a/start-server.sh b/start-server.sh new file mode 100644 index 00000000..dc8f939e --- /dev/null +++ b/start-server.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +ENV_EXAMPLE=".env.example" +ENV_FILE=".env" +IP_FILE=".public_ip" +PREFIX_FILE=".link_prefix" +SECRETS_FILE=".secrets" +COMPOSE_FILE="deploy/docker-compose.yml" + +NO_BUILD=false +for arg in "$@"; do + case "$arg" in + --no-build) NO_BUILD=true ;; + -h|--help) + echo "Usage: $0 [--no-build]" + echo "" + echo " --no-build Skip Go compilation (reuse existing binaries in bin/)" + exit 0 + ;; + esac +done + +# --- Helpers ---------------------------------------------------------------- +log() { echo "[cfg] $*"; } +step() { echo; echo "== $* =="; } +die() { echo "[ERROR] $*" >&2; exit 1; } + +random_hex() { + local bytes="${1:-32}" + openssl rand -hex "$bytes" 2>/dev/null || head -c "$bytes" /dev/urandom | xxd -p | tr -d '\n' | head -c $((bytes * 2)) +} + +random_string() { + local len="${1:-24}" + openssl rand -base64 "$((len * 2))" 2>/dev/null | tr -dc 'a-zA-Z0-9' | head -c "$len" \ + || head -c 256 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c "$len" +} + +# --- Public address (interactive) ------------------------------------------ +DEFAULT_IP="" +[[ -f "$IP_FILE" ]] && DEFAULT_IP="$(tr -d '[:space:]' < "$IP_FILE")" +if [[ -z "$DEFAULT_IP" ]]; then + DEFAULT_IP="$(curl -fsS --max-time 3 https://api.ipify.org 2>/dev/null || true)" +fi + +if [[ -n "$DEFAULT_IP" ]]; then + read -rp "Public server IP/host [${DEFAULT_IP}]: " PUBLIC_IP +else + read -rp "Public server IP/host: " PUBLIC_IP +fi +PUBLIC_IP="${PUBLIC_IP:-$DEFAULT_IP}" +[[ -z "$PUBLIC_IP" ]] && die "public IP/host is required." +echo "$PUBLIC_IP" > "$IP_FILE" +log "public address = ${PUBLIC_IP}" + +# --- Link prefix / me_url_prefix (interactive) ----------------------------- +DEFAULT_PREFIX="$PUBLIC_IP" +[[ -f "$PREFIX_FILE" ]] && DEFAULT_PREFIX="$(tr -d '[:space:]' < "$PREFIX_FILE")" +read -rp "Link prefix (me_url_prefix, e.g. ${PUBLIC_IP} or chat.example.com) [${DEFAULT_PREFIX}]: " LINK_PREFIX +LINK_PREFIX="${LINK_PREFIX:-$DEFAULT_PREFIX}" +LINK_PREFIX="$(printf '%s' "$LINK_PREFIX" | sed -E 's#^https?://##; s#/+$##')" +[[ -z "$LINK_PREFIX" ]] && die "link prefix is required." +echo "$LINK_PREFIX" > "$PREFIX_FILE" +log "link prefix (me_url_prefix) = ${LINK_PREFIX}" + +# --- Secrets (generated once, cached) -------------------------------------- +load_or_gen_secrets() { + local admin_token="" admin_password="" session_key="" + + if [[ -f "$SECRETS_FILE" ]]; then + # shellcheck disable=SC1090 + source "$SECRETS_FILE" 2>/dev/null || true + fi + + if [[ -z "${ADMIN_TOKEN:-}" ]]; then + ADMIN_TOKEN="$(random_hex 32)" + fi + if [[ -z "${ADMIN_PASSWORD:-}" ]]; then + ADMIN_PASSWORD="$(random_string 24)" + fi + if [[ -z "${SESSION_KEY:-}" ]]; then + SESSION_KEY="$(random_string 48)" + fi + + cat > "$SECRETS_FILE" <> "$ENV_FILE" + fi +} + +set_env "TELESRV_ADVERTISE_IP" "$PUBLIC_IP" +set_env "TELESRV_TURN_ADVERTISE_IP" "$PUBLIC_IP" +set_env "TELESRV_SFU_ADVERTISE_IP" "$PUBLIC_IP" +set_env "TELESRV_PUBLIC_BASE_URL" "https://${LINK_PREFIX}" +set_env "TELESRV_PASSKEY_RP_ID" "$LINK_PREFIX" +set_env "TELESRV_PUBLIC_APP_SCHEME" "owpg" +set_env "TELESRV_PUBLIC_APP_NAME" "OwpenGram" +set_env "TELESRV_ADMIN_API_TOKEN" "$ADMIN_TOKEN" +set_env "TELESRV_ADMIN_UI_PASSWORD" "$ADMIN_PASSWORD" +set_env "TELESRV_ADMIN_SESSION_KEY" "$SESSION_KEY" +set_env "TELESRV_ADMIN_UI_ADDR" "127.0.0.1:2600" +set_env "TELESRV_ADMIN_API_ADDR" "127.0.0.1:2399" +set_env "TELESRV_PUBLIC_LINK_WEB_ADDR" "127.0.0.1:2401" + +log ".env written (${ENV_FILE})" + +# --- Start infrastructure (PostgreSQL + Redis) ----------------------------- +step "[1/4] Starting infrastructure (PostgreSQL + Redis)" +docker compose -f "$COMPOSE_FILE" up -d + +# --- Wait for PostgreSQL --------------------------------------------------- +step "[2/4] Waiting for PostgreSQL" +for i in $(seq 1 30); do + if docker exec telesrv-postgres pg_isready -U telesrv -d telesrv >/dev/null 2>&1; then + echo "[ok] PostgreSQL is ready" + break + fi + if [ "$i" -eq 30 ]; then + die "PostgreSQL not ready after 60s" + fi + sleep 2 +done + +# --- Build ------------------------------------------------------------------ +step "[3/4] Building server binaries" +if [ "$NO_BUILD" = true ]; then + log "skipping build (--no-build)" + if [[ ! -f "bin/telesrv" ]] && [[ ! -f "bin/telesrv.exe" ]]; then + die "no binaries found in bin/ — run without --no-build first" + fi +else + mkdir -p bin + echo " building telesrv ..." + go build -o bin/telesrv ./cmd/telesrv + echo " building telesrv-admin ..." + go build -o bin/telesrv-admin ./cmd/telesrv-admin + echo "[ok] binaries built in bin/" +fi + +# --- Start servers ---------------------------------------------------------- +step "[4/4] Starting telesrv + telesrv-admin" + +cleanup() { + echo + echo "[stop] stopping telesrv and telesrv-admin ..." + kill "$TELESRV_PID" 2>/dev/null || true + kill "$ADMIN_PID" 2>/dev/null || true + wait "$TELESRV_PID" 2>/dev/null || true + wait "$ADMIN_PID" 2>/dev/null || true + echo "[ok] stopped." +} +trap cleanup EXIT INT TERM + +# Start telesrv (main server) +BIN="./bin/telesrv" +[[ -f "bin/telesrv.exe" ]] && BIN="./bin/telesrv.exe" +$BIN & +TELESRV_PID=$! +echo "[ok] telesrv started (PID ${TELESRV_PID})" + +# Start telesrv-admin (admin panel) +ADMIN_BIN="./bin/telesrv-admin" +[[ -f "bin/telesrv-admin.exe" ]] && ADMIN_BIN="./bin/telesrv-admin.exe" +$ADMIN_BIN & +ADMIN_PID=$! +echo "[ok] telesrv-admin started (PID ${ADMIN_PID})" + +echo +echo "============================================" +echo " OwpenGram server is running" +echo "============================================" +echo "" +echo " MTProto: ${PUBLIC_IP}:2398" +echo " Admin UI: http://127.0.0.1:2600" +echo " Admin API: http://127.0.0.1:2399" +echo "" +echo " Admin login password: ${ADMIN_PASSWORD}" +echo "" +echo " Ports to open in firewall:" +echo " TCP 2398 - MTProto (login / chats / media)" +echo " TCP 12400 - TURN/STUN control (calls)" +echo " UDP 12500-12999 - TURN media relay (calls)" +echo " UDP 12399 - SFU group calls" +echo " TCP 2400 - RTMP livestream ingest" +echo "" +echo " Press Ctrl+C to stop." +echo "============================================" + +wait "$TELESRV_PID" 2>/dev/null || true +wait "$ADMIN_PID" 2>/dev/null || true