feat(profile): add --profile flag for isolated instances - #128
Conversation
Lets prod, --testing and e.g. a Research Edition build run side by side on one machine. `--profile NAME` picks the instance; `--testing` is now an alias for `--profile testing`, so existing behaviour is unchanged. aw-qt only launches things, so its part is small: - resolve + validate the profile (same rule as aw-server-rust: lowercase alnum with -/_, max 32 chars) - export AW_PROFILE so spawned modules inherit it without threading a flag through every module CLI - profile-suffixed single-instance lockfile (aw-qt-research.lock), so a second profile is not refused as "already running" - config section [aw-qt-<profile>], falling back to [aw-qt] - port lookup reads the profile's own server config (config-<p>.toml / [server-<p>]), mirroring aw-server-rust's config path; a custom profile with no port configured warns, since it would collide with 5600 - tray tooltip and menu show the profile name Part of ActivityWatch/activitywatch#1399.
aw-qt exports AW_PROFILE for the modules it spawns (ActivityWatch/aw-qt#128), so a profile set on the launcher reaches aw-server-rust without every module growing its own flag. --profile still wins when given.
Greptile SummaryAdds named ActivityWatch profiles so separate editions or modes can run concurrently while preserving default and testing behavior.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
CLI[CLI profile options] --> Resolve[Resolve and validate profile]
Resolve --> Env[Export AW_PROFILE]
Resolve --> Lock[Acquire profile lock]
Resolve --> Config[Load profile configuration]
Config --> Port[Resolve server port]
Env --> Manager[Start selected modules]
Port --> Tray[Profile-aware tray and links]
Manager --> Tray
Reviews (3): Last reviewed commit: "fix(profile): keep default profile on le..." | Re-trigger Greptile |
|
Greptile 4/5, one P1 addressed in-thread: the Rust-first port lookup vs. module-specific probe divergence is pre-existing on master and unchanged here, so it's split out as #129 rather than folded into a flag-addition PR. All CI green (ubuntu/macos/windows). |
…ay/manager divergence When only aw-server (Python) is in autostart_modules but an aw-server-rust config file exists with a custom port, the old Rust-first logic would make the tray open the wrong URL while the manager probed the correct server. Pass autostart_modules to _read_server_port so port selection matches the server type that will actually run. Backward-compat: callers that omit the arg still get the original Rust-first behaviour. Fixes Greptile's 4/5 finding on PR ActivityWatch#128.
|
@greptileai review |
|
Fixed the default-profile root regression in 584d626. |
|
@greptileai review |
aw-qt exports AW_PROFILE for the modules it spawns (ActivityWatch/aw-qt#128), so a profile set on the launcher reaches aw-server-rust without every module growing its own flag. --profile still wins when given.
* feat(profile): named instance profiles via --profile flag Replace the two-valued `testing: bool` with a named profile string so that more than two parallel ActivityWatch instances can coexist on one machine. - `dirs.rs`: `db_path(profile)` → `sqlite.db` / `sqlite-<profile>.db`; add `validate_profile()` (lowercase alnum + `-_`, max 32 chars, starts with alnum); tests for suffix rule and validation - `config.rs`: replace `static mut TESTING: bool` with `OnceLock<String> PROFILE`; `set_profile()` is idempotent for same value, panics on conflict; `get_profile()` / `is_testing()` derived from it; config file is `config.toml` / `config-<profile>.toml` - `logging.rs`: `setup_logger(module, profile, verbose)` — logfile suffix is `<module>-<profile>_<ts>.log` for non-default profiles - `main.rs`: add `--profile NAME`; `--testing` remains as alias for `--profile testing`; debug builds still default to "testing"; profile is validated before use - `android/mod.rs`: update two call-sites to pass `"default"` Backwards compatibility: - `--testing` still works (alias for `--profile testing`) - `default` profile maps to existing unsuffixed paths (sqlite.db, config.toml) — no migration required - `testing` profile maps to existing -testing suffix paths Part of ActivityWatch/activitywatch#1399. * fix(aw-sync): update setup_logger call to pass profile string setup_logger signature changed to accept profile: &str instead of testing: bool. Convert opts.testing bool to "testing"/"default" profile string at the call site. Also run cargo fmt to fix long assert! lines in dirs.rs tests. * fix(config): eliminate TOCTOU race in set_profile via atomic OnceLock::set result Previously the function checked PROFILE.get() then PROFILE.set() in two separate steps. Two threads with different profile values could both see get()==None before either set, causing the loser's set() error to be discarded and the loser to silently proceed under the wrong profile. Fix: use the atomic OnceLock::set return value directly. If Ok(()), we won the race. If Err(_), the lock was already set by a concurrent caller; check the existing value and panic only if it differs. * fix(config): set_testing delegates to set_profile to propagate conflict panics Greptile P1: bare PROFILE.set() in set_testing silently discarded conflicts even when the existing profile differed, allowing a losing caller to proceed under the wrong instance. Delegating to set_profile() reuses its idempotent same-value check and conflict panic, matching the documented semantics. * fix(tests): mark in-process server as testing * feat(profile): isolate dir roots per profile and report profile in server info --profile only suffixed the DB filename, so a non-default instance still shared config.toml, the cache dir and the log dir with prod — the one thing profile isolation is for. Move the profile into the platformdirs appname instead ("activitywatch-<profile>"), which isolates config/data/cache/logs and everything nested under them in one place, with no per-module path changes. default and testing deliberately keep the bare "activitywatch" root: their legacy per-file suffixes (sqlite-testing.db, config-testing.toml, port 5666) already separate them, and moving their root would orphan existing installs. Also add Info.profile so clients (webui badge) can tell concurrent instances apart; it deserializes with a "default" fallback so a new client still parses an older server's /api/0/info. * feat(profile): fall back to AW_PROFILE env when --profile is absent aw-qt exports AW_PROFILE for the modules it spawns (ActivityWatch/aw-qt#128), so a profile set on the launcher reaches aw-server-rust without every module growing its own flag. --profile still wins when given. * feat(profile): isolate aw-sync config dirs via shared appname aw-sync hard-coded activitywatch/aw-sync, so a research instance would read prod's sync config. Use aw_server::dirs::appname() and the same config-{profile}.toml filename rule as the server. aw-sync now resolves --profile / AW_PROFILE / --testing and calls set_profile so appname() is the named profile, not always default. * feat(profile): testing-root fallback with legacy artifacts Adopt the activitywatch#1399 rule so rust matches aw-core#152: 1. activitywatch-testing/ exists → use it 2. else legacy testing files in activitywatch/ → stay on the shared root (sqlite-testing.db, config-testing.toml) 3. else fresh setup → create and use activitywatch-testing/ Isolated profile roots use bare sqlite.db / config.toml / log names. set_profile now runs before setup_logger so named profiles log into their own cache dir. * fix(aw-sync): keep Android filesDir config path after profile rebase Master's #666 reads the embedded server's config from filesDir for the API key. After rebasing onto that, get_server_config_path must not switch Android onto desktop XDG + appname_for (those helpers are cfg-gated off Android). Desktop still uses the isolated activitywatch-<profile> root.
Part of ActivityWatch/activitywatch#1399 (isolated profiles per edition/mode).
Lets prod,
--testingand e.g. a Research Edition build run side by side on one machine.--profile NAMEpicks the instance;--testingis now an alias for--profile testing, so existing behaviour is unchanged — with no flag, and with--testing, every path is byte-identical to today.aw-qt only launches things, so its part is deliberately small:
aw_qt/profile.py) — same rule as aw-server-rust'svalidate_profile: lowercase alphanumeric plus-/_, max 32 chars, so a profile is always a safe path segment.AW_PROFILEfor spawned modules, so the profile propagates without threading a flag through every module's CLI.aw-qt-research.lock) — otherwise the second profile is refused as "already running".[aw-qt-<profile>], falling back to[aw-qt]when the profile has no section of its own.config-<profile>.tomlfor aw-server-rust,[server-<profile>]for aw-server), mirroring aw-server-rust's config path. A custom profile with no port configured logs a warning, since it would collide with 5600.ActivityWatch (research), "Running in profile: research").testingkeeps its existing "Running in testing mode" wording.Not in this PR
Full data/config isolation lives in the
dirsimplementations — ActivityWatch/aw-server-rust#652 and its follow-up do the Rust half, the aw-core half is separate. Until those land, a custom profile isolates what aw-qt owns (lockfile, config section, port) but still shares aw-core's data dir. Nothing regresses in the meantime: without--profile, no behaviour changes at all.Verification
pytest tests/— 97 passed (36 new, covering resolution/validation, suffixes, env propagation, per-profile port lookup, config-section fallback)mypy aw_qt— 8 errors, identical to the pre-existingmasterbaseline (all PyQt6QAction | Nonestubs intrayicon.py)flake8— clean on changed files--helpshows both flags;--profile Researchand--profile research --testingfail with clear usage errors