desktop: fix tray icon depth, entry routing, socket error UX
- icon.png 16-bit -> 8-bit RGBA (tray panicked on data size) - devUrl + dist rename so the desktop entry actually loads (release served 404, dev served the fleet login) - permission denied on the localapi socket now explains group membership / re-login instead of os error 13
This commit is contained in:
parent
070148f1f5
commit
71fe075f65
4 changed files with 83 additions and 4 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.9 KiB |
|
|
@ -172,11 +172,78 @@ fn local_post(path: &str, body: &[u8]) -> Result<String, String> {
|
|||
|
||||
#[cfg(unix)]
|
||||
fn connect_local_api() -> Result<Box<dyn ReadWrite>, String> {
|
||||
let stream = std::os::unix::net::UnixStream::connect(LOCAL_SOCKET_PATH)
|
||||
.map_err(|err| format!("connect local API socket {LOCAL_SOCKET_PATH}: {err}"))?;
|
||||
let stream = std::os::unix::net::UnixStream::connect(LOCAL_SOCKET_PATH).map_err(|err| {
|
||||
if err.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
diagnose_socket_permission()
|
||||
} else {
|
||||
format!("connect local API socket {LOCAL_SOCKET_PATH}: {err}")
|
||||
}
|
||||
})?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
|
||||
// The local API socket is gated by the redflag-local group. Permission denied
|
||||
// has exactly two causes worth telling the user apart: the user was never
|
||||
// added to the group (installer predates the desktop app, or ran headless),
|
||||
// or the user IS in the group on disk but this login session was stamped
|
||||
// before the membership existed — groups only apply at login.
|
||||
#[cfg(unix)]
|
||||
fn diagnose_socket_permission() -> String {
|
||||
const GROUP: &str = "redflag-local";
|
||||
let user = std::env::var("USER")
|
||||
.or_else(|_| std::env::var("LOGNAME"))
|
||||
.unwrap_or_default();
|
||||
|
||||
let on_disk = std::fs::read_to_string("/etc/group")
|
||||
.ok()
|
||||
.and_then(|groups| {
|
||||
groups.lines().find_map(|line| {
|
||||
let mut fields = line.split(':');
|
||||
if fields.next() != Some(GROUP) {
|
||||
return None;
|
||||
}
|
||||
let gid = fields.nth(1)?.to_string();
|
||||
let members = fields.next().unwrap_or_default();
|
||||
Some((gid, members.split(',').any(|m| m == user)))
|
||||
})
|
||||
});
|
||||
|
||||
let Some((gid, user_in_group)) = on_disk else {
|
||||
return format!(
|
||||
"Cannot reach the RedFlag agent: the {GROUP} group does not exist. \
|
||||
The agent installer creates it — is the agent installed on this machine?"
|
||||
);
|
||||
};
|
||||
|
||||
let session_has_group = std::fs::read_to_string("/proc/self/status")
|
||||
.ok()
|
||||
.and_then(|status| {
|
||||
status.lines().find_map(|line| {
|
||||
line.strip_prefix("Groups:")
|
||||
.map(|ids| ids.split_whitespace().any(|id| id == gid))
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if user_in_group && !session_has_group {
|
||||
format!(
|
||||
"Almost there: {user} is in the {GROUP} group, but this login session \
|
||||
started before the membership was added. Log out and back in, then \
|
||||
reopen RedFlag."
|
||||
)
|
||||
} else if !user_in_group {
|
||||
format!(
|
||||
"Cannot reach the RedFlag agent: {user} is not in the {GROUP} group. \
|
||||
Run: sudo usermod -aG {GROUP} {user} — then log out and back in."
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"connect local API socket {LOCAL_SOCKET_PATH}: permission denied \
|
||||
(group membership looks correct — check socket directory permissions)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn connect_local_api() -> Result<Box<dyn ReadWrite>, String> {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"identifier": "com.redflag.local",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm --prefix ../web run dev:desktop",
|
||||
"devUrl": "http://127.0.0.1:3001",
|
||||
"devUrl": "http://127.0.0.1:3001/index.desktop.html",
|
||||
"beforeBuildCommand": "npm --prefix ../web run build:desktop",
|
||||
"frontendDist": "../web/dist-desktop"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,8 +2,20 @@ import { defineConfig } from 'vite'
|
|||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
// Tauri's embedded asset server resolves index.html from frontendDist; the
|
||||
// desktop entry is index.desktop.html, so rename it in the bundle output.
|
||||
const desktopEntryAsIndex = {
|
||||
name: 'desktop-entry-as-index',
|
||||
generateBundle(_options: unknown, bundle: Record<string, { fileName: string }>) {
|
||||
const entry = bundle['index.desktop.html']
|
||||
if (entry) {
|
||||
entry.fileName = 'index.html'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react(), desktopEntryAsIndex],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue