Skip to content

Commit b2e2e6a

Browse files
committed
chore: release v0.92.0
1 parent 28bb635 commit b2e2e6a

17 files changed

Lines changed: 166 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Changelog
22

3-
## v0.91.0
3+
## v0.92.0
44

55
### Added
6+
- **`gyne-consumer-profile` now restarts the consumer** — after writing `CONSUMER_NAME`/`CONSUMER_TASK_STREAM` to `.env`, the command restarts the Gyne consumer `systemctl --user` service by default so the worker re-registers under the new name (editing `.env` alone does not affect a running process). The unit is auto-detected from the enabled `--user` services (a `gyne`/`consumer` unit, excluding the gateway); pass `--service <unit>` to target a specific unit or `--no-restart` to only edit `.env`
67
- **`gyne-consumer-profile` subcommand** — update `workspace/gyne-agent/.env` on a deployed OpenClaw instance (`--instance` + `--name`, optional `--agent`, `--project`, `--task-stream`, `--json`) by changing `CONSUMER_NAME` and the matching `CONSUMER_TASK_STREAM` while preserving the rest of the file and writing a backup beside it
78
- **`gws-login` subcommand** — install Google Workspace (`gws`) credentials on a deployed instance (`--instance` + `--filename`, default `credentials.json`). `gws auth login` is an interactive browser OAuth flow with no headless mode and the instance is headless, so credentials are *injected* rather than minted on the box. Two input modes:
89
- `--credentials <file>`: a local gws JSON (e.g. from `gws auth export --unmasked`, or produced by an external OAuth flow), validated as JSON.

Cargo.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ clawmacdo gyne-consumer-profile --instance my-server --name consumer-4
642642
# Equivalent terminology for the profile name
643643
clawmacdo gyne-consumer-profile --instance my-server --consumer-name consumer-4
644644

645+
# Restart a specific unit, or skip the restart entirely
646+
clawmacdo gyne-consumer-profile --instance my-server --name consumer-4 --service gyne-agent.service
647+
clawmacdo gyne-consumer-profile --instance my-server --name consumer-4 --no-restart
648+
645649
# Non-default workspace project or base task stream
646650
clawmacdo gyne-consumer-profile --instance my-server \
647651
--project gyne-agent \
@@ -652,6 +656,8 @@ clawmacdo gyne-consumer-profile --instance my-server \
652656

653657
This command SSHes into the instance as the OpenClaw user, resolves the configured workspace for `--agent` (default `main`), and updates only `workspace/<project>/.env` (default `gyne-agent/.env`). It sets `CONSUMER_NAME=<name>` and `CONSUMER_TASK_STREAM=<TASK_STREAM>:<name>`, preserving the rest of the `.env` and creating a `.clawmacdo-gyne-*.bak` backup beside it. If `--task-stream` is omitted, the command derives the base stream from the existing `TASK_STREAM` value, falling back to `openclaw:tasks`.
654658

659+
By default it then restarts the Gyne consumer `systemctl --user` service so the worker re-registers under the new `CONSUMER_NAME` (changing `.env` alone does not affect a running process). The unit is auto-detected from the enabled `--user` services (a `gyne`/`consumer` unit, excluding the gateway); pass `--service <unit>` to target a specific unit, or `--no-restart` to only edit `.env`.
660+
655661
### OpenClaw Markdown Download
656662

