#!/usr/bin/env bash # ============================================================================= # app-token.sh — can we obtain a long-lived PeerTube app token? # # A long-lived ("app") token comes from PeerTube's `client_credentials` OAuth2 # grant, which is only allowed for APPLICATION OAuth clients (not the built-in # `local` client). To create one you first need an admin to run PeerTube's # create-client script ON the instance, then paste the client_id/client_secret # here. # # This script: # 1. Probes API reachability of the instance. # 2. Tries `client_credentials` with the given (or optional) app client. # 3. Detects whether the instance is behind an SSO/OIDC provider. # 4. Prints the exact admin command to create an app client, and how to use it # here afterward. # # Usage: # scripts/app-token.sh [--instance https://videos.erg.be] [--client --secret ] # # --instance PeerTube base URL (default: read from xamxam DB, else https://videos.erg.be) # --client OAuth client_id of an application client (if you already have one) # --secret matching client_secret # # Exit 0 => a token was minted (printed to stdout); 1 => could not. # ============================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" DB_PATH="$REPO_ROOT/app/storage/xamxam.db" INSTANCE="" CLIENT_ID="" CLIENT_SECRET="" while [[ $# -gt 0 ]]; do case "$1" in --instance) INSTANCE="$2"; shift 2 ;; --client) CLIENT_ID="$2"; shift 2 ;; --secret) CLIENT_SECRET="$2"; shift 2 ;; -h|--help) gum style "Usage:" --bold gum style " scripts/app-token.sh [--instance ] [--client ] [--secret ]" exit 0 ;; *) echo "Unknown arg: $1" >&2; exit 2 ;; esac done command -v gum >/dev/null || { echo "gum is required"; exit 2; } command -v curl >/dev/null || { echo "curl is required"; exit 2; } command -v jq >/dev/null || { echo "jq is required (brew install jq)"; exit 2; } # Default instance from the DB if not given. if [[ -z "$INSTANCE" ]]; then if [[ -f "$DB_PATH" ]] && command -v sqlite3 >/dev/null; then INSTANCE="$(sqlite3 "$DB_PATH" "SELECT instance_url FROM peertube_settings WHERE id=1;" 2>/dev/null)" INSTANCE="${INSTANCE:-https://videos.erg.be}" else INSTANCE="https://videos.erg.be" fi fi INSTANCE="${INSTANCE%/}" API="$INSTANCE/api/v1" gum style "── XAMXAM · PeerTube app-token probe ──────────────────────" \ --border double --padding "1 2" --foreground 213 gum style "Instance : $INSTANCE" --foreground 214 # ── 1. API reachability ──────────────────────────────────────────────────────── resp="$(curl -sS -m 20 -w $'\n%{http_code}' "$API/config" 2>/dev/null)" code="${resp##*$'\n'}" if [[ "$code" != "200" ]]; then gum style "✗ API unreachable (HTTP $code)" --foreground 196 echo "$resp" | head -1 >&2 exit 1 fi gum style "✓ API reachable" --foreground 42 # ── 2. Detect SSO / OIDC ─────────────────────────────────────────────────────── # Plugin listing often requires auth. Only report OIDC presence if we actually got # a data list; otherwise state that it can't be determined anonymously. oidc_flag="unknown" oidc_list="$(curl -sS -m 20 "$API/plugins?pluginType=2&count=100" 2>/dev/null)" if echo "$oidc_list" | grep -q '"total"'; then if echo "$oidc_list" | jq -e '.data[]? | select(.name | contains("oidc"))' >/dev/null 2>&1; then oidc_flag="enabled"; gum style "⚠ OIDC/SSO plugin detected on the instance" --foreground 220 else oidc_flag="none"; gum style "✓ No OIDC plugin in the auth-plugin list" --foreground 42 fi else gum style "? OIDC/SSO presence not determinable anonymously (plugin list needs admin auth)" --foreground 240 fi # ── 3. Try client_credentials ────────────────────────────────────────────────── # PeerTube only allows client_credentials on APPLICATION clients. The built-in # `local` client rejects it; if that's the only one we have, say so. if [[ -z "$CLIENT_ID" || -z "$CLIENT_SECRET" ]]; then gum style "── app client ──" --padding "0 1" --foreground 240 --bold gum style "No application client_id/client_secret supplied." --foreground 214 gum style "Fetching the built-in 'local' client to demonstrate it is NOT enough:" --foreground 240 local_client="$(curl -sS -m 20 "$API/oauth-clients/local" 2>/dev/null)" if command -v jq >/dev/null; then CLIENT_ID="$(echo "$local_client" | jq -r '.client_id // empty')" CLIENT_SECRET="$(echo "$local_client" | jq -r '.client_secret // empty')" else CLIENT_ID="$(echo "$local_client" | sed -n 's/.*"client_id":"\([^"]*\)".*/\1/p')" CLIENT_SECRET="$(echo "$local_client" | sed -n 's/.*"client_secret":"\([^"]*\)".*/\1/p')" fi fi grant="$(curl -sS -m 20 -X POST "$API/users/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" 2>/dev/null)" if echo "$grant" | grep -q '"access_token"'; then token="$(echo "$grant" | jq -r '.access_token')" expires="$(echo "$grant" | jq -r '.expires_in // "n/a"')" gum style "✓ client_credentials OK — minted access token" --foreground 42 gum style " expires_in: $expires s" --foreground 240 gum style "Access token:" --foreground 214 --bold echo "$token" exit 0 else errcode="$(echo "$grant" | jq -r '.code // empty' 2>/dev/null)" errmsg="$(echo "$grant" | jq -r '.detail // .error // "unknown"' 2>/dev/null)" if [[ "$errcode" == "unsupported_grant_type" ]]; then gum style "✗ client_credentials REJECTED (unsupported_grant_type)" --foreground 196 gum style " The '$CLIENT_ID' client is not an application client — it can only do" --foreground 214 gum style " password grant. You need an ADMIN-CREATED app client (see below)." --foreground 214 else gum style "✗ client_credentials failed: $errcode $errmsg" --foreground 196 fi fi # ── 4. Guide: how an admin creates an app client ─────────────────────────────── gum style "── How to get a long-lived app token ──" --padding "0 1" --foreground 240 --bold gum style "Run this ON the PeerTube server (as an admin):" --foreground 214 gum style "" --foreground 240 gum style " cd /var/www/peertube/prod" --foreground 240 gum style " sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/prod/config \\" --foreground 240 gum style " NODE_ENV=production node -r dotenv/config tools/create-client.js" --foreground 240 gum style "" --foreground 240 gum style "It prints a client_id and client_secret." --foreground 240 gum style "Then mint a long-lived token right here:" --foreground 214 gum style "" --foreground 240 gum style " bash $SCRIPT_DIR/app-token.sh --client --secret " --foreground 240 gum style "" --foreground 240 gum style "Note: the app client may still need the app authorized to act on behalf of" --foreground 240 gum style "a user; PeerTube issues the token with the client's own permission scope." --foreground 240 exit 1