32inline std::string safe_getenv(
const char* name) {
36 if (_dupenv_s(&buf, &sz, name) == 0 && buf !=
nullptr) {
37 std::string result(buf);
43 const char* val = std::getenv(name);
44 return val ? val :
"";
56 std::string instance_id;
57 std::string install_root;
58 std::string record_path;
59 std::string metadata_json;
91 static std::unique_ptr<NahHost> create(
const std::string& root_path =
"");
107 static std::unique_ptr<NahHost> discover(
const std::vector<std::string>& search_paths);
116 static bool isValidRoot(
const std::string& path);
121 const std::string& root()
const {
return root_; }
126 std::vector<AppInfo> listApplications()
const;
134 std::optional<AppInfo> findApplication(
const std::string&
id,
135 const std::string& version =
"")
const;
140 nah::core::HostEnvironment getHostEnvironment()
const;
149 nah::core::CompositionResult getLaunchContract(
150 const std::string& app_id,
151 const std::string& version =
"",
152 bool enable_trace =
false)
const;
161 nah::core::CompositionResult getLaunchContract(
162 const std::string& app_id,
163 const std::string& version,
164 const nah::core::CompositionOptions& options)
const;
174 int executeApplication(
175 const std::string& app_id,
176 const std::string& version =
"",
177 const std::vector<std::string>& args = {},
178 std::function<void(
const std::string&)> output_handler =
nullptr)
const;
188 const nah::core::LaunchContract& contract,
189 const std::vector<std::string>& args = {},
190 std::function<void(
const std::string&)> output_handler =
nullptr)
const;
195 bool isApplicationInstalled(
const std::string& app_id,
196 const std::string& version =
"")
const;
201 nah::core::RuntimeInventory getInventory()
const;
207 std::string validateRoot()
const;
219 nah::core::CompositionResult composeComponentLaunch(
220 const std::string& uri,
221 const std::string& referrer_uri =
"")
const;
232 const std::string& uri,
233 const std::string& referrer_uri =
"",
234 const std::vector<std::string>& args = {},
235 std::function<void(
const std::string&)> output_handler =
nullptr)
const;
242 bool canHandleComponentUri(
const std::string& uri)
const;
248 std::vector<std::pair<std::string, nah::core::ComponentDecl>> listAllComponents()
const;
251 explicit NahHost(std::string root) : root_(std::move(root)) {}
254 std::optional<nah::core::InstallRecord> loadInstallRecord(
const std::string& path)
const;
257 std::optional<nah::core::AppDeclaration> loadAppManifest(
const std::string& app_dir)
const;
259 std::string extractMetadataJson(
const std::string& app_dir)
const;
274inline int quickExecute(
const std::string& app_id,
const std::string& nah_root =
"") {
275 auto host = NahHost::create(nah_root);
276 return host->executeApplication(app_id);
284inline std::vector<std::string> listInstalledApps(
const std::string& nah_root =
"") {
285 auto host = NahHost::create(nah_root);
286 auto apps = host->listApplications();
287 std::vector<std::string> results;
288 for (
const auto& app : apps) {
289 results.push_back(app.id +
"@" + app.version);
298#ifdef NAH_HOST_IMPLEMENTATION
300inline std::unique_ptr<NahHost> NahHost::create(
const std::string& root_path) {
301 std::string resolved_root = root_path;
303 if (resolved_root.empty()) {
304 std::string env_root = detail::safe_getenv(
"NAH_ROOT");
305 if (!env_root.empty()) {
306 resolved_root = env_root;
308 resolved_root =
"/nah";
312 return std::unique_ptr<NahHost>(
new NahHost(resolved_root));
315inline std::vector<AppInfo> NahHost::listApplications()
const {
316 std::vector<AppInfo> apps;
317 std::string apps_dir = root_ +
"/registry/apps";
319 if (!nah::fs::exists(apps_dir)) {
323 auto files = nah::fs::list_directory(apps_dir);
324 for (
const auto& entry : files) {
325 if (entry.size() > 5 && entry.substr(entry.size() - 5) ==
".json") {
326 auto record = loadInstallRecord(entry);
329 info.id = record->app.id;
330 info.version = record->app.version;
331 info.instance_id = record->install.instance_id;
332 info.install_root = record->paths.install_root;
333 info.record_path = entry;
334 info.metadata_json = extractMetadataJson(record->paths.install_root);
335 apps.push_back(info);
343inline std::optional<AppInfo> NahHost::findApplication(
const std::string&
id,
344 const std::string& version)
const {
345 auto apps = listApplications();
347 std::vector<AppInfo> matches;
348 for (
const auto& app : apps) {
350 if (version.empty() || app.version == version) {
351 matches.push_back(app);
356 if (matches.empty()) {
361 if (matches.size() > 1 && version.empty()) {
362 std::sort(matches.begin(), matches.end(), [](
const AppInfo& a,
const AppInfo& b) {
363 auto va = nah::semver::parse_version(a.version);
364 auto vb = nah::semver::parse_version(b.version);
369 return a.version > b.version;
375inline nah::core::HostEnvironment NahHost::getHostEnvironment()
const {
376 std::string host_json_path = root_ +
"/host/host.json";
377 auto content = nah::fs::read_file(host_json_path);
380 return nah::core::HostEnvironment{};
383 auto result = nah::json::parse_host_environment(*content, host_json_path);
389 return nah::core::HostEnvironment{};
392inline nah::core::CompositionResult NahHost::getLaunchContract(
393 const std::string& app_id,
394 const std::string& version,
395 bool enable_trace)
const {
398 auto app_info = findApplication(app_id, version);
400 nah::core::CompositionResult result;
402 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
403 result.critical_error_context =
"Application not found: " + app_id;
408 auto record = loadInstallRecord(app_info->record_path);
410 nah::core::CompositionResult result;
412 result.critical_error = nah::core::CriticalError::INSTALL_RECORD_INVALID;
413 result.critical_error_context =
"Failed to load install record";
418 auto app_decl = loadAppManifest(app_info->install_root);
420 nah::core::CompositionResult result;
422 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
423 result.critical_error_context =
"Failed to load app manifest";
428 auto host_env = getHostEnvironment();
431 auto inventory = getInventory();
434 nah::core::CompositionOptions opts;
435 opts.enable_trace = enable_trace;
437 return nah::core::nah_compose(*app_decl, host_env, *record, inventory, opts);
440inline nah::core::CompositionResult NahHost::getLaunchContract(
441 const std::string& app_id,
442 const std::string& version,
443 const nah::core::CompositionOptions& options)
const {
446 auto app_info = findApplication(app_id, version);
448 nah::core::CompositionResult result;
450 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
451 result.critical_error_context =
"Application not found: " + app_id;
456 auto record = loadInstallRecord(app_info->record_path);
458 nah::core::CompositionResult result;
460 result.critical_error = nah::core::CriticalError::INSTALL_RECORD_INVALID;
461 result.critical_error_context =
"Failed to load install record";
466 auto app_decl = loadAppManifest(app_info->install_root);
468 nah::core::CompositionResult result;
470 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
471 result.critical_error_context =
"Failed to load app manifest";
476 auto host_env = getHostEnvironment();
479 auto inventory = getInventory();
482 return nah::core::nah_compose(*app_decl, host_env, *record, inventory, options);
485inline int NahHost::executeApplication(
486 const std::string& app_id,
487 const std::string& version,
488 const std::vector<std::string>& args,
489 std::function<
void(
const std::string&)> output_handler)
const {
491 auto result = getLaunchContract(app_id, version);
493 if (output_handler) {
494 output_handler(
"Error: " + result.critical_error_context);
499 return executeContract(result.contract, args, output_handler);
502inline int NahHost::executeContract(
503 const nah::core::LaunchContract& contract,
504 const std::vector<std::string>& ,
505 std::function<
void(
const std::string&)> output_handler)
const {
509 auto exec_result = nah::exec::execute(contract);
511 if (!exec_result.ok) {
512 if (output_handler) {
513 output_handler(
"Execution error: " + exec_result.error);
518 return exec_result.exit_code;
521inline bool NahHost::isApplicationInstalled(
const std::string& app_id,
522 const std::string& version)
const {
523 return findApplication(app_id, version).has_value();
526inline nah::core::RuntimeInventory NahHost::getInventory()
const {
527 nah::core::RuntimeInventory inventory;
528 std::string naks_dir = root_ +
"/registry/naks";
530 if (!nah::fs::exists(naks_dir)) {
534 auto files = nah::fs::list_directory(naks_dir);
535 for (
const auto& entry : files) {
537 if (entry.size() > 5 && entry.substr(entry.size() - 5) ==
".json") {
540 auto runtime_content = nah::fs::read_file(entry);
541 if (runtime_content) {
543 std::string basename = entry;
544 size_t last_slash = entry.rfind(
'/');
545 if (last_slash != std::string::npos) {
546 basename = entry.substr(last_slash + 1);
548 std::string record_ref = basename;
550 auto result = nah::json::parse_runtime_descriptor(*runtime_content, entry);
552 result.value.source_path = entry;
555 if (!result.value.paths.root.empty() && !nah::fs::is_absolute_path(result.value.paths.root)) {
556 result.value.paths.root = nah::fs::absolute_path(nah::fs::join_paths(root_, result.value.paths.root));
560 for (
auto& lib_dir : result.value.paths.lib_dirs) {
561 if (!lib_dir.empty() && !nah::fs::is_absolute_path(lib_dir)) {
562 lib_dir = nah::fs::absolute_path(nah::fs::join_paths(result.value.paths.root, lib_dir));
567 for (
auto& [name, loader] : result.value.loaders) {
568 if (!loader.exec_path.empty() && !nah::fs::is_absolute_path(loader.exec_path)) {
569 loader.exec_path = nah::fs::absolute_path(nah::fs::join_paths(result.value.paths.root, loader.exec_path));
573 inventory.runtimes[record_ref] = result.value;
582inline std::string NahHost::validateRoot()
const {
583 if (!nah::fs::exists(root_)) {
584 return "NAH root does not exist: " + root_;
588 std::vector<std::string> required_dirs = {
593 for (
const auto& dir : required_dirs) {
594 if (!nah::fs::exists(root_ + dir)) {
595 return "Missing required directory: " + root_ + dir;
602inline bool NahHost::isValidRoot(
const std::string& path) {
603 if (path.empty() || !nah::fs::exists(path)) {
608 std::vector<std::string> required_dirs = {
613 for (
const auto& dir : required_dirs) {
614 std::string full_path = path + dir;
615 if (!nah::fs::exists(full_path)) {
623inline std::unique_ptr<NahHost> NahHost::discover(
const std::vector<std::string>& search_paths) {
624 for (
const auto& path : search_paths) {
631 if (isValidRoot(path)) {
632 return std::unique_ptr<NahHost>(
new NahHost(path));
640inline std::optional<nah::core::InstallRecord> NahHost::loadInstallRecord(
const std::string& path)
const {
641 auto content = nah::fs::read_file(path);
646 auto result = nah::json::parse_install_record(*content);
649 if (!result.value.paths.install_root.empty() && !nah::fs::is_absolute_path(result.value.paths.install_root)) {
650 result.value.paths.install_root = nah::fs::absolute_path(nah::fs::join_paths(root_, result.value.paths.install_root));
658inline std::optional<nah::core::AppDeclaration> NahHost::loadAppManifest(
const std::string& app_dir)
const {
659 auto json_content = nah::fs::read_file(app_dir +
"/nap.json");
661 auto result = nah::json::parse_app_declaration(*json_content);
670inline std::string NahHost::extractMetadataJson(
const std::string& app_dir)
const {
671 auto json_content = nah::fs::read_file(app_dir +
"/nap.json");
677 auto j = nah::json::json::parse(*json_content);
679 if (j.contains(
"app") && j[
"app"].is_object()) {
683 if (j.contains(
"metadata") && j[
"metadata"].is_object()) {
684 return j[
"metadata"].dump();
697inline bool matches_uri_pattern(
const std::string& pattern,
const std::string& uri) {
698 auto pattern_parsed = nah::core::parse_component_uri(pattern);
699 auto uri_parsed = nah::core::parse_component_uri(uri);
701 if (!pattern_parsed.valid || !uri_parsed.valid) {
706 if (pattern_parsed.app_id != uri_parsed.app_id) {
711 if (pattern_parsed.component_path.size() >= 2 &&
712 pattern_parsed.component_path.substr(pattern_parsed.component_path.size() - 2) ==
"/*") {
714 std::string prefix = pattern_parsed.component_path.substr(
715 0, pattern_parsed.component_path.size() - 2
717 return uri_parsed.component_path.substr(0, prefix.size()) == prefix;
720 return pattern_parsed.component_path == uri_parsed.component_path;
724inline nah::core::CompositionResult NahHost::composeComponentLaunch(
725 const std::string& uri,
726 const std::string& referrer_uri)
const {
729 auto parsed = nah::core::parse_component_uri(uri);
731 nah::core::CompositionResult result;
733 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
734 result.critical_error_context =
"Invalid component URI: " + uri;
739 auto app_info = findApplication(parsed.app_id);
741 nah::core::CompositionResult result;
743 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
744 result.critical_error_context =
"Application not found: " + parsed.app_id;
749 auto app_decl = loadAppManifest(app_info->install_root);
751 nah::core::CompositionResult result;
753 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
754 result.critical_error_context =
"Failed to load app manifest";
759 nah::core::ComponentDecl* matched_component =
nullptr;
760 for (
auto& comp : app_decl->components) {
761 if (matches_uri_pattern(comp.uri_pattern, uri)) {
762 matched_component = ∁
767 if (!matched_component) {
768 nah::core::CompositionResult result;
770 result.critical_error = nah::core::CriticalError::MANIFEST_MISSING;
771 result.critical_error_context =
"No component matches URI: " + uri;
778 nah::core::AppDeclaration component_app = *app_decl;
779 component_app.entrypoint_path = matched_component->entrypoint;
782 if (!matched_component->loader.empty()) {
783 component_app.nak_loader = matched_component->loader;
787 for (
const auto& [key, value] : matched_component->environment) {
789 component_app.env_vars.push_back(key +
"=" + value.value);
793 component_app.permissions_filesystem.insert(
794 component_app.permissions_filesystem.end(),
795 matched_component->permissions_filesystem.begin(),
796 matched_component->permissions_filesystem.end()
798 component_app.permissions_network.insert(
799 component_app.permissions_network.end(),
800 matched_component->permissions_network.begin(),
801 matched_component->permissions_network.end()
805 auto install_record = loadInstallRecord(app_info->record_path);
806 if (!install_record) {
807 nah::core::CompositionResult result;
809 result.critical_error = nah::core::CriticalError::INSTALL_RECORD_INVALID;
810 result.critical_error_context =
"Failed to load install record";
815 if (!matched_component->loader.empty()) {
816 install_record->nak.loader = matched_component->loader;
820 auto host_env = getHostEnvironment();
821 auto inventory = getInventory();
824 nah::core::CompositionOptions comp_opts;
825 auto result = nah::core::nah_compose(component_app, host_env, *install_record, inventory, comp_opts);
832 result.contract.environment[
"NAH_COMPONENT_ID"] = matched_component->id;
833 result.contract.environment[
"NAH_COMPONENT_URI"] = uri;
834 result.contract.environment[
"NAH_COMPONENT_PATH"] = parsed.component_path;
836 if (!parsed.query.empty()) {
837 result.contract.environment[
"NAH_COMPONENT_QUERY"] = parsed.query;
839 if (!parsed.fragment.empty()) {
840 result.contract.environment[
"NAH_COMPONENT_FRAGMENT"] = parsed.fragment;
842 if (!referrer_uri.empty()) {
843 result.contract.environment[
"NAH_COMPONENT_REFERRER"] = referrer_uri;
849inline int NahHost::launchComponent(
850 const std::string& uri,
851 const std::string& referrer_uri,
852 const std::vector<std::string>& args,
853 std::function<
void(
const std::string&)> output_handler)
const {
855 auto result = composeComponentLaunch(uri, referrer_uri);
857 if (output_handler) {
858 output_handler(
"Error: " + result.critical_error_context);
863 return executeContract(result.contract, args, output_handler);
866inline bool NahHost::canHandleComponentUri(
const std::string& uri)
const {
867 auto parsed = nah::core::parse_component_uri(uri);
872 auto app_info = findApplication(parsed.app_id);
877 auto app_decl = loadAppManifest(app_info->install_root);
878 if (!app_decl || app_decl->components.empty()) {
883 for (
const auto& comp : app_decl->components) {
884 if (matches_uri_pattern(comp.uri_pattern, uri)) {
892inline std::vector<std::pair<std::string, nah::core::ComponentDecl>>
893NahHost::listAllComponents()
const {
894 std::vector<std::pair<std::string, nah::core::ComponentDecl>> result;
896 auto apps = listApplications();
897 for (
const auto& app : apps) {
898 auto manifest = loadAppManifest(app.install_root);
900 for (
const auto& comp : manifest->components) {
901 result.push_back({app.id, comp});