diff --git a/CHANGELOG.md b/CHANGELOG.md index e245041..02d5cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The version here tracks the **project/repo** as a whole. The `chati` CLI also carries its own internal version (shown by `chati --version`). +## [1.32.0] - 2026-09-04 + +### Fixed +- **OpenWebUI: a pre-existing DB no longer traps you behind a sign-in / "Account + Activation Pending" wall (#70).** chati runs OpenWebUI login-less by default, + but that only applied to a fresh DB: once a DB had accounts, OpenWebUI kept + auth on forever and any new signup landed in `pending`. `ailocal` now detects + an account-bearing DB and returns it to the login-less default, both + automatically during `upgrade webui` and on demand via a new `ailocal reset + webui`. It is skipped when you explicitly set `WEBUI_AUTH` (real multi-user + setups are left alone), and it is non-destructive: the old DB is moved to a + timestamped `login-reset-backup-*` folder and the pre-upgrade snapshot from + `ailocal restore` still applies. The SearXNG web-search wiring is unchanged and + applies to the fresh login-less DB as before. + ## [1.31.0] - 2026-09-02 ### Added diff --git a/README.md b/README.md index 099045e..b2cb29b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An Ollama-centric, high-performance chat interface for the command line, with local AI service management and OpenWebUI integration. -![version](https://img.shields.io/badge/version-1.31.0-blue) ![license](https://img.shields.io/badge/license-MIT-green) ![platform](https://img.shields.io/badge/platform-macOS-lightgrey) +![version](https://img.shields.io/badge/version-1.32.0-blue) ![license](https://img.shields.io/badge/license-MIT-green) ![platform](https://img.shields.io/badge/platform-macOS-lightgrey) ## ⚡ Quick Start (macOS) diff --git a/VERSION b/VERSION index 34aae15..359c410 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.31.0 +1.32.0 diff --git a/ai_local/ailocal b/ai_local/ailocal index b077086..26ca78c 100755 --- a/ai_local/ailocal +++ b/ai_local/ailocal @@ -3,7 +3,7 @@ # Author: Carlos Damken set -o pipefail -AILOCAL_VERSION="3.2.0" +AILOCAL_VERSION="3.3.0" # === GLOBAL CONFIGURATION === LOG_DIR="$HOME/logs" @@ -117,6 +117,87 @@ webui_installed_version() { 'import importlib.metadata as m; print(m.version("open-webui"))' 2>/dev/null } +# --- Login-less recovery (#70) ------------------------------------------------ +# chati runs OpenWebUI login-less by default, but that only applies to a FRESH +# DB. Once a DB has accounts it shows a sign-in / "Account Activation Pending" +# wall forever, and OpenWebUI refuses to turn auth back off on an existing DB. +# These helpers detect that state and return the DB to login-less by MOVING it +# to a timestamped backup (never deleting), so `ailocal restore` and the backup +# folder both keep the old data. + +# Count user accounts in a webui DB (0 if missing/unreadable). Uses python3's +# stdlib sqlite3 in read-only mode, so it needs no system `sqlite3` binary and +# is safe while the server is running. +webui_db_user_count() { + local db="$1" + [[ -f "$db" ]] || { echo 0; return; } + python3 - "$db" <<'PY' 2>/dev/null || echo 0 +import sqlite3, sys +try: + con = sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True) + print(con.execute("SELECT COUNT(*) FROM user").fetchone()[0]) +except Exception: + print(0) +PY +} + +# Does the current OpenWebUI DB have a login wall (any accounts)? +webui_db_has_accounts() { + local db; db=$(find_webui_db) + [[ -n "$db" ]] || return 1 + [[ "$(webui_db_user_count "$db")" -gt 0 ]] +} + +# Did the user explicitly ask for auth? Then we never touch their DB. +webui_auth_requested() { + case "${WEBUI_AUTH:-}" in + True|true|1|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +# Return OpenWebUI to the login-less default by moving the account-bearing DB +# aside. Non-destructive and idempotent. $1 is a short reason for the log. +reset_webui_loginless() { + local reason="${1:-manual}" + local db; db=$(find_webui_db) + if [[ -z "$db" ]]; then + log "No OpenWebUI database yet — nothing to reset; the next start is login-less." + return 0 + fi + if webui_auth_requested; then + log "WEBUI_AUTH is set on — keeping your multi-user auth DB untouched." + return 0 + fi + if ! webui_db_has_accounts; then + log "OpenWebUI is already login-less (no accounts) — nothing to reset." + return 0 + fi + stopwebui >/dev/null 2>&1 || true + local stamp bak_dir f moved=0 + stamp=$(date +%Y%m%d-%H%M%S) + bak_dir="$OPENWEBUI_DATA_DIR/login-reset-backup-$stamp" + mkdir -p "$bak_dir" + for f in webui.db webui.db-wal webui.db-shm; do + [[ -e "$OPENWEBUI_DATA_DIR/$f" ]] && { mv "$OPENWEBUI_DATA_DIR/$f" "$bak_dir/$f"; moved=1; } + done + # Older layouts kept the DB inside the venv — move that one too. + if [[ "$db" != "$OPENWEBUI_DATA_DIR/webui.db" && -e "$db" ]]; then + mv "$db" "$bak_dir/webui.db"; moved=1 + fi + if [[ "$moved" -eq 1 ]]; then + log "OpenWebUI reset to login-less ($reason). Old DB saved at: $bak_dir" + log "Next 'ailocal start webui' opens straight to chat. To get the old accounts back, move that DB back or run 'ailocal restore'." + fi +} + +reset_target() { + case "${1:-}" in + webui) reset_webui_loginless "manual" ;; + *) error_exit "Unknown reset target: ${1:-} (expected: webui)" ;; + esac +} + # === SERVICE MANAGEMENT === status() { @@ -628,6 +709,14 @@ upgrade_webui() { # if the result is broken you run `ailocal restore`. if [[ -n "$(find_webui_db)" ]]; then create_backup + # #70: a DB with accounts shows a sign-in / "pending activation" wall + # forever. Unless the user explicitly asked for auth (WEBUI_AUTH), return + # it to chati's login-less default so the update actually clears the old + # sign-in state. Non-destructive: the DB moves to a timestamped backup and + # create_backup already snapshotted the pre-upgrade state. + if webui_db_has_accounts && ! webui_auth_requested; then + reset_webui_loginless "upgrade" + fi fi log "Starting OpenWebUI upgrade (installed: ${before:-none}, force: ${force:---no})..." @@ -704,9 +793,17 @@ Services (TARGET: ollama | webui | searxng | all — default all): Upgrades (every webui upgrade snapshots the current state first): upgrade webui [--force] Upgrade; shows version before → after. --force reinstalls the venv from scratch. + A DB stuck behind a sign-in wall is returned to + login-less here (skipped if WEBUI_AUTH is set). upgrade ollama Upgrade app + refresh all models upgrade all [--force] Both +Reset (return OpenWebUI to chati's login-less default): + reset webui Back up the current DB and clear the sign-in / + "pending activation" wall so the next start opens + straight to chat. Skipped if WEBUI_AUTH is set. + Non-destructive: old DB is saved and 'restore' works. + Backup (ONE slot — the state before the last upgrade/backup): backup Snapshot now, replacing the previous one restore [--yes] Go back to the snapshot (--yes skips confirm) @@ -933,6 +1030,7 @@ case "$1" in stop) stop_target "$2" ;; restart) restart_target "$2" ;; upgrade) upgrade_target "$2" "$3" ;; + reset) reset_target "$2" ;; backup) create_backup ;; restore) restore_backup "$2" ;; @@ -945,6 +1043,7 @@ case "$1" in stopall|stop-all) stop_target all ;; startsearxng) startsearxng ;; stopsearxng) stopsearxng ;; + __webui-accounts) webui_db_user_count "$2" ;; # internal: test hook for #70 safe-upgrade) upgrade_webui "$2" ;; # backup-first is built in now restore-backup) restore_backup "$2" ;; diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 0e75b0c..28c06d7 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -200,6 +200,36 @@ test_ailocal_searxng_documented() { } run_test "ailocal exposes the searxng target" test_ailocal_searxng_documented +test_webui_loginless_reset() { + # #70: a DB with accounts is a sign-in/pending wall. ailocal must detect it + # (via the internal count hook) and expose the login-less reset verb. + local tmp; tmp=$(mktemp -d) + python3 - "$tmp/with.db" <<'PY' +import sqlite3,sys +c=sqlite3.connect(sys.argv[1]); c.execute("CREATE TABLE user(id TEXT, role TEXT)") +c.executemany("INSERT INTO user VALUES(?,?)",[("a","admin"),("b","pending")]); c.commit() +PY + python3 - "$tmp/empty.db" <<'PY' +import sqlite3,sys +c=sqlite3.connect(sys.argv[1]); c.execute("CREATE TABLE user(id TEXT, role TEXT)"); c.commit() +PY + local n + n=$(./ai_local/ailocal __webui-accounts "$tmp/with.db") + [[ "$n" == "2" ]] || { echo "expected 2 accounts, got '$n'" >&2; rm -rf "$tmp"; return 1; } + n=$(./ai_local/ailocal __webui-accounts "$tmp/empty.db") + [[ "$n" == "0" ]] || { echo "empty user table should be 0, got '$n'" >&2; rm -rf "$tmp"; return 1; } + n=$(./ai_local/ailocal __webui-accounts "$tmp/missing.db") + [[ "$n" == "0" ]] || { echo "missing db should be 0, got '$n'" >&2; rm -rf "$tmp"; return 1; } + rm -rf "$tmp" + # The reset verb is documented and rejects a bad target. + local out; out=$(./ai_local/ailocal --help 2>&1) || return 1 + assert_match "$out" "reset webui" "reset webui in help" || return 1 + if ./ai_local/ailocal reset bogus >/dev/null 2>&1; then + echo "reset bogus should fail" >&2; return 1 + fi +} +run_test "ailocal returns OpenWebUI to login-less (#70)" test_webui_loginless_reset + test_ollama_proc_pattern() { # The process-match pattern must catch ollama at a path/word boundary # but NOT a lookalike like "myollama serve". Eval the real value from