create-ekka-desktop-app 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/bin/cli.js +72 -0
- package/package.json +23 -0
- package/template/branding/app.json +6 -0
- package/template/branding/icon.icns +0 -0
- package/template/eslint.config.js +98 -0
- package/template/index.html +29 -0
- package/template/package.json +40 -0
- package/template/src/app/App.tsx +24 -0
- package/template/src/demo/DemoApp.tsx +260 -0
- package/template/src/demo/components/Banner.tsx +82 -0
- package/template/src/demo/components/EmptyState.tsx +61 -0
- package/template/src/demo/components/InfoPopover.tsx +171 -0
- package/template/src/demo/components/InfoTooltip.tsx +76 -0
- package/template/src/demo/components/LearnMore.tsx +98 -0
- package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
- package/template/src/demo/components/SetupWizard.tsx +48 -0
- package/template/src/demo/components/StatusBadge.tsx +83 -0
- package/template/src/demo/components/index.ts +10 -0
- package/template/src/demo/hooks/index.ts +6 -0
- package/template/src/demo/hooks/useAuditEvents.ts +30 -0
- package/template/src/demo/layout/Shell.tsx +110 -0
- package/template/src/demo/layout/Sidebar.tsx +192 -0
- package/template/src/demo/pages/AuditLogPage.tsx +235 -0
- package/template/src/demo/pages/DocGenPage.tsx +874 -0
- package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
- package/template/src/demo/pages/LoginPage.tsx +192 -0
- package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
- package/template/src/demo/pages/RunnerPage.tsx +445 -0
- package/template/src/demo/pages/SystemPage.tsx +557 -0
- package/template/src/demo/pages/VaultPage.tsx +805 -0
- package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
- package/template/src/ekka/audit/index.ts +7 -0
- package/template/src/ekka/audit/store.ts +68 -0
- package/template/src/ekka/audit/types.ts +22 -0
- package/template/src/ekka/auth/client.ts +212 -0
- package/template/src/ekka/auth/index.ts +30 -0
- package/template/src/ekka/auth/storage.ts +114 -0
- package/template/src/ekka/auth/types.ts +67 -0
- package/template/src/ekka/backend/demo.ts +151 -0
- package/template/src/ekka/backend/interface.ts +36 -0
- package/template/src/ekka/config.ts +48 -0
- package/template/src/ekka/constants.ts +143 -0
- package/template/src/ekka/errors.ts +54 -0
- package/template/src/ekka/index.ts +516 -0
- package/template/src/ekka/internal/backend.ts +156 -0
- package/template/src/ekka/internal/index.ts +7 -0
- package/template/src/ekka/ops/auth.ts +29 -0
- package/template/src/ekka/ops/debug.ts +68 -0
- package/template/src/ekka/ops/home.ts +101 -0
- package/template/src/ekka/ops/index.ts +16 -0
- package/template/src/ekka/ops/nodeCredentials.ts +131 -0
- package/template/src/ekka/ops/nodeSession.ts +145 -0
- package/template/src/ekka/ops/paths.ts +183 -0
- package/template/src/ekka/ops/runner.ts +86 -0
- package/template/src/ekka/ops/runtime.ts +31 -0
- package/template/src/ekka/ops/setup.ts +47 -0
- package/template/src/ekka/ops/vault.ts +459 -0
- package/template/src/ekka/ops/workflowRuns.ts +116 -0
- package/template/src/ekka/types.ts +82 -0
- package/template/src/ekka/utils/idempotency.ts +14 -0
- package/template/src/ekka/utils/index.ts +7 -0
- package/template/src/ekka/utils/time.ts +77 -0
- package/template/src/main.tsx +12 -0
- package/template/src/vite-env.d.ts +12 -0
- package/template/src-tauri/Cargo.toml +41 -0
- package/template/src-tauri/build.rs +3 -0
- package/template/src-tauri/capabilities/default.json +11 -0
- package/template/src-tauri/icons/icon.icns +0 -0
- package/template/src-tauri/icons/icon.png +0 -0
- package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
- package/template/src-tauri/src/bootstrap.rs +37 -0
- package/template/src-tauri/src/commands.rs +1215 -0
- package/template/src-tauri/src/device_secret.rs +111 -0
- package/template/src-tauri/src/engine_process.rs +538 -0
- package/template/src-tauri/src/grants.rs +129 -0
- package/template/src-tauri/src/handlers/home.rs +65 -0
- package/template/src-tauri/src/handlers/mod.rs +7 -0
- package/template/src-tauri/src/handlers/paths.rs +128 -0
- package/template/src-tauri/src/handlers/vault.rs +680 -0
- package/template/src-tauri/src/main.rs +243 -0
- package/template/src-tauri/src/node_auth.rs +858 -0
- package/template/src-tauri/src/node_credentials.rs +541 -0
- package/template/src-tauri/src/node_runner.rs +882 -0
- package/template/src-tauri/src/node_vault_crypto.rs +113 -0
- package/template/src-tauri/src/node_vault_store.rs +267 -0
- package/template/src-tauri/src/ops/auth.rs +50 -0
- package/template/src-tauri/src/ops/home.rs +251 -0
- package/template/src-tauri/src/ops/mod.rs +7 -0
- package/template/src-tauri/src/ops/runtime.rs +21 -0
- package/template/src-tauri/src/state.rs +639 -0
- package/template/src-tauri/src/types.rs +84 -0
- package/template/src-tauri/tauri.conf.json +41 -0
- package/template/tsconfig.json +26 -0
- package/template/tsconfig.tsbuildinfo +1 -0
- package/template/vite.config.ts +34 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
//! EKKA Desktop - Entry Point
|
|
2
|
+
//!
|
|
3
|
+
//! Minimal main.rs that sets up Tauri with the engine commands.
|
|
4
|
+
//! Starts embedded runner loop on app startup.
|
|
5
|
+
|
|
6
|
+
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
7
|
+
|
|
8
|
+
mod bootstrap;
|
|
9
|
+
mod commands;
|
|
10
|
+
mod device_secret;
|
|
11
|
+
mod engine_process;
|
|
12
|
+
mod grants;
|
|
13
|
+
mod handlers;
|
|
14
|
+
mod node_auth;
|
|
15
|
+
mod node_credentials;
|
|
16
|
+
mod node_runner;
|
|
17
|
+
mod node_vault_crypto;
|
|
18
|
+
mod node_vault_store;
|
|
19
|
+
mod ops;
|
|
20
|
+
mod state;
|
|
21
|
+
mod types;
|
|
22
|
+
|
|
23
|
+
use commands::{engine_connect, engine_disconnect, engine_request};
|
|
24
|
+
use engine_process::EngineProcess;
|
|
25
|
+
use state::EngineState;
|
|
26
|
+
use std::path::PathBuf;
|
|
27
|
+
use std::sync::Arc;
|
|
28
|
+
use tauri::Manager;
|
|
29
|
+
|
|
30
|
+
fn main() {
|
|
31
|
+
// Load .env.local for development (before anything else)
|
|
32
|
+
// This provides ENGINE_GRANT_VERIFY_KEY_B64, EKKA_SECURITY_EPOCH, etc.
|
|
33
|
+
if let Err(e) = dotenvy::from_filename(".env.local") {
|
|
34
|
+
// Also try parent directory (when running from src-tauri)
|
|
35
|
+
let _ = dotenvy::from_filename("../.env.local");
|
|
36
|
+
// Silence error in production where .env.local may not exist
|
|
37
|
+
if std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_err() {
|
|
38
|
+
eprintln!("Warning: .env.local not loaded and ENGINE_GRANT_VERIFY_KEY_B64 not set: {}", e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Initialize tracing for runner logs
|
|
43
|
+
tracing_subscriber::fmt()
|
|
44
|
+
.with_env_filter(
|
|
45
|
+
tracing_subscriber::EnvFilter::from_default_env()
|
|
46
|
+
.add_directive("ekka_runner_core=info".parse().unwrap())
|
|
47
|
+
.add_directive("ekka_runner_local=info".parse().unwrap())
|
|
48
|
+
.add_directive("ekka_desktop_app=info".parse().unwrap())
|
|
49
|
+
.add_directive("ekka_node_auth=info".parse().unwrap()),
|
|
50
|
+
)
|
|
51
|
+
.with_target(true)
|
|
52
|
+
.init();
|
|
53
|
+
|
|
54
|
+
// Create engine process holder
|
|
55
|
+
let engine_process = Arc::new(EngineProcess::new());
|
|
56
|
+
let engine_for_state = engine_process.clone();
|
|
57
|
+
let engine_for_setup = engine_process.clone();
|
|
58
|
+
let engine_for_shutdown = engine_process;
|
|
59
|
+
|
|
60
|
+
tauri::Builder::default()
|
|
61
|
+
.plugin(tauri_plugin_dialog::init())
|
|
62
|
+
.manage(EngineState::with_engine(engine_for_state))
|
|
63
|
+
.setup(move |app| {
|
|
64
|
+
// Attempt to spawn engine process
|
|
65
|
+
tracing::info!(op = "desktop.startup", "EKKA Desktop starting");
|
|
66
|
+
|
|
67
|
+
// Log required env vars status
|
|
68
|
+
let grant_key_set = std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_ok();
|
|
69
|
+
let security_epoch_set = std::env::var("EKKA_SECURITY_EPOCH").is_ok();
|
|
70
|
+
tracing::info!(
|
|
71
|
+
op = "desktop.required_env.loaded",
|
|
72
|
+
ENGINE_GRANT_VERIFY_KEY_B64 = grant_key_set,
|
|
73
|
+
EKKA_SECURITY_EPOCH = security_epoch_set,
|
|
74
|
+
"Required security env vars"
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Check for stored node credentials (vault-backed)
|
|
78
|
+
let has_creds = node_credentials::has_credentials();
|
|
79
|
+
|
|
80
|
+
if !has_creds {
|
|
81
|
+
tracing::warn!(
|
|
82
|
+
op = "desktop.node.credentials.missing",
|
|
83
|
+
"Node credentials not configured - onboarding required, engine start blocked"
|
|
84
|
+
);
|
|
85
|
+
// Engine will not be spawned - available will remain false
|
|
86
|
+
// UI should show onboarding flow
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Resolve resource path for bootstrap binary
|
|
90
|
+
let resource_path: Option<PathBuf> = app
|
|
91
|
+
.path()
|
|
92
|
+
.resource_dir()
|
|
93
|
+
.ok()
|
|
94
|
+
.map(|dir: PathBuf| dir.join("resources").join("ekka-engine-bootstrap"));
|
|
95
|
+
|
|
96
|
+
// Get node auth token holder and auth state to pass to spawn thread
|
|
97
|
+
let state_handle = app.state::<EngineState>();
|
|
98
|
+
let node_auth_holder = state_handle.node_auth_token.clone();
|
|
99
|
+
let node_auth_state = state_handle.node_auth_state.clone();
|
|
100
|
+
|
|
101
|
+
// Spawn engine in background thread to not block UI
|
|
102
|
+
let engine = engine_for_setup.clone();
|
|
103
|
+
std::thread::spawn(move || {
|
|
104
|
+
// Gate: Skip engine spawn if no credentials
|
|
105
|
+
if !has_creds {
|
|
106
|
+
tracing::info!(
|
|
107
|
+
op = "desktop.engine.start.blocked",
|
|
108
|
+
reason = "missing_credentials",
|
|
109
|
+
"Engine start blocked - node credentials required"
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Authenticate node with server before spawning engine
|
|
115
|
+
let engine_url = match std::env::var("EKKA_ENGINE_URL") {
|
|
116
|
+
Ok(url) => url,
|
|
117
|
+
Err(_) => {
|
|
118
|
+
tracing::warn!(
|
|
119
|
+
op = "desktop.engine.start.blocked",
|
|
120
|
+
reason = "missing_engine_url",
|
|
121
|
+
"Engine start blocked - EKKA_ENGINE_URL not set"
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Single-flight: try to acquire auth lock
|
|
128
|
+
if !node_auth_state.try_start() {
|
|
129
|
+
tracing::info!(
|
|
130
|
+
op = "desktop.node.auth.skipped",
|
|
131
|
+
reason = "already_in_progress_or_completed",
|
|
132
|
+
"Skipping auth - already attempted"
|
|
133
|
+
);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
tracing::info!(
|
|
138
|
+
op = "desktop.node.auth.attempt",
|
|
139
|
+
reason = "startup",
|
|
140
|
+
"Authenticating node from vault"
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
match node_credentials::authenticate_node(&engine_url) {
|
|
144
|
+
Ok(token) => {
|
|
145
|
+
// Store token in state (in-memory only)
|
|
146
|
+
node_auth_holder.set(token);
|
|
147
|
+
node_auth_state.set_authenticated();
|
|
148
|
+
tracing::info!(
|
|
149
|
+
op = "desktop.node.auth.complete",
|
|
150
|
+
"Node authenticated, proceeding to engine spawn"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
Err(node_credentials::CredentialsError::AuthFailed(status, ref body)) => {
|
|
154
|
+
let error_msg = format!("Auth failed: HTTP {}", status);
|
|
155
|
+
node_auth_state.set_failed(error_msg);
|
|
156
|
+
|
|
157
|
+
// Check if this is a secret error (invalid or revoked)
|
|
158
|
+
if node_credentials::is_secret_error(status, body) {
|
|
159
|
+
tracing::warn!(
|
|
160
|
+
op = "desktop.node.auth.failed",
|
|
161
|
+
status = status,
|
|
162
|
+
"Node secret is invalid or revoked - clearing credentials"
|
|
163
|
+
);
|
|
164
|
+
// Clear invalid credentials from keychain
|
|
165
|
+
let _ = node_credentials::clear_credentials();
|
|
166
|
+
} else {
|
|
167
|
+
tracing::warn!(
|
|
168
|
+
op = "desktop.node.auth.failed",
|
|
169
|
+
status = status,
|
|
170
|
+
"Node authentication failed - will not retry"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
Err(e) => {
|
|
176
|
+
let error_msg = format!("Auth failed: {}", e);
|
|
177
|
+
node_auth_state.set_failed(error_msg);
|
|
178
|
+
tracing::warn!(
|
|
179
|
+
op = "desktop.node.auth.failed",
|
|
180
|
+
error = %e,
|
|
181
|
+
"Node authentication failed - will not retry"
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Install bootstrap binary from resources if not present
|
|
188
|
+
if let Err(e) = engine_process::ensure_bootstrap_installed_from_resources(resource_path) {
|
|
189
|
+
tracing::debug!(op = "engine.bootstrap.skip", error = %e, "Bootstrap install skipped");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Log setup complete
|
|
193
|
+
tracing::info!(
|
|
194
|
+
op = "desktop.setup.complete",
|
|
195
|
+
"Device setup complete - credentials valid, proceeding to engine"
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
engine_process::spawn_and_wait(&engine);
|
|
199
|
+
let status = engine.get_status();
|
|
200
|
+
tracing::info!(
|
|
201
|
+
op = "desktop.engine_status",
|
|
202
|
+
installed = status.installed,
|
|
203
|
+
running = status.running,
|
|
204
|
+
available = status.available,
|
|
205
|
+
pid = ?status.pid,
|
|
206
|
+
version = ?status.version,
|
|
207
|
+
build = ?status.build,
|
|
208
|
+
"Engine status"
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if status.available {
|
|
212
|
+
tracing::info!(
|
|
213
|
+
op = "desktop.engine.ready",
|
|
214
|
+
"Engine is ready"
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Node runner is started via nodeSession.bootstrap operation.
|
|
220
|
+
// Node auth happens at app startup (above) using node_id + node_secret.
|
|
221
|
+
// This ensures:
|
|
222
|
+
// 1. Node credentials loaded from vault (encrypted at rest)
|
|
223
|
+
// 2. Node authenticated via POST /engine/nodes/auth
|
|
224
|
+
// 3. Node JWT (role=node) stored in memory
|
|
225
|
+
// 4. Runner uses node JWT (NOT user JWT or internal service key)
|
|
226
|
+
|
|
227
|
+
Ok(())
|
|
228
|
+
})
|
|
229
|
+
.invoke_handler(tauri::generate_handler![
|
|
230
|
+
engine_connect,
|
|
231
|
+
engine_disconnect,
|
|
232
|
+
engine_request,
|
|
233
|
+
])
|
|
234
|
+
.build(tauri::generate_context!())
|
|
235
|
+
.expect("error while building tauri application")
|
|
236
|
+
.run(move |_app, event| {
|
|
237
|
+
if let tauri::RunEvent::Exit = event {
|
|
238
|
+
// Shutdown engine process on app exit
|
|
239
|
+
tracing::debug!(op = "desktop.shutdown", "Shutting down engine process");
|
|
240
|
+
engine_process::shutdown(&engine_for_shutdown);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|