From b62f82b77e633bf6643cf36faacfccc62f76b48a Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 14 Feb 2026 07:16:54 +0100 Subject: [PATCH] feat(gotosocial): Migrate to LXC container with Alpine rootfs - Create Alpine 3.21 LXC container with gcompat for glibc compatibility - GoToSocial v0.17.0 runs inside container with host networking - Data directory bind-mounted at /data inside container - Add user management commands via chroot/lxc-attach - Add `shell` command for container access - Add `user password` command for password resets - Fix architecture variable naming (aarch64/arm64 confusion) Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 25 + .../files/usr/sbin/gotosocialctl | 426 ++++++++++++++---- 2 files changed, 368 insertions(+), 83 deletions(-) diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 3bfe0d9b..b9e10f76 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -1395,3 +1395,28 @@ _Last updated: 2026-02-11_ - Backend address: HAProxy in LXC cannot reach 127.0.0.1, must use LAN IP - WASM compilation: ~90 seconds on ARM64 at startup - Live at: https://social.gk2.secubox.in + +23. **GoToSocial LXC Migration + Pinafore Client Hub (2026-02-14)** + - **GoToSocial Architecture Change**: + - Migrated from direct host execution to LXC container + - Using Alpine 3.21 rootfs with gcompat for glibc compatibility + - GoToSocial v0.17.0 statically linked binary + - Data bind-mounted at `/data` inside container + - Container runs with `lxc.net.0.type = none` (host networking) + - **LXC Container Benefits**: + - Isolated environment with proper cgroup limits + - Easier upgrades (replace rootfs or binary only) + - Consistent execution environment + - **gotosocialctl Updates**: + - `install`: Creates Alpine LXC rootfs + installs GoToSocial + - `start/stop`: Uses `lxc-start -d` / `lxc-stop` + - `user create/password`: Works via chroot or lxc-attach + - `shell`: Opens interactive shell in container + - **Pinafore Client Hub Added**: + - New package: `secubox-app-pinafore` + - Landing page with links to Pinafore, Elk, Semaphore + - All clients pre-configured with instance domain + - `pinaforectl emancipate` for HAProxy exposure + - **Login Issue Resolution**: + - Form field is `username` not `email` (GoToSocial quirk) + - Admin user: `admin@secubox.in` / `TestAdmin123!` diff --git a/package/secubox/secubox-app-gotosocial/files/usr/sbin/gotosocialctl b/package/secubox/secubox-app-gotosocial/files/usr/sbin/gotosocialctl index 284a6b91..bba8d7fa 100644 --- a/package/secubox/secubox-app-gotosocial/files/usr/sbin/gotosocialctl +++ b/package/secubox/secubox-app-gotosocial/files/usr/sbin/gotosocialctl @@ -1,17 +1,21 @@ #!/bin/sh # GoToSocial Controller for SecuBox -# Manages GoToSocial LXC container and configuration +# Manages GoToSocial in a Debian LXC container (glibc for proper bcrypt support) set -e -VERSION="0.1.0" +VERSION="0.2.0" GTS_VERSION="0.17.0" + +# LXC container settings +LXC_NAME="gotosocial" +LXC_PATH="/srv/lxc" +LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" +LXC_CONFIG="$LXC_PATH/$LXC_NAME/config" + +# Data paths (bind mounted into container) DATA_PATH="/srv/gotosocial" -BINARY_PATH="/srv/gotosocial/gotosocial" CONFIG_FILE="/etc/config/gotosocial" -PID_FILE="/var/run/gotosocial.pid" -# GoToSocial moved to Codeberg -GTS_BINARY_URL="https://codeberg.org/superseriousbusiness/gotosocial/releases/download/v${GTS_VERSION}/gotosocial_${GTS_VERSION}_linux_arm64.tar.gz" # Logging log_info() { logger -t gotosocial -p daemon.info "$1"; echo "[INFO] $1"; } @@ -31,29 +35,122 @@ set_config() { uci commit gotosocial } -# Check if GoToSocial is installed +# LXC helpers +has_lxc() { + command -v lxc-start >/dev/null 2>&1 && \ + command -v lxc-stop >/dev/null 2>&1 +} + +lxc_running() { + lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" +} + +lxc_exists() { + [ -f "$LXC_CONFIG" ] && [ -d "$LXC_ROOTFS" ] +} + +# Check if GoToSocial is installed (container exists with binary) gts_installed() { - [ -x "$BINARY_PATH" ] + [ -x "$LXC_ROOTFS/opt/gotosocial/gotosocial" ] } -# Check if GoToSocial is running +# Check if GoToSocial is running (LXC container running) gts_running() { - [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null + lxc_running } -# Download GoToSocial binary -download_binary() { +# ============================================================================= +# LXC CONTAINER MANAGEMENT +# ============================================================================= + +lxc_stop() { + if lxc_running; then + log_info "Stopping GoToSocial container..." + lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true + sleep 2 + fi +} + +lxc_create_rootfs() { + log_info "Creating Debian rootfs for GoToSocial..." + + mkdir -p "$LXC_PATH/$LXC_NAME" + + # Download Alpine minirootfs (simple and reliable, glibc not needed since + # GoToSocial binary is statically linked) + # Actually, use Debian for glibc bcrypt compatibility + + local arch="x86_64" + case "$(uname -m)" in + aarch64) arch="aarch64" ;; + armv7l) arch="armv7" ;; + esac + + # Use Alpine minirootfs as base + local alpine_url="https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/$arch/alpine-minirootfs-3.21.2-$arch.tar.gz" + local rootfs_tar="/tmp/alpine-gts.tar.gz" + + log_info "Downloading Alpine rootfs..." + wget -q -O "$rootfs_tar" "$alpine_url" || { + log_error "Failed to download Alpine rootfs" + return 1 + } + + log_info "Extracting rootfs..." + mkdir -p "$LXC_ROOTFS" + tar -xzf "$rootfs_tar" -C "$LXC_ROOTFS" || { + log_error "Failed to extract rootfs" + return 1 + } + rm -f "$rootfs_tar" + + # Configure Alpine + cat > "$LXC_ROOTFS/etc/resolv.conf" << 'EOF' +nameserver 1.1.1.1 +nameserver 8.8.8.8 +EOF + + cat > "$LXC_ROOTFS/etc/apk/repositories" << 'EOF' +https://dl-cdn.alpinelinux.org/alpine/v3.21/main +https://dl-cdn.alpinelinux.org/alpine/v3.21/community +EOF + + # Install gcompat for glibc compatibility (needed for bcrypt) + log_info "Installing glibc compatibility layer..." + chroot "$LXC_ROOTFS" /bin/sh -c " + apk update && apk add --no-cache gcompat libc6-compat sqlite + " || log_warn "Could not install gcompat (may not be needed)" + + mkdir -p "$LXC_ROOTFS/opt/gotosocial" + mkdir -p "$LXC_ROOTFS/data" + mkdir -p "$LXC_ROOTFS/var/log" + + log_info "Alpine rootfs with glibc compatibility created successfully" +} + +# Download and install GoToSocial into the container +lxc_install_gotosocial() { local version="${1:-$GTS_VERSION}" - local url="https://codeberg.org/superseriousbusiness/gotosocial/releases/download/v${version}/gotosocial_${version}_linux_arm64.tar.gz" + + # GoToSocial uses different arch naming + local gts_arch="amd64" + case "$(uname -m)" in + aarch64) gts_arch="arm64" ;; + armv7l) gts_arch="armv7" ;; + esac + + local url="https://codeberg.org/superseriousbusiness/gotosocial/releases/download/v${version}/gotosocial_${version}_linux_${gts_arch}.tar.gz" local tmp_dir="/tmp/gotosocial_install" - log_info "Downloading GoToSocial v${version} from Codeberg..." + log_info "Downloading GoToSocial v${version} for ${arch}..." + rm -rf "$tmp_dir" mkdir -p "$tmp_dir" cd "$tmp_dir" - # Use curl with -L for redirects (wget on OpenWrt may not handle them well) - curl -L -o gotosocial.tar.gz "$url" || wget -O gotosocial.tar.gz "$url" || { + # Download with curl (handles redirects) or wget + curl -L -o gotosocial.tar.gz "$url" 2>/dev/null || \ + wget -O gotosocial.tar.gz "$url" || { log_error "Failed to download GoToSocial" return 1 } @@ -68,25 +165,83 @@ download_binary() { tar -xzf gotosocial.tar.gz - mkdir -p "$DATA_PATH" - cp gotosocial "$BINARY_PATH" - chmod +x "$BINARY_PATH" + # Install into container rootfs + cp gotosocial "$LXC_ROOTFS/opt/gotosocial/" + chmod +x "$LXC_ROOTFS/opt/gotosocial/gotosocial" # Copy web assets - [ -d "web" ] && cp -r web "$DATA_PATH/" + [ -d "web" ] && cp -r web "$LXC_ROOTFS/opt/gotosocial/" rm -rf "$tmp_dir" - log_info "GoToSocial binary installed to $DATA_PATH" + log_info "GoToSocial v${version} installed in container" } -# Create data directory structure +# Create start script inside container +lxc_create_start_script() { + cat > "$LXC_ROOTFS/opt/start-gotosocial.sh" << 'SCRIPT' +#!/bin/sh +cd /opt/gotosocial + +# Wait for data directory to be ready +sleep 2 + +# Start GoToSocial +exec /opt/gotosocial/gotosocial server start --config-path /data/config.yaml +SCRIPT + chmod +x "$LXC_ROOTFS/opt/start-gotosocial.sh" +} + +# Create LXC configuration +lxc_create_config() { + local port=$(get_config main port "8484") + local memory_limit=$(get_config main memory_limit "512M") + + # LXC arch names + local lxc_arch="x86_64" + case "$(uname -m)" in + aarch64) lxc_arch="aarch64" ;; + armv7l) lxc_arch="armhf" ;; + esac + + local mem_bytes=$(echo "$memory_limit" | sed 's/M/000000/;s/G/000000000/') + + cat > "$LXC_CONFIG" << EOF +# GoToSocial LXC Configuration +lxc.uts.name = $LXC_NAME +lxc.rootfs.path = dir:$LXC_ROOTFS +lxc.arch = $lxc_arch + +# Network: use host network for binding ports +lxc.net.0.type = none + +# Mount data directory +lxc.mount.entry = $DATA_PATH data none bind,create=dir 0 0 + +# Disable seccomp for compatibility +lxc.seccomp.profile = + +# TTY/PTY settings +lxc.tty.max = 0 +lxc.pty.max = 256 + +# cgroup v2 memory limit +lxc.cgroup2.memory.max = $mem_bytes + +# Init +lxc.init.cmd = /opt/start-gotosocial.sh +EOF + + log_info "LXC config created at $LXC_CONFIG" +} + +# Create data directory structure on host (bind mounted into container) create_data_dir() { log_info "Creating data directories..." mkdir -p "$DATA_PATH"/{storage,web} log_info "Data directories created at $DATA_PATH" } -# Generate GoToSocial config +# Generate GoToSocial config (written to DATA_PATH which is bind-mounted as /data in container) generate_config() { local host=$(get_config main host "social.local") local port=$(get_config main port "8484") @@ -104,6 +259,7 @@ generate_config() { mkdir -p "$DATA_PATH/storage" + # Note: paths are relative to container where DATA_PATH is mounted as /data cat > "$DATA_PATH/config.yaml" <> /var/log/gotosocial.log 2>&1 & - local pid=$! - echo "$pid" > "$PID_FILE" + log_info "Starting GoToSocial container..." - # Wait for startup (WASM compilation takes time) + # Start in background + lxc-start -n "$LXC_NAME" -d || { + log_error "Failed to start GoToSocial container" + return 1 + } + + # Wait for startup (WASM compilation takes time on first run) local port=$(get_config main port "8484") local count=0 while [ $count -lt 120 ]; do sleep 2 if curl -s --connect-timeout 1 "http://127.0.0.1:$port/api/v1/instance" >/dev/null 2>&1; then - log_info "GoToSocial started (PID: $pid)" + log_info "GoToSocial started" log_info "Web interface available at http://localhost:$port" return 0 fi - if ! kill -0 "$pid" 2>/dev/null; then - log_error "GoToSocial failed to start. Check /var/log/gotosocial.log" - rm -f "$PID_FILE" + if ! lxc_running; then + log_error "GoToSocial container stopped unexpectedly" + log_error "Check: lxc-attach -n gotosocial -- cat /var/log/gotosocial.log" return 1 fi count=$((count + 1)) done - log_error "GoToSocial startup timeout. Check /var/log/gotosocial.log" + log_error "GoToSocial startup timeout. Container still running, may need more time." return 1 } -# Stop GoToSocial +# Stop GoToSocial (LXC container) cmd_stop() { if ! gts_running; then log_info "GoToSocial is not running" - rm -f "$PID_FILE" return 0 fi log_info "Stopping GoToSocial..." - local pid=$(cat "$PID_FILE") - kill "$pid" 2>/dev/null - sleep 2 - kill -9 "$pid" 2>/dev/null || true - rm -f "$PID_FILE" + lxc_stop log_info "GoToSocial stopped" } @@ -327,14 +514,13 @@ EOF # Status (human readable) cmd_status_human() { if gts_running; then - echo "GoToSocial: running" - local pid=$(cat "$PID_FILE" 2>/dev/null) - echo "PID: $pid" + echo "GoToSocial: running (LXC container)" local port=$(get_config main port "8484") local host=$(get_config main host "localhost") echo "Host: $host" echo "Port: $port" + echo "Container: $LXC_NAME" # Check if web interface responds if curl -s --connect-timeout 2 "http://127.0.0.1:$port/api/v1/instance" >/dev/null 2>&1; then @@ -344,10 +530,43 @@ cmd_status_human() { fi else echo "GoToSocial: stopped" + if gts_installed; then + echo "Container: installed but not running" + else + echo "Container: not installed" + fi return 1 fi } +# Shell access to container +cmd_shell() { + if ! gts_running; then + log_error "Container not running. Start with 'gotosocialctl start' first." + return 1 + fi + + lxc-attach -n "$LXC_NAME" -- /bin/sh +} + +# Helper to run GoToSocial admin commands +gts_admin_cmd() { + # Commands can run with container stopped (just need rootfs + data) + # Use chroot to run the binary + if lxc_running; then + # Container running - use lxc-attach + lxc-attach -n "$LXC_NAME" -- /opt/gotosocial/gotosocial "$@" + else + # Container stopped - use chroot with bind mounts + # Mount data directory temporarily + mount --bind "$DATA_PATH" "$LXC_ROOTFS/data" 2>/dev/null || true + chroot "$LXC_ROOTFS" /opt/gotosocial/gotosocial "$@" + local ret=$? + umount "$LXC_ROOTFS/data" 2>/dev/null || true + return $ret + fi +} + # Create user cmd_user_create() { local username="$1" @@ -373,22 +592,22 @@ cmd_user_create() { # Generate random password if not provided [ -z "$password" ] && password=$(openssl rand -base64 12) - HOME="$DATA_PATH" "$BINARY_PATH" admin account create \ + gts_admin_cmd admin account create \ --username "$username" \ --email "$email" \ --password "$password" \ - --config-path "$DATA_PATH/config.yaml" + --config-path "/data/config.yaml" if [ "$admin" = "true" ]; then - HOME="$DATA_PATH" "$BINARY_PATH" admin account promote \ + gts_admin_cmd admin account promote \ --username "$username" \ - --config-path "$DATA_PATH/config.yaml" + --config-path "/data/config.yaml" fi # Confirm the user - HOME="$DATA_PATH" "$BINARY_PATH" admin account confirm \ + gts_admin_cmd admin account confirm \ --username "$username" \ - --config-path "$DATA_PATH/config.yaml" 2>/dev/null || true + --config-path "/data/config.yaml" 2>/dev/null || true echo "" echo "User created successfully!" @@ -442,13 +661,41 @@ cmd_user_confirm() { return 1 fi - HOME="$DATA_PATH" "$BINARY_PATH" admin account confirm \ + gts_admin_cmd admin account confirm \ --username "$username" \ - --config-path "$DATA_PATH/config.yaml" + --config-path "/data/config.yaml" log_info "User $username confirmed" } +# Reset user password +cmd_user_password() { + local username="$1" + local password="$2" + + [ -z "$username" ] && { + echo "Usage: gotosocialctl user password [new-password]" + return 1 + } + + if ! gts_installed; then + log_error "GoToSocial is not installed" + return 1 + fi + + # Generate random password if not provided + [ -z "$password" ] && password=$(openssl rand -base64 12) + + gts_admin_cmd admin account password \ + --username "$username" \ + --password "$password" \ + --config-path "/data/config.yaml" + + echo "" + echo "Password reset for $username" + echo "New password: $password" +} + # Emancipate - expose via HAProxy cmd_emancipate() { local domain="$1" @@ -590,44 +837,51 @@ cmd_logs() { cmd_help() { cat < [options] Installation: - install [version] Install GoToSocial (default: v$GTS_VERSION) + install [version] Install GoToSocial (default: v$GTS_VERSION) uninstall [--keep-data] Remove GoToSocial - update [version] Update to new version + update [version] Update to new version Service: - start Start GoToSocial - stop Stop GoToSocial - restart Restart GoToSocial - reload Reload configuration - status Show status + start Start GoToSocial container + stop Stop GoToSocial container + restart Restart GoToSocial + reload Reload configuration + status Show status (JSON) + status-human Show status (human readable) User Management: - user create [--admin] Create user - user list List users - user confirm Confirm user email + user create [password] [--admin] Create user + user list List users + user confirm Confirm user email + user password [pwd] Reset user password Exposure: - emancipate Expose via HAProxy + SSL + emancipate Expose via HAProxy + SSL + +Container: + shell Open shell in container Backup: - backup [path] Backup data - restore Restore from backup + backup [path] Backup data + restore Restore from backup Federation: - federation list List federated instances + federation list List federated instances Other: - help Show this help - version Show version + help Show this help + version Show version Examples: gotosocialctl install gotosocialctl start gotosocialctl user create alice alice@example.com --admin + gotosocialctl user password alice newpassword123 gotosocialctl emancipate social.mysite.com EOF @@ -643,7 +897,7 @@ case "$1" in ;; update) cmd_stop - download_binary "${2:-$GTS_VERSION}" + lxc_install_gotosocial "${2:-$GTS_VERSION}" cmd_start ;; start) @@ -670,10 +924,13 @@ case "$1" in logs) cmd_logs "$2" ;; + shell) + cmd_shell + ;; user) case "$2" in create) - cmd_user_create "$3" "$4" "$5" + cmd_user_create "$3" "$4" "$5" "$6" ;; list) cmd_user_list @@ -681,8 +938,11 @@ case "$1" in confirm) cmd_user_confirm "$3" ;; + password) + cmd_user_password "$3" "$4" + ;; *) - echo "Usage: gotosocialctl user {create|list|confirm}" + echo "Usage: gotosocialctl user {create|list|confirm|password}" ;; esac ;;