From 93c8cb3c4c6ca7b5e8f8f0be0ade821261dd7c73 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 25 Jun 2025 18:23:24 +1200 Subject: [PATCH] docs: Update 11 files --- bb64/.gitignore | 25 +++++ bb64/.vscode/settings.json | 69 +++++++++++++ bb64/CMakeLists.txt | 30 ++++++ bb64/README.md | 42 ++++++++ bb64/b64ed.cpp | 42 ++++++++ bb64/b64ed.hpp | 9 ++ bb64/bb64.cpp | 207 +++++++++++++++++++++++++++++++++++++ bb64/build.sh | 23 +++++ bb64/install.sh | 69 +++++++++++++ bb64/publish.sh | 113 ++++++++++++++++++++ bb64/version.h | 1 + 11 files changed, 630 insertions(+) create mode 100644 bb64/.gitignore create mode 100644 bb64/.vscode/settings.json create mode 100644 bb64/CMakeLists.txt create mode 100644 bb64/README.md create mode 100644 bb64/b64ed.cpp create mode 100644 bb64/b64ed.hpp create mode 100644 bb64/bb64.cpp create mode 100755 bb64/build.sh create mode 100755 bb64/install.sh create mode 100755 bb64/publish.sh create mode 100644 bb64/version.h diff --git a/bb64/.gitignore b/bb64/.gitignore new file mode 100644 index 0000000..c056369 --- /dev/null +++ b/bb64/.gitignore @@ -0,0 +1,25 @@ +# Build artifacts +bb64 +bb64.arm64 +bb64.amd64 + +# Musl cross toolchains +x86_64-linux-musl-cross/ +aarch64-linux-musl-cross/ +.musl-cross/ + +# Temporary files +*.tgz +*.tmp +*.swp +*~ + +# Logs +*.log + +# Secrets/tokens +.env +*.token + +# Misc +VERSION \ No newline at end of file diff --git a/bb64/.vscode/settings.json b/bb64/.vscode/settings.json new file mode 100644 index 0000000..a9e6b62 --- /dev/null +++ b/bb64/.vscode/settings.json @@ -0,0 +1,69 @@ +{ + "files.associations": { + "*.inja": "jinja-html", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "format": "cpp", + "__nullptr": "cpp", + "codecvt": "cpp" + } +} \ No newline at end of file diff --git a/bb64/CMakeLists.txt b/bb64/CMakeLists.txt new file mode 100644 index 0000000..0491504 --- /dev/null +++ b/bb64/CMakeLists.txt @@ -0,0 +1,30 @@ +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=") +endif() + +# 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}) + + +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/autogen + src + ) + +# Link libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + ) + \ No newline at end of file diff --git a/bb64/README.md b/bb64/README.md new file mode 100644 index 0000000..6325345 --- /dev/null +++ b/bb64/README.md @@ -0,0 +1,42 @@ +# bb64 + +# Installation + +Automated system-wide installation: +``` +curl -fsSL https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh | bash +``` + +## To download just the bb64 executable: + +``` +curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64 +``` + + +# Use + +Bash Base64, written in C++. + +Uses a custom Base64 character set for bash compatibility, not compatible with other utilities. + +``` +Usage: + bb64 BASE64COMMAND Decodes and runs the command + bb64 -[i|d] BASE64COMMAND Displays the decoded command + bb64 -e COMMAND Encodes the command and prints the result + bb64 -u Updates bb64 to the latest version (uses docker) +``` + +# Implementation Notes + +bb64 runs the command by replacing the current process, so it ensures that tty, environment +variables etc are all identical for the run command. It works with interactive commands, like +nano or ssh. + +bb64 supports bash scripts, as the command is run as: + `bash -c 'COMMAND'` +Where COMMAND is passed to bash as a single argument. + +If the command is run, the return value is the return value of the command. +If it isn't run, bb64 returns -1. diff --git a/bb64/b64ed.cpp b/bb64/b64ed.cpp new file mode 100644 index 0000000..cec8e1f --- /dev/null +++ b/bb64/b64ed.cpp @@ -0,0 +1,42 @@ +#include "b64ed.hpp" + +#include + +// Custom base64 encoding/decoding tables +static const std::string custom_base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+_"; + +std::string base64_encode(const std::string &in) { + std::string out; + int val = 0, valb = -6; + for (unsigned char c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(custom_base64_chars[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) out.push_back(custom_base64_chars[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) out.push_back('='); + return out; +} + +std::string base64_decode(const std::string &in) { + std::vector T(256, -1); + for (int i = 0; i < 64; i++) T[custom_base64_chars[i]] = i; + std::string out; + int val = 0, valb = -8; + for (unsigned char c : in) { + if (T[c] == -1) break; + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + out.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return out; +} \ No newline at end of file diff --git a/bb64/b64ed.hpp b/bb64/b64ed.hpp new file mode 100644 index 0000000..fa71d44 --- /dev/null +++ b/bb64/b64ed.hpp @@ -0,0 +1,9 @@ +#ifndef B64ED_HPP +#define B64ED_HPP + +#include + +std::string base64_decode(const std::string &in); +std::string base64_encode(const std::string &in); + +#endif diff --git a/bb64/bb64.cpp b/bb64/bb64.cpp new file mode 100644 index 0000000..1f0061f --- /dev/null +++ b/bb64/bb64.cpp @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "b64ed.hpp" + +// Recursively decode and print if nested bb64 command is found +void recursive_print(const std::string &decoded) +{ + std::cout << std::string(80, '-') << std::endl; + std::cout << decoded << std::endl; + std::cout << std::string(80, '-') << std::endl; + + size_t pos = decoded.find("bb64 "); + if (pos != std::string::npos) + { + std::istringstream iss(decoded.substr(pos)); + std::string cmd, arg; + iss >> cmd >> arg; + if (cmd == "bb64" && !arg.empty()) + { + std::string nested = base64_decode(arg); + std::cout << " "; + std::cout << "nested: " << nested << std::endl; + recursive_print(nested); + } + } +} + +constexpr unsigned int hash(const char *s, int off = 0) +{ + return !s[off] ? 5381 : (hash(s, off + 1) * 33) ^ s[off]; +} + +std::string tidy(const std::string &str) +{ + std::string result; + bool in_whitespace = false; + for (char c : str) + { + // Remove non-printable characters except for whitespace (space, tab, newline, carriage return) + if ((static_cast(c) < 32 && c != ' ' && c != '\t' && c != '\n' && c != '\r') || static_cast(c) == 127) + { + continue; + } + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + { + if (!in_whitespace) + { + result += ' '; + in_whitespace = true; + } + } + else + { + result += c; + in_whitespace = false; + } + } + // Remove leading whitespace + size_t start = result.find_first_not_of(' '); + if (start == std::string::npos) + return ""; + // Remove trailing whitespace + size_t end = result.find_last_not_of(' '); + return result.substr(start, end - start + 1); +} + + +std::string get_arch() +{ + // determine the architecture of the system + std::string arch; +#ifdef __aarch64__ + arch = "arm64"; +#elif __x86_64__ + arch = "amd64"; +#endif + return arch; +} + +int update_bb64() +{ + // determine path to this executable + std::filesystem::path bb64_path = std::filesystem::canonical("/proc/self/exe"); + std::filesystem::path parent_path = bb64_path.parent_path(); + + // determine the architecture of the system + std::string arch = get_arch(); + + std::string url = "https://gitea.jde.nz/public/bb64/releases/download/latest/bb64." + arch; + + // download new version, preserve permissions and ownership + std::string bash_script; + bash_script += "docker run --rm -v "+parent_path.string()+":/target"; + bash_script += " gitea.jde.nz/public/debian-curl:latest"; + bash_script += " sh -c \""; + bash_script += " curl -fsSL " + url + " -o /target/bb64_temp &&"; + bash_script += " chmod --reference=/target/bb64 /target/bb64_temp &&"; + bash_script += " chown --reference=/target/bb64 /target/bb64_temp &&"; + bash_script += " mv /target/bb64_temp /target/bb64"; + bash_script += "\""; + + std::cout << "Updating " << bb64_path << " to the latest " << arch << " version." << std::endl; + + // std::cout << "bash_script: " << std::endl + // << bash_script << std::endl; + + // run the bash script + execlp("bash", "bash", "-c", bash_script.c_str(), (char *)nullptr); + std::cerr << "Failed to execute command." << std::endl; + return -1; +} + +int decode_and_run(const std::string &encoded) +{ + // Default: decode and run + std::string decoded = base64_decode(encoded); + if (decoded.empty()) + { + std::cerr << "Failed to decode base64 command." << std::endl; + return -1; + } + // Replace current process with bash -c "decoded" + execlp("bash", "bash", "-c", decoded.c_str(), (char *)nullptr); + // If execlp returns, there was an error + std::cerr << "Failed to execute command." << std::endl; + return -1; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + std::cerr << "bb64 version " << VERSION << ", by J842." << std::endl; + // heredoc for instructions + std::cerr << R"( + +Usage: + bb64 BASE64COMMAND Decodes and runs the command + bb64 -[i|d] BASE64COMMAND Displays the decoded command + + bb64 -e COMMAND Encodes the command and prints the base64 encoded result + bb64 -e Encodes the command provided on stdin and prints the result + + bb64 -u Updates bb64 to the latest version (uses docker) + + bb64 -v Prints the version number + +)" << std::endl; + return -1; + } + + std::string mode = argv[1]; + + if (argc == 2) + { + if (mode == "-u") + return update_bb64(); + else if (mode == "-v") + { + std::cout << VERSION << std::endl; + return 0; + } + else if (mode == "-e") + { + std::ostringstream oss; + while (std::cin) + { + std::string line; + std::getline(std::cin, line); + oss << line << std::endl; + } + std::string tidier = tidy(oss.str()); + std::cout << base64_encode(tidier) << std::endl; + return 0; + } + else + return decode_and_run(mode); + } + + std::ostringstream oss; + std::string tidier; + + switch (hash(mode.c_str())) + { + case hash("-i"): + case hash("-d"): + std::cout << "Decoding command..." << std::endl + << std::endl; + recursive_print(base64_decode(argv[2])); + break; + case hash("-e"): + for (int i = 2; i < argc; ++i) + oss << (i > 2 ? " " : "") << argv[i]; + tidier = tidy(oss.str()); + std::cout << base64_encode(tidier) << std::endl; + break; + default: + std::cerr << "Invalid mode: " << mode << std::endl; + return -1; + }; +} \ No newline at end of file diff --git a/bb64/build.sh b/bb64/build.sh new file mode 100755 index 0000000..9b3fe48 --- /dev/null +++ b/bb64/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +PROJECT="bb64" + +export CMAKE_BUILD_TYPE="Debug" + +rm -rf "${SCRIPT_DIR}/output" +mkdir -p "${SCRIPT_DIR}/output" + +# make sure we have the latest base image. +docker pull gitea.jde.nz/public/dropshell-build-base:latest + +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}" + diff --git a/bb64/install.sh b/bb64/install.sh new file mode 100755 index 0000000..4eb31d9 --- /dev/null +++ b/bb64/install.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +# Installs bb64 on the local machine. +# 1. determines the architecture of the local machine +# 2. downloads the appropriate bb64 binary from the latest public release on Gitea (https://gitea.jde.nz/public/bb64/releases) +# 3. makes the bb64 binary executable +# 4. moves the bb64 binary to /usr/local/bin +# 5. prints a message to the user + +# 0. see if we were passed a folder to install to +# ----------------------------------------------------------------------------- +INSTALL_DIR="$1" +if [[ -z "$INSTALL_DIR" ]]; then + INSTALL_DIR="/usr/local/bin" +else + echo "Installing bb64 to $INSTALL_DIR" + if [[ ! -d "$INSTALL_DIR" ]]; then + mkdir -p "$INSTALL_DIR" + fi +fi + +# 0. see if we were passed a user to chown to +# ----------------------------------------------------------------------------- +CHOWN_USER="$2" +if [[ -z "$CHOWN_USER" ]]; then + CHOWN_USER=$(id -u) +fi + +# 1. Determine architecture +# ----------------------------------------------------------------------------- + +ARCH=$(uname -m) +if [[ "$ARCH" == "x86_64" ]]; then + BIN=bb64.amd64 +elif [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then + BIN=bb64.arm64 +else + echo "Unsupported architecture: $ARCH" >&2 + exit 1 +fi + +# 3. Download the appropriate binary to a temp directory +# ----------------------------------------------------------------------------- +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT +URL="https://gitea.jde.nz/public/bb64/releases/download/latest/$BIN" +echo "Downloading $BIN from $URL..." + +curl -fsSL -o "$TMPDIR/bb64" "$URL" + +# 4. Make it executable +# ----------------------------------------------------------------------------- +chmod +x "$TMPDIR/bb64" + +# 5. Move to /usr/local/bin +# ----------------------------------------------------------------------------- +docker run --rm -v "$TMPDIR:/tmp" -v "$INSTALL_DIR:/target" alpine sh -c "cp /tmp/bb64 /target/bb64; chown $CHOWN_USER /target/bb64" +rm "$TMPDIR/bb64" + +# 6. Print success message +# ----------------------------------------------------------------------------- +echo "bb64 installed successfully to $INSTALL_DIR/bb64 (arch $ARCH)" +# echo " " +# echo "Update bb64 with:" +# echo " bb64 -u" +# echo " " +# echo "try it out with:" +# echo " bb64 ZWNobyAiSGVsbG8td29ybGQhIGJiNjQgaXMgd29ya2luZy4i" diff --git a/bb64/publish.sh b/bb64/publish.sh new file mode 100755 index 0000000..bc016b1 --- /dev/null +++ b/bb64/publish.sh @@ -0,0 +1,113 @@ +#!/bin/bash +set -euo pipefail + +# Publishes bb64 to the Gitea Releases page for the repository. +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ARCH=$(uname -m) +PROJECT="bb64" +OUTPUT="${SCRIPT_DIR}/output" + +ARCH_ALIAS="amd64" +if [ "$ARCH" = "aarch64" ]; then + ARCH_ALIAS="arm64" +fi + +# Increment version +if [ ! -f version.h ]; then + echo "version.h not found!" >&2 + exit 1 +else + v=$(cat version.h | grep -o 'static const char \*VERSION = "[0-9.]*";' | cut -d'"' -f2) + oldv=$v + v=$((v+1)) + echo "Incrementing version from $oldv to $v" >&2 + echo "static const char *VERSION = \"$v\";" > version.h +fi +TAG="v$v" + +# build release version +export CMAKE_BUILD_TYPE="Release" + +docker build \ + -t "${PROJECT}-build" \ + -f "${SCRIPT_DIR}/Dockerfile.dropshell-build" \ + --build-arg PROJECT="${PROJECT}" \ + --build-arg CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \ + --output "${OUTPUT}" \ + "${SCRIPT_DIR}" + +[ -f "${OUTPUT}/${PROJECT}" ] || die "Build failed." + +cp "${OUTPUT}/${PROJECT}" "${OUTPUT}/${PROJECT}.${ARCH_ALIAS}" +cp "${OUTPUT}/${PROJECT}" "${OUTPUT}/${PROJECT}.${ARCH}" + +# Find repo info from .git/config +REPO_URL=$(git config --get remote.origin.url) +if [[ ! $REPO_URL =~ gitea ]]; then + echo "Remote origin is not a Gitea repository: $REPO_URL" >&2 + exit 1 +fi +# Extract base URL, owner, and repo +# Example: https://gitea.example.com/username/reponame.git +BASE_URL=$(echo "$REPO_URL" | sed -E 's#(https?://[^/]+)/.*#\1#') +OWNER=$(echo "$REPO_URL" | sed -E 's#.*/([^/]+)/[^/]+(\.git)?$#\1#') +REPO=$(echo "$REPO_URL" | sed -E 's#.*/([^/]+)(\.git)?$#\1#') + +API_URL="$BASE_URL/api/v1/repos/$OWNER/$REPO" + +# Check for GITEA_TOKEN_DEPLOY or GITEA_TOKEN +if [ -n "$GITEA_TOKEN_DEPLOY" ]; then + TOKEN="$GITEA_TOKEN_DEPLOY" +elif [ -n "$GITEA_TOKEN" ]; then + TOKEN="$GITEA_TOKEN" +else + echo "GITEA_TOKEN_DEPLOY or GITEA_TOKEN environment variable not set!" >&2 + exit 1 +fi + +# Create release +RELEASE_DATA=$(cat <&2 + exit 1 +fi + +# Upload binaries and install.sh +for FILE in ${PROJECT}.${ARCH_ALIAS} ${PROJECT}.${ARCH} install.sh; do + if [ -f "output/$FILE" ]; then + filetoupload="output/$FILE" + elif [ -f "$FILE" ]; then + filetoupload="$FILE" + else + continue + fi + + # Auto-detect content type + ctype=$(file --mime-type -b "$filetoupload") + + curl -s -X POST "$API_URL/releases/$RELEASE_ID/assets?name=$FILE" \ + -H "Content-Type: $ctype" \ + -H "Authorization: token $TOKEN" \ + --data-binary @"$filetoupload" + echo "Uploaded $FILE to release $TAG as $ctype." +done + +echo "Published bb64 version $v to $REPO_URL (tag $TAG) with binaries for $ARCH_ALIAS / $ARCH." + + diff --git a/bb64/version.h b/bb64/version.h new file mode 100644 index 0000000..46f56b4 --- /dev/null +++ b/bb64/version.h @@ -0,0 +1 @@ +static const char *VERSION = "39";