updated server install scripts

This commit is contained in:
onysd 2026-09-11 16:19:13 +03:00
parent 7ad68c3983
commit 4224b9d15c
5 changed files with 544 additions and 4 deletions

View file

@ -144,6 +144,16 @@ re-run commands from scratch every time.
Both launchers check prerequisites first (Go, Python 3, and the panel's own
dependencies via `tui-panel/requirements-panel.txt`), then start the panel.
Anything missing is installed for you rather than listed: the launcher hands off
to `scripts/install-prereqs.sh` (Arch and Ubuntu/Debian — asks for root once,
then installs Go, Python, Docker and OpenSSL) or `scripts/install-prereqs.ps1`
(Windows, via winget). Run either directly with `--dry-run` to see what it would
install without touching anything.
Docker on Windows is the one exception: its containers are Linux images, so the
daemon needs Docker Desktop's WSL2 backend — an install with a reboot and its own
licence terms. The script reports it with a link instead of starting it.
What it does:
- 🧙 **First-run setup wizard** — walks through the required `.env` values

View file

@ -71,10 +71,38 @@ if defined PYTHON (
)
if "%PROBLEMS%"=="1" (
rem Hand off to the winget installer rather than stopping at a shopping list.
rem OWPENGRAM_PREREQS_TRIED bounds this to a single retry, so something that
rem will not install ends in a message instead of a loop.
if defined OWPENGRAM_PREREQS_TRIED (
echo.
echo [ERROR] prerequisites are still missing after the install attempt -- see the messages above
pause
exit /b 1
)
if not exist "scripts\install-prereqs.ps1" (
echo.
echo [ERROR] missing prerequisites above -- install them and re-run this script
pause
exit /b 1
)
echo.
echo == Installing the missing prerequisites ==
powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\install-prereqs.ps1"
if errorlevel 1 (
echo.
echo [ERROR] could not install the prerequisites -- see the messages above
pause
exit /b 1
)
rem winget writes the new PATH to the registry, but this console still holds
rem the one it started with -- reload it so the re-check below can see what
rem was just installed instead of asking for a fresh terminal.
for /f "usebackq delims=" %%p in (`powershell -NoProfile -Command "[Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')"`) do set "PATH=%%p"
set "OWPENGRAM_PREREQS_TRIED=1"
echo.
call "%~f0" %*
exit /b !errorlevel!
)
echo.

View file

@ -86,7 +86,28 @@ fi
if [[ "$PROBLEMS" -ne 0 ]]; then
echo
# Hand off to the installer rather than stopping at a shopping list. It asks
# for confirmation once, takes root once, and installs only what is missing.
# The re-exec is what makes the freshly installed tools count: this shell
# resolved `go`/`python3` before any of them existed. OWPENGRAM_PREREQS_TRIED
# bounds it to a single retry, so a package that still will not install ends
# in a message instead of a loop.
if [[ -n "${OWPENGRAM_PREREQS_TRIED:-}" ]]; then
die "prerequisites are still missing after the install attempt -- see the messages above"
fi
if [[ ! -x scripts/install-prereqs.sh ]]; then
die "missing prerequisites above -- install them and re-run this script"
fi
echo "== Installing the missing prerequisites =="
if ! scripts/install-prereqs.sh; then
die "could not install the prerequisites -- see the messages above"
fi
# /etc/profile.d/go.sh only applies to shells started later, and the venv is
# not on PATH at all; both are picked up by the re-exec below because the
# re-check looks for .venv/bin/python first.
[[ -d /usr/local/go/bin ]] && export PATH="$PATH:/usr/local/go/bin"
echo
OWPENGRAM_PREREQS_TRIED=1 exec "$0" "$@"
fi
echo

185
scripts/install-prereqs.ps1 Normal file
View file

