#!/bin/bash _focalboardSqlite() { # The compose mount is ./data:/opt/focalboard/data — focalboard writes its # sqlite db at /opt/focalboard/data/focalboard.db, so the auth tools must # reach it there (the bare /data inside the container isn't mounted). runFileOp docker exec -i focalboard-service sqlite3 /opt/focalboard/data/focalboard.db "$1" 2>&1 } _focalboardBcrypt() { if ! command -v htpasswd >/dev/null 2>&1; then isError "htpasswd is required to bcrypt the password."; return 1 fi htpasswd -bnBC 10 "" "$1" | tr -d ':\n' } authAdapter_focalboard_setPassword() { local user="$1" password="$2" [[ -z "$user" ]] && { isError "Username is required."; return 1; } [[ -z "$password" ]] && password=$(generateRandomPassword) local hash; hash=$(_focalboardBcrypt "$password") || return 1 local out; out=$(_focalboardSqlite "UPDATE users SET password = '${hash//\'/\'\'}' WHERE username = '${user//\'/\'\'}'; SELECT changes();") if [[ "$out" != "1" ]]; then isError "Focalboard reset failed (no user '$user'): $out"; return 1; fi if [[ "$user" == "${CFG_FOCALBOARD_ADMIN_USER:-}" ]]; then authPersistCfg focalboard ADMIN_PASSWORD "$password" fi isSuccessful "Focalboard password set for $user — New password: $password" } authAdapter_focalboard_createUser() { local user="$1" password="$2" email="$3" isAdmin="$4" [[ -z "$user" || -z "$email" ]] && { isError "Username and email are required."; return 1; } [[ -z "$password" ]] && password=$(generateRandomPassword) local hash; hash=$(_focalboardBcrypt "$password") || return 1 local id="ez_$(date +%s)_${RANDOM}" local now=$(date +%s%N | cut -c1-13) local sql="INSERT INTO users (id, username, email, password, props, create_at, update_at, delete_at) VALUES ('$id','${user//\'/\'\'}','${email//\'/\'\'}','${hash//\'/\'\'}','{}',$now,$now,0);" local out; out=$(_focalboardSqlite "$sql") if [[ -n "$out" && "$out" != *"changes"* ]]; then isError "Focalboard create failed: $out"; return 1; fi if [[ "$isAdmin" == "true" && -z "${CFG_FOCALBOARD_ADMIN_USER:-}" ]]; then authPersistCfg focalboard ADMIN_USER "$user" authPersistCfg focalboard ADMIN_PASSWORD "$password" fi isSuccessful "Focalboard user created — User: $user — Email: $email — Password: $password" } authAdapter_focalboard_listUsers() { _focalboardSqlite ".mode tabs SELECT 'EZ_USER', email, username, '' FROM users WHERE delete_at = 0 ORDER BY username;" } authAdapter_focalboard_deleteUser() { local user="$1" [[ -z "$user" ]] && { isError "Username is required."; return 1; } local now=$(date +%s%N | cut -c1-13) local out; out=$(_focalboardSqlite "UPDATE users SET delete_at = $now WHERE username = '${user//\'/\'\'}'; SELECT changes();") [[ "$out" != "1" ]] && { isError "Focalboard delete failed (no user '$user')."; return 1; } isSuccessful "Focalboard user '$user' deleted (soft-delete)." } authAdapter_focalboard_setAdmin() { local user="$1" isAdmin="$2" isNotice "Focalboard admin toggle is not exposed via SQL — set in the workspace UI." return 1 }