657663
```bash
@@ -1219,7 +1225,7 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
12191225

12201226
---
12211227

1222-
**Current version:** 0.91.0
1228+
**Current version:** 0.92.0
12231229

12241230

12251231

crates/clawmacdo-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawmacdo-cli"
3-
version = "0.91.0"
3+
version = "0.92.0"
44
edition = "2021"
55
description = "CLI tool for migrating OpenClaw from Mac/DO to DigitalOcean"
66

crates/clawmacdo-cli/src/commands/gyne_consumer_profile.rs

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub struct GyneConsumerProfileParams {
1010
pub project: String,
1111
pub name: String,
1212
pub task_stream: Option<String>,
13+
pub restart: bool,
14+
pub service: Option<String>,
1315
pub json: bool,
1416
}
1517

@@ -126,6 +128,46 @@ fn clean_consumer_profile_name(value: &str) -> Result<String> {
126128
Ok(name.to_string())
127129
}
128130

131+
fn clean_service_unit(value: &str) -> Result<String> {
132+
let unit = value.trim();
133+
if unit.is_empty() {
134+
bail!("--service cannot be empty.");
135+
}
136+
if unit.len() > 128 {
137+
bail!("--service must be 128 bytes or fewer.");
138+
}
139+
// Restrict to systemd unit-name characters so the value is safe to embed in the restart shell.
140+
if !unit
141+
.bytes()
142+
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-' | b'@' | b'\\'))
143+
{
144+
bail!("--service may only contain letters, numbers, dots, underscores, hyphens, @, and backslashes.");
145+
}
146+
Ok(unit.to_string())
147+
}
148+
149+
/// Builds the shell that restarts the Gyne consumer `--user` systemd unit so it re-registers under the
150+
/// freshly written CONSUMER_NAME. When no unit is given it auto-detects a gyne/consumer service
151+
/// (excluding the gateway). Emits a single JSON line on stdout describing the outcome.
152+
fn build_restart_cmd(service: Option<&str>) -> String {
153+
let explicit = service.unwrap_or("");
154+
format!(
155+
"export XDG_RUNTIME_DIR=/run/user/$(id -u) DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus; \
156+
unit='{explicit}'; \
157+
if [ -z \"$unit\" ]; then \
158+
unit=$(systemctl --user list-unit-files --type=service --no-legend 2>/dev/null | awk '{{print $1}}' | grep -iE 'gyne|consumer' | grep -viE 'gateway' | head -n1); \
159+
fi; \
160+
if [ -z \"$unit\" ]; then \
161+
printf '{{\"restart\":\"skipped\",\"reason\":\"no_gyne_service_found\"}}\\n'; \
162+
else \
163+
systemctl --user daemon-reload 2>/dev/null || true; \
164+
systemctl --user restart \"$unit\" 2>/dev/null || systemctl --user start \"$unit\" 2>/dev/null || true; \
165+
for i in 1 2 3; do s=$(systemctl --user is-active \"$unit\" 2>/dev/null); [ \"$s\" = active ] && break; sleep 1; done; \
166+
printf '{{\"restart\":\"%s\",\"unit\":\"%s\"}}\\n' \"$(systemctl --user is-active \"$unit\" 2>/dev/null || echo unknown)\" \"$unit\"; \
167+
fi"
168+
)
169+
}
170+
129171
fn clean_task_stream(value: &str) -> Result<String> {
130172
let stream = value.trim();
131173
if stream.is_empty() {
@@ -352,6 +394,11 @@ pub async fn run(params: GyneConsumerProfileParams) -> Result<()> {
352394
let agent = clean_agent_id(&params.agent)?;
353395
let project = clean_project_slug(&params.project)?;
354396
let name = clean_consumer_profile_name(&params.name)?;
397+
let service = params
398+
.service
399+
.as_deref()
400+
.map(clean_service_unit)
401+
.transpose()?;
355402
let task_stream = params
356403
.task_stream
357404
.as_deref()
@@ -364,10 +411,33 @@ pub async fn run(params: GyneConsumerProfileParams) -> Result<()> {
364411

365412
println!("Updating Gyne consumer profile on {ip}...");
366413
let output = ssh_as_openclaw_with_user_async(&ip, &key, &cmd, ssh_user).await?;
367-
let value = remote_json_value(&output)?;
414+
let mut value = remote_json_value(&output)?;
368415
handle_remote_status(&value, params.json)?;
369416

417+
// Restart the consumer so it re-registers under the new CONSUMER_NAME; editing .env alone is not
418+
// enough while the systemd service is already running.
419+
let restart_value = if params.restart {
420+
if !params.json {
421+
println!("Restarting Gyne consumer service on {ip}...");
422+
}
423+
let restart_cmd = build_restart_cmd(service.as_deref());
424+
match ssh_as_openclaw_with_user_async(&ip, &key, &restart_cmd, ssh_user).await {
425+
Ok(restart_output) => remote_json_value(&restart_output).ok(),
426+
Err(err) => {
427+
if !params.json {
428+
println!(" Warning: consumer restart failed: {err}");
429+
}
430+
None
431+
}
432+
}
433+
} else {
434+
None
435+
};
436+
370437
if params.json {
438+
if let (Some(object), Some(restart)) = (value.as_object_mut(), restart_value.as_ref()) {
439+
object.insert("restart".to_string(), restart.clone());
440+
}
371441
return print_json(&value);
372442
}
373443

@@ -408,7 +478,26 @@ pub async fn run(params: GyneConsumerProfileParams) -> Result<()> {
408478
.and_then(Value::as_str)
409479
.unwrap_or("")
410480
);
411-
println!("Gyne consumer profile updated. Restart the Gyne consumer process if it is already running.");
481+
match &restart_value {
482+
Some(restart) => {
483+
let state = restart
484+
.get("restart")
485+
.and_then(Value::as_str)
486+
.unwrap_or("unknown");
487+
match restart.get("unit").and_then(Value::as_str) {
488+
Some(unit) => println!("Gyne consumer profile updated. Consumer restarted ({state}, unit: {unit})."),
489+
None => println!(
490+
"Gyne consumer profile updated, but no Gyne consumer service was found to restart. Pass --service <unit> or restart it manually."
491+
),
492+
}
493+
}
494+
None if params.restart => {
495+
println!("Gyne consumer profile updated. Restart attempted (status unknown).")
496+
}
497+
None => println!(
498+
"Gyne consumer profile updated. Restart the Gyne consumer process if it is already running (--no-restart was set)."
499+
),
500+
}
412501
Ok(())
413502
}
414503

@@ -456,6 +545,34 @@ mod tests {
456545
assert!(!script.contains("REDIS_URL="));
457546
}
458547

548+
#[test]
549+
fn service_unit_accepts_expected_and_rejects_unsafe() {
550+
assert_eq!(
551+
clean_service_unit("gyne-agent.service").unwrap(),
552+
"gyne-agent.service"
553+
);
554+
assert_eq!(
555+
clean_service_unit("gyne@main.service").unwrap(),
556+
"gyne@main.service"
557+
);
558+
assert!(clean_service_unit("").is_err());
559+
assert!(clean_service_unit("gyne; rm -rf /").is_err());
560+
assert!(clean_service_unit("gyne$(id)").is_err());
561+
}
562+
563+
#[test]
564+
fn restart_cmd_auto_detects_when_no_service_and_uses_override() {
565+
let auto = build_restart_cmd(None);
566+
assert!(auto.contains("systemctl --user restart"));
567+
assert!(auto.contains("list-unit-files"));
568+
assert!(auto.contains("gyne|consumer"));
569+
assert!(auto.contains("gateway")); // excluded via grep -v
570+
assert!(auto.contains("no_gyne_service_found"));
571+
572+
let explicit = build_restart_cmd(Some("gyne-agent.service"));
573+
assert!(explicit.contains("unit='gyne-agent.service'"));
574+
}
575+
459576
#[test]
460577
fn ssh_user_maps_non_root_clouds() {
461578
assert_eq!(ssh_user_for_provider(&Some("lightsail".into())), "ubuntu");

crates/clawmacdo-cli/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,12 @@ enum Commands {
754754
/// Base task stream used to derive CONSUMER_TASK_STREAM; defaults to TASK_STREAM in .env
755755
#[arg(long)]
756756
task_stream: Option<String>,
757+
/// Do not restart the Gyne consumer service after updating .env
758+
#[arg(long)]
759+
no_restart: bool,
760+
/// systemd --user unit to restart (auto-detected from the gyne project when omitted)
761+
#[arg(long)]
762+
service: Option<String>,
757763
/// Output structured JSON
758764
#[arg(long)]
759765
json: bool,
@@ -1774,6 +1780,8 @@ async fn async_main() -> anyhow::Result<()> {
17741780
project,
17751781
name,
17761782
task_stream,
1783+
no_restart,
1784+
service,
17771785
json,
17781786
} => {
17791787
commands::gyne_consumer_profile::run(
@@ -1783,6 +1791,8 @@ async fn async_main() -> anyhow::Result<()> {
17831791
project,
17841792
name,
17851793
task_stream,
1794+
restart: !no_restart,
1795+
service,
17861796
json,
17871797
},
17881798
)

crates/clawmacdo-cloud/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawmacdo-cloud"
3-
version = "0.91.0"
3+
version = "0.92.0"
44
edition = "2021"
55
description = "Cloud provider implementations for ClawMacdo"
66

crates/clawmacdo-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawmacdo-core"
3-
version = "0.91.0"
3+
version = "0.92.0"
44
edition = "2021"
55
description = "Core types, configuration, and error handling for ClawMacdo"
66

crates/clawmacdo-db/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawmacdo-db"
3-
version = "0.91.0"
3+
version = "0.92.0"
44
edition = "2021"
55
description = "Database operations and storage for ClawMacdo"
66

crates/clawmacdo-provision/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawmacdo-provision"
3-
version = "0.91.0"
3+
version = "0.92.0"
44
edition = "2021"
55
description = "Server provisioning and setup logic for ClawMacdo"
66

0 commit comments

Comments
 (0)