#include "kappa/service/service.hpp" #include #include #include namespace kappa::service { // --------------------------------------------------------------------------- // Backend service-file generators (defined in separate .cpp files) // systemd_service and s6_service are declared in service.hpp // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // ServiceSpec::from_service_init // --------------------------------------------------------------------------- ServiceSpec ServiceSpec::from_service_init(const dsl::NamedService& ns) { ServiceSpec spec; spec.name = ns.name; spec.description = ns.description; spec.exec = ns.exec; spec.user = ns.user; spec.ports = ns.ports; spec.env = ns.env; spec.after = ns.after; spec.working_dir = ns.working_dir; // Normalize type if (ns.type == "forking" || ns.type == "longrun" || ns.type == "notify" || ns.type == "oneshot") { spec.type = ns.type; } else { spec.type = "simple"; } // Restart policy spec.restart_policy = ns.restart; if (spec.restart_policy.empty() && ns.type == "longrun") { spec.restart_policy = "always"; } return spec; } // --------------------------------------------------------------------------- // generate_service_file — dispatch to the correct backend // --------------------------------------------------------------------------- std::string generate_service_file(InitSystem is, const ServiceSpec& spec) { switch (is) { case InitSystem::Systemd: return generate_systemd_service(spec); case InitSystem::OpenRC: return generate_openrc_service(spec); case InitSystem::S6: return generate_s6_service(spec); case InitSystem::Dinit: return generate_dinit_service(spec); case InitSystem::Runit: return generate_runit_service(spec); case InitSystem::Unknown: default: return {}; } } // --------------------------------------------------------------------------- // install_service — write the generated file(s) to disk // --------------------------------------------------------------------------- ServiceInstallResult install_service(InitSystem is, const ServiceSpec& spec, std::string_view prefix) { namespace fs = std::filesystem; auto paths = init_paths(is, prefix); if (paths.service_dir.empty()) { return {false, {}, "Unknown init system"}; } std::error_code ec; // ---- S6: directory-based layout (type + run) ---- // S6: directory-based layout. Content generated inline rather than // calling generate_s6_service() to avoid parsing the combined output. if (is == InitSystem::S6) { fs::path svc_dir = fs::path(paths.service_dir) / spec.name; fs::create_directories(svc_dir, ec); if (ec) { return {false, {}, ec.message()}; } // --- type file --- { fs::path type_path = svc_dir / "type"; std::ofstream out(type_path); if (!out) { return {false, {}, std::format("Failed to write {}", type_path.string())}; } out << ((spec.type == "oneshot") ? "oneshot" : "longrun"); } // --- run file --- fs::path run_path = svc_dir / "run"; { std::ofstream out(run_path); if (!out) { return {false, {}, std::format("Failed to write {}", run_path.string())}; } out << s6_execline_shebang; out << "# Generated by kappa — do not edit manually\n"; out << std::format("# s6 service: {}\n", spec.name); if (!spec.working_dir.empty()) { out << std::format("cd {}\n", spec.working_dir); } for (const auto& [key, value] : spec.env) { out << std::format("export {} \"{}\"\n", key, value); } if (!spec.user.empty()) { out << std::format("s6-setuidgid {}\n", spec.user); } out << spec.exec << "\n"; } // Make run file executable fs::permissions(run_path, fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec, fs::perm_options::add, ec); return {true, svc_dir.string(), {}}; } // ---- Runit: directory-based layout (run) ---- if (is == InitSystem::Runit) { fs::path svc_dir = fs::path(paths.service_dir) / spec.name; fs::create_directories(svc_dir, ec); if (ec) { return {false, {}, ec.message()}; } fs::path run_path = svc_dir / "run"; { std::ofstream out(run_path); if (!out) { return {false, {}, std::format("Failed to write {}", run_path.string())}; } out << generate_runit_service(spec); } // Make run executable fs::permissions(run_path, fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec, fs::perm_options::add, ec); return {true, svc_dir.string(), {}}; } // ---- Systemd / OpenRC / Dinit: single service file ---- std::string content = generate_service_file(is, spec); if (content.empty()) { return {false, {}, "Failed to generate service file for init system"}; } // Determine filename std::string filename; switch (is) { case InitSystem::Systemd: filename = std::format("{}.service", spec.name); break; case InitSystem::OpenRC: case InitSystem::Dinit: filename = spec.name; break; default: return {false, {}, "Unknown init system"}; } fs::path file_path = fs::path(paths.service_dir) / filename; fs::create_directories(file_path.parent_path(), ec); if (ec) { return {false, {}, ec.message()}; } { std::ofstream out(file_path); if (!out) { return {false, {}, std::format("Failed to write {}", file_path.string())}; } out << content; } // Dinit: also write companion .env file if (is == InitSystem::Dinit && !spec.env.empty()) { fs::path env_path = fs::path(paths.service_dir) / std::format("{}.env", spec.name); std::ofstream env_out(env_path); if (env_out) { for (const auto& [key, value] : spec.env) { env_out << key << "=" << value << "\n"; } } } return {true, file_path.string(), {}}; } } // namespace kappa::service