Lifecycle & proxy
At startup a module registers with the core; the core then proxies its routes and monitors its health through a heartbeat.
Registration #
The module calls the core's /internal/* routes (on 127.0.0.1:8080), protected by the X-Internal-Secret header. It sends its identity, its base_url, its routes, its sidebar entries, its subscribed events and its version. The core keeps the instance in memory and in the database (the core.module_instances table).
In practice, startup registration boils down to a single POST — in the language of your choice:
// Rust — reqwest
let core = std::env::var("KC__CORE__URL")?;
let secret = std::env::var("KC__CORE__INTERNAL_SECRET")?;
reqwest::Client::new()
.post(format!("{core}/internal/modules/register"))
.header("X-Internal-Secret", secret)
.json(&serde_json::json!({
"module_id": "demo",
"base_url": "http://127.0.0.1:3190",
"version": "0.1.0"
}))
.send().await?;<?php
// PHP — cURL
$core = getenv('KC__CORE__URL');
$secret = getenv('KC__CORE__INTERNAL_SECRET');
$ch = curl_init("$core/internal/modules/register");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Internal-Secret: $secret",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"module_id" => "demo",
"base_url" => "http://127.0.0.1:3190",
"version" => "0.1.0",
]),
]);
curl_exec($ch);# Python — requests
import os, requests
core = os.environ["KC__CORE__URL"]
secret = os.environ["KC__CORE__INTERNAL_SECRET"]
requests.post(
f"{core}/internal/modules/register",
headers={"X-Internal-Secret": secret},
json={
"module_id": "demo",
"base_url": "http://127.0.0.1:3190",
"version": "0.1.0",
},
)// Go — net/http
core := os.Getenv("KC__CORE__URL")
secret := os.Getenv("KC__CORE__INTERNAL_SECRET")
body, _ := json.Marshal(map[string]string{
"module_id": "demo",
"base_url": "http://127.0.0.1:3190",
"version": "0.1.0",
})
req, _ := http.NewRequest("POST", core+"/internal/modules/register", bytes.NewReader(body))
req.Header.Set("X-Internal-Secret", secret)
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)# Perl — LWP::UserAgent
use LWP::UserAgent;
use JSON::PP;
my $core = $ENV{'KC__CORE__URL'};
my $secret = $ENV{'KC__CORE__INTERNAL_SECRET'};
my $ua = LWP::UserAgent->new;
$ua->post("$core/internal/modules/register",
'X-Internal-Secret' => $secret,
'Content-Type' => 'application/json',
Content => encode_json({
module_id => "demo",
base_url => "http://127.0.0.1:3190",
version => "0.1.0",
}),
);The secret and the core URL are handed to the process through the environment (KC__CORE__INTERNAL_SECRET, KC__CORE__URL). See Configuration.
Lifecycle #
starting ──► healthy ──(heartbeat manqué)──► degraded ──► stopped- heartbeat — the module periodically pings the core to stay
healthy. - unregister — on clean shutdown, the module removes itself from the registry.
The reverse proxy #
The core intercepts /api/v1/<module_id>/* and rewrites it to http://127.0.0.1:<port>/*, injecting the authenticated identity:
Requête navigateur : GET /api/v1/calendar/events
▼ (core : auth + réécriture)
Vers le module : GET http://127.0.0.1:3102/events
+ X-Internal-Secret, X-Kubuno-User-Id, X-Kubuno-User-Role, X-Kubuno-User-EmailThe module is never exposed directly: it only listens on 127.0.0.1, and everything goes through the core, which authenticates and rewrites.