Files
sovereign_browser/install.sh

620 lines
23 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# install.sh — curl | bash installer for sovereign_browser
#
# Installs:
# * apt runtime deps (WebKitGTK 4.1, libsoup-3, tor, build toolchain)
# * FIPS prebuilt binary from GitHub releases (source-build fallback via
# --build-fips-from-source) with CAP_NET_ADMIN
# * sovereign_browser prebuilt binary from Gitea releases (source-build fallback)
# * a `sovereign-browser` wrapper that detaches the GUI
#
# Tor and FIPS are installed by default — they are NOT optional.
#
# Usage:
# curl -fsSL <url> | bash
# curl -fsSL <url> | bash -s -- --build-from-source
# ./install.sh [options]
# --- Config --------------------------------------------------------------
GITEA_API="https://git.laantungir.net/api/v1/repos/laantungir/sovereign_browser"
GITEA_DOWNLOAD_BASE="https://git.laantungir.net/laantungir/sovereign_browser/releases/download"
SB_REPO="https://git.laantungir.net/laantungir/sovereign_browser.git"
FIPS_GITHUB_API="https://api.github.com/repos/jmcorgan/fips/releases/latest"
FIPS_REPO="https://github.com/jmcorgan/fips.git"
INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}"
BIN_NAME="sovereign_browser"
# Runtime apt packages (non-dev). git/build-essential/pkg-config are included
# because they're needed for the FIPS source build and the source-build fallback.
APT_PACKAGES=(
libwebkit2gtk-4.1-0 libsoup-3.0-0 libqrencode4 libsqlite3-0
libsecp256k1-2 libssl3t64 libcurl4t64 zlib1g
tor git build-essential pkg-config
libcap2-bin
)
# libclang-dev is only needed when building FIPS from source (bindgen).
APT_PACKAGES_FIPS_SOURCE=(libclang-dev)
# --- Output helpers ------------------------------------------------------
if [[ -t 2 && -z "${NO_COLOR:-}" ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' NC=''
fi
print_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2; }
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $*" >&2; }
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $*" >&2; }
print_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die() { print_error "$*"; exit 1; }
# --- Privilege helper ----------------------------------------------------
# Returns "sudo" if the target path is not user-writable, else "".
sudo_for() {
local target="$1"
if [[ -w "$target" ]]; then
echo ""
else
command -v sudo >/dev/null 2>&1 || die "sudo is required to write to $target but is not available."
echo "sudo"
fi
}
# --- Usage ---------------------------------------------------------------
show_usage() {
cat <<'EOF'
sovereign_browser installer
Usage: curl -fsSL <url> | bash
or: curl -fsSL <url> | bash -s -- --build-from-source
or: ./install.sh [options]
Options:
--build-from-source Build sovereign_browser from source instead of
downloading the prebuilt binary
--build-fips-from-source Build FIPS from source (cargo) instead of
downloading the prebuilt binary. Slower; needs
libclang-dev (auto-installed).
--prefix <dir> Install prefix (default: /usr/local)
--yes Skip confirmation prompts
-h, --help Show this help
EOF
}
# --- Args ----------------------------------------------------------------
BUILD_FROM_SOURCE=false
BUILD_FIPS_FROM_SOURCE=false
ASSUME_YES=false
while [[ $# -gt 0 ]]; do
case "$1" in
--build-from-source) BUILD_FROM_SOURCE=true; shift ;;
--build-fips-from-source) BUILD_FIPS_FROM_SOURCE=true; shift ;;
--prefix) INSTALL_PREFIX="${2:-}"; shift 2 ;;
--yes|-y) ASSUME_YES=true; shift ;;
-h|--help) show_usage; exit 0 ;;
*) die "Unknown option: $1 (try --help)" ;;
esac
done
# --- Platform detection --------------------------------------------------
detect_platform() {
print_info "Detecting platform..."
local os arch
os="$(uname -s)"
arch="$(uname -m)"
[[ "$os" == "Linux" ]] || die "This installer only supports Linux (got: $os)."
if [[ "$arch" != "x86_64" ]]; then
if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
die "arm64 is not supported for the prebuilt binary. Re-run with --build-from-source (arm64 source builds are untested)."
fi
die "Unsupported architecture: $arch (x86_64 only)."
fi
local distro_id="" version_id=""
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
distro_id="${ID:-}"
version_id="${VERSION_ID:-}"
fi
local distro_ok=false
if [[ "$distro_id" == "debian" ]]; then
if [[ -n "$version_id" ]] && awk -v v="$version_id" 'BEGIN{exit !(v+0 >= 13)}'; then
distro_ok=true
fi
elif [[ "$distro_id" == "ubuntu" ]]; then
if [[ -n "$version_id" ]] && awk -v v="$version_id" 'BEGIN{exit !(v+0 >= 24.04)}'; then
distro_ok=true
fi
fi
if $distro_ok; then
print_info "Distro: $distro_id $version_id (supported)."
else
print_warning "Distro '$distro_id $version_id' is not explicitly supported."
print_warning "WebKitGTK 4.1 runtime libs are required. If apt cannot find libwebkit2gtk-4.1-0, use Debian 13 (trixie)+ or Ubuntu 24.04+."
fi
}
# --- apt dependencies ----------------------------------------------------
install_deps() {
print_info "Installing apt dependencies (runtime libs, tor, build toolchain)..."
if ! command -v sudo >/dev/null 2>&1 && [[ $EUID -ne 0 ]]; then
die "sudo is required for apt-get but is not available (not running as root)."
fi
local SUDO=""
[[ $EUID -ne 0 ]] && SUDO="sudo"
$SUDO apt-get update
if ! $SUDO apt-get install -y "${APT_PACKAGES[@]}"; then
print_error "apt-get install failed. One or more packages could not be found."
print_error "This usually means your distro is too old and lacks WebKitGTK 4.1."
print_error "Use Debian 13 (trixie) or Ubuntu 24.04+, then re-run this installer."
exit 1
fi
print_success "Dependencies installed."
}
# --- FIPS ----------------------------------------------------------------
#
# FIPS is a Rust project (https://github.com/jmcorgan/fips). It publishes
# prebuilt release tarballs on GitHub Releases containing the `fips` binary
# plus tools (fipsctl, fipstop) and systemd units. We download the prebuilt
# x86_64 Linux tarball by default — much faster than a cargo build.
#
# With --build-fips-from-source, we instead clone the repo and run
# `cargo build --release` (requires Rust 1.94.1+ via rustup, and libclang-dev
# for the rustables/bindgen nftables bindings).
# Download the prebuilt FIPS tarball from GitHub Releases and extract the
# `fips` binary. Outputs a stable binary path on stdout (the file is copied
# to a caller-managed temp file so it survives after this function returns).
# Returns non-zero on failure so the caller can fall back to a source build.
download_fips_prebuilt() {
print_info "Querying GitHub for latest FIPS release..."
local api_json tag asset_url
if ! api_json="$(curl -fsSL "$FIPS_GITHUB_API" 2>/dev/null)"; then
print_warning "Could not reach FIPS GitHub releases API."
return 1
fi
tag="$(printf '%s' "$api_json" | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/')"
if [[ -z "$tag" ]]; then
print_warning "Could not parse tag_name from FIPS GitHub release JSON."
return 1
fi
# Find the linux-x86_64 tarball asset URL.
asset_url="$(printf '%s' "$api_json" | grep -oE '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*linux-x86_64\.tar\.gz"' | head -1 | sed -E 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/')"
if [[ -z "$asset_url" ]]; then
print_warning "No linux-x86_64 tarball found in FIPS release $tag."
return 1
fi
# Use a temp dir for download + extraction. We do NOT use a RETURN trap
# here because the caller needs to copy the binary out after this
# function returns — a RETURN trap would delete the file first. Instead
# we copy the binary to a stable temp file and clean up the extraction
# dir ourselves before returning.
local tmp fips_bin stable
tmp="$(mktemp -d -t fips_dl.XXXXXX)"
print_info "Downloading FIPS prebuilt binary: $asset_url"
if ! curl -fsSL -o "$tmp/fips.tar.gz" "$asset_url"; then
rm -rf "$tmp"
print_warning "FIPS tarball download failed."
return 1
fi
if ! tar -xzf "$tmp/fips.tar.gz" -C "$tmp" 2>/dev/null; then
rm -rf "$tmp"
print_warning "FIPS tarball extraction failed."
return 1
fi
# The tarball extracts to fips-<ver>-linux-x86_64/fips
fips_bin="$(find "$tmp" -type f -name fips 2>/dev/null | head -1 || true)"
if [[ -z "$fips_bin" ]] || ! file "$fips_bin" 2>/dev/null | grep -qi ELF; then
rm -rf "$tmp"
print_warning "FIPS tarball did not contain an ELF 'fips' binary."
return 1
fi
# Copy to a stable path so the caller can access it after we clean up
# the extraction dir. mktemp gives a file the caller can rm later.
stable="$(mktemp -t fips_binary.XXXXXX)"
cp -f "$fips_bin" "$stable"
chmod +x "$stable"
rm -rf "$tmp"
printf '%s' "$stable"
}
# --- Rust toolchain (only for --build-fips-from-source) ------------------
FIPS_MIN_RUST_MAJOR=1
FIPS_MIN_RUST_MINOR=94
rust_version_ok() {
command -v cargo >/dev/null 2>&1 || return 1
local ver
ver="$(cargo --version 2>/dev/null | grep -oE 'cargo [0-9]+\.[0-9]+\.' | head -1 | sed 's/cargo //; s/\.$//')" || return 1
[[ -z "$ver" ]] && return 1
local major minor
major="${ver%%.*}"
minor="${ver#*.}"
[[ "$major" -gt "$FIPS_MIN_RUST_MAJOR" ]] && return 0
[[ "$major" -eq "$FIPS_MIN_RUST_MAJOR" && "$minor" -ge "$FIPS_MIN_RUST_MINOR" ]] && return 0
return 1
}
ensure_rust() {
if rust_version_ok; then
print_info "Rust toolchain OK ($(cargo --version))."
return 0
fi
if command -v cargo >/dev/null 2>&1; then
print_warning "Installed cargo ($(cargo --version)) is older than FIPS requires (1.94.1+). Installing a newer toolchain via rustup."
else
print_info "No cargo found. Installing Rust toolchain via rustup."
fi
local rustup_init
rustup_init="$(mktemp -t rustup-init.XXXXXX)"
if ! curl -fsSL --proto '=https' --tlsv1.2 -o "$rustup_init" https://sh.rustup.rs; then
rm -f "$rustup_init"
die "Failed to download rustup installer."
fi
if ! sh "$rustup_init" -y --profile minimal >/dev/null 2>&1; then
rm -f "$rustup_init"
die "rustup installation failed."
fi
rm -f "$rustup_init"
# shellcheck disable=SC1091
. "$HOME/.cargo/env" 2>/dev/null || true
export PATH="$HOME/.cargo/bin:$PATH"
if ! rust_version_ok; then
die "Rust toolchain installed but cargo still reports an insufficient version. FIPS requires 1.94.1+."
fi
print_success "Rust toolchain installed: $(cargo --version)."
}
# Build FIPS from source via cargo. Outputs the binary path on stdout.
build_fips_from_source() {
print_info "Building FIPS from source ($FIPS_REPO)..."
ensure_rust
local tmp
tmp="$(mktemp -d)"
trap "rm -rf '$tmp' 2>/dev/null || true" RETURN
if ! git clone --depth 1 "$FIPS_REPO" "$tmp/fips"; then
die "Failed to clone FIPS repo: $FIPS_REPO"
fi
local fips_dir="$tmp/fips"
print_info "FIPS: running cargo build --release (this can take several minutes)..."
if ! ( cd "$fips_dir" && cargo build --release ); then
die "cargo build --release failed for FIPS. FIPS is required."
fi
local fips_bin=""
for cand in \
"$fips_dir/target/release/fips" \
"$fips_dir/target/x86_64-unknown-linux-gnu/release/fips"; do
if [[ -x "$cand" ]] && file "$cand" 2>/dev/null | grep -qi ELF; then
fips_bin="$cand"
break
fi
done
if [[ -z "$fips_bin" ]]; then
fips_bin="$(find "$fips_dir/target" -type f -name fips -executable 2>/dev/null | head -1 || true)"
fi
[[ -n "$fips_bin" ]] || die "FIPS build succeeded but no 'fips' binary was found."
printf '%s' "$fips_bin"
}
install_fips() {
print_info "Installing FIPS (required, not optional)..."
# If source build was requested, install the extra build-time apt deps.
if $BUILD_FIPS_FROM_SOURCE; then
print_info "Installing FIPS source-build deps (libclang-dev for bindgen)..."
local sudo_cmd=""
if [[ $EUID -ne 0 ]]; then sudo_cmd="sudo"; fi
$sudo_cmd apt-get install -y "${APT_PACKAGES_FIPS_SOURCE[@]}" >/dev/null 2>&1 || true
fi
local fips_bin=""
if $BUILD_FIPS_FROM_SOURCE; then
fips_bin="$(build_fips_from_source)"
else
if ! fips_bin="$(download_fips_prebuilt)"; then
print_warning "Prebuilt FIPS unavailable — falling back to source build."
# Install source-build deps on the fallback path too.
local sudo_cmd=""
if [[ $EUID -ne 0 ]]; then sudo_cmd="sudo"; fi
$sudo_cmd apt-get install -y "${APT_PACKAGES_FIPS_SOURCE[@]}" >/dev/null 2>&1 || true
fips_bin="$(build_fips_from_source)"
fi
fi
[[ -n "$fips_bin" && -x "$fips_bin" ]] || die "FIPS binary not available."
local prefix_bin="$INSTALL_PREFIX/bin"
local SUDO
SUDO="$(sudo_for "$INSTALL_PREFIX")"
$SUDO mkdir -p "$prefix_bin"
$SUDO cp -f "$fips_bin" "$prefix_bin/fips"
$SUDO chmod +x "$prefix_bin/fips"
print_success "FIPS binary installed to $prefix_bin/fips"
# Clean up the temp binary (download_fips_prebuilt copies to a stable
# mktemp file; build_fips_from_source uses a RETURN-trapped dir so its
# output is already gone, but rm -f is safe on either).
rm -f "$fips_bin" 2>/dev/null || true
# setcap for CAP_NET_ADMIN (needed to create a TUN interface in managed mode).
local setcap_cmd=""
if command -v setcap >/dev/null 2>&1; then
setcap_cmd="setcap"
elif command -v sudo >/dev/null 2>&1 && $SUDO command -v setcap >/dev/null 2>&1; then
setcap_cmd="$SUDO setcap"
fi
if [[ -n "$setcap_cmd" ]]; then
if $setcap_cmd cap_net_admin+ep "$prefix_bin/fips" 2>/dev/null; then
print_success "FIPS granted CAP_NET_ADMIN."
else
if $SUDO setcap cap_net_admin+ep "$prefix_bin/fips" 2>/dev/null; then
print_success "FIPS granted CAP_NET_ADMIN."
else
print_warning "setcap failed. FIPS managed mode (TUN) won't work without CAP_NET_ADMIN."
print_warning "Install 'libcap2-bin' and re-run, or run a system FIPS instance and set fips_mode=attach."
fi
fi
else
print_warning "'setcap' not found (libcap2-bin not installed)."
print_warning "FIPS managed mode won't work without CAP_NET_ADMIN."
print_warning "Install libcap2-bin (apt-get install -y libcap2-bin) and re-run, or run a system FIPS instance and set fips_mode=attach."
fi
}
# --- Prebuilt binary download -------------------------------------------
download_binary() {
# Outputs the downloaded binary path on stdout. Returns non-zero on failure
# so the caller can fall back to a source build.
print_info "Querying Gitea for latest release..."
local api_json tag url out
if ! api_json="$(curl -fsSL "$GITEA_API/releases/latest" 2>/dev/null)"; then
print_warning "Could not reach Gitea releases API (no release yet, or network issue)."
return 1
fi
tag="$(printf '%s' "$api_json" | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/')"
if [[ -z "$tag" ]]; then
print_warning "Could not parse tag_name from Gitea release JSON."
return 1
fi
url="$GITEA_DOWNLOAD_BASE/$tag/$BIN_NAME"
out="$(mktemp -t sovereign_browser_download.XXXXXX)"
print_info "Downloading prebuilt binary: $url"
if ! curl -fsSL -o "$out" "$url"; then
rm -f "$out" 2>/dev/null || true
print_warning "Download failed for $url"
return 1
fi
if ! file "$out" 2>/dev/null | grep -qi ELF; then
rm -f "$out" 2>/dev/null || true
print_warning "Downloaded file is not an ELF binary."
return 1
fi
printf '%s\n' "$out"
print_success "Downloaded sovereign_browser $tag."
}
# --- Source build fallback -----------------------------------------------
build_from_source() {
print_info "Building sovereign_browser from source ($SB_REPO)..."
local tmp
tmp="$(mktemp -d)"
trap "rm -rf '$tmp' 2>/dev/null || true" RETURN
if ! git clone --depth 1 "$SB_REPO" "$tmp/sovereign_browser"; then
die "Failed to clone sovereign_browser repo: $SB_REPO"
fi
if ! ( cd "$tmp/sovereign_browser" && make ); then
die "Source build failed (make). Ensure build deps are installed."
fi
[[ -x "$tmp/sovereign_browser/$BIN_NAME" ]] || die "Build finished but ./$BIN_NAME was not produced."
local out
out="$(mktemp -t sovereign_browser_built.XXXXXX)"
cp -f "$tmp/sovereign_browser/$BIN_NAME" "$out"
chmod +x "$out"
printf '%s\n' "$out"
print_success "Built sovereign_browser from source."
}
# --- Install binary + wrapper -------------------------------------------
install_binary() {
local src="$1"
local prefix_bin="$INSTALL_PREFIX/bin"
local SUDO
SUDO="$(sudo_for "$INSTALL_PREFIX")"
print_info "Installing binary to $prefix_bin/$BIN_NAME"
$SUDO mkdir -p "$prefix_bin"
$SUDO cp -f "$src" "$prefix_bin/$BIN_NAME"
$SUDO chmod +x "$prefix_bin/$BIN_NAME"
# Write the kebab-case wrapper that detaches the GUI.
local wrapper="$prefix_bin/sovereign-browser"
local real_bin="$prefix_bin/$BIN_NAME"
print_info "Installing wrapper to $wrapper"
# Write to a temp file then move with sudo if needed.
local wtmp
wtmp="$(mktemp -t sovereign-browser-wrapper.XXXXXX)"
cat > "$wtmp" <<WRAPPER_EOF
#!/bin/bash
# sovereign-browser — user-facing wrapper that detaches the GUI.
# Mirrors a subset of browser.sh: start | stop | restart | status | log
set -euo pipefail
SB_BIN="$real_bin"
SB_DATA_DIR="\${HOME}/.sovereign_browser"
SB_PID_FILE="\${SB_DATA_DIR}/browser.pid"
SB_LOG_FILE="\${SB_DATA_DIR}/browser.log"
sb_start() {
mkdir -p "\$SB_DATA_DIR"
# If already running, just print status.
if [[ -f "\$SB_PID_FILE" ]] && kill -0 "\$(cat "\$SB_PID_FILE" 2>/dev/null)" 2>/dev/null; then
echo "sovereign_browser already running (PID \$(cat "\$SB_PID_FILE"))" >&2
return 0
fi
nohup "\$SB_BIN" "\$@" > "\$SB_LOG_FILE" 2>&1 &
local pid=\$!
echo "\$pid" > "\$SB_PID_FILE"
disown "\$pid" 2>/dev/null || true
echo "sovereign_browser started (PID \$pid)" >&2
}
sb_stop() {
if [[ ! -f "\$SB_PID_FILE" ]]; then
echo "sovereign_browser is not running (no PID file)" >&2
return 0
fi
local pid
pid="\$(cat "\$SB_PID_FILE" 2>/dev/null || true)"
if [[ -n "\$pid" ]] && kill -0 "\$pid" 2>/dev/null; then
kill "\$pid" 2>/dev/null || true
echo "sovereign_browser stopped (PID \$pid)" >&2
else
echo "sovereign_browser was not running (stale PID file)" >&2
fi
rm -f "\$SB_PID_FILE" 2>/dev/null || true
}
sb_status() {
if [[ -f "\$SB_PID_FILE" ]] && kill -0 "\$(cat "\$SB_PID_FILE" 2>/dev/null)" 2>/dev/null; then
echo "sovereign_browser is running (PID \$(cat "\$SB_PID_FILE"))" >&2
return 0
else
echo "sovereign_browser is not running" >&2
return 1
fi
}
sb_log() {
tail -n 50 "\$SB_LOG_FILE" 2>/dev/null || echo "(no log file yet)" >&2
}
case "\${1:-start}" in
start) shift 2>/dev/null || true; sb_start "\$@" ;;
stop) sb_stop ;;
restart) sb_stop; shift 2>/dev/null || true; sb_start "\$@" ;;
status) sb_status ;;
log) sb_log ;;
*) sb_start "\$@" ;; # default: treat all args as browser args
esac
WRAPPER_EOF
$SUDO mv -f "$wtmp" "$wrapper"
$SUDO chmod +x "$wrapper"
print_success "Wrapper installed: $wrapper"
if [[ ":${PATH}:" != *":$prefix_bin:"* ]]; then
print_warning "$prefix_bin is not in your PATH. Add it: export PATH=\"$prefix_bin:\$PATH\""
fi
}
# --- Main ----------------------------------------------------------------
main() {
detect_platform
# Summary + confirmation
local method="prebuilt binary from Gitea"
$BUILD_FROM_SOURCE && method="source build from $SB_REPO"
local fips_method="prebuilt binary from GitHub releases"
$BUILD_FIPS_FROM_SOURCE && fips_method="source build ($FIPS_REPO, cargo)"
# Use print_info so colors render via `echo -e` (a plain `cat` heredoc
# would print the literal \033 escape sequences).
print_info "sovereign_browser will be installed:"
print_info " Install prefix : $INSTALL_PREFIX"
print_info " Binary method : $method"
print_info " FIPS : $fips_method, CAP_NET_ADMIN set"
print_info " Tor : apt package 'tor'"
print_info " apt deps : ${APT_PACKAGES[*]}"
if ! $ASSUME_YES; then
print_warning "This will run apt-get install (sudo) and clone/build FIPS. Continue? [y/N]"
# Read from /dev/tty, not stdin: in `curl | bash` mode stdin is the
# curl pipe (the script text), so a plain `read` would hit EOF and
# abort immediately. /dev/tty is the user's terminal.
if [[ -r /dev/tty ]]; then
read -r reply < /dev/tty || reply=""
else
# No tty available (e.g. non-interactive CI) — require --yes.
die "No tty available for confirmation. Re-run with --yes to skip prompts."
fi
reply="${reply:-n}"
if [[ ! "${reply,,}" =~ ^y(es)?$ ]]; then
die "Aborted by user."
fi
fi
install_deps
install_fips
local bin_path=""
if $BUILD_FROM_SOURCE; then
bin_path="$(build_from_source)"
else
if ! bin_path="$(download_binary)"; then
print_warning "Prebuilt binary unavailable — falling back to source build."
bin_path="$(build_from_source)"
fi
fi
install_binary "$bin_path"
rm -f "$bin_path" 2>/dev/null || true
print_success "sovereign_browser installed."
cat >&2 <<EOF
${GREEN}[SUCCESS]${NC} Next steps:
Run: sovereign-browser
Or: sovereign-browser --login-method generate
Or: sovereign-browser --login-method generate --url https://example.com
Data dir: ~/.sovereign_browser/
Logs: sovereign-browser log
Stop: sovereign-browser stop
Status: sovereign-browser status
EOF
}
main "$@"