feat: init - dynamic service discovery via init.toml

This commit is contained in:
2026-08-10 17:00:52 +02:00
parent e25ef470e0
commit 69725658cf
4 changed files with 174 additions and 27 deletions
+44
View File
@@ -0,0 +1,44 @@
# MontaukOS init services
#
# Services are started in declaration order. Available fields:
# path executable to spawn (required)
# name display name used in the boot log (defaults to the table id)
# enabled whether init should consider the service (defaults to true)
# wait wait for the process to exit before continuing (defaults to true)
# optional quietly skip the service when its executable is absent
# fallback service id to start if spawning this service fails
# fallback_only define the service for fallback use without starting it normally
[services.dhcp]
path = "0:/os/dhcp.elf"
enabled = true
wait = false
[services.ntp]
path = "0:/os/ntp.elf"
name = "network time"
enabled = true
wait = false
[services.printd]
path = "0:/os/printd.elf"
name = "print spooler"
enabled = true
wait = false
optional = true
[services.login]
path = "0:/os/login.elf"
enabled = true
fallback = "desktop"
[services.desktop]
path = "0:/os/desktop.elf"
enabled = true
fallback = "shell"
fallback_only = true
[services.shell]
path = "0:/os/shell.elf"
enabled = true
fallback_only = true
+20 -10
View File
@@ -7,25 +7,35 @@
.SH DESCRIPTION
init is the first userspace process started by the MontaukOS
kernel. It chains system services in sequence, then launches
the interactive shell.
kernel. It discovers system services from 0:/config/init.toml
and starts them in the order their tables appear in that file.
Each service is spawned as a child process. init waits for it
to exit before starting the next one. If a service fails to
spawn, init logs an error and continues to the next stage.
Each enabled service is spawned as a child process. Services may
run in the background or block startup until they exit. Disabled
services are logged and skipped.
Log output is timestamped and color-coded:
HH:MM:SS INFO init Starting dhcp
HH:MM:SS OK init dhcp finished (pid 1)
.SH BOOT SEQUENCE
The following services are started in order:
.SH CONFIGURATION
Each service is declared as a named TOML table:
1. 0:/os/dhcp.elf Obtain network configuration via DHCP
2. 0:/os/shell.elf Launch the interactive shell
[services.dhcp]
path = "0:/os/dhcp.elf"
enabled = true
wait = false
After the shell exits, init enters an idle loop.
path is required. name controls the name shown in the boot log and
defaults to the table name. enabled and wait both default to true.
optional skips a service whose executable is not installed.
fallback names another configured service to start if spawning the
service fails. A service with fallback_only = true is available as
a fallback but is not started during the normal pass.
After all foreground services exit, init enters an idle loop.
.SH LOG LEVELS
init uses four log levels, each with a distinct color:
+109 -16
View File
@@ -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 (;;) {