Compare commits
4 Commits
5339c1f791
...
52ea711718
Author | SHA1 | Date |
---|---|---|
Tiago Garcia | 52ea711718 | |
Tiago Garcia | e22fa683e0 | |
Tiago Garcia | 3234dc6e39 | |
Tiago Garcia | 04683c8ebc |
|
@ -1,15 +1,19 @@
|
|||
# CMake build directories
|
||||
CMakeFiles/
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
*.cmake
|
||||
*.cbp
|
||||
*.layout
|
||||
*.stackdump
|
||||
CPackConfig.cmake
|
||||
Makefile
|
||||
CTestTestfile.cmake
|
||||
install_manifest.txt
|
||||
/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
|
||||
|
||||
# Compiled binaries
|
||||
/bin/
|
||||
|
|
|
@ -48,15 +48,19 @@ 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_RUNTIME_OUTPUT_DIRECTORY}/gh-wh-handler.service"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/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_RUNTIME_OUTPUT_DIRECTORY}/gh-wh-handler.service"
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/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)")
|
||||
|
|
77
README.md
77
README.md
|
@ -2,9 +2,23 @@
|
|||
|
||||
## Simple C++ WebAPI to work with GitHub Webhooks
|
||||
|
||||
Currently creating a local copy of remote files on every push
|
||||
This application is a simple C++ WebAPI that listens for GitHub Webhooks and performs actions based on the received data and configuration.
|
||||
|
||||
## Usage
|
||||
## 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
|
||||
```
|
||||
|
||||
### Run prebuilt binary
|
||||
|
||||
|
@ -12,7 +26,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/gh-wh-handler.<arch> /path/to/config.json /path/to/logs_dir
|
||||
```
|
||||
|
||||
You can see the config file format below.
|
||||
|
@ -24,7 +38,7 @@ You can see the config file format below.
|
|||
- [CrowCpp](https://crowcpp.org/master/)
|
||||
- [nlohmann::json](https://github.com/nlohmann/json)
|
||||
|
||||
#### Build the application:
|
||||
#### Build and install the application:
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
|
@ -47,7 +61,12 @@ cmake ..
|
|||
sudo make install
|
||||
```
|
||||
|
||||
#### Run the application:
|
||||
If you want to uninstall the application, you can run the following command:
|
||||
```console
|
||||
sudo make uninstall
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The application is running on a systemd service, which is both enabled and started after installation.
|
||||
|
||||
|
@ -72,12 +91,37 @@ 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 format:
|
||||
The configuration file can be found in `/services/gh-wh-handler/config.json` and has the following base format:
|
||||
|
||||
```json
|
||||
{
|
||||
"port": 65001,
|
||||
"update-files": {
|
||||
"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",
|
||||
"files": {
|
||||
|
@ -90,28 +134,9 @@ The configuration file can be found in `/services/gh-wh-handler/config.json` and
|
|||
"..."
|
||||
]
|
||||
}
|
||||
},
|
||||
"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.
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"port": 65001,
|
||||
"update-files": {
|
||||
},
|
||||
"run-scripts": {
|
||||
},
|
||||
"tokens": {
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ Description=Runs github webhook handler
|
|||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@SERVICE_EXECUTABLE@ @SERVICE_CONFIG@
|
||||
ExecStart=@SERVICE_EXECUTABLE@ @SERVICE_CONFIG@ @SERVICE_LOGS@
|
||||
Restart=always
|
||||
Type=simple
|
||||
|
||||
|
|
|
@ -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 service directory...")
|
||||
file(REMOVE /services/gh-wh-handler)
|
||||
message(STATUS "[85%] Removing all log files...")
|
||||
execute_process(COMMAND rm -fr /services/gh-wh-handler/logs)
|
||||
|
||||
message(STATUS "[100%] Uninstallation complete!")
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
class Config {
|
||||
public:
|
||||
static void create_config();
|
||||
static void create_config(std::string config_file_path);
|
||||
static nlohmann::json get_config(std::string config_file_path);
|
||||
static void open_config_menu();
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
#!/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."
|
|
@ -0,0 +1,42 @@
|
|||
#!/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."
|
||||
|
|
@ -5,28 +5,27 @@
|
|||
|
||||
#include "logger.hpp"
|
||||
|
||||
void Config::create_config() {
|
||||
void Config::create_config(std::string config_file_path) {
|
||||
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()},
|
||||
};
|
||||
if (!std::filesystem::exists("/services/gh-wh-handler")) {
|
||||
std::string path_to_config = config_file_path.substr(0, config_file_path.find_last_of('/'));
|
||||
if (!std::filesystem::exists(path_to_config)) {
|
||||
try {
|
||||
std::filesystem::create_directories("/services/gh-wh-handler");
|
||||
std::filesystem::create_directories(path_to_config);
|
||||
} catch (std::exception &e) {
|
||||
Logger::error("[Config] Error creating directory '/services/gh-wh-handler/': " + std::string(e.what()));
|
||||
Logger::error("[Config] Error creating directory '" + path_to_config +"': " + std::string(e.what()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
std::ofstream config_file("/services/gh-wh-handler/config.json");
|
||||
std::ofstream config_file(config_file_path);
|
||||
config_file << config.dump(2);
|
||||
config_file.close();
|
||||
} catch (std::exception& e) {
|
||||
Logger::error("[Config] Error creating config file: " + std::string(e.what()));
|
||||
Logger::fatal("[Config] Error creating config file: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +38,21 @@ nlohmann::json Config::get_config(std::string config_file_path) {
|
|||
config_file >> config;
|
||||
config_file.close();
|
||||
} catch (std::exception& e) {
|
||||
Logger::error("Error loading config file: " + std::string(e.what()));
|
||||
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::success("[Config] Loaded config file: " + config_file_path);
|
||||
|
|
|
@ -131,6 +131,7 @@ 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) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "logger.hpp"
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
|
@ -33,8 +34,29 @@
|
|||
|
||||
std::ofstream Logger::log_file;
|
||||
|
||||
void Logger::init(std::string log_file_path) {
|
||||
void Logger::init(std::string logs_dir) {
|
||||
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()) {
|
||||
|
@ -68,7 +90,6 @@ 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 ") {
|
||||
|
@ -98,8 +119,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;
|
||||
formatted_message += "\n" + std::string(term_width - 1, '=') + "\n" + message + "\n" + std::string(term_width - 1, '=');
|
||||
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, '=');
|
||||
} else {
|
||||
formatted_message += "[" + level + "] " + message;
|
||||
}
|
||||
|
@ -110,15 +131,15 @@ 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;
|
||||
Logger::log_file << std::string(term_width - 1, '=') << std::endl << message << std::endl << std::string(term_width - 1, '=') << std::endl;
|
||||
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;
|
||||
} else {
|
||||
Logger::log_file << "[" << level << "] " << message << std::endl;
|
||||
}
|
||||
|
||||
Logger::log_file.flush();
|
||||
|
||||
if (level == "FATAL") {
|
||||
if (level == "FATAL ") {
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
13
src/main.cpp
13
src/main.cpp
|
@ -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 < 2 || argc > 3) {
|
||||
std::cerr << "Usage: " << 0[argv] << " </path/to/config.json> [--config]" << std::endl;
|
||||
if (argc < 3 || argc > 4) {
|
||||
std::cerr << "Usage: " << 0[argv] << " </path/to/config.json> </path/to/logs_dir> [--config]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,11 @@ 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 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);
|
||||
std::string logs_dir = 2[argv];
|
||||
|
||||
if (argc == 3 && std::string(argv[2]) == "--config") {
|
||||
Logger::init(logs_dir);
|
||||
|
||||
if (argc == 4 && std::string(argv[3]) == "--config") {
|
||||
Config::open_config_menu();
|
||||
return 0;
|
||||
}
|
||||
|
@ -37,7 +38,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::create_config(config_file_path);
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
|
|
|
@ -23,6 +23,7 @@ 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)
|
||||
|
@ -39,7 +40,9 @@ 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)
|
||||
|
@ -56,8 +59,13 @@ 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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue