Fix disabled plugins not showing in Settings for re-enable
Details
Root cause: when a plugin is disabled, its assembly is deferred (not
loaded) at startup to save memory. But the Settings plugin list only
iterated over loaded plugins (_plugins), so deferred plugins were
invisible — the user could never re-enable them.
Fix:
- Expose DeferredPluginDirs on IPluginRegistry — maps plugin IDs to
their discovered directory paths for plugins that were skipped
- SettingsViewModel.LoadPluginItems() now appends deferred plugins to
the list with IsEnabled=false, allowing the user to toggle them on
- Toggling on triggers the existing EnablePlugin() deferred path which
dynamically loads the assembly from the saved directory
- HeadlessPluginRegistry implements the new property with an empty dict
Fix fresh-install StorageError and update C# paths for SQLite migration
Details
Root cause: on fresh install (no salt file, no existing DB), init_core()
opened an unencrypted on-disk database at data.privstack.db. When
auth_initialize() then tried to create an encrypted DB at the same path,
SQLCipher failed because the file already existed as unencrypted.
Fix (Rust FFI):
- Fresh install now opens in-memory placeholder (same as encrypted mode)
- Only open unencrypted on-disk for legacy databases that already exist
- auth_initialize detects legacy unencrypted DB and skips SQLCipher
creation, just initializes vault on the existing DB
Fix (C# Desktop):
- WorkspaceService: base path changed from "data.duckdb" to "data"
(Rust derives "data.privstack.db" via with_extension)
- SetupWizardViewModel: same path change
- PrivStackService: updated diagnostics to check .privstack.db,
.datasets.db, .privstack.salt instead of old .duckdb files
- App.axaml.cs: updated orphan cleanup patterns and diagnostics
- DashboardViewModel: updated file size checks and compact display
- SystemMetricsService: updated file paths, WAL suffix (.wal → -wal)
Add legacy DuckDB migration detection FFI exports
Details
Since the Rust core no longer includes the DuckDB library, migration
from old .duckdb files must be orchestrated from the C# managed side.
These FFI functions support that workflow:
- privstack_has_legacy_databases(path) → bool: detects if any .duckdb
files exist at the data path
- privstack_list_legacy_databases(path) → JSON array of found files
with names, paths, and sizes
- privstack_archive_legacy_databases(path): renames .duckdb files to
.duckdb.bak after successful migration, removes stale WAL files
The C# side should check for legacy databases on first launch with the
new version, run its migration logic (reading DuckDB via its own driver
and writing to the new SQLite/SQLCipher database), then call archive
to move the old files out of the way.
Implement two-phase FFI initialization for SQLCipher encryption
Details
The database can no longer be opened immediately at init time because
SQLCipher requires a password-derived key. This commit restructures
the FFI layer into a two-phase initialization:
Phase 1 (privstack_init): If a salt file exists (encrypted DB), opens
an in-memory placeholder so the handle exists for non-DB operations.
If no salt file exists (first run / legacy), opens unencrypted as before.
Phase 2 (auth_initialize / auth_unlock): Opens the real encrypted
database and swaps the Connection inside the shared Arc<Mutex<Connection>>.
All stores (entity, event, blob, vault) automatically use the new
connection on their next operation since they share the same Arc.
Key changes:
- PrivStackHandle gains a main_conn field holding the shared connection Arc
- PrivStackHandle::swap_connection() replaces the inner Connection and
re-initializes all store schemas on the new database
- EntityStore, EventStore, BlobStore gain reinitialize_schema() methods
for re-running CREATE TABLE IF NOT EXISTS after a connection swap
- VaultManager gains reinitialize_vaults() to clear cached Vault instances
so they re-create tables on the new connection
- auth_initialize: generates salt, derives Argon2id key, creates encrypted
DB via open_db(), swaps connection, writes salt file, then inits vault
- auth_unlock: reads salt, derives key, opens encrypted DB, swaps
connection, then unlocks vault
- auth_lock: swaps back to in-memory placeholder for encrypted DBs
- auth_change_password: rekeys DB with PRAGMA rekey + fresh salt
- Recovery functions: rekey DB after vault password reset
- auth_is_initialized: checks salt file existence as primary indicator
- Salt file: 16-byte random salt at <db_path>.privstack.salt
- derive_db_key helper: Argon2id -> raw hex key for SQLCipher
Clean up remaining DuckDB references in comments
Details
Replace stale DuckDB references with SQLite in comments across
cloud sync engine, datasets preprocessor/mutations/helpers, and
FFI dataset queries. No functional changes.
Get notified about new releases