#!/bin/bash set -Eeuo pipefail umask 077 CONFIG_FILE="$HOME/surfly/config.env" CERT_FILE="$HOME/surfly/certs/cobrowse.surflysupport.com" REPORT_FILE="$PWD/surfly_diagnostic_full.html" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT html_escape() { sed \ -e 's/\&/\&/g' \ -e 's//\>/g' \ -e 's/"/\"/g' \ -e "s/'/\'/g" } echo "Collecting system information..." TARGET_USER=$(whoami) TARGET_UID=$(id -u "$TARGET_USER" 2>/dev/null || echo "unknown") EXPECTED_XDG="/run/user/$TARGET_UID" OS_NAME=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2 || echo "unknown") PODMAN_VER=$(podman --version 2>/dev/null | awk '{print $3}' || echo "not installed") SYSTEMD_VER=$(systemctl --version 2>/dev/null | head -n1 | awk '{print $2}' || echo "unknown") REDIS_VER=$(redis-server --version 2>/dev/null | awk '{print $3}' | cut -d'=' -f2 || echo "not installed") UMASK_VALUE=$(umask) UMASK_SYMBOLIC=$(umask -S 2>/dev/null || echo "unknown") XDG_RUNTIME_DIR_VALUE="${XDG_RUNTIME_DIR:-not set}" # User validation if id "$TARGET_USER" >/dev/null 2>&1; then if [[ "$TARGET_UID" != "unknown" && "$TARGET_UID" -ge 1000 ]]; then USER_STATUS="valid non-privileged user (uid=$TARGET_UID)" USER_COLOR="green" else USER_STATUS="not recommended (uid=$TARGET_UID)" USER_COLOR="red" fi else USER_STATUS="user not found" USER_COLOR="red" fi # Linger validation LINGER_STATUS=$(loginctl show-user "$TARGET_USER" 2>/dev/null | awk -F= '/^Linger=/ {print $2}' || echo "unknown") if [[ "$LINGER_STATUS" == "yes" ]]; then LINGER_COLOR="green" else LINGER_COLOR="red" fi # XDG validation if [[ "$XDG_RUNTIME_DIR_VALUE" == "$EXPECTED_XDG" ]]; then XDG_COLOR="green" XDG_STATUS="verified" elif [[ "$XDG_RUNTIME_DIR_VALUE" == "not set" ]]; then XDG_COLOR="red" XDG_STATUS="not set" else XDG_COLOR="orange" XDG_STATUS="set but different from expected" fi # SELinux validation SELINUX_RAW=$(/usr/sbin/sestatus 2>/dev/null | awk -F: '/SELinux status/ {print $2}' | xargs || true) if [[ "${SELINUX_RAW:-unknown}" == "disabled" ]]; then SELINUX_STAT="disabled" SELINUX_COLOR="green" else SELINUX_STAT=$(/usr/sbin/sestatus 2>/dev/null | awk -F: '/Current mode/ {print $2}' | xargs || echo "unknown") if [[ "$SELINUX_STAT" == "permissive" || "$SELINUX_STAT" == "disabled" ]]; then SELINUX_COLOR="green" else SELINUX_COLOR="red" fi fi # System limits LIMIT_NOFILE_SOFT=$(ulimit -Sn 2>/dev/null || echo "unknown") LIMIT_NOFILE_HARD=$(ulimit -Hn 2>/dev/null || echo "unknown") LIMIT_NPROC_SOFT=$(ulimit -Su 2>/dev/null || echo "unknown") LIMIT_NPROC_HARD=$(ulimit -Hu 2>/dev/null || echo "unknown") if [[ "$LIMIT_NOFILE_SOFT" != "unknown" && "$LIMIT_NOFILE_SOFT" -ge 65535 ]]; then LIMIT_COLOR="green" else LIMIT_COLOR="red" fi # Unprivileged ports UNPRIV_PORT_START=$(sysctl -n net.ipv4.ip_unprivileged_port_start 2>/dev/null || echo "unknown") if [[ "$UNPRIV_PORT_START" == "0" ]]; then UNPRIV_PORT_COLOR="green" UNPRIV_PORT_STATUS="enabled" else UNPRIV_PORT_COLOR="red" UNPRIV_PORT_STATUS="not enabled" fi # Config file if [[ -f "$CONFIG_FILE" ]]; then ENV_DATA=$( grep -v '^#' "$CONFIG_FILE" 2>/dev/null | grep '=' | \ sed -E ' s/^([[:space:]]*(SECRET_KEY|CLIENT_SECRET|DASHBOARD_AUTH_TOKEN|COBRO_AUTH_TOKEN|COOKIEJAR_SECRET|PG_EXTERNAL_USER_PASS)[[:space:]]*=).*/\1********/I; s/^([[:space:]]*.*(PASS|PASSWORD|PASSWD|TOKEN|SECRET|API_KEY|ACCESS_KEY|PRIVATE_KEY|AUTH|CREDENTIALS)[[:space:]]*=).*/\1********/I; s/^([[:space:]]*(SMTP_USER|SMTP_USERNAME|SMTP_PASSWORD|MAIL_USER|MAIL_USERNAME|MAIL_PASSWORD|MAILGUN_API_KEY|SENDGRID_API_KEY|POSTMARK_API_TOKEN)[[:space:]]*=).*/\1********/I ' || true ) else ENV_DATA="config.env not found at $CONFIG_FILE" fi LICENSE_JSON=$(curl -s localhost:8017/info/ 2>/dev/null | jq '.' 2>/dev/null || echo '{"error": "API unreachable or jq missing"}') # Detect service scope: prefer user units, fallback to system units SERVICE_SCOPE="none" SERVICES="" USER_SERVICES=$( systemctl --user list-units --type=service --all --no-legend 2>/dev/null \ | awk '{print $1}' \ | grep '^ss-.*\.service$' \ | sort -u || true ) SYSTEM_SERVICES=$( systemctl list-units --type=service --all --no-legend 2>/dev/null \ | awk '{print $1}' \ | grep '^ss-.*\.service$' \ | sort -u || true ) if [[ -n "${USER_SERVICES:-}" ]]; then SERVICE_SCOPE="user" SERVICES="$USER_SERVICES" elif [[ -n "${SYSTEM_SERVICES:-}" ]]; then SERVICE_SCOPE="system" SERVICES="$SYSTEM_SERVICES" fi if [[ "$SERVICE_SCOPE" == "user" ]]; then ALL_UNITS=$(systemctl --user list-dependencies ss-surfly.target --no-pager 2>/dev/null || echo "ss-surfly.target not found in user scope") elif [[ "$SERVICE_SCOPE" == "system" ]]; then ALL_UNITS=$(systemctl list-dependencies ss-surfly.target --no-pager 2>/dev/null || echo "ss-surfly.target not found in system scope") else ALL_UNITS="No ss-* services found in user or system scope" fi # sslcheck SSLCHECK_BIN=$(command -v sslcheck || true) SSLCHECK_STATUS="" SSLCHECK_OUTPUT="" SSLCHECK_VERBOSE_OUTPUT="" if [[ -n "$SSLCHECK_BIN" ]]; then if [[ -f "$CERT_FILE" ]]; then SSLCHECK_STATUS="sslcheck found: $SSLCHECK_BIN" SSLCHECK_OUTPUT=$("$SSLCHECK_BIN" verify -c "$CERT_FILE" 2>&1 || true) SSLCHECK_VERBOSE_OUTPUT=$("$SSLCHECK_BIN" verify -c "$CERT_FILE" -v 2>&1 || true) else SSLCHECK_STATUS="sslcheck found, but certificate file not found: $CERT_FILE" SSLCHECK_OUTPUT="Certificate file not found: $CERT_FILE" SSLCHECK_VERBOSE_OUTPUT="Certificate file not found: $CERT_FILE" fi else SSLCHECK_STATUS="sslcheck command not found in PATH" SSLCHECK_OUTPUT="sslcheck command not found in PATH" SSLCHECK_VERBOSE_OUTPUT="sslcheck command not found in PATH" fi cat > "$REPORT_FILE" < Surfly Full Diagnostic Report

