merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
97
scripts/ensure-local-databases.ps1
Normal file
97
scripts/ensure-local-databases.ps1
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Ensures the branch-isolated local PostgreSQL databases exist.
|
||||
|
||||
.DESCRIPTION
|
||||
The main and v2 branches have independent migration histories. They must never
|
||||
share one schema_migrations row. This helper creates telesrv_main and
|
||||
telesrv_v2 in the local Compose PostgreSQL container without modifying or
|
||||
deleting the legacy telesrv database.
|
||||
|
||||
Optional template parameters are intended for a one-time local split when an
|
||||
existing database snapshot should be preserved. They are only used when the
|
||||
target database does not already exist.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$PostgresContainer = "telesrv-postgres",
|
||||
[string]$DbUser = "telesrv",
|
||||
[string]$MainTemplate,
|
||||
[string]$V2Template
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Assert-SafeIdentifier {
|
||||
param([string]$Name, [string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value) -or $Value -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') {
|
||||
throw "$Name must be a PostgreSQL identifier containing only letters, digits, and underscores: '$Value'"
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Docker {
|
||||
param([string[]]$Arguments)
|
||||
$oldErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
$output = & docker @Arguments 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
} finally {
|
||||
$ErrorActionPreference = $oldErrorActionPreference
|
||||
}
|
||||
$text = ($output | ForEach-Object { $_.ToString() }) -join "`n"
|
||||
if ($exitCode -ne 0) {
|
||||
throw "docker $($Arguments -join ' ') failed with exit code ${exitCode}:`n$text"
|
||||
}
|
||||
return $text.Trim()
|
||||
}
|
||||
|
||||
function Test-DatabaseExists {
|
||||
param([string]$Database)
|
||||
Assert-SafeIdentifier "database" $Database
|
||||
$result = Invoke-Docker @(
|
||||
"exec", $PostgresContainer,
|
||||
"psql", "-U", $DbUser, "-d", "postgres",
|
||||
"-v", "ON_ERROR_STOP=1", "-At", "-c",
|
||||
"SELECT 1 FROM pg_database WHERE datname = '$Database';"
|
||||
)
|
||||
return $result -eq "1"
|
||||
}
|
||||
|
||||
function Ensure-Database {
|
||||
param([string]$Database, [string]$Template)
|
||||
if (Test-DatabaseExists $Database) {
|
||||
Write-Host "[ok] PostgreSQL database already exists: $Database"
|
||||
return
|
||||
}
|
||||
|
||||
$args = @("exec", $PostgresContainer, "createdb", "-U", $DbUser, "-O", $DbUser)
|
||||
if (-not [string]::IsNullOrWhiteSpace($Template)) {
|
||||
Assert-SafeIdentifier "template database" $Template
|
||||
if (-not (Test-DatabaseExists $Template)) {
|
||||
throw "template database does not exist: $Template"
|
||||
}
|
||||
$args += @("-T", $Template)
|
||||
}
|
||||
$args += $Database
|
||||
Invoke-Docker $args | Out-Null
|
||||
$templateSuffix = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($Template)) {
|
||||
$templateSuffix = " (template: $Template)"
|
||||
}
|
||||
Write-Host "[ok] created PostgreSQL database: $Database$templateSuffix"
|
||||
}
|
||||
|
||||
Assert-SafeIdentifier "database user" $DbUser
|
||||
if ($PostgresContainer -notmatch '^[A-Za-z0-9_.-]+$') {
|
||||
throw "invalid Docker container name: '$PostgresContainer'"
|
||||
}
|
||||
|
||||
$running = Invoke-Docker @("inspect", "-f", "{{.State.Running}}", $PostgresContainer)
|
||||
if ($running -ne "true") {
|
||||
throw "PostgreSQL container is not running: $PostgresContainer"
|
||||
}
|
||||
|
||||
Ensure-Database "telesrv_main" $MainTemplate
|
||||
Ensure-Database "telesrv_v2" $V2Template
|
||||
220
scripts/new-docker-env.ps1
Normal file
220
scripts/new-docker-env.ps1
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$AdvertiseIP,
|
||||
|
||||
[Parameter()]
|
||||
[string]$PublicBaseURL = "",
|
||||
|
||||
[Parameter()]
|
||||
[string]$PublicWebBaseURL = "",
|
||||
|
||||
[Parameter()]
|
||||
[string]$AdminBindIP = "127.0.0.1",
|
||||
|
||||
[Parameter()]
|
||||
[switch]$HostNetwork,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$BridgeNetwork,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$AllowInsecureDevelopmentAuth
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($HostNetwork -and $BridgeNetwork) {
|
||||
throw "HostNetwork and BridgeNetwork are mutually exclusive."
|
||||
}
|
||||
|
||||
$parsedIP = $null
|
||||
if (-not [System.Net.IPAddress]::TryParse($AdvertiseIP, [ref]$parsedIP)) {
|
||||
throw "AdvertiseIP must be an IPv4 or IPv6 address, not a DNS name."
|
||||
}
|
||||
$isLoopback = [System.Net.IPAddress]::IsLoopback($parsedIP)
|
||||
$isIPv6 = $parsedIP.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6
|
||||
|
||||
$parsedAdminBindIP = $null
|
||||
if (-not [System.Net.IPAddress]::TryParse($AdminBindIP, [ref]$parsedAdminBindIP)) {
|
||||
throw "AdminBindIP must be an IPv4 or IPv6 address."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($PublicBaseURL)) {
|
||||
if (-not $isLoopback) {
|
||||
throw "PublicBaseURL is required when AdvertiseIP is not loopback."
|
||||
}
|
||||
$loopbackHost = if ($isIPv6) { "[::1]" } else { "127.0.0.1" }
|
||||
$PublicBaseURL = "http://${loopbackHost}:2401"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($PublicWebBaseURL)) {
|
||||
$PublicWebBaseURL = $PublicBaseURL
|
||||
}
|
||||
|
||||
function Assert-HTTPURL {
|
||||
param([string]$Name, [string]$Value)
|
||||
$uri = $null
|
||||
if (-not [Uri]::TryCreate($Value, [UriKind]::Absolute, [ref]$uri) -or
|
||||
($uri.Scheme -ne "http" -and $uri.Scheme -ne "https") -or
|
||||
-not [string]::IsNullOrEmpty($uri.UserInfo)) {
|
||||
throw "$Name must be an absolute HTTP(S) URL without embedded credentials."
|
||||
}
|
||||
}
|
||||
|
||||
Assert-HTTPURL -Name "PublicBaseURL" -Value $PublicBaseURL
|
||||
Assert-HTTPURL -Name "PublicWebBaseURL" -Value $PublicWebBaseURL
|
||||
if (-not $isLoopback -and -not $AllowInsecureDevelopmentAuth) {
|
||||
throw "Internet/LAN development-code auth requires AllowInsecureDevelopmentAuth; for production generate on loopback, then configure the webhook provider before startup."
|
||||
}
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$dockerDir = Join-Path $repoRoot "deploy\docker"
|
||||
$templatePath = Join-Path $dockerDir ".env.example"
|
||||
$outputPath = Join-Path $dockerDir ".env"
|
||||
if (-not (Test-Path -LiteralPath $templatePath -PathType Leaf)) {
|
||||
throw "Docker environment template not found: $templatePath"
|
||||
}
|
||||
if (Test-Path -LiteralPath $outputPath) {
|
||||
throw "$outputPath already exists. Initialization never overwrites live credentials."
|
||||
}
|
||||
|
||||
function New-HexSecret {
|
||||
param([int]$Bytes = 32)
|
||||
$buffer = New-Object byte[] $Bytes
|
||||
$generator = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
||||
try {
|
||||
$generator.GetBytes($buffer)
|
||||
}
|
||||
finally {
|
||||
$generator.Dispose()
|
||||
}
|
||||
return [BitConverter]::ToString($buffer).Replace("-", "").ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Get-GitValue {
|
||||
param([string[]]$GitArguments)
|
||||
try {
|
||||
$value = & git -C $repoRoot @GitArguments 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { return ($value | Select-Object -First 1).Trim() }
|
||||
}
|
||||
catch {}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
$publicBindIP = if ($isIPv6) { "::" } else { "0.0.0.0" }
|
||||
$localBindIP = if ($isIPv6) { "::1" } else { "127.0.0.1" }
|
||||
if ($isLoopback) {
|
||||
$publicBindIP = $parsedIP.ToString()
|
||||
$localBindIP = $parsedIP.ToString()
|
||||
}
|
||||
elseif (([Uri]$PublicBaseURL).Scheme -eq "http") {
|
||||
$publicLinkIP = $null
|
||||
$publicLinkHost = ([Uri]$PublicBaseURL).Host.Trim([char[]]"[]")
|
||||
if ([System.Net.IPAddress]::TryParse($publicLinkHost, [ref]$publicLinkIP) -and $publicLinkIP.Equals($parsedIP)) {
|
||||
$localBindIP = $parsedIP.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
$adminHealthIP = $parsedAdminBindIP.ToString()
|
||||
if ($adminHealthIP -eq "0.0.0.0") { $adminHealthIP = "127.0.0.1" }
|
||||
if ($adminHealthIP -eq "::") { $adminHealthIP = "::1" }
|
||||
$publicListenHost = if ($publicBindIP.Contains(":")) { "[${publicBindIP}]" } else { $publicBindIP }
|
||||
$localListenHost = if ($localBindIP.Contains(":")) { "[${localBindIP}]" } else { $localBindIP }
|
||||
$serverHealthURLHost = $localListenHost
|
||||
$adminListenIP = $parsedAdminBindIP.ToString()
|
||||
$adminListenHost = if ($adminListenIP.Contains(":")) { "[${adminListenIP}]" } else { $adminListenIP }
|
||||
$turnEnabled = (-not $isIPv6).ToString().ToLowerInvariant()
|
||||
$turnAdvertiseIP = if ($isIPv6) { "127.0.0.1" } else { $parsedIP.ToString() }
|
||||
$rtmpHost = if ($isIPv6) { "[$($parsedIP.ToString())]" } else { $parsedIP.ToString() }
|
||||
$postgresPassword = New-HexSecret 24
|
||||
|
||||
$treeState = "unknown"
|
||||
try {
|
||||
$treeOutput = & git -C $repoRoot status --porcelain 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { $treeState = if (@($treeOutput).Count -gt 0) { "dirty" } else { "clean" } }
|
||||
}
|
||||
catch {}
|
||||
|
||||
$values = [ordered]@{
|
||||
TELESRV_BUILD_COMMIT = Get-GitValue @("rev-parse", "HEAD")
|
||||
TELESRV_BUILD_BRANCH = Get-GitValue @("rev-parse", "--abbrev-ref", "HEAD")
|
||||
TELESRV_BUILD_TREE_STATE = $treeState
|
||||
TELESRV_BUILD_DATE = [DateTime]::UtcNow.ToString("o")
|
||||
POSTGRES_PASSWORD = $postgresPassword
|
||||
TELESRV_POSTGRES_DSN = "postgres://telesrv:${postgresPassword}@127.0.0.1:15432/telesrv_main?sslmode=disable"
|
||||
TELESRV_REDIS_PASSWORD = New-HexSecret 32
|
||||
TELESRV_ADMIN_API_TOKEN = New-HexSecret 32
|
||||
TELESRV_ADMIN_UI_PASSWORD = New-HexSecret 24
|
||||
TELESRV_ADMIN_SESSION_KEY = New-HexSecret 32
|
||||
TELESRV_TURN_SECRET = New-HexSecret 32
|
||||
TELESRV_OTP_WEBHOOK_SECRET = New-HexSecret 32
|
||||
TELESRV_ALLOW_INSECURE_DEVELOPMENT_AUTH = ($isLoopback -or $AllowInsecureDevelopmentAuth).ToString().ToLowerInvariant()
|
||||
TELESRV_ADVERTISE_IP = $parsedIP.ToString()
|
||||
TELESRV_PUBLIC_BASE_URL = $PublicBaseURL
|
||||
TELESRV_PUBLIC_WEB_BASE_URL = $PublicWebBaseURL
|
||||
TELESRV_SERVER_HOST_NETWORK = (-not $BridgeNetwork).ToString().ToLowerInvariant()
|
||||
TELESRV_SFU_ADVERTISE_IP = $parsedIP.ToString()
|
||||
TELESRV_TURN_ENABLE = $turnEnabled
|
||||
TELESRV_TURN_ADVERTISE_IP = $turnAdvertiseIP
|
||||
TELESRV_LIVESTREAM_RTMP_URL = "rtmp://${rtmpHost}:2400/live"
|
||||
TELESRV_PUBLIC_BIND_IP = $publicBindIP
|
||||
TELESRV_PUBLIC_LISTEN_HOST = $publicListenHost
|
||||
TELESRV_LOCAL_BIND_IP = $localBindIP
|
||||
TELESRV_LOCAL_LISTEN_HOST = $localListenHost
|
||||
TELESRV_SERVER_HEALTH_IP = $localBindIP
|
||||
TELESRV_SERVER_HEALTH_URL_HOST = $serverHealthURLHost
|
||||
TELESRV_ADMIN_BIND_IP = $adminListenIP
|
||||
TELESRV_ADMIN_LISTEN_HOST = $adminListenHost
|
||||
TELESRV_ADMIN_HEALTH_IP = $adminHealthIP
|
||||
}
|
||||
|
||||
$content = [IO.File]::ReadAllText($templatePath)
|
||||
foreach ($entry in $values.GetEnumerator()) {
|
||||
$pattern = "(?m)^$([Regex]::Escape($entry.Key))=.*$"
|
||||
if (-not [Regex]::IsMatch($content, $pattern)) { throw "Template is missing $($entry.Key)." }
|
||||
$replacement = ("{0}={1}" -f $entry.Key, $entry.Value).Replace('$', '$$')
|
||||
$content = [Regex]::Replace($content, $pattern, $replacement)
|
||||
}
|
||||
|
||||
function Protect-SecretFile {
|
||||
param([string]$Path)
|
||||
|
||||
if ($env:OS -eq "Windows_NT") {
|
||||
$owner = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
||||
$acl = New-Object System.Security.AccessControl.FileSecurity
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$acl.SetOwner($owner)
|
||||
$identities = @(
|
||||
$owner,
|
||||
(New-Object System.Security.Principal.SecurityIdentifier("S-1-5-18")),
|
||||
(New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544"))
|
||||
)
|
||||
foreach ($identity in $identities) {
|
||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
$identity,
|
||||
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
||||
[System.Security.AccessControl.AccessControlType]::Allow
|
||||
)
|
||||
[void]$acl.AddAccessRule($rule)
|
||||
}
|
||||
Set-Acl -LiteralPath $Path -AclObject $acl
|
||||
return
|
||||
}
|
||||
|
||||
& chmod 600 -- $Path
|
||||
if ($LASTEXITCODE -ne 0) { throw "chmod 600 failed for $Path" }
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($outputPath, "write owner-only Docker deployment environment")) {
|
||||
$temporaryPath = "$outputPath.tmp.$PID"
|
||||
try {
|
||||
[IO.File]::WriteAllText($temporaryPath, $content, (New-Object Text.UTF8Encoding($false)))
|
||||
Protect-SecretFile -Path $temporaryPath
|
||||
Move-Item -LiteralPath $temporaryPath -Destination $outputPath
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $temporaryPath) { Remove-Item -LiteralPath $temporaryPath -Force }
|
||||
}
|
||||
Write-Host "Created $outputPath with generated deployment credentials."
|
||||
}
|
||||
268
scripts/new-docker-env.sh
Executable file
268
scripts/new-docker-env.sh
Executable file
|
|
@ -0,0 +1,268 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./scripts/new-docker-env.sh --advertise-ip IP [options]
|
||||
|
||||
Options:
|
||||
--public-base-url URL
|
||||
--public-web-base-url URL
|
||||
--admin-bind-ip IP
|
||||
--host-network Bind the monolith's media sockets directly on the host (default).
|
||||
--bridge-network Publish a bounded TURN relay range through Docker.
|
||||
--allow-insecure-development-auth
|
||||
--output PATH
|
||||
--help
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'new-docker-env: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
validate_http_url() {
|
||||
name=$1
|
||||
value=$2
|
||||
case "$value" in
|
||||
*[[:space:]]*) die "$name must not contain whitespace" ;;
|
||||
esac
|
||||
case "$value" in
|
||||
http://*|https://*) ;;
|
||||
*) die "$name must be an absolute HTTP(S) URL" ;;
|
||||
esac
|
||||
authority=${value#*://}
|
||||
authority=${authority%%/*}
|
||||
authority=${authority%%\?*}
|
||||
authority=${authority%%\#*}
|
||||
[ -n "$authority" ] || die "$name must include a host"
|
||||
case "$authority" in
|
||||
*@*) die "$name must not contain embedded credentials" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
url_host() {
|
||||
value=$1
|
||||
authority=${value#*://}
|
||||
authority=${authority%%/*}
|
||||
authority=${authority%%\?*}
|
||||
authority=${authority%%\#*}
|
||||
case "$authority" in
|
||||
\[*\]*) host=${authority#\[}; host=${host%%\]*} ;;
|
||||
*) host=${authority%%:*} ;;
|
||||
esac
|
||||
printf '%s\n' "$host"
|
||||
}
|
||||
|
||||
validate_ipv4() {
|
||||
awk -F. '
|
||||
NF != 4 { exit 1 }
|
||||
{ for (i = 1; i <= 4; i++) if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1 }
|
||||
' <<EOF
|
||||
$1
|
||||
EOF
|
||||
}
|
||||
|
||||
validate_ipv6() {
|
||||
case "$1" in *[!0-9A-Fa-f:.]*|'') return 1 ;; esac
|
||||
if command -v perl >/dev/null 2>&1; then
|
||||
perl -MSocket=AF_INET6,inet_pton -e 'exit inet_pton(AF_INET6, $ARGV[0]) ? 0 : 1' "$1"
|
||||
return
|
||||
fi
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 -c 'import ipaddress,sys; ipaddress.IPv6Address(sys.argv[1])' "$1"
|
||||
return
|
||||
fi
|
||||
die "validating an IPv6 address requires perl or python3"
|
||||
}
|
||||
|
||||
random_hex() {
|
||||
openssl rand -hex "$1"
|
||||
}
|
||||
|
||||
script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
repo_root=$(CDPATH='' cd -- "$script_dir/.." && pwd)
|
||||
template_path="$repo_root/deploy/docker/.env.example"
|
||||
output_path="$repo_root/deploy/docker/.env"
|
||||
|
||||
advertise_ip=
|
||||
public_base_url=
|
||||
public_web_base_url=
|
||||
admin_bind_ip=127.0.0.1
|
||||
server_host_network=true
|
||||
allow_insecure=false
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--advertise-ip)
|
||||
[ "$#" -ge 2 ] || die "$1 requires a value"
|
||||
advertise_ip=$2
|
||||
shift 2
|
||||
;;
|
||||
--public-base-url)
|
||||
[ "$#" -ge 2 ] || die "$1 requires a value"
|
||||
public_base_url=$2
|
||||
shift 2
|
||||
;;
|
||||
--public-web-base-url)
|
||||
[ "$#" -ge 2 ] || die "$1 requires a value"
|
||||
public_web_base_url=$2
|
||||
shift 2
|
||||
;;
|
||||
--admin-bind-ip)
|
||||
[ "$#" -ge 2 ] || die "$1 requires a value"
|
||||
admin_bind_ip=$2
|
||||
shift 2
|
||||
;;
|
||||
--host-network) server_host_network=true; shift ;;
|
||||
--bridge-network) server_host_network=false; shift ;;
|
||||
--allow-insecure-development-auth) allow_insecure=true; shift ;;
|
||||
--output)
|
||||
[ "$#" -ge 2 ] || die "$1 requires a value"
|
||||
output_path=$2
|
||||
shift 2
|
||||
;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) die "unknown argument: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$advertise_ip" ] || die "--advertise-ip is required"
|
||||
[ -f "$template_path" ] || die "Docker environment template not found: $template_path"
|
||||
[ ! -e "$output_path" ] || die "$output_path already exists; initialization never overwrites live credentials"
|
||||
command -v openssl >/dev/null 2>&1 || die "openssl is required"
|
||||
|
||||
is_ipv6=false
|
||||
case "$advertise_ip" in
|
||||
*:*) validate_ipv6 "$advertise_ip" || die "--advertise-ip is not a valid IPv6 address"; is_ipv6=true ;;
|
||||
*) validate_ipv4 "$advertise_ip" || die "--advertise-ip is not a valid IPv4 address" ;;
|
||||
esac
|
||||
case "$admin_bind_ip" in
|
||||
*:*) validate_ipv6 "$admin_bind_ip" || die "--admin-bind-ip is not a valid IPv6 address" ;;
|
||||
*) validate_ipv4 "$admin_bind_ip" || die "--admin-bind-ip is not a valid IPv4 address" ;;
|
||||
esac
|
||||
|
||||
is_loopback=false
|
||||
if [ "$is_ipv6" = true ]; then
|
||||
case "$advertise_ip" in ::1|0:0:0:0:0:0:0:1) is_loopback=true ;; esac
|
||||
else
|
||||
case "$advertise_ip" in 127.*) is_loopback=true ;; esac
|
||||
fi
|
||||
|
||||
if [ -z "$public_base_url" ]; then
|
||||
[ "$is_loopback" = true ] || die "--public-base-url is required when --advertise-ip is not loopback"
|
||||
if [ "$is_ipv6" = true ]; then public_base_url='http://[::1]:2401'; else public_base_url='http://127.0.0.1:2401'; fi
|
||||
fi
|
||||
[ -n "$public_web_base_url" ] || public_web_base_url=$public_base_url
|
||||
validate_http_url "--public-base-url" "$public_base_url"
|
||||
validate_http_url "--public-web-base-url" "$public_web_base_url"
|
||||
|
||||
if [ "$is_loopback" = false ] && [ "$allow_insecure" = false ]; then
|
||||
die "Internet/LAN development-code auth requires --allow-insecure-development-auth; for production generate on loopback, then configure the webhook provider before startup"
|
||||
fi
|
||||
|
||||
public_bind_ip=0.0.0.0
|
||||
local_bind_ip=127.0.0.1
|
||||
if [ "$is_ipv6" = true ]; then public_bind_ip=::; local_bind_ip=::1; fi
|
||||
if [ "$is_loopback" = true ]; then
|
||||
public_bind_ip=$advertise_ip
|
||||
local_bind_ip=$advertise_ip
|
||||
elif [ "${public_base_url%%:*}" = http ] && [ "$(url_host "$public_base_url")" = "$advertise_ip" ]; then
|
||||
local_bind_ip=$advertise_ip
|
||||
fi
|
||||
|
||||
public_listen_host=$public_bind_ip
|
||||
local_listen_host=$local_bind_ip
|
||||
server_health_url_host=$local_bind_ip
|
||||
if [ "$is_ipv6" = true ]; then
|
||||
public_listen_host="[$public_bind_ip]"
|
||||
local_listen_host="[$local_bind_ip]"
|
||||
server_health_url_host="[$local_bind_ip]"
|
||||
fi
|
||||
|
||||
turn_enable=true
|
||||
turn_advertise_ip=$advertise_ip
|
||||
if [ "$is_ipv6" = true ]; then
|
||||
turn_enable=false
|
||||
turn_advertise_ip=127.0.0.1
|
||||
fi
|
||||
|
||||
admin_health_ip=$admin_bind_ip
|
||||
case "$admin_bind_ip" in 0.0.0.0) admin_health_ip=127.0.0.1 ;; ::) admin_health_ip=::1 ;; esac
|
||||
admin_listen_host=$admin_bind_ip
|
||||
case "$admin_bind_ip" in *:*) admin_listen_host="[$admin_bind_ip]" ;; esac
|
||||
rtmp_host=$advertise_ip
|
||||
[ "$is_ipv6" = true ] && rtmp_host="[$rtmp_host]"
|
||||
|
||||
build_commit=$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || printf unknown)
|
||||
build_branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || printf unknown)
|
||||
build_tree_state=unknown
|
||||
if git -C "$repo_root" status --porcelain >/dev/null 2>&1; then
|
||||
if [ -n "$(git -C "$repo_root" status --porcelain)" ]; then build_tree_state=dirty; else build_tree_state=clean; fi
|
||||
fi
|
||||
|
||||
POSTGRES_PASSWORD=$(random_hex 24)
|
||||
TELESRV_BUILD_COMMIT=$build_commit
|
||||
TELESRV_BUILD_BRANCH=$build_branch
|
||||
TELESRV_BUILD_TREE_STATE=$build_tree_state
|
||||
TELESRV_BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
TELESRV_POSTGRES_DSN="postgres://telesrv:$POSTGRES_PASSWORD@127.0.0.1:15432/telesrv_main?sslmode=disable"
|
||||
TELESRV_REDIS_PASSWORD=$(random_hex 32)
|
||||
TELESRV_ADMIN_API_TOKEN=$(random_hex 32)
|
||||
TELESRV_ADMIN_UI_PASSWORD=$(random_hex 24)
|
||||
TELESRV_ADMIN_SESSION_KEY=$(random_hex 32)
|
||||
TELESRV_TURN_SECRET=$(random_hex 32)
|
||||
TELESRV_OTP_WEBHOOK_SECRET=$(random_hex 32)
|
||||
TELESRV_ALLOW_INSECURE_DEVELOPMENT_AUTH=$allow_insecure
|
||||
[ "$is_loopback" = true ] && TELESRV_ALLOW_INSECURE_DEVELOPMENT_AUTH=true
|
||||
TELESRV_ADVERTISE_IP=$advertise_ip
|
||||
TELESRV_PUBLIC_BASE_URL=$public_base_url
|
||||
TELESRV_PUBLIC_WEB_BASE_URL=$public_web_base_url
|
||||
TELESRV_SERVER_HOST_NETWORK=$server_host_network
|
||||
TELESRV_SFU_ADVERTISE_IP=$advertise_ip
|
||||
TELESRV_TURN_ENABLE=$turn_enable
|
||||
TELESRV_TURN_ADVERTISE_IP=$turn_advertise_ip
|
||||
TELESRV_LIVESTREAM_RTMP_URL="rtmp://$rtmp_host:2400/live"
|
||||
TELESRV_PUBLIC_BIND_IP=$public_bind_ip
|
||||
TELESRV_PUBLIC_LISTEN_HOST=$public_listen_host
|
||||
TELESRV_LOCAL_BIND_IP=$local_bind_ip
|
||||
TELESRV_LOCAL_LISTEN_HOST=$local_listen_host
|
||||
TELESRV_SERVER_HEALTH_IP=$local_bind_ip
|
||||
TELESRV_SERVER_HEALTH_URL_HOST=$server_health_url_host
|
||||
TELESRV_ADMIN_BIND_IP=$admin_bind_ip
|
||||
TELESRV_ADMIN_LISTEN_HOST=$admin_listen_host
|
||||
TELESRV_ADMIN_HEALTH_IP=$admin_health_ip
|
||||
|
||||
replacement_keys='TELESRV_BUILD_COMMIT TELESRV_BUILD_BRANCH TELESRV_BUILD_TREE_STATE TELESRV_BUILD_DATE POSTGRES_PASSWORD TELESRV_POSTGRES_DSN TELESRV_REDIS_PASSWORD TELESRV_ADMIN_API_TOKEN TELESRV_ADMIN_UI_PASSWORD TELESRV_ADMIN_SESSION_KEY TELESRV_TURN_SECRET TELESRV_OTP_WEBHOOK_SECRET TELESRV_ALLOW_INSECURE_DEVELOPMENT_AUTH TELESRV_ADVERTISE_IP TELESRV_PUBLIC_BASE_URL TELESRV_PUBLIC_WEB_BASE_URL TELESRV_SERVER_HOST_NETWORK TELESRV_SFU_ADVERTISE_IP TELESRV_TURN_ENABLE TELESRV_TURN_ADVERTISE_IP TELESRV_LIVESTREAM_RTMP_URL TELESRV_PUBLIC_BIND_IP TELESRV_PUBLIC_LISTEN_HOST TELESRV_LOCAL_BIND_IP TELESRV_LOCAL_LISTEN_HOST TELESRV_SERVER_HEALTH_IP TELESRV_SERVER_HEALTH_URL_HOST TELESRV_ADMIN_BIND_IP TELESRV_ADMIN_LISTEN_HOST TELESRV_ADMIN_HEALTH_IP'
|
||||
export POSTGRES_PASSWORD TELESRV_BUILD_COMMIT TELESRV_BUILD_BRANCH TELESRV_BUILD_TREE_STATE TELESRV_BUILD_DATE
|
||||
export TELESRV_POSTGRES_DSN TELESRV_REDIS_PASSWORD TELESRV_ADMIN_API_TOKEN TELESRV_ADMIN_UI_PASSWORD
|
||||
export TELESRV_ADMIN_SESSION_KEY TELESRV_TURN_SECRET TELESRV_OTP_WEBHOOK_SECRET TELESRV_ALLOW_INSECURE_DEVELOPMENT_AUTH
|
||||
export TELESRV_ADVERTISE_IP TELESRV_PUBLIC_BASE_URL TELESRV_PUBLIC_WEB_BASE_URL TELESRV_SERVER_HOST_NETWORK
|
||||
export TELESRV_SFU_ADVERTISE_IP TELESRV_TURN_ENABLE TELESRV_TURN_ADVERTISE_IP TELESRV_LIVESTREAM_RTMP_URL
|
||||
export TELESRV_PUBLIC_BIND_IP TELESRV_PUBLIC_LISTEN_HOST TELESRV_LOCAL_BIND_IP TELESRV_LOCAL_LISTEN_HOST
|
||||
export TELESRV_SERVER_HEALTH_IP TELESRV_SERVER_HEALTH_URL_HOST TELESRV_ADMIN_BIND_IP TELESRV_ADMIN_LISTEN_HOST TELESRV_ADMIN_HEALTH_IP
|
||||
|
||||
output_dir=$(dirname -- "$output_path")
|
||||
[ -d "$output_dir" ] || die "output directory does not exist: $output_dir"
|
||||
temporary_path=$(mktemp "$output_path.tmp.XXXXXX")
|
||||
cleanup() { [ ! -e "$temporary_path" ] || unlink "$temporary_path"; }
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
awk -v keys="$replacement_keys" '
|
||||
BEGIN { count = split(keys, list, " "); for (i = 1; i <= count; i++) wanted[list[i]] = 1 }
|
||||
{
|
||||
separator = index($0, "=")
|
||||
key = separator ? substr($0, 1, separator - 1) : ""
|
||||
if (key in wanted) { print key "=" ENVIRON[key]; seen[key] = 1 } else print
|
||||
}
|
||||
END {
|
||||
for (key in wanted) if (!(key in seen)) { print "template is missing " key > "/dev/stderr"; missing = 1 }
|
||||
exit missing
|
||||
}
|
||||
' "$template_path" >"$temporary_path"
|
||||
chmod 0600 "$temporary_path"
|
||||
mv "$temporary_path" "$output_path"
|
||||
trap - EXIT HUP INT TERM
|
||||
printf 'Created %s with owner-only permissions.\n' "$output_path"
|
||||
|
|
@ -12,6 +12,9 @@ hidden, and verifies that the port is listening again.
|
|||
param(
|
||||
[string]$Listen = "0.0.0.0:2398",
|
||||
[string]$AdvertiseIP,
|
||||
[string]$PostgresDSN,
|
||||
[string]$PostgresContainer = "telesrv-postgres",
|
||||
[string]$PostgresUser = "telesrv",
|
||||
[string]$ExePath,
|
||||
[string]$LogDir,
|
||||
[int]$HealthTimeoutSeconds = 20,
|
||||
|
|
@ -221,10 +224,30 @@ New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
|||
|
||||
Push-Location $RepoRoot
|
||||
try {
|
||||
$branch = Get-GitOutput @("branch", "--show-current")
|
||||
$postgresDatabase = "external"
|
||||
if ([string]::IsNullOrWhiteSpace($PostgresDSN)) {
|
||||
switch ($branch) {
|
||||
"main" { $postgresDatabase = "telesrv_main" }
|
||||
"v2" { $postgresDatabase = "telesrv_v2" }
|
||||
default {
|
||||
throw "Branch '$branch' has no implicit local PostgreSQL database. Pass -PostgresDSN explicitly."
|
||||
}
|
||||
}
|
||||
Write-Step "Resolve branch-isolated PostgreSQL"
|
||||
& (Join-Path $PSScriptRoot "ensure-local-databases.ps1") `
|
||||
-PostgresContainer $PostgresContainer `
|
||||
-DbUser $PostgresUser
|
||||
$PostgresDSN = "postgres://telesrv:telesrv@127.0.0.1:5432/$postgresDatabase`?sslmode=disable"
|
||||
Write-Host "[ok] branch=$branch database=$postgresDatabase"
|
||||
} else {
|
||||
Write-Step "Use explicit PostgreSQL DSN"
|
||||
Write-Host "[ok] branch=$branch database=explicit-override"
|
||||
}
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
Write-Step "Build telesrv"
|
||||
$commit = Get-GitOutput @("rev-parse", "HEAD")
|
||||
$branch = Get-GitOutput @("branch", "--show-current")
|
||||
$dirty = Get-GitOutput @("status", "--porcelain", "--untracked-files=no") -Default ""
|
||||
$treeState = "clean"
|
||||
if ($dirty.Length -gt 0) {
|
||||
|
|
@ -293,6 +316,7 @@ try {
|
|||
$stderrPath = Join-Path $LogDir "telesrv-$stamp.err.log"
|
||||
|
||||
$env:TELESRV_LISTEN = $Listen
|
||||
$env:TELESRV_POSTGRES_DSN = $PostgresDSN
|
||||
if ($AdvertiseIP) {
|
||||
$env:TELESRV_ADVERTISE_IP = $AdvertiseIP
|
||||
}
|
||||
|
|
@ -338,6 +362,7 @@ try {
|
|||
Pid = $proc.Id
|
||||
Listen = $Listen
|
||||
AdvertiseIP = $env:TELESRV_ADVERTISE_IP
|
||||
PostgresDatabase = $postgresDatabase
|
||||
Exe = $ExePath
|
||||
Stdout = $stdoutPath
|
||||
Stderr = $stderrPath
|
||||
|
|
|
|||
90
scripts/start-docker.ps1
Normal file
90
scripts/start-docker.ps1
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[string]$AdvertiseIP = "",
|
||||
|
||||
[Parameter()]
|
||||
[string]$PublicBaseURL = "",
|
||||
|
||||
[Parameter()]
|
||||
[string]$PublicWebBaseURL = "",
|
||||
|
||||
[Parameter()]
|
||||
[string]$AdminBindIP = "",
|
||||
|
||||
[Parameter()]
|
||||
[switch]$HostNetwork,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$BridgeNetwork,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$AllowInsecureDevelopmentAuth,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$Build
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($HostNetwork -and $BridgeNetwork) { throw "HostNetwork and BridgeNetwork are mutually exclusive." }
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$dockerDir = Join-Path $repoRoot "deploy\docker"
|
||||
$composePath = Join-Path $dockerDir "compose.yaml"
|
||||
$envPath = Join-Path $dockerDir ".env"
|
||||
$generatorPath = Join-Path $PSScriptRoot "new-docker-env.ps1"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $envPath -PathType Leaf)) {
|
||||
if ([string]::IsNullOrWhiteSpace($AdvertiseIP)) { $AdvertiseIP = "127.0.0.1" }
|
||||
$generatorArguments = @{ AdvertiseIP = $AdvertiseIP }
|
||||
if (-not [string]::IsNullOrWhiteSpace($PublicBaseURL)) { $generatorArguments.PublicBaseURL = $PublicBaseURL }
|
||||
if (-not [string]::IsNullOrWhiteSpace($PublicWebBaseURL)) { $generatorArguments.PublicWebBaseURL = $PublicWebBaseURL }
|
||||
if (-not [string]::IsNullOrWhiteSpace($AdminBindIP)) { $generatorArguments.AdminBindIP = $AdminBindIP }
|
||||
if ($HostNetwork) { $generatorArguments.HostNetwork = $true }
|
||||
if ($BridgeNetwork) { $generatorArguments.BridgeNetwork = $true }
|
||||
if ($AllowInsecureDevelopmentAuth) { $generatorArguments.AllowInsecureDevelopmentAuth = $true }
|
||||
& $generatorPath @generatorArguments
|
||||
}
|
||||
elseif ($PSBoundParameters.Keys | Where-Object { $_ -notin @("Build") }) {
|
||||
Write-Warning "deploy/docker/.env already exists; initialization options were ignored to preserve credentials and deployment identity."
|
||||
}
|
||||
|
||||
$deployment = @{}
|
||||
foreach ($line in Get-Content -LiteralPath $envPath) {
|
||||
if ($line -match '^([A-Z0-9_]+)=(.*)$') { $deployment[$Matches[1]] = $Matches[2] }
|
||||
}
|
||||
if ($deployment['TELESRV_DEPLOYMENT_PROFILE'] -ne 'main-monolith-v1') {
|
||||
throw "$envPath belongs to an older or different topology; move it aside and rerun so credentials are regenerated safely."
|
||||
}
|
||||
|
||||
$composeBase = @("compose", "--project-directory", $dockerDir, "--env-file", $envPath, "--file", $composePath)
|
||||
$hostNetworkValue = $deployment['TELESRV_SERVER_HOST_NETWORK']
|
||||
if ([string]::IsNullOrWhiteSpace($hostNetworkValue)) { $hostNetworkValue = "true" }
|
||||
if ($hostNetworkValue -ne "true" -and $hostNetworkValue -ne "false") { throw "TELESRV_SERVER_HOST_NETWORK must be true or false." }
|
||||
if ($hostNetworkValue -eq "false") { $composeBase += @("--file", (Join-Path $dockerDir "compose.bridge-network.yaml")) }
|
||||
|
||||
function Invoke-Compose {
|
||||
param([string[]]$Arguments)
|
||||
& docker @composeBase @Arguments
|
||||
if ($LASTEXITCODE -ne 0) { throw "docker compose $($Arguments -join ' ') failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
|
||||
Invoke-Compose -Arguments @("config", "--quiet")
|
||||
if ($Build) { Invoke-Compose -Arguments @("build", "--pull") } else { Invoke-Compose -Arguments @("pull") }
|
||||
try { Invoke-Compose -Arguments @("up", "--detach", "--no-build", "--wait", "--wait-timeout", "600") }
|
||||
catch { & docker @composeBase logs --no-color --tail 160; throw }
|
||||
|
||||
Invoke-Compose -Arguments @("ps", "--all")
|
||||
Write-Host "gramsrv main Docker stack is ready. Configuration: $envPath"
|
||||
if ($deployment['TELESRV_PHONE_CODE_DELIVERY_PROVIDER'] -eq 'development') { Write-Host "Development login code: $($deployment['TELESRV_DEV_AUTH_CODE'])" }
|
||||
Write-Host "MTProto: $($deployment['TELESRV_ADVERTISE_IP']):$($deployment['TELESRV_SERVER_PORT'])"
|
||||
if ($deployment['TELESRV_TURN_ENABLE'] -eq 'true') {
|
||||
Write-Host "TURN/STUN: udp://$($deployment['TELESRV_TURN_ADVERTISE_IP']):$($deployment['TELESRV_TURN_UDP_PORT'])"
|
||||
$turnRelayMaxPort = $deployment['TELESRV_TURN_RELAY_MAX_PORT']
|
||||
if ($hostNetworkValue -eq 'false') { $turnRelayMaxPort = $deployment['TELESRV_TURN_BRIDGE_RELAY_MAX_PORT'] }
|
||||
Write-Host "TURN relay UDP range: $($deployment['TELESRV_TURN_RELAY_MIN_PORT'])-$turnRelayMaxPort"
|
||||
}
|
||||
$adminHost = $deployment['TELESRV_ADMIN_BIND_IP']
|
||||
if ($adminHost -eq "0.0.0.0" -or $adminHost -eq "::") { $adminHost = $deployment['TELESRV_ADVERTISE_IP'] }
|
||||
if ($adminHost.Contains(":")) { $adminHost = "[${adminHost}]" }
|
||||
Write-Host "Admin UI: http://${adminHost}:$($deployment['TELESRV_ADMIN_PORT']) (password is stored in $envPath)"
|
||||
103
scripts/start-docker.sh
Executable file
103
scripts/start-docker.sh
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./scripts/start-docker.sh [options]
|
||||
|
||||
Initialization options (used only when deploy/docker/.env is absent):
|
||||
--advertise-ip IP
|
||||
--public-base-url URL
|
||||
--public-web-base-url URL
|
||||
--admin-bind-ip IP
|
||||
--host-network Direct host networking for the monolith (default).
|
||||
--bridge-network Docker port publishing compatibility mode.
|
||||
--allow-insecure-development-auth
|
||||
|
||||
Other options:
|
||||
--build Build local images instead of pulling published images.
|
||||
--help
|
||||
EOF
|
||||
}
|
||||
|
||||
script_dir=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo_root=$(CDPATH='' cd -- "$script_dir/.." && pwd)
|
||||
docker_dir="$repo_root/deploy/docker"
|
||||
compose_path="$docker_dir/compose.yaml"
|
||||
env_path="$docker_dir/.env"
|
||||
generator_path="$script_dir/new-docker-env.sh"
|
||||
|
||||
advertise_ip=
|
||||
public_base_url=
|
||||
public_web_base_url=
|
||||
admin_bind_ip=
|
||||
network_mode=
|
||||
allow_insecure=false
|
||||
build=false
|
||||
initialization_options=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--advertise-ip) [[ $# -ge 2 ]] || { printf '%s requires a value\n' "$1" >&2; exit 1; }; advertise_ip=$2; initialization_options=true; shift 2 ;;
|
||||
--public-base-url) [[ $# -ge 2 ]] || { printf '%s requires a value\n' "$1" >&2; exit 1; }; public_base_url=$2; initialization_options=true; shift 2 ;;
|
||||
--public-web-base-url) [[ $# -ge 2 ]] || { printf '%s requires a value\n' "$1" >&2; exit 1; }; public_web_base_url=$2; initialization_options=true; shift 2 ;;
|
||||
--admin-bind-ip) [[ $# -ge 2 ]] || { printf '%s requires a value\n' "$1" >&2; exit 1; }; admin_bind_ip=$2; initialization_options=true; shift 2 ;;
|
||||
--host-network) network_mode=host; initialization_options=true; shift ;;
|
||||
--bridge-network) network_mode=bridge; initialization_options=true; shift ;;
|
||||
--allow-insecure-development-auth) allow_insecure=true; initialization_options=true; shift ;;
|
||||
--build) build=true; shift ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) printf 'start-docker: unknown argument: %s\n' "$1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$env_path" ]]; then
|
||||
[[ -n "$advertise_ip" ]] || advertise_ip=127.0.0.1
|
||||
generator_args=(--advertise-ip "$advertise_ip")
|
||||
[[ -z "$public_base_url" ]] || generator_args+=(--public-base-url "$public_base_url")
|
||||
[[ -z "$public_web_base_url" ]] || generator_args+=(--public-web-base-url "$public_web_base_url")
|
||||
[[ -z "$admin_bind_ip" ]] || generator_args+=(--admin-bind-ip "$admin_bind_ip")
|
||||
[[ "$network_mode" != host ]] || generator_args+=(--host-network)
|
||||
[[ "$network_mode" != bridge ]] || generator_args+=(--bridge-network)
|
||||
[[ "$allow_insecure" = false ]] || generator_args+=(--allow-insecure-development-auth)
|
||||
"$generator_path" "${generator_args[@]}"
|
||||
elif [[ "$initialization_options" = true ]]; then
|
||||
printf 'start-docker: deploy/docker/.env already exists; initialization options were ignored to preserve credentials and deployment identity.\n' >&2
|
||||
fi
|
||||
|
||||
env_value() { awk -F= -v key="$1" '$1 == key { print substr($0, length(key) + 2); exit }' "$env_path"; }
|
||||
|
||||
if [[ "$(env_value TELESRV_DEPLOYMENT_PROFILE)" != main-monolith-v1 ]]; then
|
||||
printf 'start-docker: %s belongs to an older or different topology; move it aside and rerun so credentials are regenerated safely.\n' "$env_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
configured_host_network=$(env_value TELESRV_SERVER_HOST_NETWORK)
|
||||
[[ -n "$configured_host_network" ]] || configured_host_network=true
|
||||
case "$configured_host_network" in true|false) ;; *) printf 'start-docker: TELESRV_SERVER_HOST_NETWORK must be true or false\n' >&2; exit 1 ;; esac
|
||||
|
||||
compose=(docker compose --project-directory "$docker_dir" --env-file "$env_path" --file "$compose_path")
|
||||
if [[ "$configured_host_network" = false ]]; then compose+=(--file "$docker_dir/compose.bridge-network.yaml"); fi
|
||||
"${compose[@]}" version >/dev/null
|
||||
"${compose[@]}" config --quiet
|
||||
|
||||
if [[ "$build" = true ]]; then "${compose[@]}" build --pull; else "${compose[@]}" pull; fi
|
||||
if ! "${compose[@]}" up --detach --no-build --wait --wait-timeout 600; then
|
||||
"${compose[@]}" logs --no-color --tail 160 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${compose[@]}" ps --all
|
||||
printf 'gramsrv main Docker stack is ready. Configuration: %s\n' "$env_path"
|
||||
if [[ "$(env_value TELESRV_PHONE_CODE_DELIVERY_PROVIDER)" = development ]]; then printf 'Development login code: %s\n' "$(env_value TELESRV_DEV_AUTH_CODE)"; fi
|
||||
printf 'MTProto: %s:%s\n' "$(env_value TELESRV_ADVERTISE_IP)" "$(env_value TELESRV_SERVER_PORT)"
|
||||
if [[ "$(env_value TELESRV_TURN_ENABLE)" = true ]]; then
|
||||
printf 'TURN/STUN: udp://%s:%s\n' "$(env_value TELESRV_TURN_ADVERTISE_IP)" "$(env_value TELESRV_TURN_UDP_PORT)"
|
||||
turn_relay_max_port=$(env_value TELESRV_TURN_RELAY_MAX_PORT)
|
||||
if [[ "$configured_host_network" = false ]]; then turn_relay_max_port=$(env_value TELESRV_TURN_BRIDGE_RELAY_MAX_PORT); fi
|
||||
printf 'TURN relay UDP range: %s-%s\n' "$(env_value TELESRV_TURN_RELAY_MIN_PORT)" "$turn_relay_max_port"
|
||||
fi
|
||||
admin_host=$(env_value TELESRV_ADMIN_BIND_IP)
|
||||
if [[ "$admin_host" = 0.0.0.0 || "$admin_host" = :: ]]; then admin_host=$(env_value TELESRV_ADVERTISE_IP); fi
|
||||
[[ "$admin_host" != *:* ]] || admin_host="[$admin_host]"
|
||||
printf 'Admin UI: http://%s:%s (password is stored in %s)\n' "$admin_host" "$(env_value TELESRV_ADMIN_PORT)" "$env_path"
|
||||
Loading…
Add table
Add a link
Reference in a new issue