diff --git a/CMakeLists.txt b/CMakeLists.txt index d209a73..e025648 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) +option(AMDUATD_ENABLE_UI "Build amduatd embedded UI" ON) + add_subdirectory(vendor/amduat) add_library(amduat_federation @@ -22,13 +24,22 @@ target_link_libraries(amduat_federation PRIVATE amduat_asl amduat_enc amduat_util amduat_fed ) -add_executable(amduatd src/amduatd.c) +set(amduatd_sources src/amduatd.c) +if(AMDUATD_ENABLE_UI) + list(APPEND amduatd_sources src/amduatd_ui.c) +endif() + +add_executable(amduatd ${amduatd_sources}) target_include_directories(amduatd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/amduat/src/internal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/amduat/include ) +target_compile_definitions(amduatd + PRIVATE AMDUATD_ENABLE_UI=$ +) + target_link_libraries(amduatd PRIVATE amduat_tgk amduat_pel amduat_format amduat_asl_store_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util amduat_federation diff --git a/README.md b/README.md index 09c4535..d4d8c86 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,14 @@ cmake -S . -B build cmake --build build -j ``` +To build without the embedded UI: + +```sh +cmake -S . -B build -DAMDUATD_ENABLE_UI=OFF +``` + +When the UI is enabled (default), `/v1/ui` serves the same embedded HTML as before. + ## Core dependency This repo vendors the core implementation as a git submodule at `vendor/amduat`. diff --git a/src/amduatd.c b/src/amduatd.c index af9e90f..a3fe872 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -33,6 +33,7 @@ #include "amduat/util/hex.h" #include "amduat/util/log.h" #include "amduat/hash/asl1.h" +#include "amduatd_ui.h" #include #include @@ -71,6 +72,12 @@ static bool amduatd_send_json_error(int fd, const char *reason, const char *msg); +#if AMDUATD_ENABLE_UI +bool amduatd_seed_ui_html(amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + amduat_reference_t *out_ref); +#endif + static uint64_t amduatd_now_ms(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { @@ -1088,319 +1095,6 @@ static const char k_amduatd_contract_v1_json[] = "}" "}\n"; -static const char k_amduatd_ui_html[] = - "\n" - "\n" - "\n" - " \n" - " \n" - " amduatd — Concept editor\n" - " \n" - "\n" - "\n" - "
\n" - "
\n" - "
\n" - "
\n" - " amduatd\n" - "
\n" - " \n" - "
\n" - "
\n" - "
\n" - "

Concept editor + PEL runner

\n" - "

Load shows the latest materialized bytes; Save uploads a new artifact and publishes a new version. Use the runner to execute PEL programs against stored artifacts.

\n" - "
\n" - " Open editor\n" - " Run program\n" - "
\n" - "
\n" - "
\n" - "
\n" - "
\n" - " \n" - " \n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - " \n" - "
\n" - " \n" - "
\n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - "\n" - "
\n" - "
\n" - "
Upload bytes (sets program_ref)
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - "
Run
\n" - " \n" - "
input_refs (comma-separated hex refs or names)
\n" - " \n" - "
params_ref (optional)
\n" - " \n" - "
scheme_ref (optional, default dag)
\n" - " \n" - "
\n" - " \n" - " /v1/contract\n" - " /v1/meta\n" - "
\n" - "
Response
\n" - "
\n"
-    "          
\n" - "
\n" - "
Relations
\n" - "
\n" - " \n" - "
\n" - "
\n"
-    "          
\n" - "
\n" - "
\n" - "
\n" - "
\n" - "

About

\n" - "

amduatd is a local-first mapping surface over a single ASL store root. This UI is a lightweight editor and runner for concepts and PEL programs.

\n" - "
\n" - " \n" - "
\n" - "
\n" - "
\n" - " © 2025 Niklas Rydberg.\n" - " Back to top\n" - "
\n" - "
\n" - "\n" - " \n" - "\n" - "\n"; - static volatile sig_atomic_t amduatd_should_exit = 0; static void amduatd_on_signal(int signo) { @@ -3821,20 +3515,6 @@ static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) { return true; } -typedef struct { - char method[8]; - char path[1024]; - char content_type[128]; - char accept[128]; - char x_type_tag[64]; - char x_capability[2048]; - size_t content_length; - bool has_actor; - amduat_octets_t actor; - bool has_uid; - uid_t uid; -} amduatd_http_req_t; - static void amduatd_http_req_init(amduatd_http_req_t *req) { memset(req, 0, sizeof(*req)); } @@ -4039,13 +3719,13 @@ static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) { return true; } -static bool amduatd_http_send_status(int fd, - int code, - const char *reason, - const char *content_type, - const uint8_t *body, - size_t body_len, - bool head_only) { +bool amduatd_http_send_status(int fd, + int code, + const char *reason, + const char *content_type, + const uint8_t *body, + size_t body_len, + bool head_only) { char hdr[512]; int n = snprintf(hdr, sizeof(hdr), @@ -4070,11 +3750,11 @@ static bool amduatd_http_send_status(int fd, return true; } -static bool amduatd_http_send_text(int fd, - int code, - const char *reason, - const char *text, - bool head_only) { +bool amduatd_http_send_text(int fd, + int code, + const char *reason, + const char *text, + bool head_only) { const uint8_t *body = (const uint8_t *)(text != NULL ? text : ""); size_t len = text != NULL ? strlen(text) : 0; return amduatd_http_send_status( @@ -5676,33 +5356,6 @@ seed_cleanup: return ok; } -static bool amduatd_seed_ui_html(amduat_asl_store_t *store, - const amduat_asl_store_fs_config_t *cfg, - amduat_reference_t *out_ref) { - amduat_artifact_t artifact; - amduat_asl_store_error_t err; - - if (out_ref != NULL) { - memset(out_ref, 0, sizeof(*out_ref)); - } - if (store == NULL || cfg == NULL || out_ref == NULL) { - return false; - } - - artifact = amduat_artifact(amduat_octets(k_amduatd_ui_html, - strlen(k_amduatd_ui_html))); - (void)amduat_asl_ref_derive(artifact, - cfg->config.encoding_profile_id, - cfg->config.hash_id, - out_ref, - NULL); - err = amduat_asl_store_put(store, artifact, out_ref); - if (err != AMDUAT_ASL_STORE_OK) { - return false; - } - return true; -} - static bool amduatd_handle_get_artifact(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, @@ -8170,45 +7823,6 @@ pel_programs_cleanup: return ok; } -static bool amduatd_handle_get_ui(int fd, - amduat_asl_store_t *store, - amduat_reference_t ui_ref) { - amduat_artifact_t artifact; - amduat_asl_store_error_t err; - - if (store == NULL || ui_ref.hash_id == 0 || ui_ref.digest.data == NULL || - ui_ref.digest.len == 0) { - return amduatd_http_send_text(fd, 500, "Internal Server Error", - "ui not available\n", false); - } - memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, ui_ref, &artifact); - if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { - return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); - } - if (err != AMDUAT_ASL_STORE_OK) { - amduat_asl_artifact_free(&artifact); - return amduatd_http_send_text(fd, 500, "Internal Server Error", - "store error\n", false); - } - if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) { - amduat_asl_artifact_free(&artifact); - return amduatd_http_send_text(fd, 500, "Internal Server Error", - "store error\n", false); - } - { - bool ok = amduatd_http_send_status(fd, - 200, - "OK", - "text/html; charset=utf-8", - artifact.bytes.data, - artifact.bytes.len, - false); - amduat_asl_artifact_free(&artifact); - return ok; - } -} - static bool amduatd_handle_post_concepts(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, @@ -9911,9 +9525,20 @@ static bool amduatd_handle_conn(int fd, goto conn_cleanup; } - if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/ui") == 0) { - ok = amduatd_handle_get_ui(fd, store, ui_ref); - goto conn_cleanup; + { + amduatd_ctx_t ui_ctx; + amduatd_http_resp_t ui_resp; + + ui_ctx.store = store; + ui_ctx.ui_ref = ui_ref; + ui_resp.fd = fd; + ui_resp.ok = false; + if (amduatd_ui_can_handle(&req)) { + if (amduatd_ui_handle(&ui_ctx, &req, &ui_resp)) { + ok = ui_resp.ok; + goto conn_cleanup; + } + } } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) { @@ -10190,18 +9815,22 @@ int main(int argc, char **argv) { fprintf(stderr, "error: failed to seed api contract\n"); return 8; } +#if AMDUATD_ENABLE_UI if (!amduatd_seed_ui_html(&store, &cfg, &ui_ref)) { fprintf(stderr, "error: failed to seed ui html\n"); return 8; } +#endif if (!amduatd_concepts_init(&concepts, &store, &cfg, &dcfg, root)) { fprintf(stderr, "error: failed to init concept edges\n"); return 8; } +#if AMDUATD_ENABLE_UI if (!amduatd_seed_ms_ui_state(&store, &cfg, &concepts, &dcfg)) { fprintf(stderr, "error: failed to seed ms ui state\n"); return 8; } +#endif amduat_fed_transport_stub_init(&fed_stub); memset(&fed_cfg, 0, sizeof(fed_cfg)); diff --git a/src/amduatd_ui.c b/src/amduatd_ui.c new file mode 100644 index 0000000..c4a17a5 --- /dev/null +++ b/src/amduatd_ui.c @@ -0,0 +1,461 @@ +#include "amduatd_ui.h" + +#include "amduat/asl/artifact_io.h" +#include "amduat/asl/asl_store_fs.h" +#include "amduat/asl/asl_store_fs_meta.h" +#include "amduat/asl/ref_derive.h" + +#include +#include +#include +#include + +bool amduatd_http_send_text(int fd, + int code, + const char *reason, + const char *text, + bool head_only); + +bool amduatd_http_send_status(int fd, + int code, + const char *reason, + const char *content_type, + const uint8_t *body, + size_t body_len, + bool head_only); + +static const char k_amduatd_ui_html[] = + "\n" + "\n" + "\n" + " \n" + " \n" + " amduatd — Concept editor\n" + " \n" + "\n" + "\n" + "
\n" + "
\n" + "
\n" + "
\n" + " amduatd\n" + "
\n" + " \n" + "
\n" + "
\n" + "
\n" + "

Concept editor + PEL runner

\n" + "

Load shows the latest materialized bytes; Save uploads a new artifact and publishes a new version. Use the runner to execute PEL programs against stored artifacts.

\n" + "
\n" + " Open editor\n" + " Run program\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + "\n" + "
\n" + "
\n" + "
Upload bytes (sets program_ref)
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + "
Run
\n" + " \n" + "
input_refs (comma-separated hex refs or names)
\n" + " \n" + "
params_ref (optional)
\n" + " \n" + "
scheme_ref (optional, default dag)
\n" + " \n" + "
\n" + " \n" + " /v1/contract\n" + " /v1/meta\n" + "
\n" + "
Response
\n" + "
\n"
+    "          
\n" + "
\n" + "
Relations
\n" + "
\n" + " \n" + "
\n" + "
\n"
+    "          
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "

About

\n" + "

amduatd is a local-first mapping surface over a single ASL store root. This UI is a lightweight editor and runner for concepts and PEL programs.

\n" + "
\n" + " \n" + "
\n" + "
\n" + "
\n" + " © 2025 Niklas Rydberg.\n" + " Back to top\n" + "
\n" + "
\n" + "\n" + " \n" + "\n" + "\n"; + +static void amduatd_ui_path_without_query(const char *path, + char *out, + size_t cap) { + const char *q = NULL; + size_t n = 0; + if (out == NULL || cap == 0) { + return; + } + out[0] = '\0'; + if (path == NULL) { + return; + } + q = strchr(path, '?'); + n = q != NULL ? (size_t)(q - path) : strlen(path); + if (n >= cap) { + n = cap - 1; + } + memcpy(out, path, n); + out[n] = '\0'; +} + +bool amduatd_ui_can_handle(const amduatd_http_req_t *req) { + char no_query[1024]; + + if (req == NULL) { + return false; + } + if (strcmp(req->method, "GET") != 0) { + return false; + } + amduatd_ui_path_without_query(req->path, no_query, sizeof(no_query)); + return strcmp(no_query, "/v1/ui") == 0; +} + +bool amduatd_ui_handle(amduatd_ctx_t *ctx, + const amduatd_http_req_t *req, + amduatd_http_resp_t *resp) { + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + + if (ctx == NULL || req == NULL || resp == NULL) { + return false; + } + if (!amduatd_ui_can_handle(req)) { + return false; + } + + if (ctx->store == NULL || ctx->ui_ref.hash_id == 0 || + ctx->ui_ref.digest.data == NULL || ctx->ui_ref.digest.len == 0) { + resp->ok = amduatd_http_send_text(resp->fd, + 500, + "Internal Server Error", + "ui not available\n", + false); + return true; + } + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(ctx->store, ctx->ui_ref, &artifact); + if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { + resp->ok = amduatd_http_send_text(resp->fd, + 404, + "Not Found", + "not found\n", + false); + return true; + } + if (err != AMDUAT_ASL_STORE_OK) { + amduat_asl_artifact_free(&artifact); + resp->ok = amduatd_http_send_text(resp->fd, + 500, + "Internal Server Error", + "store error\n", + false); + return true; + } + if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) { + amduat_asl_artifact_free(&artifact); + resp->ok = amduatd_http_send_text(resp->fd, + 500, + "Internal Server Error", + "store error\n", + false); + return true; + } + + resp->ok = amduatd_http_send_status(resp->fd, + 200, + "OK", + "text/html; charset=utf-8", + artifact.bytes.data, + artifact.bytes.len, + false); + amduat_asl_artifact_free(&artifact); + return true; +} + +bool amduatd_seed_ui_html(amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + amduat_reference_t *out_ref) { + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + + if (out_ref != NULL) { + memset(out_ref, 0, sizeof(*out_ref)); + } + if (store == NULL || cfg == NULL || out_ref == NULL) { + return false; + } + + artifact = amduat_artifact(amduat_octets(k_amduatd_ui_html, + strlen(k_amduatd_ui_html))); + (void)amduat_asl_ref_derive(artifact, + cfg->config.encoding_profile_id, + cfg->config.hash_id, + out_ref, + NULL); + err = amduat_asl_store_put(store, artifact, out_ref); + if (err != AMDUAT_ASL_STORE_OK) { + return false; + } + return true; +} diff --git a/src/amduatd_ui.h b/src/amduatd_ui.h new file mode 100644 index 0000000..f3bbedd --- /dev/null +++ b/src/amduatd_ui.h @@ -0,0 +1,60 @@ +#ifndef AMDUATD_UI_H +#define AMDUATD_UI_H + +#include "amduat/asl/store.h" + +#include +#include +#include + +#ifndef AMDUATD_ENABLE_UI +#define AMDUATD_ENABLE_UI 1 +#endif + +typedef struct { + char method[8]; + char path[1024]; + char content_type[128]; + char accept[128]; + char x_type_tag[64]; + char x_capability[2048]; + size_t content_length; + bool has_actor; + amduat_octets_t actor; + bool has_uid; + uid_t uid; +} amduatd_http_req_t; + +typedef struct { + int fd; + bool ok; +} amduatd_http_resp_t; + +typedef struct { + amduat_asl_store_t *store; + amduat_reference_t ui_ref; +} amduatd_ctx_t; + +#if AMDUATD_ENABLE_UI +bool amduatd_ui_can_handle(const amduatd_http_req_t *req); + +bool amduatd_ui_handle(amduatd_ctx_t *ctx, + const amduatd_http_req_t *req, + amduatd_http_resp_t *resp); +#else +static inline bool amduatd_ui_can_handle(const amduatd_http_req_t *req) { + (void)req; + return false; +} + +static inline bool amduatd_ui_handle(amduatd_ctx_t *ctx, + const amduatd_http_req_t *req, + amduatd_http_resp_t *resp) { + (void)ctx; + (void)req; + (void)resp; + return false; +} +#endif + +#endif