1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-10-01 08:44:09 +02:00

feat(services): Add slave service to configure global scale slave with

the latest lookup server

Signed-off-by: Louis Chmn <louis@chmn.me>
This commit is contained in:
Louis Chmn 2026-09-16 23:18:13 +02:00
commit d48a192f3f
4 changed files with 147 additions and 24 deletions

View file

@ -15,6 +15,7 @@ mod push;
mod redis;
mod sftp;
mod sharded;
mod slave;
mod smb;
mod webhook;
@ -37,6 +38,7 @@ pub use crate::service::push::NotifyPush;
use crate::service::redis::Redis;
use crate::service::sftp::{Sftp, SftpKey};
use crate::service::sharded::{Sharding, ShardingMigrate, ShardingMigrateUnset, SingleShard};
pub use crate::service::slave::GlobalScaleSlave;
use crate::service::smb::Smb;
use crate::service::webhook::Webhook;
use bollard::Docker;
@ -337,6 +339,9 @@ pub enum ServiceType {
/// Global scale lookup server
#[strum(serialize = "lookup")]
LookupServer,
/// Global scale slave instance
#[strum(serialize = "slave")]
GlobalScaleSlave,
}
#[enum_dispatch]
@ -375,6 +380,7 @@ pub enum Service {
AuthentikOidc(AuthentikOidc),
AuthentikScim(AuthentikScim),
LookupServer(LookupServer),
GlobalScaleSlave(GlobalScaleSlave),
Preset(PresetService),
}
@ -435,6 +441,9 @@ impl Service {
Service::AuthentikScim(AuthentikScim),
]),
ServiceType::LookupServer => Some(vec![Service::LookupServer(LookupServer)]),
ServiceType::GlobalScaleSlave => {
Some(vec![Service::GlobalScaleSlave(GlobalScaleSlave)])
}
}
} else {
presets

View file

@ -15,6 +15,8 @@ use serde_json::Value;
use std::collections::HashMap;
use std::io::Stdout;
pub(super) const GSS_JWT_KEY: &str = "random-key";
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LookupServer;
@ -136,7 +138,7 @@ impl ServiceTrait for LookupServer {
"gss.mode".into() => Value::String("master".into()),
"gss.master.accounts".into() => Value::Array(vec![Value::String("admin".into())]),
"gss.master.csp-allow".into() => Value::Array(vec![Value::String("*".into())]),
"gss.jwt.key".into() => Value::String("random-key".into()),
"gss.jwt.key".into() => Value::String(GSS_JWT_KEY.into()),
})
}
}

130
src/service/slave.rs Normal file
View file

@ -0,0 +1,130 @@
use crate::Result;
use crate::cloud::{Cloud, CloudOptions};
use crate::config::{HazeConfig, ProxyConfig};
use crate::service::lookup_server::GSS_JWT_KEY;
use crate::service::{LookupServer, ServiceTrait};
use bollard::Docker;
use bollard::query_parameters::ListContainersOptions;
use maplit::hashmap;
use miette::{IntoDiagnostic, Report};
use serde_json::Value;
use std::collections::HashMap;
/// Cloud id of the most recently started lookup server.
async fn master_cloud_id(docker: &Docker) -> Result<String> {
let mut lookup_servers: Vec<_> = docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await
.into_diagnostic()?
.iter()
.filter_map(|container| {
let labels = container.labels.as_ref()?;
if labels.get("haze-type").map(String::as_str) != Some(LookupServer.name()) {
return None;
}
let cloud_id = labels.get("haze-cloud-id")?.clone();
Some((container.created.unwrap_or_default(), cloud_id))
})
.collect();
lookup_servers.sort_by(|a, b| a.0.cmp(&b.0).reverse());
lookup_servers.into_iter().map(|(_created, id)| id).next().ok_or_else(|| {
Report::msg("No haze instance with a lookup server is running, start one with `haze start lookup`")
})
}
/// Latest cloud started with a lookup server.
async fn master_instance(docker: &Docker, config: &HazeConfig) -> Result<Cloud> {
let id = master_cloud_id(docker).await?;
Cloud::list(docker, Some(id.clone()), config)
.await?
.into_iter()
.find(|cloud| cloud.id == id)
.ok_or_else(|| Report::msg(format!("Failed to get the address of master instance {id}")))
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct GlobalScaleSlave;
#[async_trait::async_trait]
impl ServiceTrait for GlobalScaleSlave {
fn name(&self) -> &str {
"slave"
}
async fn spawn(
&self,
docker: &Docker,
_cloud_id: &str,
_network: &str,
config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
master_instance(docker, config).await?;
Ok(Vec::new())
}
fn apps(&self) -> &'static [&'static str] {
&["globalsiteselector"]
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
Ok(hashmap! {
"gs.enabled".into() => Value::Bool(true),
"gss.mode".into() => Value::String("slave".into()),
"gss.jwt.key".into() => Value::String(GSS_JWT_KEY.into()),
})
}
async fn post_setup(
&self,
docker: &Docker,
_cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<Vec<String>>> {
let master = master_instance(docker, config).await?;
let lookup_server = LookupServer
.container_name(&master.id)
.expect("lookup server has a container name");
Ok(vec![
vec![
"occ".into(),
"config:system:set".into(),
"--silent".into(),
"lookup_server".into(),
"--value".into(),
format!("http://{lookup_server}"),
],
vec![
"occ".into(),
"config:system:set".into(),
"--silent".into(),
"gss.master.url".into(),
"--value".into(),
master.address.clone(),
],
vec!["occ".into(), "globalsiteselector:users:update".into()],
])
}
async fn start_message(
&self,
docker: &Docker,
_cloud_id: &str,
_proxy: &ProxyConfig,
) -> Result<Option<String>> {
let master = master_cloud_id(docker).await?;
Ok(Some(format!("Nextcloud was setup as slave of {master}.")))
}
}