🚀 Surfly Node Diagnostic Report

Generated: $(date)

Report path: $(realpath "$REPORT_FILE")

🖥️ System Specs

OS: $(printf '%s' "$OS_NAME" | html_escape)

Podman: $(printf '%s' "$PODMAN_VER" | html_escape) (Req: 5.4.0+)

Systemd: $(printf '%s' "$SYSTEMD_VER" | html_escape) (Req: 252+)

Redis: $(printf '%s' "$REDIS_VER" | html_escape)

User ($TARGET_USER): $(printf '%s' "$USER_STATUS" | html_escape)

SELinux: $(printf '%s' "$SELINUX_STAT" | html_escape)

Loginctl Linger ($TARGET_USER): $(printf '%s' "$LINGER_STATUS" | html_escape)

Linger allows user services to keep running after logout.

XDG_RUNTIME_DIR: $(printf '%s' "$XDG_RUNTIME_DIR_VALUE" | html_escape)

Expected: $(printf '%s' "$EXPECTED_XDG" | html_escape) | Status: $(printf '%s' "$XDG_STATUS" | html_escape)

Unprivileged ports: $(printf '%s' "$UNPRIV_PORT_STATUS" | html_escape)

net.ipv4.ip_unprivileged_port_start=$(printf '%s' "$UNPRIV_PORT_START" | html_escape)

