#!/bin/sh # GoToSocial Controller for SecuBox # Manages GoToSocial LXC container and configuration set -e VERSION="0.1.0" GTS_VERSION="0.17.0" 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"; } log_error() { logger -t gotosocial -p daemon.err "$1"; echo "[ERROR] $1" >&2; } log_warn() { logger -t gotosocial -p daemon.warn "$1"; echo "[WARN] $1"; } # UCI helpers get_config() { local section="$1" local option="$2" local default="$3" uci -q get "gotosocial.${section}.${option}" || echo "$default" } set_config() { uci set "gotosocial.$1.$2=$3" uci commit gotosocial } # Check if GoToSocial is installed gts_installed() { [ -x "$BINARY_PATH" ] } # Check if GoToSocial is running gts_running() { [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null } # Download GoToSocial binary download_binary() { local version="${1:-$GTS_VERSION}" local url="https://codeberg.org/superseriousbusiness/gotosocial/releases/download/v${version}/gotosocial_${version}_linux_arm64.tar.gz" local tmp_dir="/tmp/gotosocial_install" log_info "Downloading GoToSocial v${version} from Codeberg..." 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" || { log_error "Failed to download GoToSocial" return 1 } # Verify download size (should be >10MB) local size=$(stat -c%s gotosocial.tar.gz 2>/dev/null || stat -f%z gotosocial.tar.gz 2>/dev/null || echo 0) if [ "$size" -lt 10000000 ]; then log_error "Downloaded file too small ($size bytes), likely failed" rm -f gotosocial.tar.gz return 1 fi tar -xzf gotosocial.tar.gz mkdir -p "$DATA_PATH" cp gotosocial "$BINARY_PATH" chmod +x "$BINARY_PATH" # Copy web assets [ -d "web" ] && cp -r web "$DATA_PATH/" rm -rf "$tmp_dir" log_info "GoToSocial binary installed to $DATA_PATH" } # Create data directory structure 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_config() { local host=$(get_config main host "social.local") local port=$(get_config main port "8484") local protocol=$(get_config main protocol "https") local bind=$(get_config main bind_address "0.0.0.0") local instance_name=$(get_config main instance_name "SecuBox Social") local instance_desc=$(get_config main instance_description "A SecuBox Fediverse instance") local reg_open_val=$(get_config main accounts_registration_open "0") local approval_val=$(get_config main accounts_approval_required "1") # Convert 0/1 to false/true for YAML local reg_open="false" local approval="true" [ "$reg_open_val" = "1" ] && reg_open="true" [ "$approval_val" = "0" ] && approval="false" mkdir -p "$DATA_PATH/storage" cat > "$DATA_PATH/config.yaml" < '" } # Uninstall cmd_uninstall() { local keep_data="$1" log_info "Uninstalling GoToSocial..." # Stop if running gts_running && cmd_stop # Remove binary rm -f "$BINARY_PATH" # Remove data unless --keep-data if [ "$keep_data" != "--keep-data" ]; then rm -rf "$DATA_PATH" log_info "Data removed" else log_info "Data preserved at $DATA_PATH" fi log_info "GoToSocial uninstalled" } # Start GoToSocial cmd_start() { if ! gts_installed; then log_error "GoToSocial not installed. Run 'gotosocialctl install' first." return 1 fi if gts_running; then log_info "GoToSocial is already running" return 0 fi # Regenerate config in case settings changed generate_config log_info "Starting GoToSocial..." cd "$DATA_PATH" HOME="$DATA_PATH" "$BINARY_PATH" server start --config-path "$DATA_PATH/config.yaml" >> /var/log/gotosocial.log 2>&1 & local pid=$! echo "$pid" > "$PID_FILE" # Wait for startup (WASM compilation takes time) 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 "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" return 1 fi count=$((count + 1)) done log_error "GoToSocial startup timeout. Check /var/log/gotosocial.log" return 1 } # Stop GoToSocial 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" log_info "GoToSocial stopped" } # Restart cmd_restart() { cmd_stop sleep 1 cmd_start } # Reload config cmd_reload() { log_info "Reloading configuration..." generate_config cmd_restart } # Status (JSON output for RPCD) cmd_status() { local installed="false" local running="false" local service_state="false" local host=$(get_config main host "social.example.com") local port=$(get_config main port "8484") local version=$(get_config container version "$GTS_VERSION") local tor_enabled=$(get_config federation tor_enabled "0") local dns_enabled=$(get_config proxy enabled "0") local mesh_enabled=$(get_config mesh announce_to_peers "0") gts_installed && installed="true" gts_running && running="true" # Check if API responds if [ "$running" = "true" ]; then curl -s --connect-timeout 2 "http://127.0.0.1:$port/api/v1/instance" >/dev/null 2>&1 && service_state="true" fi cat </dev/null) echo "PID: $pid" local port=$(get_config main port "8484") local host=$(get_config main host "localhost") echo "Host: $host" echo "Port: $port" # 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 echo "API: responding" else echo "API: not responding (may still be starting)" fi else echo "GoToSocial: stopped" return 1 fi } # Create user cmd_user_create() { local username="$1" local email="$2" local password="$3" local admin="${4:-false}" [ -z "$username" ] || [ -z "$email" ] && { echo "Usage: gotosocialctl user create [password] [--admin]" return 1 } [ "$3" = "--admin" ] && { admin="true"; password=""; } [ "$4" = "--admin" ] && admin="true" if ! gts_installed; then log_error "GoToSocial is not installed" return 1 fi log_info "Creating user $username..." # Generate random password if not provided [ -z "$password" ] && password=$(openssl rand -base64 12) HOME="$DATA_PATH" "$BINARY_PATH" admin account create \ --username "$username" \ --email "$email" \ --password "$password" \ --config "$DATA_PATH/config.yaml" if [ "$admin" = "true" ]; then HOME="$DATA_PATH" "$BINARY_PATH" admin account promote \ --username "$username" \ --config "$DATA_PATH/config.yaml" fi # Confirm the user HOME="$DATA_PATH" "$BINARY_PATH" admin account confirm \ --username "$username" \ --config "$DATA_PATH/config.yaml" 2>/dev/null || true echo "" echo "User created successfully!" echo "Username: $username" echo "Email: $email" echo "Password: $password" echo "" echo "Please change this password after first login." } # List users (JSON output for RPCD) cmd_users() { local db_path="$DATA_PATH/gotosocial.db" local users="[]" if [ -f "$db_path" ] && command -v sqlite3 >/dev/null; then users=$(sqlite3 -json "$db_path" "SELECT username, created_at as created, CASE WHEN suspended_at IS NULL THEN 0 ELSE 1 END as suspended, CASE WHEN confirmed_at IS NULL THEN 0 ELSE 1 END as confirmed FROM accounts WHERE domain IS NULL OR domain = '';" 2>/dev/null || echo "[]") fi echo "{\"users\":$users}" } # List users (human readable) cmd_user_list() { local db_path="$DATA_PATH/gotosocial.db" if [ -f "$db_path" ] && command -v sqlite3 >/dev/null; then sqlite3 "$db_path" "SELECT username, created_at, suspended_at FROM accounts WHERE domain IS NULL OR domain = '';" 2>/dev/null || { echo "Unable to query database directly. Use the web interface." } else echo "Use the web interface to manage users." echo "URL: https://$(get_config main host)/admin" fi } # Confirm user email cmd_user_confirm() { local username="$1" [ -z "$username" ] && { echo "Usage: gotosocialctl user confirm " return 1 } if ! gts_installed; then log_error "GoToSocial is not installed" return 1 fi HOME="$DATA_PATH" "$BINARY_PATH" admin account confirm \ --username "$username" \ --config "$DATA_PATH/config.yaml" log_info "User $username confirmed" } # Emancipate - expose via HAProxy cmd_emancipate() { local domain="$1" [ -z "$domain" ] && domain=$(get_config main host) [ -z "$domain" ] || [ "$domain" = "social.example.com" ] && { echo "Usage: gotosocialctl emancipate " echo "Example: gotosocialctl emancipate social.mysite.com" return 1 } local port=$(get_config main port "8484") local lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1") log_info "Exposing GoToSocial at $domain..." # Update config set_config main host "$domain" set_config proxy enabled "1" set_config proxy vhost_domain "$domain" # Create HAProxy backend uci set haproxy.gotosocial=backend uci set haproxy.gotosocial.name='gotosocial' uci set haproxy.gotosocial.mode='http' uci set haproxy.gotosocial.balance='roundrobin' uci set haproxy.gotosocial.enabled='1' uci set haproxy.gotosocial_srv=server uci set haproxy.gotosocial_srv.backend='gotosocial' uci set haproxy.gotosocial_srv.name='gotosocial' uci set haproxy.gotosocial_srv.address="$lan_ip" uci set haproxy.gotosocial_srv.port="$port" uci set haproxy.gotosocial_srv.weight='100' uci set haproxy.gotosocial_srv.check='1' uci set haproxy.gotosocial_srv.enabled='1' # Create vhost local vhost_name=$(echo "$domain" | tr '.-' '_') uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend='gotosocial' uci set haproxy.${vhost_name}.ssl='1' uci set haproxy.${vhost_name}.ssl_redirect='1' uci set haproxy.${vhost_name}.acme='1' uci set haproxy.${vhost_name}.enabled='1' uci commit haproxy uci commit gotosocial # Regenerate HAProxy config if command -v haproxyctl >/dev/null; then haproxyctl generate /etc/init.d/haproxy reload fi # Regenerate GoToSocial config with new domain generate_config # Restart to apply new config gts_running && cmd_restart log_info "GoToSocial exposed at https://$domain" log_info "SSL certificate will be provisioned automatically" } # Backup cmd_backup() { local backup_path="${1:-/tmp/gotosocial-backup-$(date +%Y%m%d-%H%M%S).tar.gz}" log_info "Creating backup..." # Stop for consistent backup local was_running=false if gts_running; then was_running=true cmd_stop fi tar -czf "$backup_path" -C "$DATA_PATH" . 2>/dev/null || { log_error "Backup failed" [ "$was_running" = "true" ] && cmd_start return 1 } [ "$was_running" = "true" ] && cmd_start log_info "Backup created: $backup_path" ls -lh "$backup_path" } # Restore cmd_restore() { local backup_path="$1" [ -z "$backup_path" ] || [ ! -f "$backup_path" ] && { echo "Usage: gotosocialctl restore " return 1 } log_info "Restoring from $backup_path..." # Stop if running gts_running && cmd_stop # Clear existing data rm -rf "$DATA_PATH"/* # Extract backup tar -xzf "$backup_path" -C "$DATA_PATH" || { log_error "Restore failed" return 1 } log_info "Restore complete" cmd_start } # Federation commands cmd_federation_list() { local port=$(get_config main port "8484") curl -s "http://127.0.0.1:$port/api/v1/instance/peers" 2>/dev/null | jq -r '.[]' 2>/dev/null || { echo "Unable to fetch federation list. Is GoToSocial running?" } } # Show logs (JSON output) cmd_logs() { local lines="${1:-50}" local logs logs=$(logread -e gotosocial 2>/dev/null | tail -n "$lines" | jq -R -s 'split("\n") | map(select(length > 0))' 2>/dev/null || echo "[]") echo "{\"logs\":$logs}" } # Show help cmd_help() { cat < [options] Installation: install [version] Install GoToSocial (default: v$GTS_VERSION) uninstall [--keep-data] Remove GoToSocial update [version] Update to new version Service: start Start GoToSocial stop Stop GoToSocial restart Restart GoToSocial reload Reload configuration status Show status User Management: user create [--admin] Create user user list List users user confirm Confirm user email Exposure: emancipate Expose via HAProxy + SSL Backup: backup [path] Backup data restore Restore from backup Federation: federation list List federated instances Other: help Show this help version Show version Examples: gotosocialctl install gotosocialctl start gotosocialctl user create alice alice@example.com --admin gotosocialctl emancipate social.mysite.com EOF } # Main case "$1" in install) cmd_install "$2" ;; uninstall) cmd_uninstall "$2" ;; update) cmd_stop download_binary "${2:-$GTS_VERSION}" cmd_start ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; reload) cmd_reload ;; status) cmd_status ;; status-human) cmd_status_human ;; users) cmd_users ;; logs) cmd_logs "$2" ;; user) case "$2" in create) cmd_user_create "$3" "$4" "$5" ;; list) cmd_user_list ;; confirm) cmd_user_confirm "$3" ;; *) echo "Usage: gotosocialctl user {create|list|confirm}" ;; esac ;; emancipate) cmd_emancipate "$2" ;; backup) cmd_backup "$2" ;; restore) cmd_restore "$2" ;; federation) case "$2" in list) cmd_federation_list ;; *) echo "Usage: gotosocialctl federation {list}" ;; esac ;; version) echo "gotosocialctl v$VERSION (GoToSocial v$GTS_VERSION)" ;; help|--help|-h|"") cmd_help ;; *) echo "Unknown command: $1" cmd_help exit 1 ;; esac