Auto-select web server port (for Ubuntu); --port option for PiPedalConfig

This commit is contained in:
Robin E. R. Davies
2024-11-24 12:41:30 -05:00
parent 96df3acabd
commit 8787073338
23 changed files with 261 additions and 111 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
"/usr/lib",
"~/src/vst3sdk"
],
"compilerPath": "/usr/bin/gcc",
"compilerPath": "/usr/bin/gcc-12",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64",
+42 -40
View File
@@ -266,6 +266,48 @@
}
]
},
{
"name": "(gdb) pipedalconfigX",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "/home/robin/src/pipedal/build/src/pipedalconfig",
"args": [
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
//"--get-current-port"
// "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
// "--alsa-devices"
"--prefix", "/usr",
"--install", "--port", "0"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
},
{
"name": "OTHER_VALUE",
"value": "Something something"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
@@ -312,46 +354,6 @@
}
]
},
{
"name": "(gdb) pipedalconfig",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
//"--get-current-port"
// "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
"--alsa-devices"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
},
{
"name": "OTHER_VALUE",
"value": "Something something"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) pipedal_alsa_check",
"type": "cppdbg",
+2 -1
View File
@@ -110,5 +110,6 @@
"pipedalshutdownd",
"pipedaltest",
"ToobAmp"
]
],
"C_Cpp.default.compilerPath": "/usr/bin/g++-13"
}
+1 -1
View File
@@ -98,7 +98,7 @@ endif()
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Robin E. R. Davies<rerdavies@gmail.com>") # required
set(CPACK_PACKAGE_VENDOR "Robin Davies")
set(CPACK_PACKAGE_VENDOR "Robin E. R. Davies")
set(CPACK_PACKAGE_DESCRIPTION "IoT guitar effect pedal for Raspberry Pi, with phone-friendly web interface.")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi")
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2024 Robin Davies
Copyright 2021-2024 Robin E. R. Davies
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+1 -1
View File
@@ -17,7 +17,7 @@ using namespace std;
static void PrintHelp()
{
cout << "nm-pipedal-p2p - P2P Manager for NetworkManager network stack." << endl
<< "Copyright (c) 2024 Robin Davies." << endl
<< "Copyright (c) 2024 Robin E. R. Davies." << endl
<< endl
<< "Provides P2P connections on LINUX distributions that use NetworkManager." << endl
<< endl
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022 Robin E. R. 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
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022 Robin E. R. 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
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022 Robin E. R. 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
+1 -1
View File
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
Copyright (c) 2007 Andy Lutomirski
Copyright (c) 2007 Mike Kershaw
Copyright (c) 2008 Luis R. Rodriguez
Copyright (c) 2024 Robin Davies
Copyright (c) 2024 Robin E. R. Davies
All rights reserved.
Redistribution and use in source and binary forms, with or without
+1 -1
View File
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
Copyright (c) 2007 Andy Lutomirski
Copyright (c) 2007 Mike Kershaw
Copyright (c) 2008 Luis R. Rodriguez
Copyright (c) 2024 Robin Davies
Copyright (c) 2024 Robin E. R. Davies
All rights reserved.
Redistribution and use in source and binary forms, with or without
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022 Robin E. R. 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
+5 -5
View File
@@ -1,24 +1,24 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: pipedal
Upstream-Contact: Robin Davies <rerdavies@gmail.com>
Upstream-Contact: Robin E. R. Davies <rerdavies@gmail.com>
Source: https://www.example.com/software/project
Files: *
Copyright: 2021, Robin Davies
Copyright: 2021, Robin E. R. Davies
License: Expat
Files: src/*
Copyright: 2021, Robin Davies
Copyright: 2021, Robin E. R. Davies
License: Expat
Files: react/src
Copyright: 2020, Robin Davies.
Copyright: 2020, Robin E. R. Davies.
License: Expat
Files: src/Lv2EventBufferWriter.hpp
Copyright: 2008-2014, David Robillard <d@drobilla.net>
2021, Robin Davies
2021, Robin E. R. Davies
License: ISC and Expat
Files: react/node_modules/@babel/*
+2 -2
View File
@@ -1,11 +1,11 @@
---
/:
license: Expat
copyright: 2021, Robin Davies.
copyright: 2021, Robin E. R. Davies.
src/react/:
license: Expat
copyright: 2021, Robin Davies.
copyright: 2021, Robin E. R. Davies.
react/node_modules/webpack/:
license: Expat
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2024 Robin Davies
Copyright 2021-2024 Robin E. R. Davies
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2023 Robin Davies
Copyright 2021-2023 Robin E. R. Davies
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+1 -1
View File
@@ -1014,7 +1014,7 @@ export class PiPedalModel //implements PiPedalModel
try {
await this.webSocket.connect();
} catch (error) {
this.setError("Failed to connect to server. (" + getErrorMessage(error));
this.setError("Failed to connect to server. " + getErrorMessage(error) );
return false;
}
+2 -2
View File
@@ -281,7 +281,7 @@ class PiPedalSocket {
ws.onerror = (evt: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Failed to connect.");
reject("Connection not accepted.");
};
ws.onopen = (event: Event) => {
ws.onerror = self.handleError.bind(self);
@@ -290,7 +290,7 @@ class PiPedalSocket {
resolve(ws);
};
} catch (e: any){
reject("Failed to connect. " + e.toString());
reject("Failed to connect: " + e.toString());
};
});
}
+1
View File
@@ -181,6 +181,7 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
char *n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
this->serviceName = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
+160 -23
View File
@@ -27,6 +27,7 @@
#include "alsaCheck.hpp"
#include "RegDb.hpp"
#include "Locale.hpp"
#include "Finally.hpp"
#include <filesystem>
#include <stdlib.h>
@@ -953,6 +954,110 @@ void InstallPgpKey()
sysExec(cmd.c_str());
}
}
static uint32_t autoSelectPorts[] = {
80,
81, // unofficial alternate
8080, // unofficial alternate
8081, // unofficial alternate
8008, // unofficial alternate
83 // Not official at all.
};
static bool isPortInUse(int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
throw std::runtime_error(SS("Socket creation failed: " << strerror(errno)));
}
Finally ff{[sockfd]() {
close(sockfd);
}};
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
// Try to bind to the port
int result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if (result < 0) {
if (errno == EADDRINUSE) {
return true; // Port is in use
} else {
cout << "Warning: Unexpected error binding socket to port " << port << ": " << strerror(errno) << endl;
return true;
}
}
return false; // Port is free
}
static std::string AutoSelectPort() {
constexpr size_t MAX_PORT = sizeof(autoSelectPorts)/sizeof(autoSelectPorts[0]);
int32_t port = -1;
for (size_t i = 0; i < MAX_PORT; ++i)
{
if (!isPortInUse(autoSelectPorts[i]))
{
port = autoSelectPorts[i];
break;
}
}
if (port == -1) {
cout << "Warning: Can't find an available HTTP port. Setting to port 80 anyway." << endl;
port = 80;
} else if (port != 80)
{
cout << "Warning: Port 80 is already in use. Using port " << port << " instead." << endl;
}
return SS(port);
}
bool SetWebServerPort(std::string portOption)
{
try {
auto nPos = portOption.find_last_of(':');
std::string strPort;
if (nPos != std::string::npos)
{
strPort = portOption.substr(nPos+1);
} else {
strPort = portOption;
}
std::istringstream ss(strPort);
uint16_t iPort = 0;
ss >> iPort;
if (!ss)
{
throw std::runtime_error(SS("Invalid port number: " << strPort));
}
StopService(false);
PrepareServiceConfigurationFile(iPort);
StartService(true);
cout << "PiPedal web server is listening on port " << iPort << endl;
} catch (const std::exception&e)
{
cout << "Error: " << e.what() << endl;
return false;
}
return true;
}
void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
cout << "Configuring pipedal" << endl;
@@ -1000,14 +1105,14 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
throw std::runtime_error("Invalid endpoint address: " + endpointAddress);
}
uint16_t port;
uint16_t port = 80;
auto strPort = endpointAddress.substr(endpos + 1);
try
{
auto lport = std::stoul(strPort);
if (lport == 0 || lport >= std::numeric_limits<uint16_t>::max())
{
throw std::runtime_error("out of range.");
throw std::runtime_error("Port number is too large.");
}
port = (uint16_t)lport;
std::stringstream s;
@@ -1020,6 +1125,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
s << "Invalid port number: " << strPort;
throw std::runtime_error(s.str());
}
PrepareServiceConfigurationFile(port);
bool authBindRequired = port < 512;
@@ -1188,9 +1294,18 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
// don't allow abnormal termination, which leaves the package in a state that's
// difficult to uninstall.
cout << "Error: " << e.what();
cout << "Error: " << e.what() << endl;
cout << " Run 'pipedalconfig --install' again to complete setup of PiPedal." << endl;
}
const std::string ADDR_ANY = "0.0.0.0:";
if (endpointAddress.starts_with(ADDR_ANY))
{
cout << "PiPedal web server is listening on port " << endpointAddress.substr(ADDR_ANY.length()) << endl;
} else {
cout << "PiPedal web server is listening on port " << endpointAddress << endl;
}
}
static std::string GetCurrentWebServicePort()
@@ -1199,7 +1314,7 @@ static std::string GetCurrentWebServicePort()
config.Load("/etc/pipedal/config", "");
std::ostringstream ss;
ss << config.GetSocketServerPort();
ss << config.GetSocketServerAddress() << ":" << config.GetSocketServerPort();
return ss.str();
}
@@ -1208,7 +1323,7 @@ static void PrintHelp()
PrettyPrinter pp;
pp << "pipedalconfig - Command-line post-install configuration for PiPedal"
<< "\n"
<< "Copyright (c) 2022-2024 Robin Davies."
<< "Copyright (c) 2022-2024 Robin E. R. Davies."
<< "\n"
<< "\n"
<< "See https://rerdavies.github.io/pipedal/Documentation.html for "
@@ -1239,6 +1354,10 @@ static void PrintHelp()
<< "The --port option controls which TCP/IP port the web server uses."
<< "\n\n"
<< HangingIndent() << " --port <port#>\t"
<< "Set the TCP/IP port that the web server will use."
<< "\n"
<< HangingIndent() << " --uninstall\t"
<< "Remove installed services."
<< "\n"
@@ -1401,6 +1520,29 @@ void ListValidCountryCodes()
}
}
static std::string ProcessPortOption(std::string portOption)
{
if (portOption == "" || portOption == "0") // allow testing of port autodetect.
{
std::string t = GetCurrentWebServicePort();
if (portOption != "0") {// allows easy debugging of port AutoSelectPort.
portOption = t;
} else {
portOption = "";
}
if (portOption == "")
{
portOption = AutoSelectPort();
}
}
std::string ADDR_ANY = "0.0.0.0:";
if (portOption.find(':') == string::npos)
{
portOption = ADDR_ANY + portOption;
}
return portOption;
}
int main(int argc, char **argv)
{
@@ -1457,6 +1599,10 @@ int main(int argc, char **argv)
uninstall + stop + start + enable + disable + enable_hotspot +
disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels +
fix_permissions + list_wifi_country_codes;
if (actionCount == 0 && portOption.length() != 0)
{
++actionCount;
}
if (actionCount > 1)
{
throw std::runtime_error("Please provide only one action.");
@@ -1553,12 +1699,6 @@ int main(int argc, char **argv)
{
return ListP2PChannels(parser.Arguments());
}
if (portOption.size() != 0 && !install)
{
cout << "Error: -port option can only be specified with the -install option."
<< "\n";
exit(EXIT_FAILURE);
}
auto uid = getuid();
if (uid != 0 && !nosudo)
@@ -1568,6 +1708,12 @@ int main(int argc, char **argv)
try
{
if (portOption.length() != 0 && !install)
{
portOption = ProcessPortOption(portOption);
SetWebServerPort(portOption);
return EXIT_SUCCESS;
}
if (fix_permissions)
{
FixPermissions();
@@ -1595,20 +1741,11 @@ int main(int argc, char **argv)
}
}
if (portOption == "")
{
portOption = GetCurrentWebServicePort();
if (portOption == "")
{
portOption = "80";
}
}
if (portOption.find(':') == string::npos)
{
portOption = "0.0.0.0:" + portOption;
}
portOption = ProcessPortOption(portOption);
Install(prefix, portOption);
FileSystemSync();
}
catch (const std::exception &e)
{
+1 -1
View File
@@ -44,7 +44,7 @@ void Lv2PluginChangeMonitor::Shutdown()
{
terminateThread = true;
uint64_t val = 1;
write(shutdown_eventfd,(void*)&val, sizeof(val));
auto _ = write(shutdown_eventfd,(void*)&val, sizeof(val));
monitorThread->join();
monitorThread = nullptr;
close(shutdown_eventfd);
+33 -23
View File
@@ -24,25 +24,22 @@
#include "ss.hpp"
#include <stdexcept>
#ifdef __linux__
#include "unistd.h" // for nice()`
#endif
#include <unistd.h> // for nice().
using namespace pipedal;
static constexpr int EXPECTED_RT_THREAD_PRIORITY_MAX = 81;
static constexpr int EXPECTED_RT_THREAD_PRIORITY_MAX = 95;
static constexpr int RT_AUDIO_THREAD_PRIORITY = 80;
static constexpr int NICE_AUDIO_THREAD_PRIORITY = -19;
static constexpr int RT_AUDIO_THREAD_PRIORITY = 90; // one above pipewire.
static constexpr int RT_AUDIOSERVICE_THREAD_PRIORITY = 10;
static constexpr int NICE_AUDIOSERVICE_THREAD_PRIORITY = -17;
static constexpr int RT_AUDIOSERVICE_THREAD_PRIORITY = 85; // one above pipewire service thread
static constexpr int RT_LV2SCHEDULER_THREAD_PRIORITY = -1;
static constexpr int NICE_LV2SCHEDULER_THREAD_PRIORITY = 2; // nice(2): don't let plugins screw things up by pinning the CPU
static constexpr int RT_LV2SCHEDULER_THREAD_PRIORITY = 5;
static constexpr int RT_WEBSERVER_THREAD_PRIORITY = -1;
static constexpr int NICE_WEBSERVER_THREAD_PRIORITY = -2; // don't get bogged down by UI threads.
static constexpr int NICE_WEBSERVER_PROCESS_PRIORITY = -9; // above chrome renderer, below pipewire..
bool pipedal::IsRtPreemptKernel(SchedulerPriority priority)
{
@@ -71,7 +68,7 @@ bool pipedal::IsRtPreemptKernel(SchedulerPriority priority)
#endif
}
static void SetPriority(int realtimePriority, int nicePriority, const char *priorityName)
static void SetPriority(int realtimePriority, const char *priorityName)
{
if (realtimePriority != -1)
@@ -79,12 +76,20 @@ static void SetPriority(int realtimePriority, int nicePriority, const char *prio
int initialPriority = realtimePriority;
int min = sched_get_priority_min(SCHED_RR);
int max = sched_get_priority_max(SCHED_RR);
if (max > EXPECTED_RT_THREAD_PRIORITY_MAX)
{
max = EXPECTED_RT_THREAD_PRIORITY_MAX;
}
if (realtimePriority > max) {
realtimePriority = max + EXPECTED_RT_THREAD_PRIORITY_MAX-realtimePriority;
}
if (realtimePriority < min) {
realtimePriority = min;
}
if (realtimePriority < 1)
{
realtimePriority = 1;
}
if (initialPriority != realtimePriority)
{
Lv2Log::warning(
@@ -104,15 +109,9 @@ static void SetPriority(int realtimePriority, int nicePriority, const char *prio
}
else
{
Lv2Log::debug(SS("Failed to set RT thread priority for " << priorityName << " (" << strerror(result) << ")"));
Lv2Log::warning(SS("Failed to set RT thread priority for " << priorityName << " (" << strerror(result) << ")"));
}
}
// If the RT scheduler isn't working, fall back to using NICE priority.
int result = nice(nicePriority);
if (result == -1)
{
Lv2Log::error(SS("Failed to set thread priority. (" << priorityName << ")"));
}
}
void pipedal::SetThreadPriority(SchedulerPriority priority)
@@ -122,16 +121,27 @@ void pipedal::SetThreadPriority(SchedulerPriority priority)
switch (priority)
{
case SchedulerPriority::RealtimeAudio:
SetPriority(RT_AUDIO_THREAD_PRIORITY, NICE_AUDIO_THREAD_PRIORITY, "RealtimeAudio");
SetPriority(RT_AUDIO_THREAD_PRIORITY, "RealtimeAudio");
break;
case SchedulerPriority::AudioService:
SetPriority(RT_AUDIOSERVICE_THREAD_PRIORITY, NICE_AUDIOSERVICE_THREAD_PRIORITY, "AudioService");
SetPriority(RT_AUDIOSERVICE_THREAD_PRIORITY, "AudioService");
break;
case SchedulerPriority::Lv2Scheduler:
SetPriority(RT_LV2SCHEDULER_THREAD_PRIORITY, NICE_LV2SCHEDULER_THREAD_PRIORITY, "Lv2Scheduler");
SetPriority(RT_LV2SCHEDULER_THREAD_PRIORITY, "Lv2Scheduler");
break;
case SchedulerPriority::WebServerThread:
SetPriority(RT_WEBSERVER_THREAD_PRIORITY, NICE_WEBSERVER_THREAD_PRIORITY, "Lv2Scheduler");
{
// do this by boosting the process priority
int currentNiceValue = nice(0);
if (currentNiceValue != NICE_WEBSERVER_PROCESS_PRIORITY)
{
errno = 0;
int ret = nice(NICE_WEBSERVER_PROCESS_PRIORITY-currentNiceValue);
if (ret == -1 && errno != 0) {
Lv2Log::error("Failed to set process nice value.");
}
}
}
break;
default:
Lv2Log::error("Invalid scheduler priority.");
-1
View File
@@ -39,7 +39,6 @@
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include "Lv2Log.hpp"
#include <iostream>
#include <unistd.h> // for nice(
#include <utility>
#include "util.hpp"
#include "SchedulerPriority.hpp"