- 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 <noreply@anthropic.com>
980 lines
23 KiB
Bash
980 lines
23 KiB
Bash
#!/bin/sh
|
|
# GoToSocial Controller for SecuBox
|
|
# Manages GoToSocial in a Debian LXC container (glibc for proper bcrypt support)
|
|
|
|
set -e
|
|
|
|
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"
|
|
CONFIG_FILE="/etc/config/gotosocial"
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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 "$LXC_ROOTFS/opt/gotosocial/gotosocial" ]
|
|
}
|
|
|
|
# Check if GoToSocial is running (LXC container running)
|
|
gts_running() {
|
|
lxc_running
|
|
}
|
|
|
|
# =============================================================================
|
|
# 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}"
|
|
|
|
# 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} for ${arch}..."
|
|
|
|
rm -rf "$tmp_dir"
|
|
mkdir -p "$tmp_dir"
|
|
cd "$tmp_dir"
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
|
|
# 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 "$LXC_ROOTFS/opt/gotosocial/"
|
|
|
|
rm -rf "$tmp_dir"
|
|
log_info "GoToSocial v${version} installed in container"
|
|
}
|
|
|
|
# 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 (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")
|
|
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"
|
|
|
|
# Note: paths are relative to container where DATA_PATH is mounted as /data
|
|
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"
|
|
|
|
web-template-base-dir: "/opt/gotosocial/web/template"
|
|
web-asset-base-dir: "/opt/gotosocial/web/assets"
|
|
|
|
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
|
|
|
|
log_info "Configuration generated at $DATA_PATH/config.yaml"
|
|
}
|
|
|
|
# Install GoToSocial
|
|
cmd_install() {
|
|
local version="${1:-$GTS_VERSION}"
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
log_error "Root required"
|
|
exit 1
|
|
fi
|
|
|
|
if ! has_lxc; then
|
|
log_error "LXC not available. Install lxc packages first."
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Installing GoToSocial v${version} in LXC container..."
|
|
|
|
# Create data directory on host
|
|
create_data_dir
|
|
|
|
# Create container if not exists
|
|
if ! lxc_exists; then
|
|
lxc_create_rootfs || exit 1
|
|
fi
|
|
|
|
# Install GoToSocial binary into container
|
|
lxc_install_gotosocial "$version" || exit 1
|
|
|
|
# Create start script
|
|
lxc_create_start_script
|
|
|
|
# Create LXC config
|
|
lxc_create_config || exit 1
|
|
|
|
# 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"
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
log_error "Root required"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Uninstalling GoToSocial..."
|
|
|
|
# Stop container if running
|
|
lxc_stop
|
|
|
|
# Remove container
|
|
if [ -d "$LXC_PATH/$LXC_NAME" ]; then
|
|
rm -rf "$LXC_PATH/$LXC_NAME"
|
|
log_info "Container removed"
|
|
fi
|
|
|
|
# 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 (LXC container)
|
|
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 container is already running"
|
|
return 0
|
|
fi
|
|
|
|
# Regenerate config in case settings changed
|
|
generate_config
|
|
|
|
# Regenerate LXC config
|
|
lxc_create_config
|
|
|
|
log_info "Starting GoToSocial container..."
|
|
|
|
# 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"
|
|
log_info "Web interface available at http://localhost:$port"
|
|
return 0
|
|
fi
|
|
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. Container still running, may need more time."
|
|
return 1
|
|
}
|
|
|
|
# Stop GoToSocial (LXC container)
|
|
cmd_stop() {
|
|
if ! gts_running; then
|
|
log_info "GoToSocial is not running"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Stopping GoToSocial..."
|
|
lxc_stop
|
|
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 <<EOF
|
|
{
|
|
"installed": $installed,
|
|
"container_running": $running,
|
|
"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 gts_running; then
|
|
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
|
|
echo "API: responding"
|
|
else
|
|
echo "API: not responding (may still be starting)"
|
|
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"
|
|
local email="$2"
|
|
local password="$3"
|
|
local admin="${4:-false}"
|
|
|
|
[ -z "$username" ] || [ -z "$email" ] && {
|
|
echo "Usage: gotosocialctl user create <username> <email> [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)
|
|
|
|
gts_admin_cmd admin account create \
|
|
--username "$username" \
|
|
--email "$email" \
|
|
--password "$password" \
|
|
--config-path "/data/config.yaml"
|
|
|
|
if [ "$admin" = "true" ]; then
|
|
gts_admin_cmd admin account promote \
|
|
--username "$username" \
|
|
--config-path "/data/config.yaml"
|
|
fi
|
|
|
|
# Confirm the user
|
|
gts_admin_cmd admin account confirm \
|
|
--username "$username" \
|
|
--config-path "/data/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 <username>"
|
|
return 1
|
|
}
|
|
|
|
if ! gts_installed; then
|
|
log_error "GoToSocial is not installed"
|
|
return 1
|
|
fi
|
|
|
|
gts_admin_cmd admin account confirm \
|
|
--username "$username" \
|
|
--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 <username> [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"
|
|
|
|
[ -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
|
|
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 <backup-file>"
|
|
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 <<EOF
|
|
GoToSocial Controller for SecuBox v$VERSION
|
|
Runs GoToSocial in a Debian LXC container (glibc-based for proper bcrypt support)
|
|
|
|
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 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 <user> <email> [password] [--admin] Create user
|
|
user list List users
|
|
user confirm <user> Confirm user email
|
|
user password <user> [pwd] Reset user password
|
|
|
|
Exposure:
|
|
emancipate <domain> Expose via HAProxy + SSL
|
|
|
|
Container:
|
|
shell Open shell in container
|
|
|
|
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 user password alice newpassword123
|
|
gotosocialctl emancipate social.mysite.com
|
|
|
|
EOF
|
|
}
|
|
|
|
# Main
|
|
case "$1" in
|
|
install)
|
|
cmd_install "$2"
|
|
;;
|
|
uninstall)
|
|
cmd_uninstall "$2"
|
|
;;
|
|
update)
|
|
cmd_stop
|
|
lxc_install_gotosocial "${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"
|
|
;;
|
|
shell)
|
|
cmd_shell
|
|
;;
|
|
user)
|
|
case "$2" in
|
|
create)
|
|
cmd_user_create "$3" "$4" "$5" "$6"
|
|
;;
|
|
list)
|
|
cmd_user_list
|
|
;;
|
|
confirm)
|
|
cmd_user_confirm "$3"
|
|
;;
|
|
password)
|
|
cmd_user_password "$3" "$4"
|
|
;;
|
|
*)
|
|
echo "Usage: gotosocialctl user {create|list|confirm|password}"
|
|
;;
|
|
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
|