Skip to content

Commit 065e198

Browse files
authored
Support reliable self-update for server-side and headless core instances (tinyhumansai#1458)
1 parent 8c6b127 commit 065e198

16 files changed

Lines changed: 478 additions & 78 deletions

File tree

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ OPENHUMAN_WORKSPACE=
7474
OPENHUMAN_TEMPERATURE=0.7
7575
# [optional] Skill + agent tool execution timeout in seconds (default 120, max 3600)
7676
# OPENHUMAN_TOOL_TIMEOUT_SECS=
77+
# [optional] Headless update restart contract: self_replace | supervisor
78+
# OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=self_replace
79+
# [optional] Allow bearer-authenticated RPC callers to invoke update.apply/update.run
80+
# Disable on exposed server deployments unless you explicitly want remote self-upgrade.
81+
# OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=true
7782

7883
# ---------------------------------------------------------------------------
7984
# Runtime flags

app/src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/RELEASE-MANUAL-SMOKE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Applies to every release, all platforms.
4343

4444
- [ ] **`.deb` and/or `.AppImage` install on a clean Ubuntu 22.04**`sudo dpkg -i openhuman_*.deb` or `chmod +x openhuman-*.AppImage && ./openhuman-*.AppImage`. Expected: no missing-dependency errors; app launches.
4545
- [ ] **OS-native notification toasts fire** — Trigger a notification from inside the app (e.g. memory captured, agent finished). Expected: a libnotify-style toast appears outside the app window. (CI Linux sees only Xvfb; this surface verifies on a real desktop.)
46+
- [ ] **Headless supervisor update stages without self-exit** — On a Linux service deployment with `[update] restart_strategy = "supervisor"` and `rpc_mutations_enabled = false`, stage a new core binary through the documented operator flow. Expected: the running process stays up until the supervisor restart, the staged binary is present on disk, and `systemctl restart openhuman` (or equivalent) picks up the new version.
4647

4748
### Cross-platform
4849

docs/TEST-COVERAGE-MATRIX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Canonical mapping of every product feature to its test source(s). Drives gap-fil
4848

4949
| ID | Feature | Layer | Test path(s) | Status | Notes |
5050
| ----- | ----------------------------- | ----- | -------------------------------------------------- | ------ | ------------------------------------- |
51-
| 0.3.1 | Auto Update Check | RU+MS | `src/openhuman/update/` (Rust unit), release smoke | 🟡 | Core check covered; UI prompt manual |
51+
| 0.3.1 | Auto Update Check | RU+RI+MS | `src/openhuman/update/` (Rust unit), `tests/json_rpc_e2e.rs`, release smoke | 🟡 | Core check/update policy covered; desktop prompt + release upgrade still manual |
5252
| 0.3.2 | Forced Update Handling | MS | release-manual-smoke | 🚫 | End-to-end gating verified at release |
5353
| 0.3.3 | Reinstall with Existing State | MS | release-manual-smoke | 🚫 | Workspace persistence on reinstall |
5454
| 0.3.4 | Clean Uninstall | MS | release-manual-smoke | 🚫 | OS removal paths |

gitbooks/features/cloud-deploy.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,54 @@ Then run `openhuman-core serve` under your service manager of choice
214214
(systemd, supervisord, …) with the same environment variables documented
215215
above.
216216

217+
### Headless self-update contract
218+
219+
Headless deployments should treat `openhuman.update_apply` as the safe primitive:
220+
it downloads the release asset, writes it atomically next to the current binary,
221+
and returns. Nothing exits automatically.
222+
223+
`openhuman.update_run` follows `config.update.restart_strategy`:
224+
225+
- `self_replace` (default): stage the binary, publish an in-process restart request, and let the running core respawn itself.
226+
- `supervisor`: stage the binary and return `restart_requested=false`. Your outer service manager must restart the process.
227+
228+
For long-running Linux services, set:
229+
230+
```toml
231+
[update]
232+
restart_strategy = "supervisor"
233+
rpc_mutations_enabled = false
234+
```
235+
236+
or the equivalent env vars:
237+
238+
```bash
239+
OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor
240+
OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false
241+
```
242+
243+
Recommended `systemd` stance:
244+
245+
```ini
246+
Restart=always
247+
ExecReload=/bin/kill -HUP $MAINPID
248+
```
249+
250+
Operator flow:
251+
252+
1. Call `openhuman.update_check` to discover a release.
253+
2. Configure `restart_strategy = "supervisor"` in your `update.toml` (or set
254+
`OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor`) so the core stages the
255+
new binary without trying to re-exec itself, then call
256+
`openhuman.update_apply` or `openhuman.update_run`. `restart_strategy` is a
257+
configuration setting, not an RPC parameter.
258+
3. Restart the unit explicitly: `systemctl restart openhuman`.
259+
260+
If download or staging fails, the running binary is left in place and no
261+
restart is requested. If a staged binary proves bad after restart, roll back by
262+
restoring the previous binary from your package manager, image tag, or release
263+
artifact and restarting the supervisor again.
264+
217265
The Compose file ([`docker-compose.yml`](../../docker-compose.yml)) maps the core
218266
on `:7788`, mounts a named volume `openhuman-workspace` for persistence, and
219267
sets `restart: unless-stopped` so the core comes back after host reboots.
@@ -226,6 +274,10 @@ docker compose build
226274
docker compose up -d
227275
```
228276

277+
For RPC-exposed production deployments, prefer leaving mutating update RPCs
278+
disabled (`OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false`) and perform
279+
rollouts through your existing image tag or package-management flow instead.
280+
229281
### Logs
230282

231283
```bash

src/openhuman/about_app/catalog.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ const CAPABILITIES: &[Capability] = &[
922922
name: "Apply Core Update",
923923
domain: "update",
924924
category: CapabilityCategory::Settings,
925-
description: "Download and stage a newer core binary, then restart the sidecar.",
925+
description: "Download and stage a newer core binary. Desktop builds can self-restart; headless deployments can hand restart off to a supervisor.",
926926
how_to: "Settings > Developer Options > Apply Update",
927927
status: CapabilityStatus::Beta,
928928
privacy: None,

src/openhuman/config/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ pub use schema::{
3434
ReliabilityConfig, ResourceLimitsConfig, RuntimeConfig, SandboxBackend, SandboxConfig,
3535
SchedulerConfig, SchedulerGateConfig, SchedulerGateMode, ScreenIntelligenceConfig,
3636
SecretsConfig, SecurityConfig, SlackConfig, StorageConfig, StorageProviderConfig,
37-
StorageProviderSection, StreamMode, TelegramConfig, UpdateConfig, VoiceActivationMode,
38-
VoiceServerConfig, WebSearchConfig, WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MODEL,
39-
MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
37+
StorageProviderSection, StreamMode, TelegramConfig, UpdateConfig, UpdateRestartStrategy,
38+
VoiceActivationMode, VoiceServerConfig, WebSearchConfig, WebhookConfig,
39+
DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MODEL, MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
4040
};
4141
pub use schema::{
4242
clear_active_user, default_root_openhuman_dir, pre_login_user_dir, read_active_user_id,

src/openhuman/config/schema/load.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use super::{
55
normalize_no_proxy_list, normalize_proxy_url_option, normalize_service_list,
66
parse_proxy_enabled, parse_proxy_scope, set_runtime_proxy_config, ProxyScope,
77
},
8-
Config,
8+
Config, UpdateRestartStrategy,
99
};
1010
use anyhow::{Context, Result};
1111
use directories::UserDirs;
@@ -1061,6 +1061,30 @@ impl Config {
10611061
self.update.interval_minutes = minutes;
10621062
}
10631063
}
1064+
if let Some(raw) = env.get("OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY") {
1065+
match raw.trim().to_ascii_lowercase().as_str() {
1066+
"self_replace" | "self-replace" | "self" => {
1067+
self.update.restart_strategy = UpdateRestartStrategy::SelfReplace;
1068+
}
1069+
"supervisor" | "stage_only" | "stage-only" => {
1070+
self.update.restart_strategy = UpdateRestartStrategy::Supervisor;
1071+
}
1072+
other => {
1073+
tracing::warn!(
1074+
value = other,
1075+
"ignoring invalid OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY \
1076+
(valid: self_replace, supervisor)"
1077+
);
1078+
}
1079+
}
1080+
}
1081+
if let Some(flag) = env.get("OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED") {
1082+
if let Some(enabled) =
1083+
parse_env_bool("OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED", &flag)
1084+
{
1085+
self.update.rpc_mutations_enabled = enabled;
1086+
}
1087+
}
10641088

10651089
// Dictation overrides
10661090
if let Some(flag) = env.get("OPENHUMAN_DICTATION_ENABLED") {

src/openhuman/config/schema/load_tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,35 @@ fn env_overlay_auto_update_interval_parses_u32() {
810810
assert_eq!(cfg.update.interval_minutes, 60);
811811
}
812812

813+
#[test]
814+
fn env_overlay_auto_update_restart_strategy_accepts_supported_values() {
815+
let mut cfg = Config::default();
816+
cfg.apply_env_overlay_with(
817+
&HashMapEnv::new().with("OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY", "supervisor"),
818+
);
819+
assert_eq!(
820+
cfg.update.restart_strategy,
821+
crate::openhuman::config::UpdateRestartStrategy::Supervisor
822+
);
823+
824+
cfg.apply_env_overlay_with(
825+
&HashMapEnv::new().with("OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY", "self_replace"),
826+
);
827+
assert_eq!(
828+
cfg.update.restart_strategy,
829+
crate::openhuman::config::UpdateRestartStrategy::SelfReplace
830+
);
831+
}
832+
833+
#[test]
834+
fn env_overlay_auto_update_rpc_mutations_enabled_parses_bool() {
835+
let mut cfg = Config::default();
836+
cfg.apply_env_overlay_with(
837+
&HashMapEnv::new().with("OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED", "false"),
838+
);
839+
assert!(!cfg.update.rpc_mutations_enabled);
840+
}
841+
813842
#[test]
814843
fn env_overlay_empty_lookup_leaves_defaults_intact() {
815844
// The seam with no env entries should be a no-op on a fresh Config.

src/openhuman/config/schema/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub use tools::{
6666
GitbooksConfig, HttpRequestConfig, IntegrationToggle, IntegrationsConfig, MultimodalConfig,
6767
SecretsConfig, WebSearchConfig,
6868
};
69-
pub use update::UpdateConfig;
69+
pub use update::{UpdateConfig, UpdateRestartStrategy};
7070
mod voice_server;
7171
pub use voice_server::{VoiceActivationMode, VoiceServerConfig};
7272
mod types;

0 commit comments

Comments
 (0)