Open files limit: soft=$(printf '%s' "$LIMIT_NOFILE_SOFT" | html_escape), hard=$(printf '%s' "$LIMIT_NOFILE_HARD" | html_escape)

Processes: soft=$(printf '%s' "$LIMIT_NPROC_SOFT" | html_escape), hard=$(printf '%s' "$LIMIT_NPROC_HARD" | html_escape)

Umask: $(printf '%s' "$UMASK_VALUE" | html_escape) ($(printf '%s' "$UMASK_SYMBOLIC" | html_escape))

📜 License & Metadata

$(printf '%s' "$LICENSE_JSON" | html_escape)

🔑 Configuration (config.env)

$(printf '%s' "$ENV_DATA" | html_escape)

🔐 SSL Certificate Check

Certificate path: $(printf '%s' "$CERT_FILE" | html_escape)

Status: $(printf '%s' "$SSLCHECK_STATUS" | html_escape)

sslcheck verify

$(printf '%s' "$SSLCHECK_OUTPUT" | html_escape)

sslcheck verify -v

$(printf '%s' "$SSLCHECK_VERBOSE_OUTPUT" | html_escape)

🏗️ Service Dependencies

Detected service scope: $(printf '%s' "$SERVICE_SCOPE" | html_escape)

$(printf '%s' "$ALL_UNITS" | html_escape)

📋 Service Logs

EOF else for SERVICE in $SERVICES; do SAFE_ID=$(echo "$SERVICE" | tr '.@-' '___') cat >> "$REPORT_FILE" <$SERVICE EOF done fi cat >> "$REPORT_FILE" <
EOF if [[ -n "${SERVICES:-}" ]]; then for SERVICE in $SERVICES; do SAFE_ID=$(echo "$SERVICE" | tr '.@-' '___') LOG_FILE="$TMP_DIR/$SAFE_ID.log" if [[ "$SERVICE_SCOPE" == "user" ]]; then journalctl --user-unit "$SERVICE" --no-pager -o short-iso 2>&1 | html_escape > "$LOG_FILE" else journalctl -u "$SERVICE" --no-pager -o short-iso 2>&1 | html_escape > "$LOG_FILE" fi cat >> "$REPORT_FILE" <

$SERVICE

Full journalctl output for this service

EOF
        cat "$LOG_FILE" >> "$REPORT_FILE"
        cat >> "$REPORT_FILE" <
                
EOF done fi cat >> "$REPORT_FILE" <
EOF chmod 600 "$REPORT_FILE" echo "------------------------------------------------" echo "Local report generated: $(realpath "$REPORT_FILE")" ls -l "$REPORT_FILE" read -r -p "Would you like to export this report to a shareable link? (y/n): " confirm if [[ $confirm == [yY] ]]; then if command -v nc >/dev/null 2>&1; then URL=$(cat "$REPORT_FILE" | nc termbin.com 9999 || true) if [[ -n "${URL:-}" ]]; then echo "Successfully exported! Link: $URL" else echo "Export failed." fi else echo "nc command not found. Cannot export." fi fi