112 applicationName = std::filesystem::path(argv[0]).filename();
115 struct passwd* pw =
nullptr;
116 struct group* gr =
nullptr;
118 if ((pw = getpwuid(getuid())) ==
nullptr) {
120 }
else if ((gr = getgrgid(pw->pw_gid)) ==
nullptr) {
122 }
else if ((euid = geteuid()) == 0) {
123 configDirectory =
"/etc/snode.c";
124 logDirectory =
"/var/log/snode.c";
125 pidDirectory =
"/var/run/snode.c";
127 const char* homedir =
nullptr;
128 if ((homedir = std::getenv(
"XDG_CONFIG_HOME")) ==
nullptr) {
129 if ((homedir = std::getenv(
"HOME")) ==
nullptr) {
130 homedir = pw->pw_dir;
134 if (homedir !=
nullptr) {
135 configDirectory = std::string(homedir) +
"/.config/snode.c";
136 logDirectory = std::string(homedir) +
"/.local/log/snode.c";
137 pidDirectory = std::string(homedir) +
"/.local/run/snode.c";
143 if (proceed && !std::filesystem::exists(configDirectory)) {
144 if (std::filesystem::create_directories(configDirectory)) {
145 std::filesystem::permissions(
147 (std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec) &
148 ~std::filesystem::perms::others_all);
149 if (geteuid() == 0) {
150 struct group* gr =
nullptr;
151 if ((gr = getgrnam(
XSTR(GROUP_NAME))) !=
nullptr) {
152 if (chown(configDirectory.c_str(), euid, gr->gr_gid) < 0) {
153 std::cout <<
"Warning: Can not set group ownership of '" << configDirectory
154 <<
"' to 'snodec':" << strerror(errno) << std::endl;
157 std::cout <<
"Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
158 std::cout <<
" and add the current user to this group." << std::endl;
159 std::filesystem::remove(configDirectory);
164 std::cout <<
"Error: Can not create directory '" << configDirectory <<
"'" << std::endl;
169 if (proceed && !std::filesystem::exists(logDirectory)) {
170 if (std::filesystem::create_directories(logDirectory)) {
171 std::filesystem::permissions(logDirectory,
172 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
173 ~std::filesystem::perms::others_all);
174 if (geteuid() == 0) {
175 struct group* gr =
nullptr;
176 if ((gr = getgrnam(
XSTR(GROUP_NAME))) !=
nullptr) {
177 if (chown(logDirectory.c_str(), euid, gr->gr_gid) < 0) {
178 std::cout <<
"Warning: Can not set group ownership of '" << logDirectory <<
"' to 'snodec':" << strerror(errno)
182 std::cout <<
"Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
183 std::cout <<
" and add the current user to this group." << std::endl;
184 std::filesystem::remove(configDirectory);
189 std::cout <<
"Error: Can not create directory '" << logDirectory <<
"'" << std::endl;
194 if (proceed && !std::filesystem::exists(pidDirectory)) {
195 if (std::filesystem::create_directories(pidDirectory)) {
196 std::filesystem::permissions(pidDirectory,
197 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
198 ~std::filesystem::perms::others_all);
199 if (geteuid() == 0) {
200 struct group* gr =
nullptr;
201 if ((gr = getgrnam(
XSTR(GROUP_NAME))) !=
nullptr) {
202 if (chown(pidDirectory.c_str(), euid, gr->gr_gid) < 0) {
203 std::cout <<
"Warning: Can not set group ownership of '" << pidDirectory <<
"' to 'snodec':" << strerror(errno)
207 std::cout <<
"Error: Can not find group 'snodec'. Add it using groupadd or addgroup." << std::endl;
208 std::cout <<
" and add the current user to this group." << std::endl;
209 std::filesystem::remove(configDirectory);
214 std::cout <<
"Error: Can not create directory '" << pidDirectory <<
"'" << std::endl;
220 app->description(
"Configuration for Application '" + applicationName +
"'");
222 app->footer(
"Application '" + applicationName +
223 "' powered by SNode.C\n"
224 "(C) 2020-2025 Volker Christian <me@vchrist.at>\n"
225 "https://github.com/SNodeC/snode.c");
229 configDirectory +
"/" + applicationName +
".conf",
230 "Read a config file",
233 ->type_name(
"configfile")
234 ->check(!CLI::ExistingDirectory);
236 app->add_option_function<std::string>(
238 []([[maybe_unused]]
const std::string& configFile) {
239 throw CLI::CallForWriteConfig(configFile);
241 "Write config file and exit")
242 ->configurable(
false)
243 ->default_val(configDirectory +
"/" + applicationName +
".conf")
244 ->type_name(
"[configfile]")
245 ->check(!CLI::ExistingDirectory)
250 "Kill running daemon")
251 ->configurable(
false)
252 ->disable_flag_override();
255 "-i,--instance-alias",
256 "Make an instance also known as an alias in configuration files")
257 ->configurable(
false)
258 ->type_name(
"instance=instance_alias [instance=instance_alias [...]]")
259 ->each([](
const std::string& item) {
260 const auto it = item.find(
'=');
261 if (it != std::string::npos) {
262 aliases[item.substr(0, it)] = item.substr(it + 1);
264 throw CLI::ConversionError(
"Can not convert '" + item +
"' to a 'instance=instance_alias' pair");
268 addStandardFlags(app.get());
270 logLevelOpt = app->add_option(
275 ->check(CLI::Range(0, 6))
276 ->group(app->get_formatter()->get_label(
"Persistent Options"));
278 verboseLevelOpt = app->add_option(
279 "-v,--verbose-level",
283 ->check(CLI::Range(0, 10))
284 ->group(app->get_formatter()->get_label(
"Persistent Options"));
286 quietOpt = app->add_flag(
287 "-q{true},!-u,--quiet{true}",
289 ->default_val(
"false")
291 ->check(CLI::IsMember({
"true",
"false"}))
292 ->group(app->get_formatter()->get_label(
"Persistent Options"));
294 logFileOpt = app->add_option(
297 ->default_val(logDirectory +
"/" + applicationName +
".log")
298 ->type_name(
"logfile")
299 ->check(!CLI::ExistingDirectory)
300 ->group(app->get_formatter()->get_label(
"Persistent Options"));
302 enforceLogFileOpt = app->add_flag(
303 "-e{true},!-n,--enforce-log-file{true}",
304 "Enforce writing of logs to file for foreground applications")
305 ->default_val(
"false")
307 ->check(CLI::IsMember({
"true",
"false"}))
308 ->group(app->get_formatter()->get_label(
"Persistent Options"));
310 daemonizeOpt = app->add_flag(
311 "-d{true},!-f,--daemonize{true}",
312 "Start application as daemon")
313 ->default_val(
"false")
315 ->check(CLI::IsMember({
"true",
"false"}))
316 ->group(app->get_formatter()->get_label(
"Persistent Options"));
318 userNameOpt = app->add_option(
320 "Run daemon under specific user permissions")
321 ->default_val(pw->pw_name)
322 ->type_name(
"username")
323 ->needs(daemonizeOpt)
324 ->group(app->get_formatter()->get_label(
"Persistent Options"));
326 groupNameOpt = app->add_option(
328 "Run daemon under specific group permissions")
329 ->default_val(gr->gr_name)
330 ->type_name(
"groupname")
331 ->needs(daemonizeOpt)
332 ->group(app->get_formatter()->get_label(
"Persistent Options"));
336 app->set_version_flag(
"--version",
"1.0-rc1",
"Framework version");
410 CLI::Option* disabledOpt = app->get_option_no_throw(
"--disabled");
411 const bool disabled = disabledOpt !=
nullptr ? disabledOpt->as<
bool>() :
false;
413 for (
const CLI::Option* option : app->get_options()) {
414 if (option->get_configurable()) {
418 case CLI::CallForCommandline::Mode::STANDARD:
419 if (option->count() > 0) {
420 value = option->as<std::string>();
421 }
else if (option->get_required()) {
422 value =
"<REQUIRED>";
425 case CLI::CallForCommandline::Mode::REQUIRED:
426 if (option->get_required()) {
427 if (option->count() > 0) {
428 value = option->as<std::string>();
430 value =
"<REQUIRED>";
434 case CLI::CallForCommandline::Mode::FULL:
435 if (option->count() > 0) {
436 value = option->as<std::string>();
437 }
else if (!option->get_default_str().empty()) {
438 value = option->get_default_str();
439 }
else if (!option->get_required()) {
442 value =
"<REQUIRED>";
445 case CLI::CallForCommandline::Mode::DEFAULT: {
446 if (!option->get_default_str().empty()) {
447 value = option->get_default_str();
448 }
else if (!option->get_required()) {
451 value =
"<REQUIRED>";
457 if (!value.empty()) {
458 out <<
"--" << option->get_single_name() << ((option->get_items_expected_max() == 0) ?
"=" :
" ") << value <<
" ";
462 }
else if (disabledOpt->get_default_str() ==
"false") {
463 out <<
"--disabled=true ";
533 bool success =
false;
537 app->parse(argc, argv);
539 }
catch (
const DaemonError& e) {
540 std::cout <<
"Daemon error: " << e.what() <<
" ... exiting" << std::endl;
541 }
catch (
const DaemonFailure& e) {
542 std::cout <<
"Daemon failure: " << e.what() <<
" ... exiting" << std::endl;
543 }
catch (
const DaemonSignaled& e) {
544 std::cout <<
"Pid: " << getpid() <<
", child pid: " << e.getPid() <<
": " << e.what() << std::endl;
545 }
catch (
const CLI::CallForHelp&) {
546 std::cout << app->help() << std::endl;
547 }
catch (
const CLI::CallForAllHelp&) {
548 std::cout << app->help(
"", CLI::AppFormatMode::All) << std::endl;
549 }
catch (
const CLI::CallForVersion&) {
550 std::cout << app->version() << std::endl << std::endl;
552 std::cout << e.what() << std::endl;
553 std::cout << std::endl
554 << Color::Code::FG_GREEN <<
"command@line" << Color::Code::FG_DEFAULT <<
":" << Color::Code::FG_BLUE <<
"~/> "
555 << Color::Code::FG_DEFAULT << createCommandLineTemplate(e.getApp(), e.getMode()) << std::endl
559 std::cout << e
.getApp()->config_to_str(
true,
true);
560 }
catch (
const CLI::ParseError& e1) {
561 std::cout <<
"Error showing config file: " << e.getApp() <<
" " << e1.get_name() <<
" " << e1.what() << std::endl;
565 std::cout << e.what() << std::endl;
566 std::ofstream confFile(e.getConfigFile());
567 if (confFile.is_open()) {
569 confFile << app->config_to_str(
true,
true);
571 }
catch (
const CLI::ParseError& e1) {
573 std::cout <<
"Error writing config file: " << e1.get_name() <<
" " << e1.what() << std::endl;
577 std::cout <<
"Error writing config file: " << std::strerror(errno) << std::endl;
579 }
catch (
const CLI::ConversionError& e) {
580 std::cout <<
"[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT <<
"] " << e.what() << std::endl;
582 }
catch (
const CLI::ArgumentMismatch& e) {
583 std::cout <<
"[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT <<
"] " << e.what() << std::endl;
585 }
catch (
const CLI::ConfigError& e) {
586 std::cout <<
"[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT <<
"] " << e.what() << std::endl;
587 std::cout <<
" Adding '-w' on the command line may solve this problem" << std::endl;
589 }
catch (
const CLI::ParseError& e) {
590 const std::string what = e.what();
591 if (what.find(
"[Option Group: ") != std::string::npos) {
593 std::cout << Color::Code::FG_RED <<
"[BootstrapError]" << Color::Code::FG_DEFAULT
594 <<
" Anonymous instance(s) not configured in source code " << std::endl;
596 std::cout <<
"[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT <<
"] " << what << std::endl;
600 }
catch (
const CLI::ParseError&) {
601 std::cout << std::endl <<
"Append -h or --help to your command line for more information." << std::endl;
602 }
catch (
const CLI::Error& e) {
603 std::cout <<
"Error: " << e.get_name() <<
" " << e.what() << std::endl;
604 std::cout <<
"Append -h or --help to your command line for more information." << std::endl;
745 "-h{standard},--help{standard}",
746 [app]([[maybe_unused]] std::int64_t count) {
747 const std::size_t disabledCount =
748 app->get_subcommands([](CLI::App* app) ->
bool {
749 return app->get_group() ==
"Instance" && app->get_option(
"--disabled")->as<
bool>();
752 const std::size_t enabledCount =
753 app->get_subcommands([](CLI::App* app) ->
bool {
754 return app->get_group() ==
"Instance" && !app->get_option(
"--disabled")->as<
bool>();
758 for (
auto* instance : app->get_subcommands({})) {
759 const std::string& group = instance->get_group();
760 if (group ==
"Instance") {
761 if (instance->get_option(
"--disabled")->as<
bool>()) {
762 instance->group(std::string(
"Instance").append((disabledCount > 1) ?
"s" :
"").append(
" (disabled)"));
764 instance->group(std::string(
"Instance").append((enabledCount > 1) ?
"s" :
""));
769 const std::string& result = app->get_option(
"--help")->as<std::string>();
771 if (result ==
"standard") {
772 throw CLI::CallForHelp();
774 if (result ==
"expanded") {
775 throw CLI::CallForAllHelp();
778 "Print help message")
779 ->configurable(
false)
780 ->check(CLI::IsMember({
"standard",
"expanded"}))
781 ->trigger_on_parse();