Enhance development workflow and deployment practices for Archipelago

- Updated the Development-Workflow documentation to clarify deployment strategy, emphasizing direct deployment to the live system for testing.
- Added detailed instructions for the deployment command, including syncing code, building frontend and backend, and restarting services.
- Improved SSH key management section to assist with authentication issues.
- Expanded the testing workflow to include steps for checking logs and syncing changes back to the ISO build.
- Updated the ISO build integration section to ensure system-level changes are captured for future builds.
- Refactored various sections for clarity and completeness, including deployment paths and system configuration files.
This commit is contained in:
Dorian
2026-02-01 13:24:03 +00:00
parent 00d1af12f0
commit 34fc06726e
28 changed files with 1248 additions and 285 deletions

View File

@@ -6,6 +6,7 @@ use futures_util::{SinkExt, StreamExt};
use hyper::{Method, Request, Response, StatusCode};
use hyper_ws_listener::WsStream;
use std::sync::Arc;
use tokio::sync::broadcast;
use tokio_tungstenite::tungstenite::Message;
use tracing::{debug, info};
@@ -94,11 +95,14 @@ impl ApiHandler {
debug!("Sent initial data dump at revision {}", initial_msg.rev);
}
// Subscribe to state updates
let mut state_rx = state_manager.subscribe();
// Send periodic pings to keep connection alive
let ping_interval = tokio::time::interval(tokio::time::Duration::from_secs(30));
tokio::pin!(ping_interval);
// Keep connection open; UI may send/receive JSON patches. For now just accept and ignore.
// Keep connection open and forward state updates to client
loop {
tokio::select! {
_ = ping_interval.tick() => {
@@ -107,6 +111,28 @@ impl ApiHandler {
break;
}
}
// Forward state updates from broadcast channel to WebSocket
update = state_rx.recv() => {
match update {
Ok(msg) => {
if let Ok(json_msg) = serde_json::to_string(&msg) {
if let Err(e) = tx.send(Message::Text(json_msg)).await {
debug!("Failed to send state update: {}", e);
break;
}
debug!("Sent state update at revision {}", msg.rev);
}
}
Err(broadcast::error::RecvError::Lagged(skipped)) => {
debug!("Client lagged behind, skipped {} messages", skipped);
// Continue receiving - the client will get the next update
}
Err(broadcast::error::RecvError::Closed) => {
debug!("Broadcast channel closed");
break;
}
}
}
msg = rx.next() => {
match msg {
Some(Ok(Message::Close(_))) => break,

View File

@@ -445,15 +445,30 @@ impl RpcHandler {
.ok_or_else(|| anyhow::anyhow!("Missing package id"))?;
// Convert package ID to container name (e.g., "bitcoin" -> "archy-bitcoin")
let container_name = format!("archy-{}", package_id);
// Use docker CLI to start the container
let output = tokio::process::Command::new("docker")
.arg("start")
.arg(&container_name)
// But also check if container exists without the prefix
let container_name = if let Ok(output) = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name=^{}$", package_id)])
.output()
.await
.context("Failed to execute docker start")?;
{
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
debug!("Found container without prefix: {}", package_id);
package_id.to_string()
} else {
debug!("Using archy- prefix: archy-{}", package_id);
format!("archy-{}", package_id)
}
} else {
format!("archy-{}", package_id)
};
// Use podman CLI to start the container
let output = tokio::process::Command::new("sudo")
.args(["podman", "start", &container_name])
.output()
.await
.context("Failed to execute podman start")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
@@ -474,15 +489,29 @@ impl RpcHandler {
.ok_or_else(|| anyhow::anyhow!("Missing package id"))?;
// Convert package ID to container name
let container_name = format!("archy-{}", package_id);
// Use docker CLI to stop the container
let output = tokio::process::Command::new("docker")
.arg("stop")
.arg(&container_name)
let container_name = if let Ok(output) = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name=^{}$", package_id)])
.output()
.await
.context("Failed to execute docker stop")?;
{
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
debug!("Found container without prefix: {}", package_id);
package_id.to_string()
} else {
debug!("Using archy- prefix: archy-{}", package_id);
format!("archy-{}", package_id)
}
} else {
format!("archy-{}", package_id)
};
// Use podman CLI to stop the container
let output = tokio::process::Command::new("sudo")
.args(["podman", "stop", &container_name])
.output()
.await
.context("Failed to execute podman stop")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
@@ -503,15 +532,29 @@ impl RpcHandler {
.ok_or_else(|| anyhow::anyhow!("Missing package id"))?;
// Convert package ID to container name
let container_name = format!("archy-{}", package_id);
// Use docker CLI to restart the container
let output = tokio::process::Command::new("docker")
.arg("restart")
.arg(&container_name)
let container_name = if let Ok(output) = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name=^{}$", package_id)])
.output()
.await
.context("Failed to execute docker restart")?;
{
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
debug!("Found container without prefix: {}", package_id);
package_id.to_string()
} else {
debug!("Using archy- prefix: archy-{}", package_id);
format!("archy-{}", package_id)
}
} else {
format!("archy-{}", package_id)
};
// Use podman CLI to restart the container
let output = tokio::process::Command::new("sudo")
.args(["podman", "restart", &container_name])
.output()
.await
.context("Failed to execute podman restart")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);