Changes
diff --git a/config.example.yaml b/config.example.yaml
index f7ad897..60d5f16 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -1,8 +1,10 @@
# Copy this file to a local path and run:
-# status_tracker ./config.yaml
+# probius ./config.yaml
worker_count: 4
database_path: status-tracker.sqlite
+# unix_socket: /run/probius/probius.sock
+# socket_permission: 0660
# Groups are a sequence. Service IDs are mapping keys and must be unique.
groups:
diff --git a/packages/arch/PKGBUILD b/packages/arch/PKGBUILD
index 4bff02c..9b2e85c 100644
--- a/packages/arch/PKGBUILD
+++ b/packages/arch/PKGBUILD
@@ -22,9 +22,9 @@ source=(
)
sha256sums=(
'SKIP'
- '84b07e2cdc9673bd6a933eb40b92e29249cef13ec0f682bf049f94c5f779db63'
+ 'c16a0f6151f0c64681339ac131e07bde8707ab994ba5707d541c6fb1501cca0c'
'0f57b093e9e0412f2bfaced764c27929a581e6cdaa2842c904701337959712ff'
- 'a5ba1c774bf5531250e37d6e3e83aacbeb02e6afb3ecfe80b7820f43c210d22e'
+ 'c486ce4ddedb590db2ed7406e112b70d1ad55628f9c6b049286e2ab144661d72'
)
pkgver()
diff --git a/packages/arch/probius.service b/packages/arch/probius.service
index 79bb259..6fa67df 100644
--- a/packages/arch/probius.service
+++ b/packages/arch/probius.service
@@ -7,6 +7,7 @@ Wants=network-online.target
User=probius
Group=probius
ExecStart=/usr/bin/probius /etc/probius.yaml
+RuntimeDirectory=probius
StateDirectory=probius
Restart=on-failure
RestartSec=5s
diff --git a/packages/arch/probius.yaml b/packages/arch/probius.yaml
index f0abef2..9aa9f3e 100644
--- a/packages/arch/probius.yaml
+++ b/packages/arch/probius.yaml
@@ -1,3 +1,5 @@
worker_count: 4
database_path: /var/lib/probius/probius.sqlite
+# unix_socket: /run/probius/probius.sock
+# socket_permission: 0660
groups: []
diff --git a/src/configuration.cpp b/src/configuration.cpp
index fd82131..00a5c71 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -128,6 +128,38 @@ mw::E<std::optional<Integer>> optionalInteger(
return std::optional<Integer>{result};
}
+mw::E<std::optional<unsigned int>> optionalSocketPermission(
+ Node node, std::string_view key, std::string_view context)
+{
+ auto value = child(node, key);
+ if(!value.readable())
+ {
+ return std::optional<unsigned int>{};
+ }
+ if(!value.has_val())
+ {
+ return std::unexpected(configError(
+ context, std::string("invalid socket permission ") +
+ std::string(key)));
+ }
+ std::string_view text(value.val().begin(), value.val().size());
+ if(text.starts_with("0o") || text.starts_with("0O"))
+ {
+ text.remove_prefix(2);
+ }
+ unsigned int result = 0;
+ const auto parsed = std::from_chars(
+ text.begin(), text.end(), result, 8);
+ if(text.empty() || parsed.ec != std::errc{} ||
+ parsed.ptr != text.end() || result > 0777)
+ {
+ return std::unexpected(configError(
+ context, std::string(key) +
+ " must be an octal mode from 000 through 777"));
+ }
+ return std::optional<unsigned int>{result};
+}
+
std::string lower(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(),
@@ -315,6 +347,20 @@ mw::E<Configuration> parseTree(ryml::Tree& tree)
return std::unexpected(configError(
"configuration", "database_path must not be empty"));
}
+ ASSIGN_OR_RETURN(auto unix_socket,
+ optionalString(root, "unix_socket", "configuration"));
+ if(unix_socket && unix_socket->find('\0') != std::string::npos)
+ {
+ return std::unexpected(configError(
+ "configuration", "unix_socket contains a null character"));
+ }
+ if(unix_socket)
+ {
+ result.unix_socket = std::move(*unix_socket);
+ }
+ ASSIGN_OR_RETURN(result.socket_permission,
+ optionalSocketPermission(
+ root, "socket_permission", "configuration"));
auto groups = child(root, "groups");
if(!groups.readable() || !groups.is_seq())
{
diff --git a/src/configuration.h b/src/configuration.h
index 32a9dac..9fe9f9c 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -2,6 +2,7 @@
#include <cstddef>
#include <filesystem>
+#include <optional>
#include <string>
#include <vector>
@@ -27,6 +28,10 @@ struct Configuration
std::string database_path;
/// Service groups in configuration order.
std::vector<ServiceGroupConfig> groups;
+ /// Unix-domain socket path; empty selects the default TCP listener.
+ std::string unix_socket;
+ /// Permission bits applied to the Unix-domain socket, when configured.
+ std::optional<unsigned int> socket_permission;
/// Load and validate a YAML configuration file.
static mw::E<Configuration> fromYaml(
diff --git a/src/main.cpp b/src/main.cpp
index b51004d..e57a346 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,6 @@
#include <filesystem>
#include <iostream>
+#include <string>
#include <string_view>
#include <thread>
#include <utility>
@@ -13,7 +14,7 @@ namespace
void printUsage(std::ostream& output)
{
- output << "Usage: status_tracker FILE\n";
+ output << "Usage: probius FILE\n";
}
}
@@ -50,7 +51,17 @@ int main(int argc, char** argv)
std::cerr << scheduler.error().msg() << '\n';
return 1;
}
- App app(mw::IPSocketInfo{"127.0.0.1", 8080},
+ mw::HTTPServer::ListenAddress listen =
+ mw::IPSocketInfo{"127.0.0.1", 8080};
+ std::string listen_description = "http://127.0.0.1:8080";
+ if(!configuration->unix_socket.empty())
+ {
+ mw::SocketFileInfo socket(configuration->unix_socket);
+ socket.permission = configuration->socket_permission;
+ listen = std::move(socket);
+ listen_description = "Unix socket " + configuration->unix_socket;
+ }
+ App app(listen,
std::move(*configuration), **data_source);
auto started = app.start();
if(!started)
@@ -68,7 +79,7 @@ int main(int argc, char** argv)
app.stop();
}
});
- std::cout << "Status Tracker listening on http://127.0.0.1:8080\n"
+ std::cout << "Probius listening on " << listen_description << '\n'
<< std::flush;
app.wait();
scheduler_thread.request_stop();
diff --git a/tests/configuration_test.cpp b/tests/configuration_test.cpp
index 6853e0c..f2f8b84 100644
--- a/tests/configuration_test.cpp
+++ b/tests/configuration_test.cpp
@@ -36,6 +36,8 @@ TEST(Configuration, LoadsGroupedProtocolSettings)
TemporaryConfig file(
"worker_count: 4\n"
"database_path: tracker.sqlite\n"
+ "unix_socket: /run/probius.sock\n"
+ "socket_permission: 0660\n"
"groups:\n"
" - name: Public services\n"
" services:\n"
@@ -86,6 +88,9 @@ TEST(Configuration, LoadsGroupedProtocolSettings)
<< (configuration ? "" : configuration.error().msg());
EXPECT_EQ(configuration->worker_count, 4);
EXPECT_EQ(configuration->database_path, "tracker.sqlite");
+ EXPECT_EQ(configuration->unix_socket, "/run/probius.sock");
+ ASSERT_TRUE(configuration->socket_permission);
+ EXPECT_EQ(*configuration->socket_permission, 0660);
ASSERT_EQ(configuration->groups.size(), 1);
ASSERT_EQ(configuration->groups.front().services.size(), 4);
@@ -132,7 +137,17 @@ TEST(Configuration, RejectsInvalidValues)
" name: x\n"
" description: x\n"
" interval: {value: 1, unit: minute}\n"
- " endpoint: {protocol: TCP, host: x, port: 0}\n"
+ " endpoint: {protocol: TCP, host: x, port: 0}\n",
+ "worker_count: 1\n"
+ "database_path: db\n"
+ "unix_socket: /run/probius.sock\n"
+ "socket_permission: 0668\n"
+ "groups: []\n",
+ "worker_count: 1\n"
+ "database_path: db\n"
+ "unix_socket: /run/probius.sock\n"
+ "socket_permission: 01000\n"
+ "groups: []\n"
};
for(const auto& content : contents)
{