@ -0,0 +1,185 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Installs everything owpengram-server.bat checks for, via winget: Go, Python 3
(+ the panel's packages in a venv) and OpenSSL.
.DESCRIPTION
Docker is the deliberate exception. It is only reported, with a link: on Windows
the containers this server needs (PostgreSQL, Redis, MinIO) are Linux images, so
the daemon has to sit on a Linux kernel -- which means Docker Desktop with WSL2,
an install that wants a reboot and carries its own licence terms. That is not a
thing to start behind someone's back.
.PARAMETER DryRun
Only report what would be installed.
.PARAMETER Yes
Skip the confirmation prompt.
#>
param(
[switch]$DryRun,
[switch]$Yes
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$DockerDocs = 'https://docs.docker.com/desktop/setup/install/windows-install/'
$GoMinMinor = 25
function Write-Ok { param([string]$Text) Write-Host "[ok] $Text" -ForegroundColor Green }
function Write-Info { param([string]$Text) Write-Host "[..] $Text" -ForegroundColor Cyan }
function Write-Warn { param([string]$Text) Write-Host "[WARN] $Text" -ForegroundColor Yellow }
function Write-Err { param([string]$Text) Write-Host "[ERROR] $Text" -ForegroundColor Red }
function Test-Command { param([string]$Name) [bool](Get-Command $Name -ErrorAction SilentlyContinue) }
# winget puts new tools on the machine/user PATH, but this process was started
# with the old one -- without this the venv step cannot find the Python it just
# installed, and the closing "go version" would report the absence of Go.
function Update-PathFromRegistry {
$machine = [Environment]::GetEnvironmentVariable('Path', 'Machine')
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
$env:Path = (@($machine, $user) | Where-Object { $_ }) -join ';'
}
# python.exe on a stock Windows is often the Microsoft Store's app-execution
# alias: present on PATH, and it opens the Store instead of running anything.
# Only a real version string counts, which is the same trap owpengram-server.bat
# documents.
function Get-WorkingPython {
foreach ($candidate in @('py -3', 'python3', 'python')) {
$parts = $candidate.Split(' ')
if (-not (Test-Command $parts[0])) { continue }
try {
$out = & $parts[0] @($parts[1..($parts.Length - 1)] + '--version') 2>&1 | Out-String
} catch { continue }
if ($out -match 'Python 3\.') { return $candidate }
}
return $null
}
function Test-GoRecentEnough {
if (-not (Test-Command 'go')) { return $false }
try { $version = (& go env GOVERSION 2>$null | Out-String).Trim() } catch { return $false }
if ($version -match '^go1\.(\d+)') { return [int]$Matches[1] -ge $GoMinMinor }
return $false
}
# Same precedence owpengram-server.bat uses when it picks an interpreter: the
# venv when one exists, otherwise whatever Python is on PATH. Checking only the
# venv would report the packages missing on a machine that already has them
# installed system-wide, and build a venv nobody asked for.
function Test-PanelDepsInstalled {
$checker = Join-Path $RepoRoot 'tui-panel\check_deps.py'
$venvPython = Join-Path $RepoRoot '.venv\Scripts\python.exe'
if (Test-Path $venvPython) {
$missing = & $venvPython $checker 2>$null | Out-String
return [string]::IsNullOrWhiteSpace($missing)
}
$fallback = Get-WorkingPython
if (-not $fallback) { return $false }
$parts = $fallback.Split(' ')
$missing = & $parts[0] @($parts[1..($parts.Length - 1)] + $checker) 2>$null | Out-String
return [string]::IsNullOrWhiteSpace($missing)
}
function Install-WingetPackage {
param([string]$Id, [string]$Label)
Write-Info "Installing $Label ($Id)"
& winget install --exact --id $Id --source winget `
--accept-package-agreements --accept-source-agreements --disable-interactivity
# 0 is installed; -1978335189 is "no applicable upgrade", i.e. already current.
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne -1978335189) {
throw "winget failed to install $Id (exit $LASTEXITCODE)"
}
Update-PathFromRegistry
}
if (-not (Test-Command 'winget')) {
Write-Err 'winget is not available. Install "App Installer" from the Microsoft Store, then run this again.'
exit 1
}
# --- what is missing ---------------------------------------------------------
$python = Get-WorkingPython
$needed = [System.Collections.Generic.List[string]]::new()
if (-not (Test-GoRecentEnough)) { $needed.Add('go') }
if (-not $python) { $needed.Add('python') }
if (-not (Test-Command 'openssl')) { $needed.Add('openssl') }
if (-not (Test-PanelDepsInstalled)) { $needed.Add('pydeps') }
$dockerMissing = -not (Test-Command 'docker')
if ($needed.Count -eq 0 -and -not $dockerMissing) {
Write-Ok 'All prerequisites are already installed.'
exit 0
}
Write-Host ''
Write-Host '== Missing prerequisites ==' -ForegroundColor Yellow
foreach ($item in $needed) {
switch ($item) {
'go' { Write-Host " - Go 1.$GoMinMinor+ (builds owpengram-server and the admin panel)" }
'python' { Write-Host ' - Python 3 (runs the server-panel TUI)' }
'pydeps' { Write-Host ' - Python packages: textual, psutil, cryptography (into .\.venv)' }
'openssl' { Write-Host " - OpenSSL (exports the server's RSA public key for clients)" }
}
}
if ($dockerMissing) { Write-Host ' - Docker (runs PostgreSQL, Redis and MinIO) -- install this one yourself, see below' }
Write-Host ''
if ($DryRun) {
Write-Ok 'Dry run -- nothing was installed.'
exit 0
}
if ($needed.Count -gt 0 -and -not $Yes) {
$answer = Read-Host 'Install these now? [Y/n]'
if ($answer -and $answer -notmatch '^(y|yes)$') {
Write-Err 'cancelled'
exit 1
}
}
if ($needed -contains 'go') { Install-WingetPackage -Id 'GoLang.Go' -Label 'Go' }
if ($needed -contains 'python') { Install-WingetPackage -Id 'Python.Python.3.13' -Label 'Python 3.13' }
if ($needed -contains 'openssl') { Install-WingetPackage -Id 'ShiningLight.OpenSSL.Light' -Label 'OpenSSL' }
# --- the panel's Python packages ---------------------------------------------
# A venv rather than the machine-wide interpreter, to match what the Linux side
# does and what owpengram-server.bat already prefers once one exists.
if ($needed -contains 'pydeps' -or $needed -contains 'python') {
$python = Get-WorkingPython
if (-not $python) {
Write-Err 'Python still is not callable. Open a new terminal and run this script again.'
exit 1
}
Write-Info 'Installing the panel''s Python packages into .\.venv'
$venvPython = Join-Path $RepoRoot '.venv\Scripts\python.exe'
if (-not (Test-Path $venvPython)) {
$parts = $python.Split(' ')
& $parts[0] @($parts[1..($parts.Length - 1)] + @('-m', 'venv', (Join-Path $RepoRoot '.venv')))
if ($LASTEXITCODE -ne 0) { throw 'could not create .venv' }
}
& $venvPython -m pip install --quiet --upgrade pip
& $venvPython -m pip install --quiet -r (Join-Path $RepoRoot 'tui-panel\requirements-panel.txt')
if ($LASTEXITCODE -ne 0) { throw 'could not install the panel''s Python packages' }
Write-Ok 'Python packages installed'
}
Write-Host ''
if ($dockerMissing) {
Write-Warn 'Docker still has to be installed by hand.'
Write-Host " The server's PostgreSQL, Redis and MinIO are Linux containers, so Windows"
Write-Host ' needs Docker Desktop (it brings the WSL2 backend that runs them).'
Write-Host " $DockerDocs"
Write-Host ' Install it, reboot if it asks, then run this script again.'
Write-Host ''
}
if ($needed.Count -gt 0) {
Write-Ok 'Prerequisites are ready.'
Write-Host ' Open a new terminal so the updated PATH is picked up, then start the server with:'
Write-Host ' owpengram-server.bat'
}

296
scripts/install-prereqs.sh Executable file
View file

@ -0,0 +1,296 @@
#!/usr/bin/env bash
# Installs everything owpengram-server.sh checks for, so a fresh machine needs
# one command instead of a shopping list: Go, Python 3 (+ the panel's packages
# in a venv), Docker and OpenSSL.
#
# ./scripts/install-prereqs.sh install whatever is missing
# ./scripts/install-prereqs.sh --dry-run only report what it would install
# ./scripts/install-prereqs.sh --yes no confirmation prompt
#
# Supported: Arch (pacman) and Ubuntu/Debian (apt). Anything else is reported
# rather than guessed at -- a wrong package manager on a production box is a
# worse outcome than a clear "install these yourself".
#
# Root is taken once, up front, and held for the whole run: a sudo prompt
# appearing ten minutes in, after the user walked away, is how half-installed
# machines happen.
set -euo pipefail
cd "$(dirname "$0")/.."
REPO_ROOT="$PWD"
ok() { printf '[ok] %s\n' "$*"; }
info() { printf '[..] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*"; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
DRY_RUN=0
ASSUME_YES=0
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--yes|-y) ASSUME_YES=1 ;;
-h|--help) sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "unknown argument: $arg" ;;
esac
done
# --- distro ------------------------------------------------------------------
# ID_LIKE is what makes derivatives work without listing every one of them:
# EndeavourOS says ID_LIKE=arch, Pop!_OS and Mint say ID_LIKE=ubuntu/debian.
FAMILY=""
DISTRO_NAME="unknown"
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
DISTRO_NAME="${PRETTY_NAME:-$ID}"
case " ${ID:-} ${ID_LIKE:-} " in
*" arch "*) FAMILY="arch" ;;
*" ubuntu "*|*" debian "*) FAMILY="debian" ;;
esac
fi
[[ -n "$FAMILY" ]] || die "unsupported distribution: ${DISTRO_NAME}. Supported: Arch and Ubuntu/Debian. Install Go 1.25+, Python 3, Docker and OpenSSL by hand, then run ./owpengram-server.sh"
# --- what is missing ---------------------------------------------------------
have() { command -v "$1" >/dev/null 2>&1; }
# Go's own version gate: go.mod asks for 1.25, and a too-old toolchain fails at
# build time with a message that does not obviously point back here.
GO_MIN_MINOR=25
go_is_recent_enough() {
have go || return 1
local v minor
v="$(go env GOVERSION 2>/dev/null || true)" # e.g. go1.25.7
minor="${v#go1.}"
minor="${minor%%.*}"
[[ "$minor" =~ ^[0-9]+$ ]] && (( minor >= GO_MIN_MINOR ))
}
venv_python() { printf '%s/.venv/bin/python' "$REPO_ROOT"; }
# Same precedence owpengram-server.sh uses when it picks an interpreter: the
# venv when one exists, otherwise whatever python3 is on PATH. Checking only the
# venv would report the packages missing on a machine that already has them
# installed system-wide, and build a venv nobody asked for.
pydeps_satisfied() {
local py
if [[ -x "$(venv_python)" ]]; then
py="$(venv_python)"
elif have python3; then
py="python3"
else
return 1
fi
[[ -z "$("$py" tui-panel/check_deps.py 2>/dev/null)" ]]
}
NEEDED=()
go_is_recent_enough || NEEDED+=("go")
have python3 || NEEDED+=("python")
have docker || NEEDED+=("docker")
have openssl || NEEDED+=("openssl")
# The venv is built with python3, so it can only be settled after Python is.
pydeps_satisfied || NEEDED+=("pydeps")
if [[ ${#NEEDED[@]} -eq 0 ]]; then
ok "All prerequisites are already installed."
exit 0
fi
echo "== Missing prerequisites on ${DISTRO_NAME} =="
for item in "${NEEDED[@]}"; do
case "$item" in
go) echo " - Go 1.${GO_MIN_MINOR}+ (builds owpengram-server and the admin panel)" ;;
python) echo " - Python 3 (runs the server-panel TUI)" ;;
pydeps) echo " - Python packages: textual, psutil, cryptography (into ./.venv)" ;;
docker) echo " - Docker (runs PostgreSQL, Redis and MinIO)" ;;
openssl) echo " - OpenSSL (exports the server's RSA public key for clients)" ;;
esac
done
echo
if [[ "$DRY_RUN" -eq 1 ]]; then
ok "Dry run -- nothing was installed."
exit 0
fi
if [[ "$ASSUME_YES" -ne 1 ]]; then
read -r -p "Install these now? This needs root. [Y/n] " answer
case "${answer:-y}" in
[Yy]|[Yy][Ee][Ss]|"") ;;
*) die "cancelled" ;;
esac
fi
# Ask for the password once, then refresh the timestamp in the background so a
# long Go download or apt run does not hit a second prompt mid-way.
if [[ $EUID -ne 0 ]]; then
have sudo || die "sudo is not installed and this is not running as root"
sudo -v || die "could not acquire root"
while true; do sudo -n true 2>/dev/null; sleep 50; done &
SUDO_KEEPALIVE=$!
trap 'kill "$SUDO_KEEPALIVE" 2>/dev/null || true' EXIT
SUDO="sudo"
else
SUDO=""
fi
needs() { local x; for x in "${NEEDED[@]}"; do [[ "$x" == "$1" ]] && return 0; done; return 1; }
# --- package manager ---------------------------------------------------------
APT_UPDATED=0
apt_update_once() {
if [[ "$APT_UPDATED" -eq 0 ]]; then
$SUDO apt-get update -qq
APT_UPDATED=1
fi
}
pkg_install() {
case "$FAMILY" in
arch) $SUDO pacman -S --needed --noconfirm "$@" ;;
debian) apt_update_once; $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$@" ;;
esac
}
# --- Python + OpenSSL --------------------------------------------------------
if needs python; then
info "Installing Python 3"
case "$FAMILY" in
arch) pkg_install python ;;
# python3-venv is separate on Debian/Ubuntu and is what the panel's
# dependencies go into: both distros mark the system Python
# externally-managed, so pip into it is refused by design.
debian) pkg_install python3 python3-venv python3-pip ;;
esac
ok "Python installed: $(python3 --version)"
fi
if needs openssl; then
info "Installing OpenSSL"
pkg_install openssl
ok "OpenSSL installed: $(openssl version)"
fi
# --- Go ----------------------------------------------------------------------
# Arch ships a current Go; Debian/Ubuntu do not (24.04 is still on 1.22, below
# what go.mod needs), so there the official tarball is the only sane source.
install_go_tarball() {
local version arch tarball url tmp expected actual
version="$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n1)"
[[ "$version" == go* ]] || die "could not determine the latest Go version"
case "$(uname -m)" in
x86_64) arch="amd64" ;;
aarch64) arch="arm64" ;;
armv7l) arch="armv6l" ;;
*) die "unsupported CPU architecture for the Go tarball: $(uname -m)" ;;
esac
tarball="${version}.linux-${arch}.tar.gz"
url="https://go.dev/dl/${tarball}"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' RETURN
info "Downloading ${tarball}"
curl -fsSL "$url" -o "$tmp/$tarball"
# The published checksum lives in go.dev's release index, so the download is
# verified against the site's metadata rather than trusted on arrival.
expected="$(curl -fsSL 'https://go.dev/dl/?mode=json&include=all' \
| python3 -c 'import json,sys
want = sys.argv[1]
for release in json.load(sys.stdin):
for f in release.get("files", []):
if f.get("filename") == want:
print(f.get("sha256", ""))
raise SystemExit
' "$tarball")"
[[ -n "$expected" ]] || die "no published checksum for ${tarball}"
actual="$(sha256sum "$tmp/$tarball" | cut -d' ' -f1)"
[[ "$actual" == "$expected" ]] || die "checksum mismatch for ${tarball}: expected ${expected}, got ${actual}"
ok "Checksum verified"
$SUDO rm -rf /usr/local/go
$SUDO tar -C /usr/local -xzf "$tmp/$tarball"
# /usr/local/go/bin is not on a default PATH; drop a profile snippet so new
# shells find it. The current shell still will not, hence the note at the end.
printf 'export PATH=$PATH:/usr/local/go/bin\n' | $SUDO tee /etc/profile.d/go.sh >/dev/null
$SUDO chmod 0644 /etc/profile.d/go.sh
export PATH="$PATH:/usr/local/go/bin"
}
GO_NEEDS_NEW_SHELL=0
if needs go; then
info "Installing Go"
case "$FAMILY" in
arch) pkg_install go ;;
debian) install_go_tarball; GO_NEEDS_NEW_SHELL=1 ;;
esac
ok "Go installed: $(go version)"
fi
# --- Docker ------------------------------------------------------------------
DOCKER_NEEDS_RELOGIN=0
install_docker_debian() {
local codename repo_distro
# Derivatives (Mint, Pop!_OS) carry their own codename that Docker's repo
# does not publish; UBUNTU_CODENAME is the upstream one it does.
# shellcheck disable=SC1091
. /etc/os-release
codename="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}"
[[ -n "$codename" ]] || die "could not determine the distribution codename for Docker's repository"
case " ${ID:-} ${ID_LIKE:-} " in
*" ubuntu "*) repo_distro="ubuntu" ;;
*) repo_distro="debian" ;;
esac
pkg_install ca-certificates curl gnupg
$SUDO install -m 0755 -d /etc/apt/keyrings
$SUDO curl -fsSL "https://download.docker.com/linux/${repo_distro}/gpg" -o /etc/apt/keyrings/docker.asc
$SUDO chmod a+r /etc/apt/keyrings/docker.asc
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/%s %s stable\n' \
"$(dpkg --print-architecture)" "$repo_distro" "$codename" \
| $SUDO tee /etc/apt/sources.list.d/docker.list >/dev/null
APT_UPDATED=0 # the new repository has to be picked up
pkg_install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
}
if needs docker; then
info "Installing Docker"
case "$FAMILY" in
arch) pkg_install docker docker-compose ;;
debian) install_docker_debian ;;
esac
if have systemctl; then
$SUDO systemctl enable --now docker
ok "Docker service started"
else
warn "systemd not found -- start the Docker daemon yourself before running ./owpengram-server.sh"
fi
# Without this every docker call needs sudo, which the panel does not use.
if [[ $EUID -ne 0 ]] && ! id -nG "$USER" | tr ' ' '\n' | grep -qx docker; then
$SUDO usermod -aG docker "$USER"
DOCKER_NEEDS_RELOGIN=1
fi
ok "Docker installed: $(docker --version)"
fi
# --- the panel's Python packages ---------------------------------------------
# Always a venv, never the system interpreter: Arch and Debian both refuse pip
# into it (PEP 668), and owpengram-server.sh already prefers ./.venv when present.
if needs pydeps; then
info "Installing the panel's Python packages into ./.venv"
[[ -x "$(venv_python)" ]] || python3 -m venv "$REPO_ROOT/.venv"
"$(venv_python)" -m pip install --quiet --upgrade pip
"$(venv_python)" -m pip install --quiet -r tui-panel/requirements-panel.txt
ok "Python packages installed"
fi
echo
ok "Prerequisites are ready."
if [[ "$GO_NEEDS_NEW_SHELL" -eq 1 ]]; then
echo " Go was installed to /usr/local/go. Open a new shell, or run:"
echo " export PATH=\$PATH:/usr/local/go/bin"
fi
if [[ "$DOCKER_NEEDS_RELOGIN" -eq 1 ]]; then
echo " You were added to the 'docker' group. Log out and back in (or run"
echo " 'newgrp docker') before docker works without sudo."
fi
echo " Then start the server with: ./owpengram-server.sh"