secubox-openwrt/package/secubox/secubox-app-gotosocial/files/usr/sbin/gotosocialctl
CyberMind-FR f20bb1df6b feat(gotosocial): Add GoToSocial Fediverse server packages
Add secubox-app-gotosocial and luci-app-gotosocial for running a lightweight
ActivityPub social network server in LXC container.

Features:
- gotosocialctl CLI with install, start, stop, user management
- LXC container deployment (ARM64)
- HAProxy integration via emancipate command
- UCI configuration for instance, container, proxy, federation settings
- LuCI web interface with overview, users, and settings tabs
- Mesh integration support for auto-federation between SecuBox nodes
- Backup/restore functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 13:52:58 +01:00

779 lines
18 KiB
Bash

#!/bin/sh
# GoToSocial Controller for SecuBox
# Manages GoToSocial LXC container and configuration
set -e
VERSION="0.1.0"
GTS_VERSION="0.17.3"
LXC_NAME="gotosocial"
LXC_PATH="/srv/lxc/gotosocial"
DATA_PATH="/srv/gotosocial"
CONFIG_FILE="/etc/config/gotosocial"
GTS_BINARY_URL="https://github.com/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 container exists
container_exists() {
[ -d "$LXC_PATH/rootfs" ]
}
# Check if container is running
container_running() {
lxc-info -n "$LXC_NAME" 2>/dev/null | grep -q "RUNNING"
}
# Download GoToSocial binary
download_binary() {
local version="${1:-$GTS_VERSION}"
local url="https://github.com/superseriousbusiness/gotosocial/releases/download/v${version}/gotosocial_${version}_linux_arm64.tar.gz"
local tmp_dir="/tmp/gotosocial_install"
log_info "Downloading GoToSocial v${version}..."
mkdir -p "$tmp_dir"
cd "$tmp_dir"
wget -q -O gotosocial.tar.gz "$url" || {
log_error "Failed to download GoToSocial"
return 1
}
tar -xzf gotosocial.tar.gz
mkdir -p "$LXC_PATH/rootfs/opt/gotosocial"
cp gotosocial "$LXC_PATH/rootfs/opt/gotosocial/"
chmod +x "$LXC_PATH/rootfs/opt/gotosocial/gotosocial"
# Copy web assets
[ -d "web" ] && cp -r web "$LXC_PATH/rootfs/opt/gotosocial/"
rm -rf "$tmp_dir"
log_info "GoToSocial binary installed"
}
# Create minimal rootfs
create_rootfs() {
local rootfs="$LXC_PATH/rootfs"
log_info "Creating minimal rootfs..."
mkdir -p "$rootfs"/{opt/gotosocial,data,etc,proc,sys,dev,tmp,run}
# Create basic filesystem structure
mkdir -p "$rootfs/etc/ssl/certs"
# Copy SSL certificates from host
cp /etc/ssl/certs/ca-certificates.crt "$rootfs/etc/ssl/certs/" 2>/dev/null || \
cat /etc/ssl/certs/*.pem > "$rootfs/etc/ssl/certs/ca-certificates.crt" 2>/dev/null || true
# Create passwd/group for GoToSocial
echo "root:x:0:0:root:/root:/bin/sh" > "$rootfs/etc/passwd"
echo "gotosocial:x:1000:1000:GoToSocial:/data:/bin/false" >> "$rootfs/etc/passwd"
echo "root:x:0:" > "$rootfs/etc/group"
echo "gotosocial:x:1000:" >> "$rootfs/etc/group"
# Create resolv.conf
cp /etc/resolv.conf "$rootfs/etc/"
# Create hosts file
cat > "$rootfs/etc/hosts" <<EOF
127.0.0.1 localhost
::1 localhost
EOF
log_info "Rootfs created"
}
# Generate LXC config
create_lxc_config() {
local host=$(get_config main host "social.local")
local port=$(get_config main port "8484")
local data_path=$(get_config container data_path "$DATA_PATH")
log_info "Creating LXC configuration..."
mkdir -p "$LXC_PATH"
cat > "$LXC_PATH/config" <<EOF
# GoToSocial LXC Configuration
lxc.uts.name = $LXC_NAME
lxc.rootfs.path = dir:$LXC_PATH/rootfs
lxc.arch = aarch64
# Network: use host network
lxc.net.0.type = none
# Mount points
lxc.mount.auto = proc:mixed sys:ro
lxc.mount.entry = $data_path data none bind,create=dir 0 0
# Environment
lxc.environment = GTS_HOST=$host
lxc.environment = GTS_PORT=$port
lxc.environment = GTS_DB_TYPE=sqlite
lxc.environment = GTS_DB_ADDRESS=/data/gotosocial.db
lxc.environment = GTS_STORAGE_LOCAL_BASE_PATH=/data/storage
lxc.environment = GTS_LETSENCRYPT_ENABLED=false
lxc.environment = HOME=/data
# Security
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio
# Init command
lxc.init.cmd = /opt/gotosocial/gotosocial server
EOF
log_info "LXC config created"
}
# 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=$(get_config main accounts_registration_open "false")
local approval=$(get_config main accounts_approval_required "true")
local data_path=$(get_config container data_path "$DATA_PATH")
mkdir -p "$data_path"
cat > "$data_path/config.yaml" <<EOF
# GoToSocial Configuration
# Generated by SecuBox gotosocialctl
host: "$host"
account-domain: "$host"
protocol: "$protocol"
bind-address: "$bind"
port: $port
db-type: "sqlite"
db-address: "/data/gotosocial.db"
storage-backend: "local"
storage-local-base-path: "/data/storage"
instance-expose-public-timeline: true
instance-expose-suspended: false
instance-expose-suspended-web: false
accounts-registration-open: $reg_open
accounts-approval-required: $approval
accounts-reason-required: true
media-image-max-size: 10485760
media-video-max-size: 41943040
media-description-min-chars: 0
media-description-max-chars: 500
media-remote-cache-days: 30
statuses-max-chars: 5000
statuses-cw-max-chars: 100
statuses-poll-max-options: 6
statuses-poll-option-max-chars: 50
statuses-media-max-files: 6
letsencrypt-enabled: false
oidc-enabled: false
smtp-host: ""
smtp-port: 0
syslog-enabled: false
syslog-protocol: "udp"
syslog-address: "localhost:514"
log-level: "info"
log-db-queries: false
advanced-cookies-samesite: "lax"
advanced-rate-limit-requests: 300
advanced-throttling-multiplier: 8
cache:
gts:
account-max-size: 2000
account-ttl: "30m"
account-sweep-freq: "1m"
status-max-size: 2000
status-ttl: "30m"
status-sweep-freq: "1m"
EOF
# Create storage directories
mkdir -p "$data_path/storage"
log_info "Configuration generated at $data_path/config.yaml"
}
# Install GoToSocial
cmd_install() {
local version="${1:-$GTS_VERSION}"
log_info "Installing GoToSocial v${version}..."
# Check dependencies
command -v lxc-start >/dev/null || {
log_error "LXC not installed. Install lxc package first."
return 1
}
# Create directories
mkdir -p "$LXC_PATH" "$DATA_PATH"
# Create rootfs
create_rootfs
# Download binary
download_binary "$version"
# Create LXC config
create_lxc_config
# Generate GoToSocial config
generate_config
log_info "GoToSocial installed successfully"
log_info "Run 'gotosocialctl start' to start the service"
log_info "Then create a user with 'gotosocialctl user create <username> <email>'"
}
# Uninstall
cmd_uninstall() {
local keep_data="$1"
log_info "Uninstalling GoToSocial..."
# Stop container if running
container_running && cmd_stop
# Remove container
rm -rf "$LXC_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 container
cmd_start() {
if ! container_exists; then
log_error "GoToSocial not installed. Run 'gotosocialctl install' first."
return 1
fi
if container_running; then
log_info "GoToSocial is already running"
return 0
fi
# Regenerate config in case settings changed
create_lxc_config
generate_config
log_info "Starting GoToSocial container..."
lxc-start -n "$LXC_NAME" -d -P "$(dirname $LXC_PATH)" || {
log_error "Failed to start container"
return 1
}
sleep 2
if container_running; then
log_info "GoToSocial started"
local port=$(get_config main port "8484")
log_info "Web interface available at http://localhost:$port"
else
log_error "Container failed to start"
return 1
fi
}
# Stop container
cmd_stop() {
if ! container_running; then
log_info "GoToSocial is not running"
return 0
fi
log_info "Stopping GoToSocial..."
lxc-stop -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" || true
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 container_state="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")
container_exists && installed="true"
container_running && container_state="true"
# Check if API responds
if [ "$container_state" = "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 <<EOF
{
"installed": $installed,
"container_running": $container_state,
"service_running": $service_state,
"host": "$host",
"port": "$port",
"version": "$version",
"tor_enabled": $([ "$tor_enabled" = "1" ] && echo "true" || echo "false"),
"dns_enabled": $([ "$dns_enabled" = "1" ] && echo "true" || echo "false"),
"mesh_enabled": $([ "$mesh_enabled" = "1" ] && echo "true" || echo "false")
}
EOF
}
# Status (human readable)
cmd_status_human() {
if container_running; then
echo "GoToSocial: running"
lxc-info -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" 2>/dev/null | grep -E "State|PID|CPU|Memory"
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 admin="${3:-false}"
[ -z "$username" ] || [ -z "$email" ] && {
echo "Usage: gotosocialctl user create <username> <email> [--admin]"
return 1
}
[ "$3" = "--admin" ] && admin="true"
if ! container_running; then
log_error "GoToSocial is not running"
return 1
fi
log_info "Creating user $username..."
# Generate random password
local password=$(openssl rand -base64 12)
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
/opt/gotosocial/gotosocial admin account create \
--username "$username" \
--email "$email" \
--password "$password" \
--config /data/config.yaml
if [ "$admin" = "true" ]; then
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
/opt/gotosocial/gotosocial admin account promote \
--username "$username" \
--config /data/config.yaml
fi
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() {
if ! container_running; then
log_error "GoToSocial is not running"
return 1
fi
local port=$(get_config main port "8484")
# Use API to list accounts (requires admin token)
# For now, check the database directly
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 <username>"
return 1
}
if ! container_running; then
log_error "GoToSocial is not running"
return 1
fi
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
/opt/gotosocial/gotosocial admin account confirm \
--username "$username" \
--config /data/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 <domain>"
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
container_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 container for consistent backup
local was_running=false
if container_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 <backup-file>"
return 1
}
log_info "Restoring from $backup_path..."
# Stop container
container_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 <<EOF
GoToSocial Controller for SecuBox v$VERSION
Usage: gotosocialctl <command> [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 <user> <email> [--admin] Create user
user list List users
user confirm <user> Confirm user email
Exposure:
emancipate <domain> Expose via HAProxy + SSL
Backup:
backup [path] Backup data
restore <path> 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