This commit is contained in:
47
getpkg/CMakeLists.txt
Normal file
47
getpkg/CMakeLists.txt
Normal file
@ -0,0 +1,47 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Project setup
|
||||
if(NOT DEFINED PROJECT_NAME)
|
||||
message(FATAL_ERROR "PROJECT_NAME is not defined. Pass it via -DPROJECT_NAME=<name>")
|
||||
endif()
|
||||
|
||||
string(TIMESTAMP PROJECT_VERSION "%Y.%m%d.%H%M")
|
||||
project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
|
||||
# Build configuration
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-static")
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(CMAKE_PREFIX_PATH /usr/local)
|
||||
|
||||
# Create executable
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
|
||||
# Configure version.hpp
|
||||
configure_file("src/version.hpp.in" "src/autogen/version.hpp" @ONLY)
|
||||
|
||||
# Pre-build script
|
||||
add_custom_target(run_prebuild_script ALL
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_dependencies(${PROJECT_NAME} run_prebuild_script)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src/autogen
|
||||
src)
|
||||
|
||||
# Find packages
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(Drogon CONFIG REQUIRED)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
nlohmann_json::nlohmann_json Drogon::Drogon
|
||||
/usr/local/lib/libpgcommon.a /usr/local/lib/libpgport.a
|
||||
lzma dl)
|
||||
|
72
getpkg/Dockerfile.dropshell-build
Normal file
72
getpkg/Dockerfile.dropshell-build
Normal file
@ -0,0 +1,72 @@
|
||||
ARG IMAGE_TAG
|
||||
FROM gitea.jde.nz/public/dropshell-build-base:latest AS builder
|
||||
|
||||
ARG PROJECT
|
||||
ARG CMAKE_BUILD_TYPE=Debug
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Create cache directories
|
||||
RUN mkdir -p /ccache
|
||||
|
||||
# Set up ccache
|
||||
ENV CCACHE_DIR=/ccache
|
||||
ENV CCACHE_COMPILERCHECK=content
|
||||
ENV CCACHE_MAXSIZE=2G
|
||||
|
||||
# Copy only build files first (for better layer caching)
|
||||
COPY CMakeLists.txt cmake_prebuild.sh ./
|
||||
COPY src/version.hpp.in src/
|
||||
|
||||
# Run prebuild script early (this rarely changes)
|
||||
RUN bash cmake_prebuild.sh
|
||||
|
||||
# Copy source files (this invalidates cache when source changes)
|
||||
COPY src/ src/
|
||||
|
||||
# Configure project (this step is cached unless CMakeLists.txt changes)
|
||||
RUN --mount=type=cache,target=/ccache \
|
||||
--mount=type=cache,target=/build \
|
||||
mkdir -p /build && \
|
||||
SSL_LIB=$(find /usr/local -name "libssl.a" | head -1) && \
|
||||
CRYPTO_LIB=$(find /usr/local -name "libcrypto.a" | head -1) && \
|
||||
echo "Found SSL: $SSL_LIB, Crypto: $CRYPTO_LIB" && \
|
||||
cmake -G Ninja -S /app -B /build \
|
||||
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold -static -g" \
|
||||
-DCMAKE_CXX_FLAGS="-g -fno-omit-frame-pointer" \
|
||||
-DCMAKE_C_FLAGS="-g -fno-omit-frame-pointer" \
|
||||
-DPROJECT_NAME="${PROJECT}" \
|
||||
-DCMAKE_STRIP=OFF \
|
||||
-DOPENSSL_SSL_LIBRARY="$SSL_LIB" \
|
||||
-DOPENSSL_CRYPTO_LIBRARY="$CRYPTO_LIB" \
|
||||
-DOPENSSL_INCLUDE_DIR=/usr/local/include \
|
||||
${CMAKE_TOOLCHAIN_FILE:+-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE}
|
||||
|
||||
# Run prebuild script
|
||||
RUN --mount=type=cache,target=/ccache \
|
||||
--mount=type=cache,target=/build \
|
||||
cmake --build /build --target run_prebuild_script
|
||||
|
||||
# Build project (ccache will help here when only some files change)
|
||||
RUN --mount=type=cache,target=/ccache \
|
||||
--mount=type=cache,target=/build \
|
||||
cmake --build /build
|
||||
|
||||
# Copy the built executable to a regular directory for the final stage
|
||||
RUN --mount=type=cache,target=/build \
|
||||
mkdir -p /output && \
|
||||
find /build -type f -executable -name "*${PROJECT}*" -exec cp {} /output/${PROJECT} \; || \
|
||||
find /build -type f -executable -exec cp {} /output/${PROJECT} \;
|
||||
|
||||
# Final stage that only contains the binary
|
||||
FROM scratch AS project
|
||||
|
||||
ARG PROJECT
|
||||
|
||||
# Copy the actual binary from the regular directory
|
||||
COPY --from=builder /output/${PROJECT} /${PROJECT}
|
||||
|
22
getpkg/build.sh
Executable file
22
getpkg/build.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
|
||||
export CMAKE_BUILD_TYPE="Debug"
|
||||
|
||||
rm -rf "${SCRIPT_DIR}/output"
|
||||
mkdir -p "${SCRIPT_DIR}/output"
|
||||
|
||||
PROJECT="dropshell-tool"
|
||||
|
||||
docker build \
|
||||
-t "${PROJECT}-build" \
|
||||
-f "${SCRIPT_DIR}/Dockerfile.dropshell-build" \
|
||||
--build-arg PROJECT="${PROJECT}" \
|
||||
--build-arg CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
|
||||
--output "${SCRIPT_DIR}/output" \
|
||||
"${SCRIPT_DIR}"
|
||||
|
8
getpkg/cmake_prebuild.sh
Executable file
8
getpkg/cmake_prebuild.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
echo "cmake_prebuild.sh complete."
|
||||
|
19
getpkg/install.sh
Executable file
19
getpkg/install.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
echo "Installing dropshell-tool"
|
||||
|
||||
ARCH=$(uname -m)
|
||||
|
||||
wget "https://getbin.xyz/dropshell-tool:latest-${ARCH}" -O bootstrap && chmod a+x bootstrap
|
||||
|
||||
./bootstrap install dropshell-tool
|
||||
|
||||
rm ./bootstrap
|
||||
|
||||
VERSION=$(dropshell-tool version)
|
||||
|
||||
echo "Dropshell tool $VERSION installed"
|
46
getpkg/publish.sh
Executable file
46
getpkg/publish.sh
Executable file
@ -0,0 +1,46 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
ARCH=$(uname -m)
|
||||
TEMP_DIR="${SCRIPT_DIR}/temp"
|
||||
SOS="${TEMP_DIR}/sos"
|
||||
|
||||
echo "Publishing ${PROJECT} to gitea.jde.nz/public/${PROJECT}:latest"
|
||||
|
||||
function die() {
|
||||
title "error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -n $SOS_WRITE_TOKEN ]] || die "SOS_WRITE_TOKEN not specified"
|
||||
|
||||
# clear output dir
|
||||
rm -rf "${SCRIPT_DIR}/output"
|
||||
mkdir -p "${SCRIPT_DIR}/output"
|
||||
|
||||
# build release version
|
||||
export CMAKE_BUILD_TYPE="Release"
|
||||
export PROJECT="dropshell-tool"
|
||||
|
||||
docker build \
|
||||
-t "${PROJECT}-build" \
|
||||
-f "${SCRIPT_DIR}/Dockerfile.dropshell-build" \
|
||||
--build-arg PROJECT="${PROJECT}" \
|
||||
--build-arg CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
|
||||
--output "${SCRIPT_DIR}/output" \
|
||||
"${SCRIPT_DIR}"
|
||||
|
||||
[ -f ${SCRIPT_DIR}/output/dropshell-tool ] || die "Build failed."
|
||||
|
||||
# download the sos binary
|
||||
mkdir -p "${TEMP_DIR}"
|
||||
trap 'rm -rf "${TEMP_DIR}"' EXIT
|
||||
curl -L -o "${SOS}" "https://getbin.xyz/sos"
|
||||
chmod +x "${SOS}"
|
||||
|
||||
# upload arch-specific binary
|
||||
"${SOS}" upload "getbin.xyz" "${SCRIPT_DIR}/output/${PROJECT}" "${PROJECT}:latest-${ARCH}"
|
||||
|
||||
# upload generic install script (ok if multiple times as we iterate through arch's)
|
||||
"${SOS}" upload "getbin.xyz" "${SCRIPT_DIR}/install.sh" "${PROJECT}-install:latest"
|
71
getpkg/src/ArchiveManager.cpp
Normal file
71
getpkg/src/ArchiveManager.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ArchiveManager.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ArchiveManager::ArchiveManager() {}
|
||||
|
||||
bool ArchiveManager::pack(const std::string& folderPath, const std::string& archivePath) {
|
||||
// Use system tar to create gzipped tarball
|
||||
std::ostringstream cmd;
|
||||
cmd << "tar -czf '" << archivePath << "' -C '" << folderPath << "' .";
|
||||
int ret = std::system(cmd.str().c_str());
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool ArchiveManager::unpack(const std::string& archivePath, const std::string& outDir) {
|
||||
fs::create_directories(outDir);
|
||||
std::ostringstream cmd;
|
||||
cmd << "tar -xzf '" << archivePath << "' -C '" << outDir << "'";
|
||||
int ret = std::system(cmd.str().c_str());
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool ArchiveManager::readConfigJson(const std::string& archivePath, std::string& outJson) {
|
||||
// Extract config json to stdout
|
||||
std::ostringstream cmd;
|
||||
cmd << "tar -xOzf '" << archivePath << "' dropshell-tool-config.json";
|
||||
FILE* pipe = popen(cmd.str().c_str(), "r");
|
||||
if (!pipe) return false;
|
||||
char buffer[4096];
|
||||
std::ostringstream ss;
|
||||
size_t n;
|
||||
while ((n = fread(buffer, 1, sizeof(buffer), pipe)) > 0) {
|
||||
ss.write(buffer, n);
|
||||
}
|
||||
int ret = pclose(pipe);
|
||||
if (ret != 0) return false;
|
||||
outJson = ss.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveManager::writeConfigJson(const std::string& archivePath, const std::string& json) {
|
||||
// 1. Extract archive to temp dir
|
||||
std::string tmpDir = "/tmp/dropshell_tool_tmp_" + std::to_string(::getpid());
|
||||
fs::create_directories(tmpDir);
|
||||
std::ostringstream extractCmd;
|
||||
extractCmd << "tar -xzf '" << archivePath << "' -C '" << tmpDir << "'";
|
||||
if (std::system(extractCmd.str().c_str()) != 0) return false;
|
||||
// 2. Write new config json
|
||||
std::ofstream ofs(tmpDir + "/dropshell-tool-config.json", std::ios::binary);
|
||||
if (!ofs) return false;
|
||||
ofs << json;
|
||||
ofs.close();
|
||||
// 3. Repack
|
||||
std::ostringstream packCmd;
|
||||
packCmd << "tar -czf '" << archivePath << "' -C '" << tmpDir << "' .";
|
||||
int ret = std::system(packCmd.str().c_str());
|
||||
// 4. Cleanup
|
||||
std::ostringstream rmCmd;
|
||||
rmCmd << "rm -rf '" << tmpDir << "'";
|
||||
std::system(rmCmd.str().c_str());
|
||||
return ret == 0;
|
||||
}
|
11
getpkg/src/ArchiveManager.hpp
Normal file
11
getpkg/src/ArchiveManager.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class ArchiveManager {
|
||||
public:
|
||||
ArchiveManager();
|
||||
bool pack(const std::string& folderPath, const std::string& archivePath);
|
||||
bool unpack(const std::string& archivePath, const std::string& outDir);
|
||||
bool readConfigJson(const std::string& archivePath, std::string& outJson);
|
||||
bool writeConfigJson(const std::string& archivePath, const std::string& json);
|
||||
};
|
134
getpkg/src/BashrcEditor.cpp
Normal file
134
getpkg/src/BashrcEditor.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "BashrcEditor.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include "DropshellScriptManager.hpp"
|
||||
|
||||
namespace dropshelltool
|
||||
{
|
||||
const std::filesystem::path DROPSHELL_RC_PATH = std::filesystem::path(std::getenv("HOME")) / ".bashrc_dropshell_tool";
|
||||
static const std::filesystem::path BASHRC_PATH = std::filesystem::path(std::getenv("HOME")) / ".bashrc";
|
||||
|
||||
std::string removeWhitespace(const std::string &s)
|
||||
{
|
||||
std::string out;
|
||||
for (char c : s)
|
||||
{
|
||||
if (!isspace(static_cast<unsigned char>(c)))
|
||||
out += c;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static constexpr const char *BLOCK_START = "#---DROPSHELL-TOOL-START---";
|
||||
static constexpr const char *BLOCK_END = "#---DROPSHELL-TOOL-END---";
|
||||
|
||||
BashrcEditor::BashrcEditor()
|
||||
{
|
||||
}
|
||||
|
||||
bool BashrcEditor::hasSourceLine() const
|
||||
{
|
||||
std::ifstream infile(BASHRC_PATH);
|
||||
if (!infile)
|
||||
return false;
|
||||
std::string line;
|
||||
bool inBlock = false;
|
||||
const std::string blockStart = removeWhitespace(BLOCK_START);
|
||||
const std::string blockEnd = removeWhitespace(BLOCK_END);
|
||||
const std::string target = "source \"" + DROPSHELL_RC_PATH.string() + "\"";
|
||||
while (std::getline(infile, line))
|
||||
{
|
||||
std::string trimmed = removeWhitespace(line);
|
||||
if (trimmed == blockStart)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BashrcEditor::addSourceLine()
|
||||
{
|
||||
std::ifstream infile(BASHRC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
bool inBlock = false;
|
||||
const std::string blockStart = removeWhitespace(BLOCK_START);
|
||||
const std::string blockEnd = removeWhitespace(BLOCK_END);
|
||||
while (std::getline(infile, line))
|
||||
{
|
||||
std::string trimmed = removeWhitespace(line);
|
||||
if (trimmed == blockStart)
|
||||
{
|
||||
inBlock = true;
|
||||
continue;
|
||||
}
|
||||
if (trimmed == blockEnd)
|
||||
{
|
||||
inBlock = false;
|
||||
continue;
|
||||
}
|
||||
if (!inBlock)
|
||||
{
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
lines.push_back(BLOCK_START);
|
||||
lines.push_back("source \"" + DROPSHELL_RC_PATH.string() + "\"");
|
||||
lines.push_back(BLOCK_END);
|
||||
std::ofstream outfile(BASHRC_PATH, std::ios::trunc);
|
||||
for (const auto &l : lines)
|
||||
{
|
||||
outfile << l << "\n";
|
||||
}
|
||||
|
||||
outfile.close();
|
||||
|
||||
if (!std::filesystem::exists(DROPSHELL_RC_PATH))
|
||||
{
|
||||
DropshellScriptManager scriptManager;
|
||||
scriptManager.ensureExists();
|
||||
}
|
||||
}
|
||||
|
||||
void BashrcEditor::removeSourceLine()
|
||||
{
|
||||
std::ifstream infile(BASHRC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
bool inBlock = false;
|
||||
const std::string blockStart = removeWhitespace(BLOCK_START);
|
||||
const std::string blockEnd = removeWhitespace(BLOCK_END);
|
||||
while (std::getline(infile, line))
|
||||
{
|
||||
std::string trimmed = removeWhitespace(line);
|
||||
if (trimmed == blockStart)
|
||||
{
|
||||
inBlock = true;
|
||||
continue;
|
||||
}
|
||||
if (trimmed == blockEnd)
|
||||
{
|
||||
inBlock = false;
|
||||
continue;
|
||||
}
|
||||
if (!inBlock)
|
||||
{
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
std::ofstream outfile(BASHRC_PATH, std::ios::trunc);
|
||||
for (const auto &l : lines)
|
||||
{
|
||||
outfile << l << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
20
getpkg/src/BashrcEditor.hpp
Normal file
20
getpkg/src/BashrcEditor.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
namespace dropshelltool {
|
||||
|
||||
extern const std::filesystem::path DROPSHELL_RC_PATH;
|
||||
|
||||
class BashrcEditor {
|
||||
public:
|
||||
BashrcEditor();
|
||||
// Checks if the unique block with the source line exists
|
||||
bool hasSourceLine() const;
|
||||
// Adds or updates the unique block with the given scriptPath
|
||||
void addSourceLine();
|
||||
// Removes the unique block
|
||||
void removeSourceLine();
|
||||
};
|
||||
|
||||
} // namespace dropshelltool
|
170
getpkg/src/DropshellScriptManager.cpp
Normal file
170
getpkg/src/DropshellScriptManager.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
#include "DropshellScriptManager.hpp"
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
namespace dropshelltool {
|
||||
extern const std::filesystem::path DROPSHELL_RC_PATH;
|
||||
}
|
||||
|
||||
DropshellScriptManager::DropshellScriptManager() {}
|
||||
|
||||
// Helper function to trim whitespace from both ends of a string
|
||||
static std::string trim(const std::string& s) {
|
||||
auto start = s.begin();
|
||||
while (start != s.end() && std::isspace(*start)) ++start;
|
||||
auto end = s.end();
|
||||
do { --end; } while (std::distance(start, end) > 0 && std::isspace(*end));
|
||||
return std::string(start, end + 1);
|
||||
}
|
||||
|
||||
void DropshellScriptManager::ensureExists() const {
|
||||
if (!std::filesystem::exists(dropshelltool::DROPSHELL_RC_PATH)) {
|
||||
std::filesystem::create_directories(dropshelltool::DROPSHELL_RC_PATH.parent_path());
|
||||
std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc);
|
||||
outfile << "# This file is managed by dropshell-tool. Do not edit manually!" << std::endl;
|
||||
outfile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void DropshellScriptManager::addToolEntry(const std::string& toolName, const std::string& toolDir) {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
std::string exportLine = "export PATH=\"" + toolDir + ":$PATH\" # dropshell-tool:" + toolName;
|
||||
bool found = false;
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.find("# dropshell-tool:" + toolName) != std::string::npos) {
|
||||
found = true;
|
||||
lines.push_back(exportLine);
|
||||
} else {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
if (!found) lines.push_back(exportLine);
|
||||
std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc);
|
||||
for (const auto& l : lines) outfile << l << "\n";
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
void DropshellScriptManager::removeToolEntry(const std::string& toolName) {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.find("# dropshell-tool:" + toolName) == std::string::npos) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc);
|
||||
for (const auto& l : lines) outfile << l << "\n";
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
void DropshellScriptManager::addAlias(const std::string& alias, const std::string& toolName) {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
std::string aliasLine = "alias " + alias + "='" + toolName + "' # dropshell-alias:" + alias;
|
||||
bool found = false;
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.find("# dropshell-alias:" + alias) != std::string::npos) {
|
||||
found = true;
|
||||
lines.push_back(aliasLine);
|
||||
} else {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
if (!found) lines.push_back(aliasLine);
|
||||
std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc);
|
||||
for (const auto& l : lines) outfile << l << "\n";
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
void DropshellScriptManager::addAutocomplete(const std::string& toolName) {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
std::string funcName = "_" + toolName + "_autocomplete";
|
||||
std::string blockStart = "# DROPSHELL-AUTOCOMPLETE-START: " + toolName;
|
||||
std::string blockEnd = "# DROPSHELL-AUTOCOMPLETE-END: " + toolName;
|
||||
std::string funcDef;
|
||||
funcDef += blockStart + "\n";
|
||||
funcDef += funcName + "() {\n";
|
||||
funcDef += " local cur=\"${COMP_WORDS[COMP_CWORD]}\"\n";
|
||||
funcDef += " mapfile -t completions < <(" + toolName + " autocomplete \"${COMP_WORDS[@]:1:${COMP_CWORD}-1}\")\n";
|
||||
funcDef += " mapfile -t COMPREPLY < <(compgen -W \"${completions[*]}\" -- \"$cur\")\n";
|
||||
funcDef += "}\n";
|
||||
funcDef += blockEnd + "\n";
|
||||
std::string completeLine = "complete -F " + funcName + " " + toolName + " # dropshell-autocomplete:" + toolName;
|
||||
bool blockFound = false, completeFound = false;
|
||||
bool inBlock = false;
|
||||
while (std::getline(infile, line)) {
|
||||
if (!inBlock && trim(line) == blockStart) {
|
||||
blockFound = true;
|
||||
inBlock = true;
|
||||
continue;
|
||||
}
|
||||
if (inBlock) {
|
||||
if (trim(line) == blockEnd) {
|
||||
inBlock = false;
|
||||
lines.push_back(funcDef); // Add new function block
|
||||
}
|
||||
continue; // Skip all lines in the old block
|
||||
}
|
||||
if (line.find("# dropshell-autocomplete:" + toolName) != std::string::npos) {
|
||||
completeFound = true;
|
||||
lines.push_back(completeLine);
|
||||
} else {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
if (!blockFound) lines.push_back(funcDef);
|
||||
if (!completeFound) lines.push_back(completeLine);
|
||||
std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc);
|
||||
for (const auto& l : lines) outfile << l << "\n";
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
bool DropshellScriptManager::hasAlias(const std::string& alias) const {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::string line;
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.find("# dropshell-alias:" + alias) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> DropshellScriptManager::listAliases() const {
|
||||
ensureExists();
|
||||
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
|
||||
std::string line;
|
||||
std::vector<std::string> aliases;
|
||||
while (std::getline(infile, line)) {
|
||||
auto pos = line.find("# dropshell-alias:");
|
||||
if (pos != std::string::npos) {
|
||||
auto aliasStart = line.find("alias ");
|
||||
if (aliasStart != std::string::npos) {
|
||||
auto eq = line.find('=', aliasStart + 6);
|
||||
if (eq != std::string::npos) {
|
||||
std::string alias = line.substr(aliasStart + 6, eq - (aliasStart + 6));
|
||||
aliases.push_back(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliases;
|
||||
}
|
15
getpkg/src/DropshellScriptManager.hpp
Normal file
15
getpkg/src/DropshellScriptManager.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class DropshellScriptManager {
|
||||
public:
|
||||
DropshellScriptManager();
|
||||
void ensureExists() const;
|
||||
void addToolEntry(const std::string& toolName, const std::string& toolDir);
|
||||
void removeToolEntry(const std::string& toolName);
|
||||
void addAlias(const std::string& alias, const std::string& toolName);
|
||||
void addAutocomplete(const std::string& toolName);
|
||||
bool hasAlias(const std::string& alias) const;
|
||||
std::vector<std::string> listAliases() const;
|
||||
};
|
172
getpkg/src/GetbinClient.cpp
Normal file
172
getpkg/src/GetbinClient.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#include "GetbinClient.hpp"
|
||||
#include <drogon/HttpClient.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static constexpr const char* SERVER_HOST = "tools.dropshell.app";
|
||||
|
||||
GetbinClient::GetbinClient() {}
|
||||
|
||||
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
std::string object_path = "/object/" + toolName + ":" + arch;
|
||||
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath(object_path);
|
||||
|
||||
bool success = false;
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
|
||||
std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ofstream ofs(outPath, std::ios::binary);
|
||||
if (!ofs) return;
|
||||
|
||||
const auto& body = response->getBody();
|
||||
ofs.write(body.data(), body.size());
|
||||
success = ofs.good();
|
||||
});
|
||||
|
||||
// Wait for the async request to complete
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
while (!success) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Add timeout logic here if needed
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
|
||||
// Read file
|
||||
std::ifstream ifs(archivePath, std::ios::binary);
|
||||
if (!ifs) {
|
||||
std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::string file_content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
|
||||
// Compose metadata (minimal, can be extended)
|
||||
json metadata = { {"labeltags", json::array()} };
|
||||
// Try to extract tool:arch from filename
|
||||
std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1);
|
||||
size_t dot = filename.find('.');
|
||||
std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename;
|
||||
metadata["labeltags"].push_back(labeltag);
|
||||
|
||||
// Create request with raw body upload (simplified approach)
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Put);
|
||||
req->setPath("/upload");
|
||||
req->addHeader("Authorization", "Bearer " + token);
|
||||
req->addHeader("Content-Type", "application/gzip");
|
||||
req->addHeader("X-Metadata", metadata.dump());
|
||||
req->setBody(file_content);
|
||||
|
||||
bool success = false;
|
||||
std::string response_body;
|
||||
int status_code = 0;
|
||||
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response) {
|
||||
std::cerr << "[GetbinClient::upload] HTTP /upload request failed (did not get OK response)." << std::endl;
|
||||
if (response) {
|
||||
std::cerr << response->getStatusCode() << std::endl;
|
||||
std::cerr << response->getBody() << std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
status_code = static_cast<int>(response->getStatusCode());
|
||||
response_body = response->getBody();
|
||||
|
||||
if (status_code != 200 && status_code != 201) {
|
||||
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response for URL/hash
|
||||
try {
|
||||
auto resp_json = json::parse(response_body);
|
||||
if (resp_json.contains("url")) outUrl = resp_json["url"].get<std::string>();
|
||||
if (resp_json.contains("hash")) outHash = resp_json["hash"].get<std::string>();
|
||||
success = true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "[GetbinClient::upload] Unknown error while parsing JSON response." << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the async request to complete
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
while (!success && status_code == 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Add timeout logic here if needed
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
std::string exists_path = "/exists/" + toolName + ":" + arch;
|
||||
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath(exists_path);
|
||||
|
||||
bool success = false;
|
||||
std::string response_body;
|
||||
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
response_body = response->getBody();
|
||||
|
||||
// Try to parse hash from response body (assume plain text or JSON)
|
||||
try {
|
||||
// Try JSON
|
||||
auto resp_json = json::parse(response_body);
|
||||
if (resp_json.contains("hash")) {
|
||||
outHash = resp_json["hash"].get<std::string>();
|
||||
success = true;
|
||||
return;
|
||||
}
|
||||
} catch (...) {
|
||||
// Not JSON, treat as plain text
|
||||
outHash = response_body;
|
||||
success = !outHash.empty();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the async request to complete
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
while (!success && response_body.empty()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Add timeout logic here if needed
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
10
getpkg/src/GetbinClient.hpp
Normal file
10
getpkg/src/GetbinClient.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class GetbinClient {
|
||||
public:
|
||||
GetbinClient();
|
||||
bool download(const std::string& toolName, const std::string& arch, const std::string& outPath);
|
||||
bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token);
|
||||
bool getHash(const std::string& toolName, const std::string& arch, std::string& outHash);
|
||||
};
|
245
getpkg/src/assert.hpp
Normal file
245
getpkg/src/assert.hpp
Normal file
@ -0,0 +1,245 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <source_location>
|
||||
#include <cxxabi.h>
|
||||
// execinfo.h not available in Alpine Linux - using alternative approach
|
||||
#include <dlfcn.h>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
// ANSI color codes
|
||||
namespace colors {
|
||||
constexpr const char* reset = "\033[0m";
|
||||
constexpr const char* red = "\033[31m";
|
||||
constexpr const char* green = "\033[32m";
|
||||
constexpr const char* yellow = "\033[33m";
|
||||
constexpr const char* blue = "\033[34m";
|
||||
constexpr const char* magenta = "\033[35m";
|
||||
constexpr const char* cyan = "\033[36m";
|
||||
}
|
||||
|
||||
// Simple RAII wrapper for file descriptor
|
||||
class FileDescriptor {
|
||||
int fd_ = -1;
|
||||
public:
|
||||
explicit FileDescriptor(int fd) : fd_(fd) {}
|
||||
~FileDescriptor() { if (fd_ != -1) close(fd_); }
|
||||
operator int() const { return fd_; }
|
||||
FileDescriptor(const FileDescriptor&) = delete;
|
||||
FileDescriptor& operator=(const FileDescriptor&) = delete;
|
||||
};
|
||||
|
||||
struct SourceInfo {
|
||||
std::string function;
|
||||
std::string file;
|
||||
int line = 0;
|
||||
};
|
||||
|
||||
std::vector<SourceInfo> get_source_info_batch(const char* executable, const std::vector<void*>& addresses) {
|
||||
if (addresses.empty()) return {};
|
||||
|
||||
// Build command with all addresses
|
||||
std::string cmd = std::string("addr2line -f -e ") + executable;
|
||||
for (void* addr : addresses) {
|
||||
char addr_buf[32];
|
||||
snprintf(addr_buf, sizeof(addr_buf), " %p", addr);
|
||||
cmd += addr_buf;
|
||||
}
|
||||
cmd += " 2>/dev/null";
|
||||
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) return std::vector<SourceInfo>(addresses.size());
|
||||
|
||||
std::vector<SourceInfo> results(addresses.size());
|
||||
char buffer[1024] = {0};
|
||||
size_t current_frame = 0;
|
||||
|
||||
// Read function names and file/line info for each address
|
||||
while (current_frame < addresses.size() && fgets(buffer, sizeof(buffer), pipe)) {
|
||||
// Remove trailing newline
|
||||
size_t len = strlen(buffer);
|
||||
if (len > 0 && buffer[len-1] == '\n') {
|
||||
buffer[len-1] = '\0';
|
||||
}
|
||||
|
||||
// Demangle function name
|
||||
int status = 0;
|
||||
char* demangled = abi::__cxa_demangle(buffer, nullptr, nullptr, &status);
|
||||
std::string func_name;
|
||||
if (demangled) {
|
||||
func_name = demangled;
|
||||
free(demangled);
|
||||
} else {
|
||||
func_name = buffer;
|
||||
}
|
||||
|
||||
// Simplify function signature: replace argument list with (...)
|
||||
size_t paren_pos = func_name.find('(');
|
||||
if (paren_pos != std::string::npos) {
|
||||
func_name = func_name.substr(0, paren_pos) + "(...)";
|
||||
}
|
||||
|
||||
results[current_frame].function = func_name;
|
||||
|
||||
// Read file and line number
|
||||
if (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
char* colon = strchr(buffer, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
results[current_frame].line = atoi(colon + 1);
|
||||
|
||||
// Extract just the filename
|
||||
const char* slash = strrchr(buffer, '/');
|
||||
results[current_frame].file = (slash ? slash + 1 : buffer);
|
||||
}
|
||||
}
|
||||
|
||||
current_frame++;
|
||||
}
|
||||
|
||||
pclose(pipe);
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper function to get the program counter (return address) in a portable way
|
||||
inline void* get_pc() {
|
||||
void* pc;
|
||||
#if defined(__aarch64__) || defined(__arm__)
|
||||
// For ARM/AArch64, we can use the link register (x30 on AArch64, r14 on ARM)
|
||||
#if defined(__aarch64__)
|
||||
asm volatile ("mov %0, x30" : "=r" (pc));
|
||||
#else
|
||||
asm volatile ("mov %0, lr" : "=r" (pc));
|
||||
#endif
|
||||
#else
|
||||
// For x86/x86_64, use __builtin_return_address
|
||||
pc = __builtin_return_address(0);
|
||||
#endif
|
||||
return pc;
|
||||
}
|
||||
|
||||
void print_stacktrace() {
|
||||
std::cerr << colors::yellow << "Stack trace:" << colors::reset << "\n";
|
||||
|
||||
char exe_path[1024] = {0};
|
||||
ssize_t count = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (count == -1) {
|
||||
strcpy(exe_path, "[unknown]");
|
||||
} else {
|
||||
exe_path[count] = '\0';
|
||||
}
|
||||
|
||||
std::vector<void*> addresses;
|
||||
|
||||
// Get current frame pointer and return address
|
||||
void* frame_ptr = __builtin_frame_address(0);
|
||||
void* return_addr = get_pc();
|
||||
|
||||
// Add current return address
|
||||
if (return_addr) {
|
||||
addresses.push_back(return_addr);
|
||||
}
|
||||
|
||||
// Try to walk up the stack if we have a valid frame pointer
|
||||
if (frame_ptr) {
|
||||
void** frame = static_cast<void**>(frame_ptr);
|
||||
// Limit the number of frames to prevent potential issues
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// The return address is typically at frame[1]
|
||||
if (frame[1] == nullptr) break;
|
||||
addresses.push_back(frame[1]);
|
||||
// Move to the next frame
|
||||
if (frame[0] == nullptr) break;
|
||||
frame = static_cast<void**>(frame[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (addresses.empty()) {
|
||||
std::cerr << " " << colors::red << "[no frames available]" << colors::reset << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get source info
|
||||
std::vector<SourceInfo> results = get_source_info_batch(exe_path, addresses);
|
||||
|
||||
// Filter out frames from assert.hpp
|
||||
results.erase(std::remove_if(results.begin(), results.end(),
|
||||
[](const SourceInfo& frame) {
|
||||
if (frame.file.empty()) return false;
|
||||
std::string_view file(frame.file);
|
||||
return file.find("assert.hpp") != std::string_view::npos;
|
||||
}),
|
||||
results.end()
|
||||
);
|
||||
|
||||
if (results.empty()) {
|
||||
std::cerr << " " << colors::red << "[no user frames available]" << colors::reset << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: find the maximum function name length for alignment
|
||||
size_t max_func_length = 0;
|
||||
for (const auto& frame : results) {
|
||||
size_t length = frame.function.empty()
|
||||
? strlen("[unknown function]")
|
||||
: frame.function.length();
|
||||
if (length > max_func_length) {
|
||||
max_func_length = length;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: print with aligned output
|
||||
for (size_t i = 0; i < results.size(); ++i) {
|
||||
const auto& frame = results[i];
|
||||
std::cerr << " ";
|
||||
|
||||
// Print function name with alignment
|
||||
size_t current_length;
|
||||
if (!frame.function.empty()) {
|
||||
std::cerr << colors::green << frame.function << colors::reset;
|
||||
current_length = frame.function.length();
|
||||
} else {
|
||||
std::cerr << colors::red << "[unknown function]" << colors::reset;
|
||||
current_length = strlen("[unknown function]");
|
||||
}
|
||||
|
||||
// Add padding to align the file/line text
|
||||
if (!frame.file.empty() && frame.line > 0) {
|
||||
// Calculate padding (minimum 1 space)
|
||||
size_t padding = (max_func_length > current_length)
|
||||
? (max_func_length - current_length + 1)
|
||||
: 1;
|
||||
std::cerr << std::string(padding, ' ')
|
||||
<< " " << colors::blue << frame.file << colors::reset
|
||||
<< ":" << colors::magenta << frame.line << colors::reset;
|
||||
}
|
||||
|
||||
std::cerr << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void assert_failed(
|
||||
bool condition,
|
||||
std::string_view message,
|
||||
std::source_location location
|
||||
) {
|
||||
if (!condition) {
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
|
||||
std::cerr << colors::red << "Assertion failed at " << location.file_name() << ":" << location.line() << ": "
|
||||
<< location.function_name() << ": " << message << colors::reset << "\n";
|
||||
print_stacktrace();
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
#define ASSERT(condition, message) assert_failed(condition, message, std::source_location::current())
|
21
getpkg/src/autogen/version.hpp
Normal file
21
getpkg/src/autogen/version.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
|
||||
version.hpp is automatically generated by the build system, from version.hpp.in.
|
||||
|
||||
DO NOT EDIT VERSION.HPP!
|
||||
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
// Version information
|
||||
const std::string VERSION = "@PROJECT_VERSION@";
|
||||
const std::string RELEASE_DATE = "@RELEASE_DATE@";
|
||||
const std::string AUTHOR = "j842";
|
||||
const std::string LICENSE = "MIT";
|
||||
|
||||
} // namespace dropshell
|
327
getpkg/src/main.cpp
Normal file
327
getpkg/src/main.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
dropshell-tool
|
||||
|
||||
dropshell-tool install <tool_name>
|
||||
- confirms dropshell-tool is fully initialised:
|
||||
- adds a line to the user's .bashrc file to source the dropshell-tool ~/.bashrc_dropshell_tool script, if it doesn't exist
|
||||
- creates an empty ~/.bashrc_dropshell_tool script if it doesn't exist
|
||||
- creates the ~/.config/dropshell-tool directory, if it does not exist
|
||||
- creates the ~/.local/bin/dropshell-tool directory, if it does not exist
|
||||
|
||||
- removes the tool from the user's system if it is already installed, and the tool's entries in the ~/.bashrc_dropshell_tool script
|
||||
- downloads the tool archive (tgz) from tools.dropshell.app (tool_name:ARCH), where ARCH is the architecture of the user's system, and unpacks it to the new tool directory: ~/.local/bin/dropshell-tool/<tool_name>/
|
||||
- sets the PATH to include the tool directory, by modifying the ~/.bashrc_dropshell_tool script
|
||||
- adds an entry for autocompletion for the tool to the ~/.bashrc_dropshell_tool script, where autocomplete just runs <tool_name> autocomplete <args>
|
||||
- creates a ~/.config/dropshell-tool/tool_name.json file, which contains the tool's name, version (by running <tool_name> version), hash from tools.dropshell.app, and architecture
|
||||
- reads the json file from the tgz called dropshell-tool-config.json:
|
||||
- for each alias in the aliases array:
|
||||
- check if another command is using the alias, and continue only if not
|
||||
- check that the alias is not already in the ~/.bashrc_dropshell_tool script and continue only if not
|
||||
- add an entry to the ~/.bashrc_dropshell_tool script to run the tool via the alias
|
||||
- if the json file has a setup_script entry, run the script named in the entry in the tool directory - using sudo if the entry has sudo set to true.
|
||||
|
||||
dropshell-tool publish <tool_name:ARCH> <folder>
|
||||
- checks that dropshell-tool-config.json exists in the folder, and is valid (see above)
|
||||
- creates a tgz archive of the folder, and uploads it to tools.dropshell.app, a simple object server.
|
||||
- prints the URL and hash of the uploaded archive
|
||||
- uses the token from env variable SOS_WRITE_TOKEN to write to tools.dropshell.app
|
||||
|
||||
dropshell-tool update <tool_name>
|
||||
- compares the hash from the ~/.config/dropshell-tool/tool_name.json file with the hash from tools.dropshell.app (tool_name:ARCH), and continues only if they are different
|
||||
- checks the version from the ~/.config/dropshell-tool/tool_name.json file with the version from tools.dropshell.app (tool_name:ARCH), and continues only if the remote version is newer (installed is older)
|
||||
- installs the tool as per the install command
|
||||
|
||||
dropshell-tool update all
|
||||
- runs update on all installed tools
|
||||
|
||||
dropshell-tool autocomplete <args>
|
||||
- shows autocomplete for dropshell-tool, and then exits
|
||||
- the tool list to choose from when calling install is hard coded in the autocomplete function
|
||||
|
||||
dropshell-tool version
|
||||
- prints the version of dropshell-tool
|
||||
|
||||
dropshell-tool create <tool_name> <directory_name>
|
||||
- creates a new tool source directory in relative path <directory_name> if it doesn't exist
|
||||
- creates a dropshell-tool-config.json file in the tool source directory if it doesn't exist, with the following entries:
|
||||
- aliases: an array of aliases for the tool
|
||||
- setup_script: the name of the setup script to run (setup_script.sh)
|
||||
- creates a setup_script.sh file in the tool source directory if it doesn't exist, that just prints a completion message and exits
|
||||
|
||||
dropshell-tool help
|
||||
- shows this help message
|
||||
|
||||
|
||||
*/
|
||||
#include "version.hpp"
|
||||
#include "BashrcEditor.hpp"
|
||||
#include "DropshellScriptManager.hpp"
|
||||
#include "GetbinClient.hpp"
|
||||
#include "ArchiveManager.hpp"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace {
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string get_arch() {
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
return "x86_64";
|
||||
#elif defined(__aarch64__)
|
||||
return "aarch64";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_home() {
|
||||
const char* home = getenv("HOME");
|
||||
return home ? std::string(home) : std::string("");
|
||||
}
|
||||
|
||||
int install_tool(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: dropshell-tool install <tool_name>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string toolName = argv[2];
|
||||
std::string arch = get_arch();
|
||||
std::string home = get_home();
|
||||
std::filesystem::path configDir = std::filesystem::path(home) / ".config/dropshell-tool";
|
||||
std::filesystem::path binDir = std::filesystem::path(home) / ".local/bin/dropshell-tool" / toolName;
|
||||
std::filesystem::path archivePath = configDir / (toolName + ".tgz");
|
||||
std::filesystem::create_directories(configDir);
|
||||
std::filesystem::create_directories(binDir);
|
||||
dropshelltool::BashrcEditor bashrcEditor;
|
||||
bashrcEditor.addSourceLine();
|
||||
DropshellScriptManager scriptManager;
|
||||
scriptManager.removeToolEntry(toolName);
|
||||
if (std::filesystem::exists(binDir)) std::filesystem::remove_all(binDir);
|
||||
GetbinClient getbin;
|
||||
std::cout << "Downloading " << toolName << ":" << arch << "..." << std::endl;
|
||||
if (!getbin.download(toolName, arch, archivePath.string())) {
|
||||
std::cerr << "Failed to download tool archive." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
ArchiveManager archiver;
|
||||
if (!archiver.unpack(archivePath.string(), binDir.string())) {
|
||||
std::cerr << "Failed to unpack tool archive." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
scriptManager.addToolEntry(toolName, binDir.string());
|
||||
scriptManager.addAutocomplete(toolName);
|
||||
std::string hash;
|
||||
getbin.getHash(toolName, arch, hash);
|
||||
std::string version;
|
||||
std::string toolPath = binDir.string() + "/" + toolName;
|
||||
FILE* fp = popen((toolPath + " version").c_str(), "r");
|
||||
if (fp) {
|
||||
char buf[128];
|
||||
if (fgets(buf, sizeof(buf), fp)) version = std::string(buf);
|
||||
pclose(fp);
|
||||
}
|
||||
json toolInfo = {
|
||||
{"name", toolName},
|
||||
{"version", version},
|
||||
{"hash", hash},
|
||||
{"arch", arch}
|
||||
};
|
||||
std::ofstream toolInfoFile(configDir / (toolName + ".json"));
|
||||
toolInfoFile << toolInfo.dump(2);
|
||||
toolInfoFile.close();
|
||||
std::string configJson;
|
||||
if (archiver.readConfigJson(archivePath.string(), configJson)) {
|
||||
try {
|
||||
auto config = json::parse(configJson);
|
||||
if (config.contains("aliases")) {
|
||||
for (const auto& alias : config["aliases"]) {
|
||||
std::string aliasStr = alias.get<std::string>();
|
||||
if (!scriptManager.hasAlias(aliasStr)) {
|
||||
scriptManager.addAlias(aliasStr, toolName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.contains("setup_script")) {
|
||||
std::string setupScript = config["setup_script"].get<std::string>();
|
||||
bool useSudo = config.value("sudo", false);
|
||||
std::string setupPath = binDir.string() + "/" + setupScript;
|
||||
std::string cmd = (useSudo ? "sudo " : "") + setupPath;
|
||||
std::system(cmd.c_str());
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Warning: failed to parse dropshell-tool-config.json" << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "Installed " << toolName << " successfully." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int publish_tool(int argc, char* argv[]) {
|
||||
if (argc < 4) {
|
||||
std::cerr << "Usage: dropshell-tool publish <tool_name:ARCH> <folder>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string labeltag = argv[2];
|
||||
std::string folder = argv[3];
|
||||
std::string home = get_home();
|
||||
std::filesystem::path configPath = std::filesystem::path(folder) / "dropshell-tool-config.json";
|
||||
if (!std::filesystem::exists(configPath)) {
|
||||
std::cerr << "dropshell-tool-config.json not found in " << folder << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
|
||||
std::filesystem::create_directories(archivePath.parent_path());
|
||||
|
||||
ArchiveManager archiver;
|
||||
if (!archiver.pack(folder, archivePath.string())) {
|
||||
std::cerr << "Failed to create archive." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/tools.dropshell.app/write_token.txt";
|
||||
std::string token;
|
||||
if (std::filesystem::exists(tokenPath)) {
|
||||
std::ifstream tfile(tokenPath);
|
||||
std::getline(tfile, token);
|
||||
} else {
|
||||
std::cout << "Enter tools.dropshell.app write token: ";
|
||||
std::getline(std::cin, token);
|
||||
std::filesystem::create_directories(tokenPath.parent_path());
|
||||
std::ofstream tfile(tokenPath);
|
||||
tfile << token << std::endl;
|
||||
}
|
||||
GetbinClient getbin;
|
||||
std::string url, hash;
|
||||
if (!getbin.upload(archivePath.string(), url, hash, token)) {
|
||||
std::cerr << "Failed to upload archive." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_tool(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: dropshell-tool update <tool_name|all>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string toolName = argv[2];
|
||||
std::string home = get_home();
|
||||
std::filesystem::path configDir = std::filesystem::path(home) / ".config/dropshell-tool";
|
||||
if (toolName == "all") {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
std::string tname = entry.path().stem();
|
||||
char* fakeArgv[] = {argv[0], (char*)"update", (char*)tname.c_str()};
|
||||
update_tool(3, fakeArgv);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
std::filesystem::path toolInfoPath = configDir / (toolName + ".json");
|
||||
if (!std::filesystem::exists(toolInfoPath)) {
|
||||
std::cerr << "Tool not installed: " << toolName << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::ifstream tfile(toolInfoPath);
|
||||
json toolInfo;
|
||||
tfile >> toolInfo;
|
||||
tfile.close();
|
||||
std::string arch = toolInfo.value("arch", get_arch());
|
||||
std::string localHash = toolInfo.value("hash", "");
|
||||
std::string localVersion = toolInfo.value("version", "");
|
||||
GetbinClient getbin;
|
||||
std::string remoteHash;
|
||||
getbin.getHash(toolName, arch, remoteHash);
|
||||
if (remoteHash.empty() || remoteHash == localHash) {
|
||||
std::cout << "No update needed for " << toolName << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::cout << "Updating " << toolName << "..." << std::endl;
|
||||
char* fakeArgv[] = {argv[0], (char*)"install", (char*)toolName.c_str()};
|
||||
return install_tool(3, fakeArgv);
|
||||
}
|
||||
|
||||
int create_tool(int argc, char* argv[]) {
|
||||
if (argc < 4) {
|
||||
std::cerr << "Usage: dropshell-tool create <tool_name> <directory_name>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string toolName = argv[2];
|
||||
std::string directoryName = argv[3];
|
||||
std::filesystem::path toolDir = std::filesystem::current_path() / directoryName;
|
||||
if (!std::filesystem::exists(toolDir)) {
|
||||
std::filesystem::create_directories(toolDir);
|
||||
std::cout << "Created directory: " << toolDir << std::endl;
|
||||
} else {
|
||||
std::cout << "Directory already exists: " << toolDir << std::endl;
|
||||
}
|
||||
std::filesystem::path configPath = toolDir / "dropshell-tool-config.json";
|
||||
if (!std::filesystem::exists(configPath)) {
|
||||
nlohmann::json config = {
|
||||
{"aliases", nlohmann::json::array()},
|
||||
{"setup_script", "setup_script.sh"}
|
||||
};
|
||||
std::ofstream configFile(configPath);
|
||||
configFile << config.dump(2);
|
||||
configFile.close();
|
||||
std::cout << "Created config: " << configPath << std::endl;
|
||||
} else {
|
||||
std::cout << "Config already exists: " << configPath << std::endl;
|
||||
}
|
||||
std::filesystem::path setupScriptPath = toolDir / "setup_script.sh";
|
||||
if (!std::filesystem::exists(setupScriptPath)) {
|
||||
std::ofstream setupFile(setupScriptPath);
|
||||
setupFile << "#!/bin/bash\necho 'Setup complete.'\n";
|
||||
setupFile.close();
|
||||
std::filesystem::permissions(setupScriptPath, std::filesystem::perms::owner_exec | std::filesystem::perms::owner_write | std::filesystem::perms::owner_read);
|
||||
std::cout << "Created setup script: " << setupScriptPath << std::endl;
|
||||
} else {
|
||||
std::cout << "Setup script already exists: " << setupScriptPath << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cout << "Usage: dropshell-tool <command> [args...]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string command = argv[1];
|
||||
if (command == "install") {
|
||||
return install_tool(argc, argv);
|
||||
} else if (command == "publish") {
|
||||
return publish_tool(argc, argv);
|
||||
} else if (command == "update") {
|
||||
return update_tool(argc, argv);
|
||||
} else if (command == "autocomplete") {
|
||||
std::vector<std::string> args(argv + 2, argv + argc);
|
||||
if (args.empty()) std::cout << R"(install
|
||||
publish
|
||||
update
|
||||
version
|
||||
create
|
||||
help
|
||||
)";
|
||||
} else if (command == "version") {
|
||||
std::cout << dropshell::VERSION << std::endl;
|
||||
} else if (command == "create") {
|
||||
return create_tool(argc, argv);
|
||||
} else if (command == "help") {
|
||||
std::cout << "Usage: dropshell-tool <command> [args...]" << std::endl;
|
||||
std::cout << "Commands:" << std::endl;
|
||||
std::cout << " install <tool_name>" << std::endl;
|
||||
std::cout << " publish <tool_name:ARCH> <folder>" << std::endl;
|
||||
std::cout << " update <tool_name>" << std::endl;
|
||||
std::cout << " version" << std::endl;
|
||||
} else {
|
||||
std::cout << "Unknown command: " << command << std::endl;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
21
getpkg/src/version.hpp.in
Normal file
21
getpkg/src/version.hpp.in
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
|
||||
version.hpp is automatically generated by the build system, from version.hpp.in.
|
||||
|
||||
DO NOT EDIT VERSION.HPP!
|
||||
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
// Version information
|
||||
const std::string VERSION = "@PROJECT_VERSION@";
|
||||
const std::string RELEASE_DATE = "@RELEASE_DATE@";
|
||||
const std::string AUTHOR = "j842";
|
||||
const std::string LICENSE = "MIT";
|
||||
|
||||
} // namespace dropshell
|
9
getpkg/test.sh
Executable file
9
getpkg/test.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
VERSION=$("${SCRIPT_DIR}/output/dropshell-tool" version)
|
||||
|
||||
echo "Version: $VERSION"
|
Reference in New Issue
Block a user