mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
feat: OTA updates for start-tunnel via apt repository (untested)
- Add apt repo publish script (build/apt/publish-deb.sh) for S3-hosted repo - Add apt source config and GPG key placeholder (apt/) - Add tunnel.update.check and tunnel.update.apply RPC endpoints - Wire up update API in tunnel frontend (api service + mock) - Uses systemd-run --scope to survive service restart during update
This commit is contained in:
@@ -53,6 +53,24 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_call_remote::<CliContext>(),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
ParentHandler::<C>::new()
|
||||
.subcommand(
|
||||
"check",
|
||||
from_fn_async(super::update::check_update)
|
||||
.with_display_serializable()
|
||||
.with_about("about.check-for-updates")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"apply",
|
||||
from_fn_async(super::update::apply_update)
|
||||
.with_display_serializable()
|
||||
.with_about("about.apply-available-update")
|
||||
.with_call_remote::<CliContext>(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod api;
|
||||
pub mod auth;
|
||||
pub mod context;
|
||||
pub mod db;
|
||||
pub mod update;
|
||||
pub mod web;
|
||||
pub mod wg;
|
||||
|
||||
|
||||
109
core/src/tunnel/update.rs
Normal file
109
core/src/tunnel/update.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use std::process::Stdio;
|
||||
|
||||
use rpc_toolkit::Empty;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::tunnel::context::TunnelContext;
|
||||
use crate::util::Invoke;
|
||||
|
||||
#[derive(Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TunnelUpdateResult {
|
||||
/// "up-to-date", "update-available", or "updating"
|
||||
pub status: String,
|
||||
/// Currently installed version
|
||||
pub installed: String,
|
||||
/// Available candidate version
|
||||
pub candidate: String,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn check_update(_ctx: TunnelContext, _: Empty) -> Result<TunnelUpdateResult, Error> {
|
||||
Command::new("apt-get")
|
||||
.arg("update")
|
||||
.invoke(ErrorKind::UpdateFailed)
|
||||
.await?;
|
||||
|
||||
let policy_output = Command::new("apt-cache")
|
||||
.arg("policy")
|
||||
.arg("start-tunnel")
|
||||
.invoke(ErrorKind::UpdateFailed)
|
||||
.await?;
|
||||
|
||||
let policy_str = String::from_utf8_lossy(&policy_output).to_string();
|
||||
let installed = parse_version_field(&policy_str, "Installed:");
|
||||
let candidate = parse_version_field(&policy_str, "Candidate:");
|
||||
|
||||
let status = if installed == candidate {
|
||||
"up-to-date"
|
||||
} else {
|
||||
"update-available"
|
||||
};
|
||||
|
||||
Ok(TunnelUpdateResult {
|
||||
status: status.to_string(),
|
||||
installed: installed.unwrap_or_default(),
|
||||
candidate: candidate.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn apply_update(_ctx: TunnelContext, _: Empty) -> Result<TunnelUpdateResult, Error> {
|
||||
let policy_output = Command::new("apt-cache")
|
||||
.arg("policy")
|
||||
.arg("start-tunnel")
|
||||
.invoke(ErrorKind::UpdateFailed)
|
||||
.await?;
|
||||
|
||||
let policy_str = String::from_utf8_lossy(&policy_output).to_string();
|
||||
let installed = parse_version_field(&policy_str, "Installed:");
|
||||
let candidate = parse_version_field(&policy_str, "Candidate:");
|
||||
|
||||
if installed == candidate {
|
||||
return Ok(TunnelUpdateResult {
|
||||
status: "up-to-date".to_string(),
|
||||
installed: installed.unwrap_or_default(),
|
||||
candidate: candidate.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
|
||||
// Spawn in a separate cgroup via systemd-run so the process survives
|
||||
// when the postinst script restarts start-tunneld.service.
|
||||
// After the install completes, reboot the system.
|
||||
Command::new("systemd-run")
|
||||
.arg("--scope")
|
||||
.arg("--")
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg("apt-get install --only-upgrade -y start-tunnel && reboot")
|
||||
.env("DEBIAN_FRONTEND", "noninteractive")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.with_kind(ErrorKind::UpdateFailed)?;
|
||||
|
||||
Ok(TunnelUpdateResult {
|
||||
status: "updating".to_string(),
|
||||
installed: installed.unwrap_or_default(),
|
||||
candidate: candidate.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_version_field(policy: &str, field: &str) -> Option<String> {
|
||||
policy
|
||||
.lines()
|
||||
.find(|l| l.trim().starts_with(field))
|
||||
.and_then(|l| l.split_whitespace().nth(1))
|
||||
.filter(|v| *v != "(none)")
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_bindings_tunnel_update() {
|
||||
TunnelUpdateResult::export_all_to("bindings/tunnel").unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user