Compare commits

..

No commits in common. "52ea711718c4f6219d1c05f2588d03d9dc1676b2" and "5339c1f791418ab3b14981d59c94a8df90436208" have entirely different histories.

14 changed files with 112 additions and 308 deletions

26
.gitignore vendored
View File

@ -1,19 +1,15 @@
# CMake build directories
/build/*
!/build/config.json
!/build/gh-wh-handler.service.in
!/build/uninstall.cmake.in
/CMakeFiles/
/CMakeCache.txt
/cmake_install.cmake
/*.cmake
/*.cbp
/*.layout
/*.stackdump
/CPackConfig.cmake
/Makefile
/CTestTestfile.cmake
/install_manifest.txt
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
*.cmake
*.cbp
*.layout
*.stackdump
CPackConfig.cmake
Makefile
CTestTestfile.cmake
install_manifest.txt
# Compiled binaries
/bin/

View File

@ -48,19 +48,15 @@ set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS
# Install the executable
set(SERVICE_EXECUTABLE "/services/gh-wh-handler/${EXECUTABLE_NAME}")
set(SERVICE_CONFIG "/services/gh-wh-handler/config.json")
set(SERVICE_LOGS "/services/gh-wh-handler/logs")
configure_file(
"${CMAKE_CURRENT_BINARY_DIR}/gh-wh-handler.service.in"
"${CMAKE_CURRENT_BINARY_DIR}/gh-wh-handler.service"
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gh-wh-handler.service"
@ONLY)
install(CODE "file(MAKE_DIRECTORY /services/gh-wh-handler)")
install(CODE "file(MAKE_DIRECTORY /services/gh-wh-handler/logs)")
install(TARGETS ${EXECUTABLE_NAME} DESTINATION /services/gh-wh-handler)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/config.json"
DESTINATION /services/gh-wh-handler)
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink
/services/gh-wh-handler/${EXECUTABLE_NAME} /usr/bin/gh-wh-handler)")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gh-wh-handler.service"
install(FILES "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gh-wh-handler.service"
DESTINATION /etc/systemd/system)
install(CODE "execute_process(COMMAND systemctl daemon-reload)")
install(CODE "execute_process(COMMAND systemctl enable gh-wh-handler)")

View File

@ -2,23 +2,9 @@
## Simple C++ WebAPI to work with GitHub Webhooks
This application is a simple C++ WebAPI that listens for GitHub Webhooks and performs actions based on the received data and configuration.
Currently creating a local copy of remote files on every push
## Installation
### Use installation script (recommended)
Run the installation script to install the application:
```console
curl -fsSL https://cdn.tiagorg.pt/gh-wh-handler/install.sh | sudo sh
```
You can uninstall the application using the following command:
```console
curl -fsSL https://cdn.tiagorg.pt/gh-wh-handler/uninstall.sh | sudo sh
```
## Usage
### Run prebuilt binary
@ -26,7 +12,7 @@ Head over to the [Releases Page](https://github.com/TiagoRG/gh-wh-handler/releas
Run the application using your configuration file:
```console
/path/to/gh-wh-handler.<arch> /path/to/config.json /path/to/logs_dir
/path/to/gh-wh-handler.<arch> /path/to/config.json
```
You can see the config file format below.
@ -38,7 +24,7 @@ You can see the config file format below.
- [CrowCpp](https://crowcpp.org/master/)
- [nlohmann::json](https://github.com/nlohmann/json)
#### Build and install the application:
#### Build the application:
1. Clone the repository:
@ -61,12 +47,7 @@ cmake ..
sudo make install
```
If you want to uninstall the application, you can run the following command:
```console
sudo make uninstall
```
## Usage
#### Run the application:
The application is running on a systemd service, which is both enabled and started after installation.
@ -91,36 +72,11 @@ As of now, the configuration menu is not yet implemented so you have to create t
### Config File
The configuration file can be found in `/services/gh-wh-handler/config.json` and has the following base format:
The configuration file can be found in `/services/gh-wh-handler/config.json` and has the following format:
```json
{
"port": 65001,
"tokens": {
"owner/repo-name": "token"
}
}
```
This configuration will then have more fields for each endpoint that you want to configure.
Note: Tokens are only required for private repositories.
## Endpoints
Currently, the only endpoint for the application is `/update-files`, which is used to update the local files on every push as well as run post-update scripts.
### `/update-files`
#### Webhook event: `push`
This endpoint allows the application to update specific files on the server when a push to a specific branch is made. This way, there's no need to manually update the files on the server or to pull the entire repository.
It also allows the application to run post-update scripts after the files are updated.
The configuration file must contain the `update-files` field, which is an object with the following format:
```json
"update-files": {
"owner/repo-name": {
"branch": "main",
@ -134,9 +90,28 @@ The configuration file must contain the `update-files` field, which is an object
"..."
]
}
},
"run-scripts": {
"owner/repo-name": {
"branch": "main",
"actions": [
"command",
"script",
]
}
},
"tokens": {
"owner/repo-name": "token"
}
}
```
## Endpoints
Currently, the only endpoint for the application is `/update-files`, which is used to update the local files on every push as well as run post-update scripts.
Since only the `/update-files` endpoint is implemented, the configuration file may not contain the `run-scripts` field.
## License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.

View File

@ -1,9 +0,0 @@
{
"port": 65001,
"update-files": {
},
"run-scripts": {
},
"tokens": {
}
}

View File

@ -3,7 +3,7 @@ Description=Runs github webhook handler
After=network.target
[Service]
ExecStart=@SERVICE_EXECUTABLE@ @SERVICE_CONFIG@ @SERVICE_LOGS@
ExecStart=@SERVICE_EXECUTABLE@ @SERVICE_CONFIG@
Restart=always
Type=simple

View File

@ -16,7 +16,7 @@ execute_process(COMMAND rm /usr/bin/gh-wh-handler)
message(STATUS "[70%] Removing service executable from service directory...")
file(REMOVE /services/gh-wh-handler/@EXECUTABLE_NAME@)
message(STATUS "[85%] Removing all log files...")
execute_process(COMMAND rm -fr /services/gh-wh-handler/logs)
message(STATUS "[85%] Removing service directory...")
file(REMOVE /services/gh-wh-handler)
message(STATUS "[100%] Uninstallation complete!")

View File

@ -5,7 +5,7 @@
class Config {
public:
static void create_config(std::string config_file_path);
static void create_config();
static nlohmann::json get_config(std::string config_file_path);
static void open_config_menu();

View File

@ -1,68 +0,0 @@
#!/bin/sh
# Check if the script is being run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root."
exit 1
fi
# Save the current directory
CUR_DIR=$(pwd)
# Get system architecture
ARCH=$(uname -m)
# Change to the temporary directory
cd /tmp
# Download the latest version of the package for the system architecture
# Exit if the download fails
echo "Downloading gh-wh-handler..."
curl -fsSL https://cdn.tiagorg.pt/gh-wh-handler/gh-wh-handler.${ARCH}.latest.tar.gz -o gh-wh-handler.tar.gz || { echo "Download failed."; exit 1; }
# Extract the package
echo "Extracting gh-wh-handler..."
tar -xzf gh-wh-handler.tar.gz || { echo "Extraction failed."; exit 1; }
# Change to the extracted directory
cd gh-wh-handler
# Install the package
echo "Installing gh-wh-handler..."
# Create service directory
echo "Creating service directory..."
mkdir -p /services/gh-wh-handler
mkdir -p /services/gh-wh-handler/logs
# Copy the binary and configuration file to the service directory
echo "Copying files..."
cp "gh-wh-handler.${ARCH}" /services/gh-wh-handler/
cp "config.json" /services/gh-wh-handler/
# Create a symbolic link to the binary in /usr/bin
echo "Creating symbolic link..."
ln -sf /services/gh-wh-handler/gh-wh-handler.${ARCH} /usr/bin/gh-wh-handler
# Copy the service file to the systemd directory
echo "Copying service file..."
cp "gh-wh-handler.service" /etc/systemd/system/
# Reload systemd
echo "Reloading systemd..."
systemctl daemon-reload
# Enable and start the service
echo "Enabling and starting service..."
systemctl enable gh-wh-handler
systemctl start gh-wh-handler
# Clean up
echo "Cleaning up..."
cd /tmp
rm -rf gh-wh-handler
rm gh-wh-handler.tar.gz
# Change back to the original directory
cd $CUR_DIR
echo "Installation complete."

View File

@ -1,42 +0,0 @@
#!/bin/sh
# Check if the script is being run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root."
exit 1
fi
# Save the current directory
CUR_DIR=$(pwd)
# Get system architecture
ARCH=$(uname -m)
echo "Uninstalling gh-wh-handler..."
# Stop and disable the service
echo "Stopping and disabling service..."
systemctl stop gh-wh-handler
systemctl disable gh-wh-handler
# Remove the service file
echo "Removing service file..."
rm /etc/systemd/system/gh-wh-handler.service
# Reload systemd
echo "Reloading systemd..."
systemctl daemon-reload
# Remove the symbolic link
echo "Removing symbolic link..."
rm /usr/bin/gh-wh-handler
# Remove the logs directory and binary
echo "Removing files..."
rm -rf /services/gh-wh-handler/logs
rm -f /services/gh-wh-handler/gh-wh-handler.${ARCH}
# Change back to the original directory
cd $CUR_DIR
echo "Uninstallation complete."

View File

@ -5,27 +5,28 @@
#include "logger.hpp"
void Config::create_config(std::string config_file_path) {
void Config::create_config() {
std::cout << "Creating config file" << std::endl;
nlohmann::json config = {
{"port", 65001},
{"update-files", nlohmann::json::array()},
{"run-scripts", nlohmann::json::array()},
{"tokens", nlohmann::json::array()},
};
std::string path_to_config = config_file_path.substr(0, config_file_path.find_last_of('/'));
if (!std::filesystem::exists(path_to_config)) {
if (!std::filesystem::exists("/services/gh-wh-handler")) {
try {
std::filesystem::create_directories(path_to_config);
std::filesystem::create_directories("/services/gh-wh-handler");
} catch (std::exception &e) {
Logger::error("[Config] Error creating directory '" + path_to_config +"': " + std::string(e.what()));
Logger::error("[Config] Error creating directory '/services/gh-wh-handler/': " + std::string(e.what()));
return;
}
}
try {
std::ofstream config_file(config_file_path);
std::ofstream config_file("/services/gh-wh-handler/config.json");
config_file << config.dump(2);
config_file.close();
} catch (std::exception& e) {
Logger::fatal("[Config] Error creating config file: " + std::string(e.what()));
Logger::error("[Config] Error creating config file: " + std::string(e.what()));
}
}
@ -38,21 +39,7 @@ nlohmann::json Config::get_config(std::string config_file_path) {
config_file >> config;
config_file.close();
} catch (std::exception& e) {
Logger::fatal("Error loading config file: " + std::string(e.what()));
}
if (config.is_null()) {
Logger::fatal("[Config] Config file is empty");
}
if (config.find("port") == config.end()) {
Logger::warn("[Config] Port not found in config file, using default port 65001");
config["port"] = 65001;
}
if (config.find("tokens") == config.end()) {
Logger::warn("[Config] Tokens not found in config file, using empty array. Private repositories will not be accessible.");
config["tokens"] = nlohmann::json::array();
Logger::error("Error loading config file: " + std::string(e.what()));
}
Logger::success("[Config] Loaded config file: " + config_file_path);

View File

@ -131,7 +131,6 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
std::string remote_path = file[0];
std::string local_path = config_update_files[repo]["files"][remote_path];
if (local_path.find_last_of('/') != std::string::npos)
try {
std::filesystem::create_directories(local_path.substr(0, local_path.find_last_of('/')));
} catch (const std::exception &e) {

View File

@ -1,5 +1,4 @@
#include "logger.hpp"
#include <filesystem>
#include <iostream>
#include <fstream>
#include <ctime>
@ -34,29 +33,8 @@
std::ofstream Logger::log_file;
void Logger::init(std::string logs_dir) {
void Logger::init(std::string log_file_path) {
std::cout << "Initializing logger" << std::endl;
std::cout << "Logs directory: " << logs_dir << std::endl;
// check if logs_dir exists
if (!std::filesystem::exists(logs_dir)) {
try {
std::filesystem::create_directories(logs_dir);
} catch (std::exception &e) {
std::cerr << "Error creating logs directory: " << e.what() << std::endl;
std::exit(1);
}
}
// check if logs_dir ends with a slash
if (logs_dir.back() != '/') {
logs_dir += "/";
}
std::time_t now = std::time(nullptr);
std::tm *now_tm = std::localtime(&now);
char time_buffer[80];
std::strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d_%H-%M-%S", now_tm);
std::string log_file_path = logs_dir + "gh-wh-handler_" + time_buffer + ".log";
std::cout << "Log file: " << log_file_path << std::endl;
Logger::log_file.open(log_file_path, std::ios::app);
if (!Logger::log_file.is_open()) {
@ -90,6 +68,7 @@ void Logger::code(std::string message) {
}
void Logger::log(std::string message, std::string level) {
// Implement logger with terminal colors if terminal supports it
std::string formatted_message = "";
if (isatty(fileno(stdout))) {
if (level == "INFO ") {
@ -119,8 +98,8 @@ void Logger::log(std::string message, std::string level) {
if (level == "CODE") {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
formatted_message += "\n" + std::string(term_width, '=') + "\n" + message + "\n" + std::string(term_width, '=');
int term_width = w.ws_col;
formatted_message += "\n" + std::string(term_width - 1, '=') + "\n" + message + "\n" + std::string(term_width - 1, '=');
} else {
formatted_message += "[" + level + "] " + message;
}
@ -131,8 +110,8 @@ void Logger::log(std::string message, std::string level) {
if (level == "CODE") {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
Logger::log_file << std::string(term_width, '=') << std::endl << message << std::endl << std::string(term_width, '=') << std::endl;
int term_width = w.ws_col;
Logger::log_file << std::string(term_width - 1, '=') << std::endl << message << std::endl << std::string(term_width - 1, '=') << std::endl;
} else {
Logger::log_file << "[" << level << "] " << message << std::endl;
}

View File

@ -15,8 +15,8 @@ void signal_handler(const int signum) {
int main(int argc, char **argv) {
// Check for config file argument, exit if it's not there
if (argc < 3 || argc > 4) {
std::cerr << "Usage: " << 0[argv] << " </path/to/config.json> </path/to/logs_dir> [--config]" << std::endl;
if (argc < 2 || argc > 3) {
std::cerr << "Usage: " << 0[argv] << " </path/to/config.json> [--config]" << std::endl;
return 1;
}
@ -26,11 +26,10 @@ int main(int argc, char **argv) {
std::strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d_%H-%M-%S", now_tm);
std::string config_file_path = 1[argv];
std::string logs_dir = 2[argv];
std::string log_file_path = config_file_path.substr(0, config_file_path.find_last_of("/")) + "/gh-wh-handler_" + time_buffer + ".log";
Logger::init(log_file_path);
Logger::init(logs_dir);
if (argc == 4 && std::string(argv[3]) == "--config") {
if (argc == 3 && std::string(argv[2]) == "--config") {
Config::open_config_menu();
return 0;
}
@ -38,7 +37,7 @@ int main(int argc, char **argv) {
// Check if config file exists
if (!std::filesystem::exists(config_file_path)) {
Logger::warn("Config file does not exist, creating...");
Config::create_config(config_file_path);
Config::create_config();
}
// Load configuration

View File

@ -23,7 +23,6 @@ Routes::Routes(nlohmann::json config) {
return crow::response(200, response.dump());
});
if (!config_update_files.is_null()) {
Logger::info("Registering route \"/update-files\"");
CROW_ROUTE(this->app, "/update-files")
.methods("POST"_method)
@ -40,9 +39,7 @@ Routes::Routes(nlohmann::json config) {
return crow::response(500, response.dump());
}
});
}
if (!config_run_scripts.is_null()) {
Logger::info("Registering route \"/run-scripts\"");
CROW_ROUTE(this->app, "/run-scripts")
.methods("POST"_method)
@ -59,13 +56,8 @@ Routes::Routes(nlohmann::json config) {
return crow::response(500, response.dump());
}
});
}
Logger::info("Starting server");
try {
this->app.port(config["port"].get<int>()).multithreaded().run();
} catch (const std::exception &e) {
Logger::fatal("Error starting server: " + std::string(e.what()));
}
Logger::info("Server stopped");
}