c3da75ba34
Fixes #17
810 lines
25 KiB
C++
810 lines
25 KiB
C++
// Copyright (c) 2022 Robin Davies
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
// this software and associated documentation files (the "Software"), to deal in
|
|
// the Software without restriction, including without limitation the rights to
|
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|
// subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#include <iostream>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "CommandLineParser.hpp"
|
|
#include "PiPedalException.hpp"
|
|
#include <filesystem>
|
|
#include <stdlib.h>
|
|
#include "WriteTemplateFile.hpp"
|
|
#include <fstream>
|
|
#include "SetWifiConfig.hpp"
|
|
#include "SysExec.hpp"
|
|
#include <sys/wait.h>
|
|
#include <pwd.h>
|
|
#include "JackServerSettings.hpp"
|
|
|
|
#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())
|
|
|
|
using namespace std;
|
|
using namespace pipedal;
|
|
|
|
#define SERVICE_ACCOUNT_NAME "pipedal_d"
|
|
#define SERVICE_GROUP_NAME "pipedal_d"
|
|
#define JACK_SERVICE_ACCOUNT_NAME "jack"
|
|
#define AUDIO_SERVICE_GROUP_NAME "audio"
|
|
#define JACK_SERVICE_GROUP_NAME AUDIO_SERVICE_GROUP_NAME
|
|
|
|
#define SYSTEMCTL_BIN "/usr/bin/systemctl"
|
|
#define GROUPADD_BIN "/usr/sbin/groupadd"
|
|
#define USERADD_BIN "/usr/sbin/useradd"
|
|
#define USERMOD_BIN "/usr/sbin/usermod"
|
|
#define CHGRP_BIN "/usr/bin/chgrp"
|
|
#define CHOWN_BIN "/usr/bin/chown"
|
|
#define CHMOD_BIN "/usr/bin/chmod"
|
|
|
|
#define SERVICE_PATH "/usr/lib/systemd/system"
|
|
#define NATIVE_SERVICE "pipedald"
|
|
#define ADMIN_SERVICE "pipedaladmind"
|
|
#define JACK_SERVICE "jack"
|
|
|
|
#define REMOVE_OLD_SERVICE 1 // Grandfathering: whether to remove the old shutdown service (now pipedaladmind)
|
|
#define OLD_SHUTDOWN_SERVICE "pipedalshutdownd"
|
|
|
|
std::filesystem::path GetServiceFileName(const std::string &serviceName)
|
|
{
|
|
return std::filesystem::path(SERVICE_PATH) / (serviceName + ".service");
|
|
}
|
|
|
|
std::filesystem::path findOnPath(const std::string &command)
|
|
{
|
|
std::string path = getenv("PATH");
|
|
std::vector<std::string> paths;
|
|
size_t t = 0;
|
|
while (t < path.length())
|
|
{
|
|
size_t pos = path.find(':', t);
|
|
if (pos == string::npos)
|
|
{
|
|
pos = path.length();
|
|
}
|
|
std::string thisPath = path.substr(t, pos - t);
|
|
std::filesystem::path path = std::filesystem::path(thisPath) / command;
|
|
if (std::filesystem::exists(path))
|
|
{
|
|
return path;
|
|
}
|
|
t = pos + 1;
|
|
}
|
|
std::stringstream s;
|
|
s << "'" << command << "' is not installed.";
|
|
throw PiPedalException(s.str());
|
|
}
|
|
|
|
void EnableService()
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " enable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to enable the " NATIVE_SERVICE " service.";
|
|
}
|
|
if (sysExec(SYSTEMCTL_BIN " enable " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to enable the " ADMIN_SERVICE " service.";
|
|
}
|
|
}
|
|
void DisableService()
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " disable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to disable the " NATIVE_SERVICE " service.";
|
|
}
|
|
if (sysExec(SYSTEMCTL_BIN " disable " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to disable the " ADMIN_SERVICE " service.";
|
|
}
|
|
#if REMOVE_OLD_SERVICE
|
|
if (sysExec(SYSTEMCTL_BIN " disable " OLD_SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to disable the " OLD_SHUTDOWN_SERVICE " service.";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void StopService(bool excludeShutdownService = false)
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " stop " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to stop the " NATIVE_SERVICE " service.";
|
|
}
|
|
if (!excludeShutdownService)
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " stop " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to stop the " ADMIN_SERVICE " service.";
|
|
}
|
|
#if REMOVE_OLD_SERVICE
|
|
if (sysExec(SYSTEMCTL_BIN " stop " OLD_SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
cout << "Error: Failed to stop the " OLD_SHUTDOWN_SERVICE " service.";
|
|
}
|
|
#endif
|
|
}
|
|
if (sysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to stop the " JACK_SERVICE " service.");
|
|
}
|
|
}
|
|
|
|
void StartService(bool excludeShutdownService = false)
|
|
{
|
|
|
|
silentSysExec("/usr/bin/pulseaudio --kill"); // interferes with Jack audio service startup.
|
|
if (!excludeShutdownService)
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " start " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to start the " ADMIN_SERVICE " service.");
|
|
}
|
|
}
|
|
if (sysExec(SYSTEMCTL_BIN " start " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to start the " NATIVE_SERVICE " service.");
|
|
}
|
|
JackServerSettings serverSettings;
|
|
serverSettings.ReadJackConfiguration();
|
|
if (serverSettings.IsValid())
|
|
{
|
|
if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to start the " JACK_SERVICE " service.");
|
|
}
|
|
}
|
|
}
|
|
void RestartService(bool excludeShutdownService)
|
|
{
|
|
|
|
StopService(excludeShutdownService);
|
|
StartService(excludeShutdownService);
|
|
}
|
|
|
|
static bool userExists(const char *userName)
|
|
{
|
|
struct passwd pwd;
|
|
struct passwd *result;
|
|
char *buf;
|
|
size_t bufsize;
|
|
int s;
|
|
|
|
bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
|
|
if (bufsize == -1) /* Value was indeterminate */
|
|
bufsize = 16384; /* Should be more than enough */
|
|
|
|
buf = (char *)malloc(bufsize);
|
|
if (buf == NULL)
|
|
{
|
|
perror("malloc");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
s = getpwnam_r(userName, &pwd, buf, bufsize, &result);
|
|
if (result == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void RemoveLine(const std::string&path, const std::string lineToRemove)
|
|
{
|
|
std::vector<std::string> lines;
|
|
try
|
|
{
|
|
if (std::filesystem::exists(path))
|
|
{
|
|
{
|
|
ifstream f(path);
|
|
if (!f.is_open())
|
|
{
|
|
throw PiPedalException(SS("Can't open " << path));
|
|
}
|
|
while (true)
|
|
{
|
|
std::string line;
|
|
if (f.eof())
|
|
break;
|
|
std::getline(f, line);
|
|
if (line != lineToRemove)
|
|
{
|
|
lines.push_back(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
std::ofstream f(path);
|
|
if (!f.is_open())
|
|
{
|
|
throw PiPedalException(SS("Can't write to " << path));
|
|
}
|
|
for (auto&line: lines)
|
|
{
|
|
f << line << endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
cout << "Failed to update " << path << ". " << e.what();
|
|
}
|
|
}
|
|
|
|
const std::string PAM_LINE = "JACK_PROMISCUOUS_SERVER DEFAULT=" JACK_SERVICE_GROUP_NAME;
|
|
void UninstallPamEnv()
|
|
{
|
|
RemoveLine(
|
|
"/etc/security/pam_env.conf",
|
|
PAM_LINE
|
|
);
|
|
}
|
|
void InstallPamEnv()
|
|
{
|
|
std::string newLine = PAM_LINE;
|
|
std::vector<std::string> lines;
|
|
std::filesystem::path path = "/etc/security/pam_env.conf";
|
|
try
|
|
{
|
|
if (std::filesystem::exists(path))
|
|
{
|
|
{
|
|
ifstream f(path);
|
|
if (!f.is_open())
|
|
{
|
|
throw PiPedalException(SS("Can't open " << path));
|
|
}
|
|
while (true)
|
|
{
|
|
std::string line;
|
|
if (f.eof())
|
|
break;
|
|
std::getline(f, line);
|
|
lines.push_back(line);
|
|
}
|
|
}
|
|
|
|
for (auto &line : lines)
|
|
{
|
|
if (line == newLine)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
lines.push_back(newLine);
|
|
|
|
{
|
|
std::ofstream f(path);
|
|
if (!f.is_open())
|
|
{
|
|
throw PiPedalException(SS("Can't write to " << path));
|
|
}
|
|
for (auto&line: lines)
|
|
{
|
|
f << line << endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
cout << "Failed to update " << path << ". " << e.what();
|
|
}
|
|
}
|
|
|
|
void InstallLimits()
|
|
{
|
|
if (sysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to create audio service group.");
|
|
}
|
|
|
|
std::filesystem::path limitsConfig = "/usr/security/pipedal.conf";
|
|
|
|
if (!std::filesystem::exists(limitsConfig))
|
|
{
|
|
ofstream output(limitsConfig);
|
|
output << "# Realtime priority group used by pipedal/jack daemon" << endl;
|
|
output << "@audio - rtprio 95" << endl;
|
|
output << "@audio - memlock unlimited" << endl;
|
|
}
|
|
}
|
|
|
|
void UninstallLimits()
|
|
{
|
|
std::filesystem::path limitsConfig = "/usr/security/pipedal.conf";
|
|
|
|
if (std::filesystem::exists(limitsConfig))
|
|
{
|
|
std::filesystem::remove(limitsConfig);
|
|
}
|
|
}
|
|
|
|
void MaybeStartJackService()
|
|
{
|
|
std::filesystem::path drcFile = "/etc/jackdrc";
|
|
|
|
if (std::filesystem::exists(drcFile) && std::filesystem::file_size(drcFile) != 0)
|
|
{
|
|
sysExec(SYSTEMCTL_BIN " start jack");
|
|
}
|
|
else
|
|
{
|
|
silentSysExec(SYSTEMCTL_BIN " mask jack");
|
|
}
|
|
}
|
|
void InstallJackService()
|
|
{
|
|
InstallLimits();
|
|
InstallPamEnv();
|
|
if (sysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to create jack service group.");
|
|
}
|
|
if (!userExists(JACK_SERVICE_ACCOUNT_NAME))
|
|
{
|
|
if (sysExec(USERADD_BIN " " JACK_SERVICE_ACCOUNT_NAME " -g " JACK_SERVICE_GROUP_NAME " -m -N -r") != EXIT_SUCCESS)
|
|
{
|
|
// throw PiPedalException("Failed to create service account.");
|
|
}
|
|
// lock account for login.
|
|
silentSysExec("passwd -l " JACK_SERVICE_ACCOUNT_NAME);
|
|
}
|
|
// Add to audio groups.
|
|
// sysExec(USERMOD_BIN " -a -G " JACK_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
|
|
sysExec(USERMOD_BIN " -a -G" AUDIO_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
|
|
|
|
// If ONBOARDING, add to bluetooth group.
|
|
#ifdef ONBOARDING
|
|
sysExec(USERMOD_BIN " -a -G bluetooth " JACK_SERVICE_ACCOUNT_NAME);
|
|
#endif
|
|
|
|
// deploy the systemd service file
|
|
std::map<std::string, std::string> map; // nothing to customize.
|
|
|
|
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateJack.service"), GetServiceFileName(JACK_SERVICE));
|
|
|
|
MaybeStartJackService();
|
|
}
|
|
|
|
|
|
int SudoExec(char **argv)
|
|
{
|
|
int pbPid;
|
|
int returnValue = 0;
|
|
|
|
if ((pbPid = fork()) == 0)
|
|
{
|
|
execv(argv[0], argv);
|
|
exit(-1);
|
|
}
|
|
else
|
|
{
|
|
waitpid(pbPid, &returnValue, 0);
|
|
int exitStatus = WEXITSTATUS(returnValue);
|
|
return exitStatus;
|
|
}
|
|
}
|
|
|
|
void Uninstall()
|
|
{
|
|
StopService();
|
|
DisableService();
|
|
silentSysExec(SYSTEMCTL_BIN " stop jack");
|
|
silentSysExec(SYSTEMCTL_BIN " disable jack");
|
|
std::filesystem::remove("/usr/bin/systemd/system/" OLD_SHUTDOWN_SERVICE ".service");
|
|
std::filesystem::remove("/usr/bin/systemd/system/" ADMIN_SERVICE ".service");
|
|
std::filesystem::remove("/usr/bin/systemd/system/" NATIVE_SERVICE ".service");
|
|
std::filesystem::remove("/usr/bin/systemd/system/" JACK_SERVICE ".service");
|
|
UninstallPamEnv();
|
|
UninstallLimits();
|
|
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
|
|
|
}
|
|
|
|
|
|
void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress)
|
|
{
|
|
if (sysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to create audio service group.");
|
|
}
|
|
|
|
InstallJackService();
|
|
auto endpos = endpointAddress.find_last_of(':');
|
|
if (endpos == string::npos)
|
|
{
|
|
throw PiPedalException("Invalid endpoint address: " + endpointAddress);
|
|
}
|
|
uint16_t port;
|
|
auto strPort = endpointAddress.substr(endpos + 1);
|
|
try
|
|
{
|
|
auto lport = std::stoul(strPort);
|
|
if (lport == 0 || lport >= std::numeric_limits<uint16_t>::max())
|
|
{
|
|
throw PiPedalException("out of range.");
|
|
}
|
|
port = (uint16_t)lport;
|
|
std::stringstream s;
|
|
s << port;
|
|
strPort = s.str(); // normalized.
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
std::stringstream s;
|
|
s << "Invalid port number: " << strPort;
|
|
throw PiPedalException(s.str());
|
|
}
|
|
|
|
bool authBindRequired = port < 512;
|
|
|
|
// Create and configure service account.
|
|
|
|
if (sysExec(GROUPADD_BIN " -f " SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
|
{
|
|
throw PiPedalException("Failed to create service group.");
|
|
}
|
|
|
|
if (!userExists(SERVICE_ACCOUNT_NAME))
|
|
{
|
|
if (sysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -m --home /var/pipedal/home -N -r") != EXIT_SUCCESS)
|
|
{
|
|
// throw PiPedalException("Failed to create service account.");
|
|
}
|
|
// lock account for login.
|
|
silentSysExec("passwd -l " SERVICE_ACCOUNT_NAME);
|
|
}
|
|
|
|
// Add to audio groups.
|
|
sysExec(USERMOD_BIN " -a -G " AUDIO_SERVICE_GROUP_NAME " " SERVICE_ACCOUNT_NAME);
|
|
|
|
// create and configure /var directory.
|
|
|
|
std::filesystem::path varDirectory("/var/pipedal");
|
|
std::filesystem::create_directory(varDirectory);
|
|
|
|
{
|
|
std::stringstream s;
|
|
s << CHGRP_BIN " " SERVICE_GROUP_NAME " " << varDirectory.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream s;
|
|
s << CHOWN_BIN << " " << SERVICE_ACCOUNT_NAME << " " << varDirectory.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
|
|
{
|
|
std::stringstream s;
|
|
s << CHMOD_BIN << " 775 " << varDirectory.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream s;
|
|
s << CHMOD_BIN << " g+s " << varDirectory.c_str(); // child files/directories inherit ownership.
|
|
sysExec(s.str().c_str());
|
|
}
|
|
|
|
// authbind port.
|
|
|
|
if (authBindRequired)
|
|
{
|
|
std::filesystem::create_directories("/etc/authbind/byport");
|
|
std::filesystem::path portAuthFile = std::filesystem::path("/etc/authbind/byport") / strPort;
|
|
|
|
{
|
|
// create it.
|
|
std::ofstream f(portAuthFile);
|
|
if (!f.is_open()) {
|
|
throw PiPedalException("Failed to create " + portAuthFile.string());
|
|
}
|
|
}
|
|
{
|
|
// own it.
|
|
std::stringstream s;
|
|
s << CHOWN_BIN << " " SERVICE_ACCOUNT_NAME " " << portAuthFile.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
{
|
|
// group own it.
|
|
std::stringstream s;
|
|
s << CHGRP_BIN << " " SERVICE_GROUP_NAME " " << portAuthFile.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream s;
|
|
s << CHMOD_BIN << " 770 " << portAuthFile.c_str();
|
|
sysExec(s.str().c_str());
|
|
}
|
|
}
|
|
|
|
cout << "Creating Systemd file." << endl;
|
|
|
|
std::map<string, string> map;
|
|
|
|
int shutdownPort = 3147;
|
|
map["DESCRIPTION"] = "PiPedal Web Service";
|
|
{
|
|
std::stringstream s;
|
|
|
|
if (authBindRequired)
|
|
{
|
|
s << findOnPath("authbind").string() << " --deep ";
|
|
}
|
|
s
|
|
<< (programPrefix / "bin" / NATIVE_SERVICE).string()
|
|
<< " /etc/pipedal/config /etc/pipedal/react -port " << endpointAddress << " -systemd -shutdownPort " << shutdownPort;
|
|
|
|
map["COMMAND"] = s.str();
|
|
}
|
|
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/template.service"), GetServiceFileName(NATIVE_SERVICE));
|
|
|
|
map["DESCRIPTION"] = "PiPedal Shutdown Service";
|
|
{
|
|
std::stringstream s;
|
|
|
|
s
|
|
<< (programPrefix / "bin" / ADMIN_SERVICE).string()
|
|
<< " -port " << shutdownPort;
|
|
|
|
map["COMMAND"] = s.str();
|
|
}
|
|
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateShutdown.service"), GetServiceFileName(ADMIN_SERVICE));
|
|
|
|
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
|
|
|
cout << "Starting services" << endl;
|
|
RestartService(false);
|
|
EnableService();
|
|
|
|
cout << "Complete" << endl;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
CommandLineParser parser;
|
|
bool install = false;
|
|
bool uninstall = false;
|
|
bool help = false;
|
|
bool helpError = false;
|
|
bool stop = false, start = false;
|
|
bool enable = false, disable = false, restart = false;
|
|
bool enable_ap = false, disable_ap = false;
|
|
bool nopkexec = false;
|
|
bool excludeShutdownService = false;
|
|
std::string prefixOption;
|
|
std::string portOption;
|
|
|
|
parser.AddOption("--nopkexec", &nopkexec); // strictly a debugging aid.
|
|
parser.AddOption("--install", &install);
|
|
parser.AddOption("--uninstall", &uninstall);
|
|
parser.AddOption("--stop", &stop);
|
|
parser.AddOption("--start", &start);
|
|
parser.AddOption("--enable", &enable);
|
|
parser.AddOption("--disable", &disable);
|
|
parser.AddOption("--restart", &restart);
|
|
parser.AddOption("-h", &help);
|
|
parser.AddOption("--help", &help);
|
|
parser.AddOption("--prefix", &prefixOption);
|
|
parser.AddOption("--port", &portOption);
|
|
parser.AddOption("--enable_ap", &enable_ap);
|
|
parser.AddOption("--disable_ap", &disable_ap);
|
|
parser.AddOption("--excludeShutdownService", &excludeShutdownService); // private (unstable) option used by shutdown service.
|
|
try
|
|
{
|
|
parser.Parse(argc, (const char **)argv);
|
|
|
|
int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart;
|
|
if (actionCount > 1)
|
|
{
|
|
throw PiPedalException("Please provide only one action.");
|
|
}
|
|
if (actionCount == 0)
|
|
{
|
|
help = true;
|
|
}
|
|
if (enable_ap)
|
|
{
|
|
if (parser.Arguments().size() != 4)
|
|
{
|
|
cout << "Error: "
|
|
<< "Incorrect number of arguments supplied for --enable_ap option." << endl;
|
|
helpError = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (parser.Arguments().size() != 0)
|
|
{
|
|
cout << "Error: Unexpected argument : " << parser.Arguments()[0] << endl;
|
|
helpError = true;
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::string s = e.what();
|
|
cout << "Error: " << s << endl;
|
|
helpError = true;
|
|
}
|
|
|
|
if (help || helpError)
|
|
{
|
|
if (helpError)
|
|
cout << endl;
|
|
|
|
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
|
|
<< "Copyright (c) 2022 Robin Davies. All rights reserved." << endl
|
|
<< endl
|
|
<< "Syntax:" << endl
|
|
<< " pipedalconfig [Options...]" << endl
|
|
<< "Options: " << endl
|
|
<< " -h --help Display this message." << endl
|
|
<< endl
|
|
<< " --install Install services and service accounts." << endl
|
|
<< endl
|
|
<< " --prefix Either /usr/local or /usr as appropriate for the install" << endl
|
|
<< " (only valid with the -install option). Usually determined " << endl
|
|
<< " automatically." << endl
|
|
<< endl
|
|
<< " --uninstall Remove installed services." << endl
|
|
<< endl
|
|
<< " --enable Start the pipedal service at boot time." << endl
|
|
<< endl
|
|
<< " --disable Do not start the pipedal service at boot time." << endl
|
|
<< endl
|
|
<< " --stop Stop the pipedal services." << endl
|
|
<< endl
|
|
<< " --start Start the pipedal services." << endl
|
|
<< endl
|
|
<< " --restart Restart the pipedal services." << endl
|
|
<< endl
|
|
<< " --port Port for the web server (only with -install option)." << endl
|
|
<< " Either a port (e.g. '81'), or a full endpoint (e.g.: " << endl
|
|
<< " '127.0.0.1:8080'). If no address is specified, the " << endl
|
|
<< " web server will listen on 0.0.0.0 (any)." << endl
|
|
<< endl
|
|
<< " --enable_ap <country_code> <ssid> <wep_password> <channel>" << endl
|
|
<< " Enable the Wi-Fi access point. <country_code> is the " << endl
|
|
<< " 2-letter ISO-3166 country code for the country in " << endl
|
|
<< " which you are currently located. The country code " << endl
|
|
<< " determines which channels may be legally used in (and " << endl
|
|
<< " which features need to be enabled) in order to use a " << endl
|
|
<< " Wi-Fi channel in your legislative regime." << endl
|
|
<< endl
|
|
<< " --disable_ap Disabled the Wi-Fi access point." << endl
|
|
<< endl
|
|
<< "Description:" << endl
|
|
<< " pipedalconfig performs post-install configuration of pipedal." << endl
|
|
<< endl;
|
|
exit(helpError ? EXIT_FAILURE : EXIT_SUCCESS);
|
|
}
|
|
if (portOption.size() != 0 && !install)
|
|
{
|
|
cout << "Error: -port option can only be specified with the -install option." << endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
auto uid = getuid();
|
|
if (uid != 0 && !nopkexec)
|
|
{
|
|
// re-execute with PKEXEC in order to prompt for SUDO credentials once only.
|
|
std::vector<char *> args;
|
|
std::string pkexec = "/usr/bin/pkexec"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
|
args.push_back((char *)(pkexec.c_str()));
|
|
|
|
std::string sPath = getSelfExePath();
|
|
args.push_back(const_cast<char *>(sPath.c_str()));
|
|
for (int arg = 1; arg < argc; ++arg)
|
|
{
|
|
args.push_back(const_cast<char *>(argv[arg]));
|
|
}
|
|
|
|
args.push_back(nullptr);
|
|
|
|
char **rawArgv = &args[0];
|
|
return SudoExec(rawArgv);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (install)
|
|
{
|
|
std::filesystem::path prefix;
|
|
if (prefixOption.length() != 0)
|
|
{
|
|
prefix = std::filesystem::path(prefixOption);
|
|
}
|
|
else
|
|
{
|
|
prefix = std::filesystem::path(argv[0]).parent_path().parent_path();
|
|
|
|
std::filesystem::path pipedalPath = prefix / "bin/pipedald";
|
|
if (!std::filesystem::exists(pipedalPath))
|
|
{
|
|
std::stringstream s;
|
|
s << "Can't find pipedald executable at " << pipedalPath << ". Try again using the -prefix option.";
|
|
throw PiPedalException(s.str());
|
|
}
|
|
}
|
|
|
|
if (portOption == "")
|
|
{
|
|
portOption = "80";
|
|
}
|
|
if (portOption.find(':') == string::npos)
|
|
{
|
|
portOption = "0.0.0.0:" + portOption;
|
|
}
|
|
Install(prefix, portOption);
|
|
}
|
|
else if (uninstall)
|
|
{
|
|
Uninstall();
|
|
}
|
|
else if (stop)
|
|
{
|
|
StopService();
|
|
}
|
|
else if (start)
|
|
{
|
|
StartService();
|
|
}
|
|
else if (restart)
|
|
{
|
|
RestartService(excludeShutdownService);
|
|
}
|
|
else if (enable)
|
|
{
|
|
EnableService();
|
|
}
|
|
else if (disable)
|
|
{
|
|
DisableService();
|
|
}
|
|
else if (enable_ap)
|
|
{
|
|
auto argv = parser.Arguments();
|
|
WifiConfigSettings settings;
|
|
settings.valid_ = true;
|
|
settings.enable_ = true;
|
|
settings.countryCode_ = argv[0];
|
|
settings.hotspotName_ = argv[1];
|
|
settings.password_ = argv[2];
|
|
settings.channel_ = argv[3];
|
|
settings.hasPassword_ = settings.password_.length() != 0;
|
|
|
|
SetWifiConfig(settings);
|
|
}
|
|
else if (disable_ap)
|
|
{
|
|
WifiConfigSettings settings;
|
|
settings.valid_ = true;
|
|
settings.enable_ = false;
|
|
SetWifiConfig(settings);
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
cout << "Error: " << e.what() << endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} |