Quickstart

Build and install a module locally. Every module is self-contained: it pulls its shared dependencies from git tags and npm packages, with no need for the sibling repositories.

1. Clone and build #

git clone https://github.com/kubuno/calendar && cd calendar
cargo build --release
(cd frontend && npm ci && npm run build)

2. Package and install #

bash build_deb.sh --install     # → dist/kubuno-<id>_*.deb puis apt install local
Note

The installed layout is /usr/lib/kubuno/modules/<id>/…. A module must have been installed once to exist in that directory.

3. Fast dev loop #

To iterate without rebuilding a .deb:

bash _tools/deploy_local.sh <module>             # backend + frontend
bash _tools/deploy_local.sh <module> --frontend  # frontend seul (le plus rapide)

Your first endpoint — in the language of your choice #

A module is nothing more than a local HTTP service that the core proxies. The core natively supervises the Rust, Python and Node runtimes, but any language that speaks HTTP can be a module: declare runtime = "binary" in the manifest and provide the executable. Here is the same /ping endpoint in five languages:

// Rust — Axum
use axum::{routing::get, Json, Router};
use serde_json::json;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/ping", get(|| async { Json(json!({ "ok": true })) }));
    let l = tokio::net::TcpListener::bind("127.0.0.1:3190").await.unwrap();
    axum::serve(l, app).await.unwrap();
}
<?php
// PHP — lancé avec : php -S 127.0.0.1:3190 router.php
header('Content-Type: application/json');
if (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) === '/ping') {
    echo json_encode(['ok' => true]);
    exit;
}
http_response_code(404);
# Python — Flask
from flask import Flask, jsonify

app = Flask(__name__)

@app.get("/ping")
def ping():
    return jsonify(ok=True)

app.run(host="127.0.0.1", port=3190)
// Go — net/http
package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode(map[string]bool{"ok": true})
    })
    http.ListenAndServe("127.0.0.1:3190", nil)
}
# Perl — Mojolicious::Lite
use Mojolicious::Lite -signatures;

get '/ping' => sub ($c) {
    $c->render(json => { ok => \1 });
};

app->start('daemon', '-l', 'http://127.0.0.1:3190');
Note

The port (3190 here) must match the one declared under [server] in module.toml. The module only listens on 127.0.0.1: the core is what exposes it.

4. End-of-task checklist #

  • cargo build --release (+ SQLX_OFFLINE=true when using the .sqlx cache).
  • cd frontend && npm run build then tsc -b.
  • Actually test (curl, browser) — don't just compile.
  • cargo clean after a heavy build.
Important

If you touch a shared crate or an @kubuno/* lib, plan a new git tag / npm version, then bump the dependency in the modules.