feat: init - dynamic service discovery via init.toml
This commit is contained in:
+109
-16
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* Init system for MontaukOS (PID 0)
|
||||
* Chains system services then launches the shell.
|
||||
* Discovers and starts system services from 0:/config/init.toml.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/config.h>
|
||||
#include <libc/stdio.h>
|
||||
|
||||
// ---- ANSI color codes ----
|
||||
@@ -88,31 +89,123 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- Service configuration ----
|
||||
|
||||
static constexpr const char* SERVICE_PREFIX = "services.";
|
||||
|
||||
static bool is_service_table(const montauk::toml::Value* value) {
|
||||
if (!value || value->type != montauk::toml::Type::Table || !value->key)
|
||||
return false;
|
||||
if (!montauk::starts_with(value->key, SERVICE_PREFIX))
|
||||
return false;
|
||||
|
||||
// Only [services.<id>] declares a service. Nested tables belong to that
|
||||
// service and must not be treated as additional services.
|
||||
const char* id = value->key + montauk::slen(SERVICE_PREFIX);
|
||||
if (!*id) return false;
|
||||
while (*id) {
|
||||
if (*id == '.') return false;
|
||||
id++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void service_key(char* out, int out_size, const char* id, const char* field) {
|
||||
snprintf(out, out_size, "%s%s.%s", SERVICE_PREFIX, id, field);
|
||||
}
|
||||
|
||||
static montauk::toml::Value* find_service(const montauk::toml::Doc& doc, const char* id) {
|
||||
char table_key[256];
|
||||
snprintf(table_key, sizeof(table_key), "%s%s", SERVICE_PREFIX, id);
|
||||
return doc.get_table(table_key);
|
||||
}
|
||||
|
||||
static bool run_configured_service(const montauk::toml::Doc& doc,
|
||||
montauk::toml::Value* service,
|
||||
int fallback_depth = 0) {
|
||||
const char* id = service->key + montauk::slen(SERVICE_PREFIX);
|
||||
char key[256];
|
||||
char msg[192];
|
||||
|
||||
service_key(key, sizeof(key), id, "name");
|
||||
const char* name = doc.get_string(key, id);
|
||||
|
||||
service_key(key, sizeof(key), id, "enabled");
|
||||
if (!doc.get_bool(key, true)) {
|
||||
snprintf(msg, sizeof(msg), "%s disabled, skipping", name);
|
||||
log_info(msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
service_key(key, sizeof(key), id, "path");
|
||||
const char* path = doc.get_string(key, "");
|
||||
if (!path[0]) {
|
||||
snprintf(msg, sizeof(msg), "Service %s has no path, skipping", id);
|
||||
log_err(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
service_key(key, sizeof(key), id, "optional");
|
||||
bool optional = doc.get_bool(key, false);
|
||||
if (optional && !service_installed(path)) {
|
||||
snprintf(msg, sizeof(msg), "%s not installed, skipping", name);
|
||||
log_info(msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
service_key(key, sizeof(key), id, "wait");
|
||||
bool wait = doc.get_bool(key, true);
|
||||
if (run_service(path, name, wait)) return true;
|
||||
|
||||
service_key(key, sizeof(key), id, "fallback");
|
||||
const char* fallback = doc.get_string(key, "");
|
||||
if (!fallback[0]) return false;
|
||||
|
||||
if (fallback_depth >= 8) {
|
||||
log_err("Service fallback chain is too deep");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* fallback_service = find_service(doc, fallback);
|
||||
if (!fallback_service) {
|
||||
snprintf(msg, sizeof(msg), "Fallback service %s is not configured", fallback);
|
||||
log_err(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf(msg, sizeof(msg), "%s failed, falling back to %s", name, fallback);
|
||||
log_warn(msg);
|
||||
return run_configured_service(doc, fallback_service, fallback_depth + 1);
|
||||
}
|
||||
|
||||
// ---- Main ----
|
||||
|
||||
extern "C" void _start() {
|
||||
|
||||
log_info("The Montauk Operating System");
|
||||
|
||||
// ---- Stage 1: Network configuration (non-blocking) ----
|
||||
run_service("0:/os/dhcp.elf", "dhcp", false);
|
||||
run_service("0:/os/ntp.elf", "network time", false);
|
||||
auto config = montauk::config::load("init");
|
||||
int discovered = 0;
|
||||
|
||||
// Printing is an optional install component; skip quietly when absent.
|
||||
if (service_installed("0:/os/printd.elf"))
|
||||
run_service("0:/os/printd.elf", "print spooler", false);
|
||||
else
|
||||
log_info("Print spooler not installed, skipping");
|
||||
// Table order is file order, making the config itself the startup order.
|
||||
for (int i = 0; i < config.entries.count; i++) {
|
||||
auto* service = config.entries.items[i];
|
||||
if (!is_service_table(service)) continue;
|
||||
discovered++;
|
||||
|
||||
// ---- Stage 2: Login screen -> desktop (falls back to desktop, then shell) ----
|
||||
if (!run_service("0:/os/login.elf", "login")) {
|
||||
log_warn("Login failed, falling back to desktop");
|
||||
if (!run_service("0:/os/desktop.elf", "desktop")) {
|
||||
log_warn("Desktop failed, falling back to shell");
|
||||
run_service("0:/os/shell.elf", "shell");
|
||||
}
|
||||
const char* id = service->key + montauk::slen(SERVICE_PREFIX);
|
||||
char key[256];
|
||||
service_key(key, sizeof(key), id, "fallback_only");
|
||||
if (config.get_bool(key, false)) continue;
|
||||
|
||||
run_configured_service(config, service);
|
||||
}
|
||||
|
||||
if (discovered == 0)
|
||||
log_err("No services found in 0:/config/init.toml");
|
||||
|
||||
config.destroy();
|
||||
|
||||
log_warn("All services exited");
|
||||
|
||||
for (;;) {
|
||||
|
||||
Reference in New Issue
Block a user