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

feat(services): Add a service for the lookup server

Signed-off-by: Louis Chmn <louis@chmn.me>
This commit is contained in:
Louis Chmn 2026-09-16 22:40:18 +02:00
commit 46ea577dbd
3 changed files with 213 additions and 0 deletions

View file

@ -85,6 +85,8 @@ Additionally, you can use the following options when starting an instance:
- `saml`: set up authentik as a SAML IDP. - `saml`: set up authentik as a SAML IDP.
- `oidc`: set up authentik as an OIDC IDP. - `oidc`: set up authentik as an OIDC IDP.
- `scim`: set up authentik as a SCIM server. - `scim`: set up authentik as a SCIM server.
- `lookup`: set up a
[lookup server](https://github.com/nextcloud/lookup-server).
- `office`: set up a Nextcloud Office server. - `office`: set up a Nextcloud Office server.
- `onlyoffice` setup an onlyoffice document server. - `onlyoffice` setup an onlyoffice document server.
- `push` set up [client push](https://github.com/nextcloud/notify_push). - `push` set up [client push](https://github.com/nextcloud/notify_push).
@ -293,6 +295,68 @@ name to allow for testing federation between instances. Alternatively, you can
set up the haze proxy and the proxied domains to get https support between set up the haze proxy and the proxied domains to get https support between
instances. instances.
## Global scale
You can start a lookup-server service when starting an instance:
```bash
haze start --name gs-master lookup
```
The lookup server is then accessible by other instances at
`http://haze-gs-master-lookup`.
The Nextcloud instance started with this command is already properly configured
as master node.
For the slaves instance, here is a sane preset:
```toml
[[preset]]
name = "gs-slave"
apps = ["globalsiteselector"]
config = {
"gs.enabled" = true,
"gs.federation" = "global",
"gss.jwt.key" = "random-key", # Matches the key set in the docker container of the lookup server.
"gss.mode" = "slave",
}
```
You'll then have to manually point your slave instances to the lookup and master
instances:
```bash
haze start --name gs-slave1 gs-slave
# Assuming that the master instance was started with `--name gs-master`.
haze gs-slave1 occ config:system:set gss.master.url --value="http://haze-gs-master.haze.example.com"
haze gs-slave1 occ config:system:set lookup_server --value="http://haze-gs-master-lookup"
```
If you want to use the `ManualUserMapping` module, you'll have to set the
following config on the master instance:
```bash
haze gs-master occ config:system:set gss.user.discovery.module --value="\\OCA\\GlobalSiteSelector\\UserDiscoveryModules\\ManualUserMapping"
haze gs-master occ config:system:set gss.discovery.manual.mapping.file --value="/shared/config/gs-user-mapping.json" # See the volume section below for how to mount this file into the container.
haze gs-master occ config:system:set gss.discovery.manual.mapping.regex --type boolean --value=true
```
```toml
[[volume]] # Needed if you are using "ManualUserMapping" module.
source = "/home/louis/.config/haze/config/gs-user-mapping.json"
target = "/shared/config/gs-user-mapping.json"
read_only = true
```
You can test the connection between the instances and the lookup server by
running the following command on the slave instance:
```bash
haze gs-master occ globalsiteselector:discovery
haze gs-slave1 occ globalsiteselector:discovery
```
## Proxy ## Proxy
By default, instances can be accessed by their IP. In order to get more By default, instances can be accessed by their IP. In order to get more

View file

@ -4,6 +4,7 @@ mod dav;
mod imaginary; mod imaginary;
mod kaspersky; mod kaspersky;
mod ldap; mod ldap;
mod lookup_server;
mod mail; mod mail;
mod objectstore; mod objectstore;
mod oc; mod oc;
@ -25,6 +26,7 @@ use crate::service::dav::Dav;
use crate::service::imaginary::Imaginary; use crate::service::imaginary::Imaginary;
use crate::service::kaspersky::{Kaspersky, KasperskyIcap}; use crate::service::kaspersky::{Kaspersky, KasperskyIcap};
pub use crate::service::ldap::{Ldap, LdapAdmin}; pub use crate::service::ldap::{Ldap, LdapAdmin};
pub use crate::service::lookup_server::LookupServer;
use crate::service::mail::Mail; use crate::service::mail::Mail;
pub use crate::service::objectstore::ObjectStore; pub use crate::service::objectstore::ObjectStore;
use crate::service::oc::Oc; use crate::service::oc::Oc;
@ -332,6 +334,9 @@ pub enum ServiceType {
Oidc, Oidc,
/// Configure Authentik as a SCIM server for Nextcloud /// Configure Authentik as a SCIM server for Nextcloud
Scim, Scim,
/// Global scale lookup server
#[strum(serialize = "lookup")]
LookupServer,
} }
#[enum_dispatch] #[enum_dispatch]
@ -369,6 +374,7 @@ pub enum Service {
AuthentikSaml(AuthentikSaml), AuthentikSaml(AuthentikSaml),
AuthentikOidc(AuthentikOidc), AuthentikOidc(AuthentikOidc),
AuthentikScim(AuthentikScim), AuthentikScim(AuthentikScim),
LookupServer(LookupServer),
Preset(PresetService), Preset(PresetService),
} }
@ -428,6 +434,7 @@ impl Service {
Service::Authentik(Authentik), Service::Authentik(Authentik),
Service::AuthentikScim(AuthentikScim), Service::AuthentikScim(AuthentikScim),
]), ]),
ServiceType::LookupServer => Some(vec![Service::LookupServer(LookupServer)]),
} }
} else { } else {
presets presets

View file

@ -0,0 +1,142 @@
use crate::Result;
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
use crate::network::ensure_network_exists;
use crate::service::ServiceTrait;
use bollard::Docker;
use bollard::config::{NetworkConnectRequest, NetworkingConfig};
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig};
use bollard::query_parameters::CreateContainerOptions;
use maplit::hashmap;
use miette::IntoDiagnostic;
use serde_json::Value;
use std::collections::HashMap;
use std::io::Stdout;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LookupServer;
#[async_trait::async_trait]
impl ServiceTrait for LookupServer {
fn name(&self) -> &str {
"lookup"
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ghcr.io/juliusknorr/nextcloud-dev-lookupserver:latest";
pull_image(docker, image).await?;
// The lookup server needs to be reachable from other instances, so it's
// attached to the shared network in addition to the instance network
ensure_network_exists(docker, "haze").await?;
let options = Some(CreateContainerOptions {
name: self.container_name(cloud_id),
..CreateContainerOptions::default()
});
let container_config = ContainerCreateBody {
image: Some(image.into()),
host_config: Some(HostConfig {
network_mode: Some(network.to_string()),
..Default::default()
}),
labels: Some(hashmap! {
"haze-type".into() => self.name().into(),
"haze-cloud-id".into() => cloud_id.into(),
}),
networking_config: Some(NetworkingConfig {
endpoints_config: Some(hashmap! {
network.into() => EndpointSettings {
aliases: Some(vec![self.name().to_string()]),
..Default::default()
}
}),
}),
..Default::default()
};
let id = docker
.create_container(options, container_config)
.await
.into_diagnostic()?
.id;
docker.start_container(&id, None).await.into_diagnostic()?;
if let Err(e) = docker
.connect_network(
"haze",
NetworkConnectRequest {
container: id.clone(),
endpoint_config: Some(EndpointSettings {
aliases: self.container_name(cloud_id).map(|name| vec![name]),
..Default::default()
}),
},
)
.await
.into_diagnostic()
{
docker.remove_container(&id, None).await.ok();
return Err(e);
}
Ok(vec![id.into()])
}
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-lookup", cloud_id))
}
fn apps(&self) -> &'static [&'static str] {
&["globalsiteselector"]
}
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
if !self.is_running(docker, cloud_id).await? {
return Ok(false);
}
let exit = exec(
docker,
self.container_name(cloud_id).unwrap(),
"root",
vec![
"bash",
"-c",
r#"php -r 'exit(str_contains(@file_get_contents("http://127.0.0.1/index.php/status") ?: "", "version") ? 0 : 1);'"#,
],
Vec::<String>::default(),
Option::<Stdout>::None,
)
.await?;
Ok(exit.to_result().is_ok())
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
Ok(hashmap! {
"lookup_server".into() => Value::String("http://lookup".into()),
"gs.enabled".into() => Value::Bool(true),
"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()),
})
}
}