crimata-os/dock/src/main.c
Andrew Gundersen a825aec162 Add dock service, provision README, MIT License
- crimata-dock: C daemon (libmicrohttpd + libsystemd) on port 7701
  - GET /apps: lists installed apps with live running status from systemd
  - POST /apps/{id}/start|stop: controls crimata-*.service units
- Add README with install paths and port map
- Add MIT License

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-02-26 23:51:56 -05:00

142 lines
4.9 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <microhttpd.h>
#include "apps.h"
#include "systemd.h"
#define PORT 7701
#define BUF_SIZE 16384
/* ── JSON response helper ─────────────────────────────────────────────────── */
static enum MHD_Result send_json(struct MHD_Connection *conn,
unsigned int status, const char *body)
{
struct MHD_Response *resp = MHD_create_response_from_buffer(
strlen(body), (void *)body, MHD_RESPMEM_MUST_COPY);
MHD_add_response_header(resp, "Content-Type", "application/json");
MHD_add_response_header(resp, "Access-Control-Allow-Origin", "*");
enum MHD_Result r = MHD_queue_response(conn, status, resp);
MHD_destroy_response(resp);
return r;
}
/* ── GET /apps ────────────────────────────────────────────────────────────── */
static enum MHD_Result handle_list(struct MHD_Connection *conn)
{
app_t apps[MAX_APPS];
int count = apps_scan(apps);
/* Check running status for each app */
for (int i = 0; i < count; i++) {
char unit[MAX_STR + 16];
snprintf(unit, sizeof(unit), "crimata-%s.service", apps[i].id);
apps[i].running = (systemd_is_active(unit) == 1) ? 1 : 0;
}
/* Build JSON array */
char buf[BUF_SIZE];
int pos = 0;
pos += snprintf(buf + pos, sizeof(buf) - pos, "[");
for (int i = 0; i < count; i++) {
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%s{\"id\":\"%s\",\"name\":\"%s\",\"icon\":\"%s\","
"\"port\":%d,\"running\":%s}",
i > 0 ? "," : "",
apps[i].id, apps[i].name, apps[i].icon,
apps[i].port, apps[i].running ? "true" : "false");
}
pos += snprintf(buf + pos, sizeof(buf) - pos, "]");
return send_json(conn, MHD_HTTP_OK, buf);
}
/* ── POST /apps/{id}/start|stop ───────────────────────────────────────────── */
static enum MHD_Result handle_action(struct MHD_Connection *conn,
const char *app_id, int start)
{
/* Verify app exists */
app_t apps[MAX_APPS];
int count = apps_scan(apps);
int found = 0;
for (int i = 0; i < count; i++) {
if (strcmp(apps[i].id, app_id) == 0) { found = 1; break; }
}
if (!found)
return send_json(conn, MHD_HTTP_NOT_FOUND,
"{\"error\":\"app not found\"}");
char unit[MAX_STR + 16];
snprintf(unit, sizeof(unit), "crimata-%s.service", app_id);
int r = start ? systemd_start(unit) : systemd_stop(unit);
if (r < 0)
return send_json(conn, MHD_HTTP_INTERNAL_SERVER_ERROR,
"{\"error\":\"systemd call failed\"}");
return send_json(conn, MHD_HTTP_OK, "{\"ok\":true}");
}
/* ── Main request handler ─────────────────────────────────────────────────── */
static enum MHD_Result handler(void *cls,
struct MHD_Connection *conn,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **con_cls)
{
(void)cls; (void)version; (void)upload_data; (void)upload_data_size;
(void)con_cls;
if (strcmp(url, "/health") == 0 && strcmp(method, "GET") == 0)
return send_json(conn, MHD_HTTP_OK, "{\"status\":\"ok\"}");
if (strcmp(url, "/apps") == 0 && strcmp(method, "GET") == 0)
return handle_list(conn);
char app_id[MAX_STR];
if (strcmp(method, "POST") == 0) {
if (sscanf(url, "/apps/%127[^/]/start", app_id) == 1)
return handle_action(conn, app_id, 1);
if (sscanf(url, "/apps/%127[^/]/stop", app_id) == 1)
return handle_action(conn, app_id, 0);
}
return send_json(conn, MHD_HTTP_NOT_FOUND, "{\"error\":\"not found\"}");
}
/* ── Entry point ──────────────────────────────────────────────────────────── */
int main(void)
{
struct MHD_Daemon *daemon = MHD_start_daemon(
MHD_USE_INTERNAL_POLLING_THREAD,
PORT, NULL, NULL,
&handler, NULL,
MHD_OPTION_END
);
if (!daemon) {
fprintf(stderr, "failed to start daemon on port %d\n", PORT);
return 1;
}
printf("crimata-dock listening on :%d\n", PORT);
getchar();
MHD_stop_daemon(daemon);
return 0;
}