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
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 103 #define MONTAUK_BUILD_NUMBER 104
+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 .SH DESCRIPTION
init is the first userspace process started by the MontaukOS init is the first userspace process started by the MontaukOS
kernel. It chains system services in sequence, then launches kernel. It discovers system services from 0:/config/init.toml
the interactive shell. and starts them in the order their tables appear in that file.
Each service is spawned as a child process. init waits for it Each enabled service is spawned as a child process. Services may
to exit before starting the next one. If a service fails to run in the background or block startup until they exit. Disabled
spawn, init logs an error and continues to the next stage. services are logged and skipped.
Log output is timestamped and color-coded: Log output is timestamped and color-coded:
HH:MM:SS INFO init Starting dhcp HH:MM:SS INFO init Starting dhcp
HH:MM:SS OK init dhcp finished (pid 1) HH:MM:SS OK init dhcp finished (pid 1)
.SH BOOT SEQUENCE .SH CONFIGURATION
The following services are started in order: Each service is declared as a named TOML table:
1. 0:/os/dhcp.elf Obtain network configuration via DHCP [services.dhcp]
2. 0:/os/shell.elf Launch the interactive shell 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 .SH LOG LEVELS
init uses four log levels, each with a distinct color: init uses four log levels, each with a distinct color:
+109 -16
View File
@@ -1,11 +1,12 @@
/* /*
* main.cpp * main.cpp
* Init system for MontaukOS (PID 0) * 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 * Copyright (c) 2026 Daniel Hammer
*/ */
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/config.h>
#include <libc/stdio.h> #include <libc/stdio.h>
// ---- ANSI color codes ---- // ---- ANSI color codes ----
@@ -88,31 +89,123 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
return 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 ---- // ---- Main ----
extern "C" void _start() { extern "C" void _start() {
log_info("The Montauk Operating System"); log_info("The Montauk Operating System");
// ---- Stage 1: Network configuration (non-blocking) ---- auto config = montauk::config::load("init");
run_service("0:/os/dhcp.elf", "dhcp", false); int discovered = 0;
run_service("0:/os/ntp.elf", "network time", false);
// Printing is an optional install component; skip quietly when absent. // Table order is file order, making the config itself the startup order.
if (service_installed("0:/os/printd.elf")) for (int i = 0; i < config.entries.count; i++) {
run_service("0:/os/printd.elf", "print spooler", false); auto* service = config.entries.items[i];
else if (!is_service_table(service)) continue;
log_info("Print spooler not installed, skipping"); discovered++;
// ---- Stage 2: Login screen -> desktop (falls back to desktop, then shell) ---- const char* id = service->key + montauk::slen(SERVICE_PREFIX);
if (!run_service("0:/os/login.elf", "login")) { char key[256];
log_warn("Login failed, falling back to desktop"); service_key(key, sizeof(key), id, "fallback_only");
if (!run_service("0:/os/desktop.elf", "desktop")) { if (config.get_bool(key, false)) continue;
log_warn("Desktop failed, falling back to shell");
run_service("0:/os/shell.elf", "shell"); 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"); log_warn("All services exited");
for (;;) { for (;;) {