Watch
1
0
Fork
You've already forked RedFlag
0

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:
Fimeg 2026-06-12 13:19:51 -04:00
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

Before After
Before After

View file

@ -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()

View file

@ -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"
},

View file

@ -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"),