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 Both launchers check prerequisites first (Go, Python 3, and the panel's own
dependencies via `tui-panel/requirements-panel.txt`), then start the panel. 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: What it does:
- 🧙 **First-run setup wizard** — walks through the required `.env` values - 🧙 **First-run setup wizard** — walks through the required `.env` values

View file

@ -71,11 +71,39 @@ if defined PYTHON (
) )
if "%PROBLEMS%"=="1" ( 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.
echo [ERROR] missing prerequisites above -- install them and re-run this script echo [ERROR] missing prerequisites above -- install them and re-run this script
pause pause
exit /b 1 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. echo.
echo [cfg] All prerequisites OK. echo [cfg] All prerequisites OK.

View file

@ -86,8 +86,29 @@ fi
if [[ "$PROBLEMS" -ne 0 ]]; then if [[ "$PROBLEMS" -ne 0 ]]; then
echo 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" die "missing prerequisites above -- install them and re-run this script"
fi 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 echo
echo "[cfg] All prerequisites OK." echo "[cfg] All prerequisites OK."

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"