Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.31.0
1.32.0
101 changes: 100 additions & 1 deletion ai_local/ailocal
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:-<none>} (expected: webui)" ;;
esac
}

# === SERVICE MANAGEMENT ===

status() {
Expand Down Expand Up @@ -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})..."
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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" ;;

Expand All @@ -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" ;;

Expand Down
30 changes: 30 additions & 0 deletions tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down