feat(wfe): integrate workflow engine for up, seed, verify, bootstrap
Dispatch `sunbeam up`, `sunbeam seed`, `sunbeam verify`, and `sunbeam bootstrap` through WFE workflows instead of monolithic functions. Steps communicate via JSON workflow data and each workflow is persisted in a per-context SQLite database.
This commit is contained in:
@@ -39,6 +39,8 @@ const PG_USERS: &[&str] = &[
|
||||
"find",
|
||||
"calendars",
|
||||
"projects",
|
||||
"penpot",
|
||||
"stalwart",
|
||||
];
|
||||
|
||||
const SMTP_URI: &str = "smtp://postfix.lasuite.svc.cluster.local:25/?skip_ssl_verify=true";
|
||||
@@ -1044,9 +1046,9 @@ mod tests {
|
||||
fn test_constants() {
|
||||
assert_eq!(ADMIN_USERNAME, "estudio-admin");
|
||||
assert_eq!(GITEA_ADMIN_USER, "gitea_admin");
|
||||
assert_eq!(PG_USERS.len(), 13);
|
||||
assert_eq!(PG_USERS.len(), 15);
|
||||
assert!(PG_USERS.contains(&"kratos"));
|
||||
assert!(PG_USERS.contains(&"projects"));
|
||||
assert!(PG_USERS.contains(&"stalwart"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1109,6 +1111,8 @@ mod tests {
|
||||
"find",
|
||||
"calendars",
|
||||
"projects",
|
||||
"penpot",
|
||||
"stalwart",
|
||||
];
|
||||
assert_eq!(PG_USERS, &expected[..]);
|
||||
}
|
||||
|
||||
@@ -454,6 +454,17 @@ pub async fn seed_openbao() -> Result<Option<SeedResult>> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let stalwart = get_or_create(
|
||||
&bao,
|
||||
"stalwart",
|
||||
&[
|
||||
("admin-password", &rand_token as &dyn Fn() -> String),
|
||||
("dkim-private-key", &empty_fn),
|
||||
],
|
||||
&mut dirty_paths,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let admin_fn = || "admin".to_string();
|
||||
let collabora = get_or_create(
|
||||
&bao,
|
||||
@@ -531,6 +542,7 @@ pub async fn seed_openbao() -> Result<Option<SeedResult>> {
|
||||
("projects", &projects),
|
||||
("calendars", &calendars),
|
||||
("messages", &messages),
|
||||
("stalwart", &stalwart),
|
||||
("collabora", &collabora),
|
||||
("tuwunel", &tuwunel),
|
||||
("grafana", &grafana),
|
||||
@@ -606,7 +618,7 @@ pub async fn seed_openbao() -> Result<Option<SeedResult>> {
|
||||
"auth/kubernetes/role/vso",
|
||||
&serde_json::json!({
|
||||
"bound_service_account_names": "default",
|
||||
"bound_service_account_namespaces": "ory,devtools,storage,lasuite,matrix,media,data,monitoring",
|
||||
"bound_service_account_namespaces": "ory,devtools,storage,lasuite,stalwart,matrix,media,data,monitoring",
|
||||
"policies": "vso-reader",
|
||||
"ttl": "1h"
|
||||
}),
|
||||
|
||||
@@ -51,11 +51,8 @@ pub enum SyncStatus {
|
||||
// Path helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Base directory for vault keystore files.
|
||||
fn base_dir(override_dir: Option<&Path>) -> PathBuf {
|
||||
if let Some(d) = override_dir {
|
||||
return d.to_path_buf();
|
||||
}
|
||||
/// Legacy vault dir — used only for migration.
|
||||
fn legacy_vault_dir() -> PathBuf {
|
||||
dirs::data_dir()
|
||||
.unwrap_or_else(|| {
|
||||
dirs::home_dir()
|
||||
@@ -66,6 +63,41 @@ fn base_dir(override_dir: Option<&Path>) -> PathBuf {
|
||||
.join("vault")
|
||||
}
|
||||
|
||||
/// Base directory for vault keystore files: ~/.sunbeam/vault/
|
||||
fn base_dir(override_dir: Option<&Path>) -> PathBuf {
|
||||
if let Some(d) = override_dir {
|
||||
return d.to_path_buf();
|
||||
}
|
||||
let new_dir = dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".sunbeam")
|
||||
.join("vault");
|
||||
|
||||
// Migration: copy files from legacy location if new dir doesn't exist yet
|
||||
if !new_dir.exists() {
|
||||
let legacy = legacy_vault_dir();
|
||||
if legacy.is_dir() {
|
||||
let _ = std::fs::create_dir_all(&new_dir);
|
||||
if let Ok(entries) = std::fs::read_dir(&legacy) {
|
||||
for entry in entries.flatten() {
|
||||
let dest = new_dir.join(entry.file_name());
|
||||
let _ = std::fs::copy(entry.path(), &dest);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let _ = std::fs::set_permissions(
|
||||
&dest,
|
||||
std::fs::Permissions::from_mode(0o600),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_dir
|
||||
}
|
||||
|
||||
/// Path to the encrypted keystore file for a domain.
|
||||
pub fn keystore_path(domain: &str) -> PathBuf {
|
||||
keystore_path_in(domain, None)
|
||||
@@ -83,6 +115,11 @@ pub fn keystore_exists(domain: &str) -> bool {
|
||||
keystore_path(domain).exists()
|
||||
}
|
||||
|
||||
/// Whether a keystore exists in a specific directory (context-aware).
|
||||
pub fn keystore_exists_at(domain: &str, dir: &Path) -> bool {
|
||||
keystore_path_in(domain, Some(dir)).exists()
|
||||
}
|
||||
|
||||
fn keystore_exists_in(domain: &str, dir: Option<&Path>) -> bool {
|
||||
keystore_path_in(domain, dir).exists()
|
||||
}
|
||||
@@ -92,7 +129,13 @@ fn keystore_exists_in(domain: &str, dir: Option<&Path>) -> bool {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn machine_salt_path(override_dir: Option<&Path>) -> PathBuf {
|
||||
base_dir(override_dir).join(".machine-salt")
|
||||
if let Some(d) = override_dir {
|
||||
return d.join(".machine-salt");
|
||||
}
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".sunbeam")
|
||||
.join(".machine-salt")
|
||||
}
|
||||
|
||||
fn load_or_create_machine_salt(override_dir: Option<&Path>) -> Result<Vec<u8>> {
|
||||
@@ -203,11 +246,16 @@ fn decrypt(data: &[u8], domain: &str, override_dir: Option<&Path>) -> Result<Vec
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Save a keystore, encrypted, to the local filesystem.
|
||||
/// Save a keystore, encrypted, to the local filesystem (default dir).
|
||||
pub fn save_keystore(ks: &VaultKeystore) -> Result<()> {
|
||||
save_keystore_in(ks, None)
|
||||
}
|
||||
|
||||
/// Save a keystore to a specific directory (context-aware).
|
||||
pub fn save_keystore_to(ks: &VaultKeystore, dir: &Path) -> Result<()> {
|
||||
save_keystore_in(ks, Some(dir))
|
||||
}
|
||||
|
||||
fn save_keystore_in(ks: &VaultKeystore, override_dir: Option<&Path>) -> Result<()> {
|
||||
let path = keystore_path_in(&ks.domain, override_dir);
|
||||
|
||||
@@ -235,11 +283,16 @@ fn save_keystore_in(ks: &VaultKeystore, override_dir: Option<&Path>) -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load and decrypt a keystore from the local filesystem.
|
||||
/// Load and decrypt a keystore from the local filesystem (default dir).
|
||||
pub fn load_keystore(domain: &str) -> Result<VaultKeystore> {
|
||||
load_keystore_in(domain, None)
|
||||
}
|
||||
|
||||
/// Load a keystore from a specific directory (context-aware).
|
||||
pub fn load_keystore_from(domain: &str, dir: &Path) -> Result<VaultKeystore> {
|
||||
load_keystore_in(domain, Some(dir))
|
||||
}
|
||||
|
||||
fn load_keystore_in(domain: &str, override_dir: Option<&Path>) -> Result<VaultKeystore> {
|
||||
let path = keystore_path_in(domain, override_dir);
|
||||
if !path.exists() {
|
||||
@@ -275,6 +328,11 @@ pub fn verify_vault_keys(domain: &str) -> Result<VaultKeystore> {
|
||||
verify_vault_keys_in(domain, None)
|
||||
}
|
||||
|
||||
/// Verify vault keys from a specific directory (context-aware).
|
||||
pub fn verify_vault_keys_from(domain: &str, dir: &Path) -> Result<VaultKeystore> {
|
||||
verify_vault_keys_in(domain, Some(dir))
|
||||
}
|
||||
|
||||
fn verify_vault_keys_in(domain: &str, override_dir: Option<&Path>) -> Result<VaultKeystore> {
|
||||
let ks = load_keystore_in(domain, override_dir)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user