Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e2015fb
fix(uninstall): stop managed services before removing the tooling tha…
fredespi Aug 21, 2026
1f9ca82
fix(uninstall): pin the stop-before-remove ordering with tests and ve…
fredespi Sep 9, 2026
20364b6
Merge remote-tracking branch 'origin/main' into rocm-uninstall-report…
fredespi Sep 10, 2026
6b697a3
test(e2e): index the new lifecycle scenario per the naming convention
fredespi Sep 10, 2026
451a8ee
fix(uninstall): stop judging stale service records by their recorded …
fredespi Sep 10, 2026
911a399
fix(uninstall): one atomic-write helper, a resolvable probe host, an …
fredespi Sep 10, 2026
1f4d1a9
fix(uninstall): keep the gate honest across a retry, and stop the dae…
fredespi Sep 10, 2026
4db3a7f
fix(uninstall): gate the test-only port constant to the platform that…
fredespi Sep 10, 2026
4fa47a8
fix(uninstall): never signal a daemon pid it cannot prove is rocmd
fredespi Sep 11, 2026
bdf3661
test(rocm-core): cover the atomic write helper directly
fredespi Sep 11, 2026
89f69c5
fix(uninstall): treat an unrecorded daemon identity as unverified, no…
fredespi Sep 11, 2026
4c9a81a
fix(uninstall): probe a wildcard-bound endpoint at the address it ans…
fredespi Sep 11, 2026
074b37b
test(rocm-core): pin atomic publishing at the service-record level
fredespi Sep 11, 2026
a908893
fix(uninstall): make the daemon-state abort followable, and pin the r…
fredespi Sep 11, 2026
08d5ddd
docs: say what the local atomic-write helpers share, and what they do…
fredespi Sep 11, 2026
746b81f
fix(uninstall): classify the daemon pid from a single start-time reading
fredespi Sep 14, 2026
3cc2f4d
docs(uninstall): say what each remediation test pins, and name the ga…
fredespi Sep 14, 2026
24dbb5a
fix(uninstall): probe both loopback families, honor a stopped daemon,…
fredespi Sep 14, 2026
9e2dd91
fix(uninstall): answer the platform question with a process known to …
fredespi Sep 14, 2026
1cd5c95
style(uninstall): satisfy rustfmt on the two lines the last push missed
fredespi Sep 14, 2026
d6f97c5
test(rocmd): pin the start-time capture the uninstall guard depends on
fredespi Sep 14, 2026
541db48
fix(uninstall): stop nothing on a doomed run, and stop guessing from …
fredespi Sep 14, 2026
50cff02
fix(uninstall): restore the cfg gate my helper insertion displaced
fredespi Sep 14, 2026
64d5a20
fix(uninstall): pin the platform discriminator, and read before destr…
fredespi Sep 14, 2026
a3bc596
test(uninstall): pin the platform conjunct on the only lane that can …
fredespi Sep 14, 2026
5a21760
fix(uninstall): check before destroying, and make each gate outcome a…
fredespi Sep 14, 2026
617c17f
test(uninstall): drive the stranger-on-a-recycled-port arm end to end
fredespi Sep 15, 2026
0907d82
fix(uninstall): disclose the third fail-open, and pin the other two
fredespi Sep 15, 2026
e9bd2af
fix(uninstall): make every failure class prove it carries a way out
fredespi Sep 15, 2026
5202140
fix(uninstall): make the remedy-order assertion able to fail, and stop
fredespi Sep 15, 2026
f323e7f
test(uninstall): pin the split failure reason, and bound the Linux-on…
fredespi Sep 15, 2026
64e8914
Merge origin/main into rocm-uninstall-reports-success-while-leaving-a
fredespi Oct 2, 2026
63b8f69
fix(uninstall): scenario for the refusal path, docs, plan-warning tes…
fredespi Oct 2, 2026
b7423d5
fix(rocmd): add daemon_start_ticks to the relocated audit-log test
fredespi Oct 2, 2026
22283cd
fix(uninstall): order lifecycle-25 after 24, gate the new plan-warnin…
fredespi Oct 2, 2026
bb460ef
Merge remote-tracking branch 'origin/main' into rocm-uninstall-report…
fredespi Oct 5, 2026
27e1092
refactor(uninstall): extract the stop gate into uninstall_gate.rs, ad…
fredespi Oct 5, 2026
5450c37
fix(uninstall): record a Windows process start-time so the daemon kil…
fredespi Oct 5, 2026
b062582
test(uninstall): Windows now reads start-times, so the bare-record te…
fredespi Oct 5, 2026
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
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,13 @@ rocm uninstall [--yes] [--dry-run]
[--keep-binaries] [--keep-config] [--keep-data] [--keep-cache]
```

`rocm uninstall` stops any managed model server that is still running before it
removes anything. If a server cannot be stopped (or its service record cannot
be read), the command exits non-zero, leaves every file in place, and says what
to repair or stop by hand, so the tooling needed to stop it is never deleted
out from under a running server. A run that keeps the binaries and data
(`--keep-binaries --keep-data`) leaves running servers alone.

### Shell completions

`rocm completions <shell>` prints a completion script for the given shell to
Expand Down
1 change: 0 additions & 1 deletion apps/rocm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ libc.workspace = true

[target.'cfg(windows)'.dependencies]
windows-native-keyring-store = "1.1"
windows-sys = { version = "0.61", features = ["Win32_Storage_FileSystem"] }

[target.'cfg(target_os = "macos")'.dependencies]
apple-native-keyring-store = { version = "1.0", features = ["keychain"] }
Expand Down
1,959 changes: 1,926 additions & 33 deletions apps/rocm/src/main.rs

Large diffs are not rendered by default.

68 changes: 15 additions & 53 deletions apps/rocm/src/therock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4610,6 +4610,18 @@ fn temp_sibling_path(path: &Path, suffix: &OsStr) -> Result<PathBuf> {
Ok(parent.join(file_name))
}

/// Stage-and-publish a file here, sharing only the publish step with `rocm-core`.
///
/// Deliberately not [`rocm_core::write_file_atomically`], and not a copy of it
/// either: only the Windows-sensitive publish (`ReplaceFileW` and its fallbacks)
/// is single-sourced, via [`publish_temp_file`]. The staging half stays local
/// because it carries the `suffix_for_attempt` and `before_publish` seams the
/// tests below drive to force temp-name collisions, write failures and rename
/// races — injection points `rocm_core`'s caller-facing helper does not expose.
///
/// Consequence worth knowing: this path does **not** `sync_all` before
/// publishing, so unlike the `rocm-core` helper it is atomic but carries no
/// crash-durability guarantee for the staged bytes.
fn write_file_atomically(path: &Path, bytes: &[u8]) -> Result<()> {
let temp_id = format!("{}-{}", std::process::id(), unix_time_millis());
write_file_atomically_with(
Expand Down Expand Up @@ -4726,60 +4738,10 @@ where
.with_context(|| format!("failed to publish {}", path.display()))
}

#[cfg(not(windows))]
fn publish_temp_file(tmp: &Path, path: &Path) -> io::Result<()> {
fs::rename(tmp, path)
}

#[cfg(windows)]
/// The publish step lives in `rocm-core` so there is one implementation of the
/// Windows `ReplaceFileW` handling for the whole workspace.
fn publish_temp_file(tmp: &Path, path: &Path) -> io::Result<()> {
if path.try_exists()? {
return replace_file_windows(path, tmp);
}

match fs::rename(tmp, path) {
Ok(()) => Ok(()),
Err(rename_error) => {
if path.try_exists()? {
replace_file_windows(path, tmp)
} else {
Err(rename_error)
}
}
}
}

#[cfg(windows)]
#[allow(unsafe_code)]
fn replace_file_windows(path: &Path, replacement: &Path) -> io::Result<()> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::ReplaceFileW;

let path_wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
let replacement_wide: Vec<u16> = replacement
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect();

// SAFETY: both path buffers are valid, NUL-terminated UTF-16 strings and
// remain alive for the duration of the synchronous Windows API call. The
// optional backup, exclude, and reserved pointers are intentionally null.
let replaced = unsafe {
ReplaceFileW(
path_wide.as_ptr(),
replacement_wide.as_ptr(),
std::ptr::null(),
0,
std::ptr::null(),
std::ptr::null(),
)
};
if replaced == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
rocm_core::publish_temp_file(tmp, path)
}

fn extract_tarball(archive_path: &Path, target_dir: &Path) -> Result<()> {
Expand Down
Loading
Loading