Compare commits

..

No commits in common. "main" and "2025.0524.1314" have entirely different histories.

65 changed files with 27258 additions and 12171 deletions

View File

@ -12,28 +12,12 @@ jobs:
sudo apt-get install -y openssh-server sudo apt-get install -y openssh-server
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install build dependencies - name: Build
run: | run: |
cd source cd ${{ gitea.workspace }}/docker
./install_build_prerequisites.sh ./compile.sh
- name: Build Native
run: |
cd source
./build_native.sh
- name: Test - name: Test
run: | run: |
cd source cd ${{ gitea.workspace }}/docker/output
./test.sh ./dropshell_x86_64 list
- name: Build Production ./dropshell_x86_64 help
run: |
cd source
./build_production.sh
- name: Test
run: |
cd source
./test.sh
- name: Publish
run: |
cd source
./publish.sh

1
.gitignore vendored
View File

@ -39,6 +39,7 @@ cmake_install.cmake
Makefile Makefile
# IDE specific files # IDE specific files
.vscode/
.idea/ .idea/
*.swp *.swp
*.swo *.swo

View File

@ -1,17 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/source/build/src/autogen"
],
"defines": [],
"compilerPath": "/usr/bin/g++",
"cStandard": "c23",
"cppStandard": "c++23",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}

103
.vscode/settings.json vendored
View File

@ -1,103 +0,0 @@
{
"files.associations": {
"*.inja": "jinja-html",
"*.ipp": "cpp",
"random": "cpp",
"ostream": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csetjmp": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"expected": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ranges": "cpp",
"scoped_allocator": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp",
"format": "cpp",
"stdfloat": "cpp",
"__nullptr": "cpp",
"__node_handle": "cpp",
"__split_buffer": "cpp",
"filesystem": "cpp",
"queue": "cpp",
"stack": "cpp",
"__bit_reference": "cpp",
"__functional_base": "cpp",
"__memory": "cpp",
"locale": "cpp",
"stacktrace": "cpp",
"__locale": "cpp"
}
}

View File

@ -5,23 +5,15 @@ A system management tool for server operations, written in C++.
## Installation ## Installation
``` ```
curl -fsSL https://gitea.jde.nz/public/dropshell/releases/download/latest/install.sh | bash curl -fsSL https://gitea.jde.nz/public/dropshell/releases/download/latest/install.sh | sudo bash
``` ```
This installs as dropshell for the local user, with a symbolic link ds. This installs as dropshell, with a symlink ds if the ds command does not already exist.
You'll need to run:
```
~/.local/bin/dropshell edit
~/.local/bin/dropshell install
source ~/.bashrc
```
to configure dropshell and install the local components.
## Remote Server Setup ## Installation of Agent
### Initial setup Install the Agent on each server you wish to manage. Supports amd64 (x86_64) and arm64 (aarch64) architectures.
Auto setup script which creates a dropshell user, and includes installing docker if not already present:
``` ```
curl -fsSL https://gitea.jde.nz/public/dropshell/releases/download/latest/server_autosetup.sh | sudo bash curl -fsSL https://gitea.jde.nz/public/dropshell/releases/download/latest/server_autosetup.sh | sudo bash
``` ```
@ -34,21 +26,7 @@ Manual steps:
1. Test ssh'ing into the server. 1. Test ssh'ing into the server.
### Configure and Use Remote Server ## Install Services
#### Add to local dropshell configuration, and install remote agent Set up a server and install a service:
Back on the dropshell host: 1. `ds create-server SERVERNAME`
1. `dropshell create-server SERVERNAME`
1. `dropshell edit SERVERNAME`
1. `dropshell install SERVERNAME`
#### Install Services
Create and install a service
1. `ds template list` -- see what templates are available to install.
1. `ds create-service SERVERNAME SERVICENAME TEMPLATE`
1. `ds edit SERVERNAME SERVICENAME`
1. Edit other config files if needed.
1. `ds install SERVERNAME SERVICENAME`
1. `ds list`
The service should now be seen to be running.

View File

@ -1,22 +0,0 @@
#!/bin/bash
set -euo pipefail
# download and install dropshell
# 1. Determine architecture
# -----------------------------------------------------------------------------
ARCH=$(uname -m)
TARGET_PATH="${HOME}/.local/bin/dropshell"
[ ! -f "${TARGET_PATH}" ] || rm -f "${TARGET_PATH}"
mkdir -p "$(dirname "${TARGET_PATH}")"
curl -L -s -o "${TARGET_PATH}" "https://getbin.xyz/dropshell.${ARCH}" || die "Failed to download dropshell for ${ARCH}"
chmod +x "${TARGET_PATH}"
echo "dropshell installed successfully to $TARGET_PATH"
echo " "
echo "Please:"
echo "1. run '${TARGET_PATH} edit' to edit the configuration."
echo "2. run '${TARGET_PATH} install' to install dropshell components on this computer."
echo "3. run 'source ~/.bashrc' to add to your path and autocomplete for the current shell."

50
install.sh Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
set -e
# download and install dropshell
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run this script as root (use sudo)"
exit 1
fi
# 1. Determine architecture
# -----------------------------------------------------------------------------
ARCH=$(uname -m)
if [[ "$ARCH" == "x86_64" ]]; then
BIN=dropshell.amd64
elif [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
BIN=dropshell.arm64
else
echo "Unsupported architecture: $ARCH" >&2
exit 1
fi
# 2. Download the appropriate binary to a temp directory
# -----------------------------------------------------------------------------
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
URL="https://gitea.jde.nz/public/dropshell/releases/download/latest/$BIN"
echo "Downloading $BIN from $URL..."
curl -fsSL -o "$TMPDIR/dropshell" "$URL"
if [ ! -f "$TMPDIR/dropshell" ]; then
echo "Failed to download dropshell" >&2
exit 1
fi
chmod +x "$TMPDIR/dropshell"
cp "$TMPDIR/dropshell" /usr/local/bin/dropshell
if [ -f /usr/local/bin/ds ]; then
rm -f /usr/local/bin/ds
fi
ln -s /usr/local/bin/dropshell /usr/local/bin/ds
rm -rf "$TMPDIR"
echo "dropshell installed successfully to /usr/local/bin/dropshell"

View File

@ -1,6 +1,4 @@
#!/bin/bash # can you make this script run in bash, but fall back to sh if bash is not installed?
# set up a remote server for use with dropshell.
# check if we are running as root # check if we are running as root
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then

View File

@ -1,15 +1,6 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(dropshell VERSION 1.0.0 LANGUAGES CXX) project(dropshell VERSION 1.0.0 LANGUAGES CXX)
# Force static linking globally
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE)
set(CMAKE_POSITION_INDEPENDENT_CODE OFF)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(ZLIB_USE_STATIC_LIBS "ON")
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -46,16 +37,17 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
file(GLOB_RECURSE SOURCES "src/*.cpp") file(GLOB_RECURSE SOURCES "src/*.cpp")
file(GLOB_RECURSE HEADERS "src/*.hpp") file(GLOB_RECURSE HEADERS "src/*.hpp")
# Add custom target to run cmake_prebuild.sh at the start of the build process # Add custom target to run make_createagent.sh at the start of the build process
add_custom_target(run_prebuild_script ALL add_custom_target(run_createagent ALL
COMMAND ${CMAKE_COMMAND} -E echo "Running cmake_prebuild.sh..." COMMAND ${CMAKE_COMMAND} -E echo "Running make_createagent.sh..."
COMMAND ${CMAKE_COMMAND} -E env bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh COMMAND ${CMAKE_COMMAND} -E env bash ${CMAKE_CURRENT_SOURCE_DIR}/make_createagent.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running make_createagent.sh before build"
) )
# Add executable # Add executable
add_executable(dropshell ${SOURCES}) add_executable(dropshell ${SOURCES})
add_dependencies(dropshell run_prebuild_script) add_dependencies(dropshell run_createagent)
# Mark the generated files as GENERATED so CMake knows they'll be created during build # Mark the generated files as GENERATED so CMake knows they'll be created during build
set_source_files_properties( set_source_files_properties(
@ -89,45 +81,79 @@ if(WIN32)
) )
endif() endif()
# Configure libassert
include(FetchContent)
FetchContent_Declare(
libassert
GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git
GIT_TAG v2.1.5
)
FetchContent_MakeAvailable(libassert)
# Add cpptrace
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.8.3
)
FetchContent_MakeAvailable(cpptrace)
# Add nlohmann/json
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)
# Link libraries # Link libraries
target_link_libraries(dropshell PRIVATE target_link_libraries(dropshell PRIVATE
libassert::assert
cpptrace::cpptrace
nlohmann_json::nlohmann_json
)
# Set static linking flags
set_target_properties(dropshell PROPERTIES
LINK_FLAGS "-static"
) )
# Install targets # Install targets
install(TARGETS dropshell install(TARGETS dropshell
RUNTIME DESTINATION $ENV{HOME}/.local/bin RUNTIME DESTINATION bin
) )
# Create symbolic link 'ds' pointing to 'dropshell'
install(CODE "
message(STATUS \"Checking if 'ds' command already exists...\")
execute_process(
COMMAND which ds
RESULT_VARIABLE DS_NOT_EXISTS
OUTPUT_QUIET
ERROR_QUIET
)
if(DS_NOT_EXISTS)
message(STATUS \"Command 'ds' does not exist. Creating symlink.\")
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink
\${CMAKE_INSTALL_PREFIX}/bin/dropshell
\${CMAKE_INSTALL_PREFIX}/bin/ds
)
else()
message(STATUS \"Command 'ds' already exists. Skipping symlink creation.\")
endif()
")
# Install completion script
install(FILES src/dropshell-completion.bash
DESTINATION /etc/bash_completion.d
RENAME dropshell
)
# Create a symlink for the completion script to work with 'ds' command
install(CODE "
# First check if 'ds' command exists after our installation
execute_process(
COMMAND which ds
RESULT_VARIABLE DS_NOT_EXISTS
OUTPUT_VARIABLE DS_PATH
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Only proceed if 'ds' exists
if(NOT DS_NOT_EXISTS)
# Check if 'ds' is a symlink pointing to dropshell
execute_process(
COMMAND readlink -f \${DS_PATH}
RESULT_VARIABLE READLINK_FAILED
OUTPUT_VARIABLE REAL_PATH
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Get the path to our dropshell binary
set(DROPSHELL_PATH \${CMAKE_INSTALL_PREFIX}/bin/dropshell)
# Check if the real path is our dropshell binary
if(NOT READLINK_FAILED AND \"\${REAL_PATH}\" STREQUAL \"\${DROPSHELL_PATH}\")
message(STATUS \"Command 'ds' exists and points to dropshell. Creating completion script symlink.\")
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink
/etc/bash_completion.d/dropshell
/etc/bash_completion.d/ds
)
else()
message(STATUS \"Command 'ds' exists but doesn't point to dropshell. Skipping completion symlink.\")
endif()
else()
message(STATUS \"Command 'ds' not found. Skipping completion symlink.\")
endif()
")

View File

@ -1,65 +0,0 @@
FROM --platform=$BUILDPLATFORM alpine:latest AS builder
# Add build arguments for platform
ARG TARGETPLATFORM
ARG BUILDPLATFORM
# Install build dependencies
RUN apk add --no-cache \
build-base \
cmake \
git \
musl-dev \
curl \
bash \
musl \
g++ \
ninja \
linux-headers
# Install cross-compilation tools for ARM64
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
apk add --no-cache \
crossbuild-essential-arm64 \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu; \
fi
# Set working directory
WORKDIR /build
# Copy source files
COPY . .
# Configure and build
RUN mkdir -p build_static
# Set up cross-compilation environment for ARM64
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
export CC=aarch64-linux-gnu-gcc \
export CXX=aarch64-linux-gnu-g++ \
export CMAKE_TOOLCHAIN_FILE=/build/toolchain.cmake; \
fi
# Create toolchain file for ARM64
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
echo "set(CMAKE_SYSTEM_NAME Linux)" > toolchain.cmake && \
echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake && \
echo "set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)" >> toolchain.cmake && \
echo "set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)" >> toolchain.cmake && \
echo "set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)" >> toolchain.cmake && \
echo "set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)" >> toolchain.cmake && \
echo "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)" >> toolchain.cmake; \
fi
RUN cmake -G Ninja -B build_static -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_EXE_LINKER_FLAGS="-static" \
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
-DBUILD_SHARED_LIBS=OFF \
${CMAKE_TOOLCHAIN_FILE:+-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE}
RUN cmake --build build_static
# Final stage that only contains the binary
FROM scratch AS dropshell
COPY --from=builder /build/build_static/dropshell /dropshell

View File

@ -1,8 +1,5 @@
#!/bin/bash #!/bin/bash
# install the dropshell host agent on this computer.
# (not for remote servers)
SCRIPT_DIR=$(dirname "$0") SCRIPT_DIR=$(dirname "$0")
echo "Installing dropshell host agent on this computer..." echo "Installing dropshell host agent on this computer..."
@ -25,6 +22,27 @@ _check_required_env_vars() {
done done
} }
# Checks if Docker is installed, running, and user has permission. Returns 1 on failure.
_check_docker_installed() {
if ! command -v docker &> /dev/null; then
echo "Docker is not installed"
return 1
fi
# check if docker daemon is running
if ! docker info &> /dev/null; then
echo "Docker daemon is not running"
return 1
fi
# check if user has permission to run docker
if ! docker run --rm hello-world &> /dev/null; then
echo "User does not have permission to run docker"
return 1
fi
return 0
}
function install_bb64() { function install_bb64() {
# check curl installed # check curl installed
@ -59,5 +77,7 @@ set +a
_check_required_env_vars "AGENT_LOCAL_PATH" _check_required_env_vars "AGENT_LOCAL_PATH"
echo "Installing host agent into $AGENT_LOCAL_PATH" echo "Installing host agent into $AGENT_LOCAL_PATH"
_check_docker_installed || _die "Docker is required."
install_bb64 install_bb64

View File

@ -38,7 +38,6 @@ CURRENT_EXIT_CODE=0
load_dotenv(){ load_dotenv(){
local file_path=$1 local file_path=$1
if [ -f "${file_path}" ]; then if [ -f "${file_path}" ]; then
# shellcheck source=/dev/null
source "${file_path}" source "${file_path}"
fi fi
} }
@ -70,9 +69,9 @@ function run_command() {
load_dotenv "${service_path}/config/.template_info.env" load_dotenv "${service_path}/config/.template_info.env"
# update the main variables. # update the main variables.
export CONFIG_PATH="${service_path}/config" CONFIG_PATH="${service_path}/config"
export SERVICE="${SERVICE_NAME}" SERVICE="${SERVICE_NAME}"
export DOCKER_CLI_HINTS=false DOCKER_CLI_HINTS=false
set +a set +a

View File

@ -21,13 +21,16 @@ fi
_check_required_env_vars "AGENT_PATH" _check_required_env_vars "AGENT_PATH"
function install_bb64() { function install_bb64() {
if ! curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | \ curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | bash -s -- "$AGENT_PATH" "$(id -u $USER):$(id -g $USER)"
bash -s -- "$AGENT_PATH" "$(id -u "$USER"):$(id -g "$USER")"; then
# test result code from curl
if [ $? -ne 0 ]; then
_die "Failed to install bb64. Curl returned non-zero exit code." _die "Failed to install bb64. Curl returned non-zero exit code."
fi fi
# test if bb64 is installed # test if bb64 is installed
if ! VER=$("$AGENT_PATH/bb64" -v); then VER=$("$AGENT_PATH/bb64" -v)
if [ $? -ne 0 ]; then
_die "bb64 did not install correctly." _die "bb64 did not install correctly."
fi fi
@ -50,12 +53,6 @@ if ! command -v docker &> /dev/null; then
exit 1 exit 1
fi fi
# check rsync installation
if ! command -v rsync &> /dev/null; then
echo "Rsync is not installed. Rsync is required for agent installation."
exit 1
fi
#------------------------------------------------------------------------- #-------------------------------------------------------------------------

View File

@ -41,18 +41,18 @@ _create_and_start_container() {
local run_cmd="$1" local run_cmd="$1"
local container_name="$2" local container_name="$2"
if _is_container_exists "$container_name"; then if _is_container_exists $container_name; then
_is_container_running "$container_name" && return 0 _is_container_running $container_name && return 0
_start_container "$container_name" _start_container $container_name
else else
$run_cmd $run_cmd
fi fi
if ! _is_container_running "$container_name"; then if ! _is_container_running $container_name; then
_die "Container ${container_name} failed to start" _die "Container ${container_name} failed to start"
fi fi
ID=$(_get_container_id "$container_name") ID=$(_get_container_id $container_name)
echo "Container ${container_name} is running with ID ${ID}" echo "Container ${container_name} is running with ID ${ID}"
} }
@ -93,7 +93,6 @@ _check_docker_installed() {
# Checks if a container (any state) exists. Returns 1 if not found. # Checks if a container (any state) exists. Returns 1 if not found.
_is_container_exists() { _is_container_exists() {
[ -n "${1:-}" ] || { echo "_is_container_exists: Container name is empty" >&2; return 1; }
if ! docker ps -a --format "{{.Names}}" | grep -q "^$1$"; then if ! docker ps -a --format "{{.Names}}" | grep -q "^$1$"; then
return 1 return 1
fi fi
@ -102,7 +101,6 @@ _is_container_exists() {
# Checks if a container is currently running. Returns 1 if not running. # Checks if a container is currently running. Returns 1 if not running.
_is_container_running() { _is_container_running() {
[ -n "${1:-}" ] || { echo "_is_container_running: Container name is empty" >&2; return 1; }
if ! docker ps --format "{{.Names}}" | grep -q "^$1$"; then if ! docker ps --format "{{.Names}}" | grep -q "^$1$"; then
return 1 return 1
fi fi
@ -121,39 +119,39 @@ _get_container_status() {
# Starts an existing, stopped container. # Starts an existing, stopped container.
_start_container() { _start_container() {
_is_container_exists "$1" || return 1 _is_container_exists $1 || return 1
_is_container_running "$1" && return 0 _is_container_running $1 && return 0
docker start "$1" docker start $1
} }
# Stops a running container. # Stops a running container.
_stop_container() { _stop_container() {
_is_container_running "$1" || return 0; _is_container_running $1 || return 0;
docker stop "$1" docker stop $1
} }
# Stops (if needed) and removes a container. # Stops (if needed) and removes a container.
_remove_container() { _remove_container() {
_stop_container "$1" _stop_container $1
_is_container_exists "$1" || return 0; _is_container_exists $1 || return 0;
docker rm "$1" docker rm $1
} }
# Prints the logs for a container. # Prints the logs for a container.
_get_container_logs() { _get_container_logs() {
if ! _is_container_exists "$1"; then if ! _is_container_exists $1; then
echo "Container $1 does not exist" echo "Container $1 does not exist"
return 1 return 1
fi fi
docker logs "$1" docker logs $1
} }
# Checks if listed environment variables are set; calls _die() if any are missing. # Checks if listed environment variables are set; calls _die() if any are missing.
_check_required_env_vars() { _check_required_env_vars() {
local required_vars=("$@") local required_vars=("$@")
for var in "${required_vars[@]}"; do for var in "${required_vars[@]}"; do
if [ -z "${!var:-}" ]; then if [ -z "${!var}" ]; then
_die "Required environment variable $var is not set" _die "Required environment variable $var is not set"
fi fi
done done

View File

@ -12,26 +12,26 @@ _autocommandrun_volume() {
case "$command" in case "$command" in
create) create)
if docker volume ls | grep -q "${volume_name}"; then if docker volume ls | grep -q ${volume_name}; then
echo "Volume ${volume_name} already exists - leaving unchanged" echo "Volume ${volume_name} already exists - leaving unchanged"
return return
fi fi
echo "Creating volume ${volume_name}" echo "Creating volume ${volume_name}"
docker volume create "${volume_name}" docker volume create ${volume_name}
;; ;;
destroy) nuke)
echo "Destroying volume ${volume_name}" echo "Nuking volume ${volume_name}"
docker volume rm "${volume_name}" docker volume rm ${volume_name}
;; ;;
backup) backup)
echo "Backing up volume ${volume_name}" echo "Backing up volume ${volume_name}"
docker run --rm -v "${volume_name}":/volume -v "${backup_folder}":/backup debian bash -c "tar -czvf /backup/backup.tgz -C /volume . && chown -R $MYID:$MYGRP /backup" docker run --rm -v ${volume_name}:/volume -v ${backup_folder}:/backup debian bash -c "tar -czvf /backup/backup.tgz -C /volume . && chown -R $MYID:$MYGRP /backup"
;; ;;
restore) restore)
echo "Restoring volume ${volume_name}" echo "Restoring volume ${volume_name}"
docker volume rm "${volume_name}" docker volume rm ${volume_name}
docker volume create "${volume_name}" docker volume create ${volume_name}
docker run --rm -v "${volume_name}":/volume -v "${backup_folder}":/backup debian bash -c "tar -xzvf /backup/backup.tgz -C /volume --strip-components=1" docker run --rm -v ${volume_name}:/volume -v ${backup_folder}:/backup debian bash -c "tar -xzvf /backup/backup.tgz -C /volume --strip-components=1"
;; ;;
esac esac
} }
@ -48,24 +48,22 @@ _autocommandrun_path() {
return return
fi fi
echo "Creating path ${path}" echo "Creating path ${path}"
mkdir -p "${path}" mkdir -p ${path}
;; ;;
destroy) nuke)
echo "Destroying path ${path}" echo "Nuking path ${path}"
local path_parent; local path_parent=$(dirname ${path})
path_parent=$(dirname "${path}") local path_child=$(basename ${path})
local path_child;
path_child=$(basename "${path}")
if [ -d "${path_parent}/${path_child}" ]; then if [ -d "${path_parent}/${path_child}" ]; then
docker run --rm -v "${path_parent}":/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to destroy path ${path}" docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to nuke path ${path}"
else else
echo "Path ${path} does not exist - nothing to destroy" echo "Path ${path} does not exist - nothing to nuke"
fi fi
;; ;;
backup) backup)
echo "Backing up path ${path}" echo "Backing up path ${path}"
if [ -d "${path}" ]; then if [ -d "${path}" ]; then
docker run --rm -v "${path}":/path -v "${backup_folder}":/backup debian bash -c "tar -czvf /backup/backup.tgz -C /path . && chown -R $MYID:$MYGRP /backup" docker run --rm -v ${path}:/path -v ${backup_folder}:/backup debian bash -c "tar -czvf /backup/backup.tgz -C /path . && chown -R $MYID:$MYGRP /backup"
else else
echo "Path ${path} does not exist - nothing to backup" echo "Path ${path} does not exist - nothing to backup"
fi fi
@ -75,9 +73,9 @@ _autocommandrun_path() {
echo "Backup file ${backup_folder}/backup.tgz does not exist - nothing to restore" echo "Backup file ${backup_folder}/backup.tgz does not exist - nothing to restore"
else else
echo "Clearing existing data in path ${path}" echo "Clearing existing data in path ${path}"
docker run --rm -v "${path}":/path debian bash -c "rm -rfv /path/{*,.*}" docker run --rm -v ${path}:/path debian bash -c "rm -rfv /path/{*,.*}"
echo "Restoring path ${path} from backup file ${backup_folder}/backup.tgz" echo "Restoring path ${path} from backup file ${backup_folder}/backup.tgz"
tar -xzvf "${backup_folder}/backup.tgz" -C "${path}" --strip-components=1 tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1
fi fi
;; ;;
esac esac
@ -90,36 +88,31 @@ _autocommandrun_file() {
case "$command" in case "$command" in
create) create)
local file_parent; filepath_parent=$(dirname ${filepath})
file_parent=$(dirname "${filepath}") filepath_child=$(basename ${filepath})
local file_name; if [ ! -d "${filepath_parent}" ]; then
file_name=$(basename "${filepath}") echo "Parent directory ${filepath_parent} of ${filepath_child} does not exist - creating"
if [ ! -d "${file_parent}" ]; then mkdir -p ${filepath_parent}
echo "Parent directory ${file_parent} of ${file_name} does not exist - creating"
mkdir -p "${file_parent}"
fi fi
;; ;;
destroy) nuke)
rm -f "${filepath}" rm -f ${filepath}
;; ;;
backup) backup)
echo "Backing up file ${filepath}" echo "Backing up file ${filepath}"
local file_parent; local file_parent=$(dirname ${filepath})
file_parent=$(dirname "${filepath}") local file_name=$(basename ${filepath})
local file_name;
file_name=$(basename "${filepath}")
if [ -f "${file_parent}/${file_name}" ]; then if [ -f "${file_parent}/${file_name}" ]; then
docker run --rm -v "${file_parent}":/volume -v "${backup_folder}":/backup debian bash -c "cp /volume/${file_name} /backup/${file_name} && chown -R $MYID:$MYGRP /backup" docker run --rm -v ${file_parent}:/volume -v ${backup_folder}:/backup debian bash -c "cp /volume/${file_name} /backup/${file_name} && chown -R $MYID:$MYGRP /backup"
else else
echo "File ${filepath} does not exist - nothing to backup" echo "File ${filepath} does not exist - nothing to backup"
fi fi
;; ;;
restore) restore)
echo "Restoring file ${filepath}" echo "Restoring file ${filepath}"
local file_name; local file_name=$(basename ${filepath})
file_name=$(basename "${filepath}") rm -f ${filepath} || die "Unable to remove existing file ${filepath}, restore failed."
rm -f "${filepath}" || return_die "Unable to remove existing file ${filepath}, restore failed." cp ${backup_folder}/${file_name} ${filepath} || die "Unable to copy file ${backup_folder}/${file_name} to ${filepath}, restore failed."
cp "${backup_folder}/${file_name}" "${filepath}" || return_die "Unable to copy file ${backup_folder}/${file_name} to ${filepath}, restore failed."
;; ;;
esac esac
} }
@ -160,10 +153,9 @@ _autocommandparse() {
local value="${pair#*=}" local value="${pair#*=}"
# create backup folder unique to key/value. # create backup folder unique to key/value.
local bfolder; local bfolder=$(echo "${key}_${value}" | tr -cd '[:alnum:]_-')
bfolder=$(echo "${key}_${value}" | tr -cd '[:alnum:]_-')
local targetpath="${backup_temp_path}/${bfolder}" local targetpath="${backup_temp_path}/${bfolder}"
mkdir -p "${targetpath}" mkdir -p ${targetpath}
# Key must be one of volume, path or file # Key must be one of volume, path or file
case "$key" in case "$key" in
@ -189,8 +181,8 @@ datacreate() {
} }
datadestroy() { datanuke() {
_autocommandparse destroy none "$@" _autocommandparse nuke none "$@"
} }
databackup() { databackup() {
@ -199,7 +191,7 @@ databackup() {
mkdir -p "$BACKUP_TEMP_PATH" mkdir -p "$BACKUP_TEMP_PATH"
echo "_autocommandparse [backup] [$BACKUP_TEMP_PATH]" "$@" echo "_autocommandparse [backup] [$BACKUP_TEMP_PATH] [$@]"
_autocommandparse backup "$BACKUP_TEMP_PATH" "$@" _autocommandparse backup "$BACKUP_TEMP_PATH" "$@"
tar zcvf "$BACKUP_FILE" -C "$BACKUP_TEMP_PATH" . tar zcvf "$BACKUP_FILE" -C "$BACKUP_TEMP_PATH" .
@ -209,7 +201,7 @@ datarestore() {
_check_required_env_vars "BACKUP_FILE" "TEMP_DIR" _check_required_env_vars "BACKUP_FILE" "TEMP_DIR"
BACKUP_TEMP_PATH="$TEMP_DIR/restore" BACKUP_TEMP_PATH="$TEMP_DIR/restore"
echo "_autocommandparse [restore] [$BACKUP_TEMP_PATH]" "$@" echo "_autocommandparse [restore] [$BACKUP_TEMP_PATH] [$@]"
mkdir -p "$BACKUP_TEMP_PATH" mkdir -p "$BACKUP_TEMP_PATH"
tar zxvf "$BACKUP_FILE" -C "$BACKUP_TEMP_PATH" --strip-components=1 tar zxvf "$BACKUP_FILE" -C "$BACKUP_TEMP_PATH" --strip-components=1

105
source/build.sh Executable file
View File

@ -0,0 +1,105 @@
#!/bin/bash
# Exit on error
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
JOBS=4
# Determine number of CPU cores for parallel build
if command -v nproc >/dev/null 2>&1; then
JOBS=$(nproc)
fi
# Function to print status messages
print_status() {
echo -e "${GREEN}[*] $1${NC}"
}
print_error() {
echo -e "${RED}[!] $1${NC}"
}
print_warning() {
echo -e "${YELLOW}[!] $1${NC}"
}
# ensure we have latest dehydrate.
dehydrate -u
# Check if build directory exists, if not create it
if [ ! -d "build" ]; then
print_status "Creating build directory..."
mkdir build
fi
# Enter build directory
cd build
# Check if CMake is installed
if ! command -v cmake &> /dev/null; then
print_error "CMake is not installed. Please install CMake first."
exit 1
fi
# Check if make is installed
if ! command -v make &> /dev/null; then
print_error "Make is not installed. Please install Make first."
exit 1
fi
# Check if pkg-config is installed
if ! command -v pkg-config &> /dev/null; then
print_error "pkg-config is not installed. Please install pkg-config first."
print_warning "On Ubuntu/Debian: sudo apt-get install pkg-config"
print_warning "On Fedora: sudo dnf install pkg-config"
print_warning "On Arch: sudo pacman -S pkg-config"
exit 1
fi
# Check if ncurses is installed
if ! pkg-config --exists ncurses; then
print_error "ncurses is not installed. Please install ncurses first."
print_warning "On Ubuntu/Debian: sudo apt-get install libncurses-dev"
print_warning "On Fedora: sudo dnf install ncurses-devel"
print_warning "On Arch: sudo pacman -S ncurses"
exit 1
fi
# Configure with CMake
print_status "Configuring with CMake..."
cmake .. -DCMAKE_BUILD_TYPE=Debug
#cmake .. -DCMAKE_BUILD_TYPE=Release
# Build the project
print_status "Building project..."
make -j"$JOBS"
# Check if build was successful
if [ $? -eq 0 ]; then
print_status "Build successful!"
print_status "Binary location: $(pwd)/dropshell"
else
print_error "Build failed!"
exit 1
fi
print_status "Auto-installing dropshell..."
sudo make install
if [ $? -eq 0 ]; then
print_status "Installation successful!"
else
print_error "Installation failed!"
exit 1
fi
# Return to original directory
cd ..
print_status "Build process completed!"

View File

@ -1,53 +0,0 @@
#!/bin/bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
OUTPUT_DIR=${SCRIPT_DIR}/output
INSTALL_DIR=${HOME}/.local/bin
mkdir -p "${OUTPUT_DIR}"
# Exit on error
set -euo pipefail
ARCH=$(uname -m)
if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "aarch64" ]; then
echo "Unsupported architecture: $ARCH"
exit 1
fi
function build_native() {
local BUILDDIR=${SCRIPT_DIR}/build/native
local PREVDIR=$PWD
local JOBS;
JOBS=$(nproc) # Set JOBS to the number of available CPU cores
mkdir -p "${BUILDDIR}"
cd "${SCRIPT_DIR}" || exit 1
CC="${HOME}/.musl-cross/${ARCH}-linux-musl-native/bin/${ARCH}-linux-musl-gcc"
CXX="${HOME}/.musl-cross/${ARCH}-linux-musl-native/bin/${ARCH}-linux-musl-g++"
cmake -B "${BUILDDIR}" -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_LINKER=mold \
-DCMAKE_C_COMPILER="${CC}" \
-DCMAKE_CXX_COMPILER="${CXX}"
cd "${BUILDDIR}" || exit 1
ninja -j"$JOBS"
#upx ${BUILDDIR}/dropshell
cp "${BUILDDIR}/dropshell" "${OUTPUT_DIR}/dropshell.${ARCH}"
cd "${PREVDIR}" || exit 1
}
build_native
echo "Auto-installing dropshell locally..."
mkdir -p "${INSTALL_DIR}"
cp "${OUTPUT_DIR}/dropshell.${ARCH}" "${INSTALL_DIR}/dropshell"
echo "Build process completed!"

View File

@ -1,37 +0,0 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Create output directory
mkdir -p "${SCRIPT_DIR}/output"
PREV_DIR=$(pwd)
cd "${SCRIPT_DIR}"
trap 'cd "${PREV_DIR}"' EXIT
function build_arch() {
local arch=$1
if [ ! -f "${HOME}/.musl-cross/${arch}-linux-musl-cross/bin/${arch}-linux-musl-c++" ]; then
echo "Musl cross compiler for ${arch} not found. Please run install_build_prerequisites.sh first."
exit 1
fi
CMAKE_BUILD_TYPE=Release
CC="${HOME}/.musl-cross/${arch}-linux-musl-cross/bin/${arch}-linux-musl-gcc"
CXX="${HOME}/.musl-cross/${arch}-linux-musl-cross/bin/${arch}-linux-musl-g++"
BUILDDIR="${SCRIPT_DIR}/build/${arch}"
mkdir -p "${BUILDDIR}"
cmake -B "${BUILDDIR}" -G Ninja -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" -DCMAKE_C_COMPILER="${CC}" -DCMAKE_CXX_COMPILER="${CXX}"
cmake --build "${BUILDDIR}"
upx "${BUILDDIR}/dropshell"
cp "${BUILDDIR}/dropshell" "${SCRIPT_DIR}/output/dropshell.${arch}"
}
build_arch x86_64
build_arch aarch64
echo "Static binaries have been created:"
ls -la output

View File

@ -37,23 +37,11 @@ fi
print_status "Detected OS: $OS $VER" print_status "Detected OS: $OS $VER"
#----------------------------------------------------------------------------------------------------------
# INSTALL PREREQUISITE PACKAGES
#----------------------------------------------------------------------------------------------------------
# Define packages based on distribution # Define packages based on distribution
case $OS in case $OS in
"Ubuntu"|"Debian GNU/Linux") "Ubuntu"|"Debian GNU/Linux")
# Common packages for both Ubuntu and Debian # Common packages for both Ubuntu and Debian
PACKAGES="bash cmake make g++ devscripts debhelper build-essential upx musl-tools wget tar ccache ninja-build" PACKAGES="cmake make g++ devscripts debhelper"
INSTALLCMD="apt-get install -y"
UPDATECMD="apt-get update"
;;
"Alpine Linux")
PACKAGES="bash build-base cmake git nlohmann-json wget tar curl ninja mold nodejs npm"
INSTALLCMD="apk add --no-cache"
UPDATECMD="apk update"
;; ;;
*) *)
print_error "Unsupported distribution: $OS" print_error "Unsupported distribution: $OS"
@ -63,29 +51,19 @@ esac
# Function to check if a package is installed # Function to check if a package is installed
is_package_installed() { is_package_installed() {
if [ "$OS" = "Alpine Linux" ]; then
# Use apk info <pkg> and check exit status
apk info "$1" >/dev/null 2>&1
return $?
else
dpkg -l "$1" 2>/dev/null | grep -q "^ii" dpkg -l "$1" 2>/dev/null | grep -q "^ii"
fi
} }
UPDATED=false # Update package lists
print_status "Updating package lists..."
apt-get update
# Install missing packages # Install missing packages
print_status "Checking and installing required packages..." print_status "Checking and installing required packages..."
for pkg in $PACKAGES; do for pkg in $PACKAGES; do
if ! is_package_installed "$pkg"; then if ! is_package_installed "$pkg"; then
if [ "$UPDATED" = false ]; then
print_status "Updating package lists..."
$UPDATECMD
UPDATED=true
fi
print_status "Installing $pkg..." print_status "Installing $pkg..."
$INSTALLCMD "$pkg" apt-get install -y "$pkg"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
print_error "Failed to install $pkg" print_error "Failed to install $pkg"
exit 1 exit 1
@ -95,71 +73,82 @@ for pkg in $PACKAGES; do
fi fi
done done
# ---------------------------------------------------------------------------------------------------------- # Verify all required tools are installed
# MUSL CROSS COMPILERS print_status "Verifying installation..."
# ---------------------------------------------------------------------------------------------------------- for tool in cmake make g++; do
if ! command -v "$tool" &> /dev/null; then
print_error "$tool is not installed properly"
exit 1
fi
done
# Install other required packages
apt install -y musl-tools wget tar
# Set install directory # Set install directory
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
USER_HOME=$(eval echo "~$SUDO_USER") USER_HOME=$(eval echo ~$SUDO_USER)
else else
USER_HOME="$HOME" USER_HOME="$HOME"
fi fi
INSTALL_DIR="$USER_HOME/.musl-cross" INSTALL_DIR="$USER_HOME/.musl-cross"
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
MUSL_CC_URL="https://musl.cc"
TMPDIR=$(mktemp -d) TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT trap 'rm -rf "$TMPDIR"' EXIT
function install_musl_cross() { # x86_64
local TOOLCHAIN="$1" if [ ! -d "$INSTALL_DIR/x86_64-linux-musl-cross" ]; then
local MUSL_CC_URL="https://musl.cc" echo "Downloading x86_64 musl cross toolchain..."
if [ ! -d "$INSTALL_DIR/$TOOLCHAIN" ]; then wget -nc -O "$TMPDIR/x86_64-linux-musl-cross.tgz" $MUSL_CC_URL/x86_64-linux-musl-cross.tgz
echo "Downloading $TOOLCHAIN musl cross toolchain..." tar -C "$INSTALL_DIR" -xvf "$TMPDIR/x86_64-linux-musl-cross.tgz"
wget -nc -O "$TMPDIR/$TOOLCHAIN.tgz" "$MUSL_CC_URL/$TOOLCHAIN.tgz" fi
tar -C "$INSTALL_DIR" -xvf "$TMPDIR/$TOOLCHAIN.tgz"
fi
}
function check_path() { # aarch64
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then if [ ! -d "$INSTALL_DIR/aarch64-linux-musl-cross" ]; then
local BASHRC="$USER_HOME/.bashrc" echo "Downloading aarch64 musl cross toolchain..."
local TOOLCHAIN="$1" wget -nc -O "$TMPDIR/aarch64-linux-musl-cross.tgz" $MUSL_CC_URL/aarch64-linux-musl-cross.tgz
local MUSL_PATH="$INSTALL_DIR/$TOOLCHAIN/bin" tar -C "$INSTALL_DIR" -xvf "$TMPDIR/aarch64-linux-musl-cross.tgz"
if ! echo "$PATH" | grep -q "$MUSL_PATH"; then fi
echo "Adding $MUSL_PATH to PATH in $BASHRC"
PATH_LINE="export PATH=\"$MUSL_PATH:\$PATH\""
if ! grep -Fxq "$PATH_LINE" "$BASHRC"; then
echo "" >> "$BASHRC"
echo "# Add musl cross compilers to PATH for dropshell" >> "$BASHRC"
echo "$PATH_LINE" >> "$BASHRC"
echo "Added musl cross compilers to $BASHRC"
echo "You should run 'source ~/.bashrc' to update your PATH"
else
echo "You should run 'source ~/.bashrc' to update your PATH"
fi
fi
fi
}
# Print instructions for adding to PATH
# cat <<EOF
TOOLCHAIN_LIST=( # To use the musl cross compilers, add the following to your shell:
"aarch64-linux-musl-cross" # export PATH="$INSTALL_DIR/x86_64-linux-musl-cross/bin:$INSTALL_DIR/aarch64-linux-musl-cross/bin:$PATH"
"x86_64-linux-musl-cross"
"x86_64-linux-musl-native"
)
for TOOLCHAIN in "${TOOLCHAIN_LIST[@]}"; do # Or run:
install_musl_cross "$TOOLCHAIN" # export PATH="$INSTALL_DIR/x86_64-linux-musl-cross/bin:$INSTALL_DIR/aarch64-linux-musl-cross/bin:\$PATH"
check_path "$TOOLCHAIN"
done # EOF
# Clean up # Clean up
rm -rf "$TMPDIR" rm -rf "$TMPDIR"
# ---------------------------------------------------------------------------------------------------------- # If run with sudo, add to invoking user's ~/.bashrc
# COMPLETE if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
# ---------------------------------------------------------------------------------------------------------- BASHRC="$USER_HOME/.bashrc"
EXPORT_LINE="export PATH=\"$INSTALL_DIR/x86_64-linux-musl-cross/bin:$INSTALL_DIR/aarch64-linux-musl-cross/bin:\$PATH\""
if ! grep -Fxq "$EXPORT_LINE" "$BASHRC"; then
echo "" >> "$BASHRC"
echo "# Add musl cross compilers to PATH for bb64" >> "$BASHRC"
echo "$EXPORT_LINE" >> "$BASHRC"
echo "Added musl cross compilers to $BASHRC"
else
echo "musl cross compiler PATH already present in $BASHRC"
fi
fi
# check if dehydrate command is installed
if ! command -v dehydrate &> /dev/null; then
curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash
fi
dehydrate -u
print_status "All dependencies installed successfully!" print_status "All dependencies installed successfully!"
print_status "You can now run ./build.sh to build the project" print_status "You can now run ./build.sh to build the project"

View File

@ -1,24 +1,20 @@
#!/bin/bash #!/bin/bash
set -e set -e
# CMake pre-build script.
# Runs before the build process.
# This script creates two files: # This script creates two files:
# src/utils/createagent.hpp # src/utils/createagent.hpp
# src/utils/createagent.cpp # src/utils/createagent.cpp
# #
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR=$(dirname "$0")
# check if dehydrate is installed # check if dehydrate is installed
if ! command -v dehydrate &> /dev/null; then if ! command -v dehydrate &> /dev/null; then
echo "dehydrate could not be found - installing" echo "dehydrate could not be found - installing"
curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash
else
# ensure we have latest dehydrate.
dehydrate -u
fi fi
mkdir -p "${SCRIPT_DIR}/src/autogen" mkdir -p "${SCRIPT_DIR}/src/autogen"
dehydrate "${SCRIPT_DIR}/agent-remote" "${SCRIPT_DIR}/src/autogen" dehydrate "${SCRIPT_DIR}/agent-remote" "${SCRIPT_DIR}/src/autogen"
dehydrate "${SCRIPT_DIR}/agent-local" "${SCRIPT_DIR}/src/autogen" dehydrate "${SCRIPT_DIR}/agent-local" "${SCRIPT_DIR}/src/autogen"

59
source/multibuild.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
# build amd64 and arm64 versions of dropshell, to:
# build/dropshell.amd64
# build/dropshell.arm64
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
rm -f $SCRIPT_DIR/build_amd64/dropshell $SCRIPT_DIR/build_arm64/dropshell $SCRIPT_DIR/output/dropshell.amd64 $SCRIPT_DIR/output/dropshell.arm64
# Determine number of CPU cores for parallel build
if command -v nproc >/dev/null 2>&1; then
JOBS=$(nproc)
else
JOBS=4 # fallback default
fi
PREV_PWD=$PWD
cd $SCRIPT_DIR
# Build for amd64 (musl)
echo "Building for amd64 (musl)..."
cmake -B build_amd64 -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=x86_64-linux-musl-gcc \
-DCMAKE_CXX_COMPILER=x86_64-linux-musl-g++ \
-DCMAKE_EXE_LINKER_FLAGS="-static" \
-DCMAKE_CXX_FLAGS="-march=x86-64" .
cmake --build build_amd64 --target dropshell --config Release -j"$JOBS"
mkdir -p output
cp build_amd64/dropshell output/dropshell.amd64
# Build for arm64 (musl)
echo "Building for arm64 (musl)..."
cmake -B build_arm64 -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=aarch64-linux-musl-gcc \
-DCMAKE_CXX_COMPILER=aarch64-linux-musl-g++ \
-DCMAKE_EXE_LINKER_FLAGS="-static" \
-DCMAKE_CXX_FLAGS="-march=armv8-a" \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 .
cmake --build build_arm64 --target dropshell --config Release -j"$JOBS"
mkdir -p output
cp build_arm64/dropshell output/dropshell.arm64
if [ ! -f output/dropshell.amd64 ]; then
echo "output/dropshell.amd64 not found!" >&2
exit 1
fi
if [ ! -f output/dropshell.arm64 ]; then
echo "output/dropshell.arm64 not found!" >&2
exit 1
fi
echo "Builds complete:"
ls -lh output/dropshell.*
cd $PREV_PWD

View File

@ -6,52 +6,103 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Script directory: $SCRIPT_DIR" echo "Script directory: $SCRIPT_DIR"
# Check for GITEA_TOKEN_DEPLOY or GITEA_TOKEN # Check for GITEA_TOKEN_DEPLOY or GITEA_TOKEN
TOKEN="${GITEA_TOKEN_DEPLOY:-${GITEA_TOKEN}}" if [ -n "$GITEA_TOKEN_DEPLOY" ]; then
[ -z "$TOKEN" ] && { echo "Neither GITEA_TOKEN_DEPLOY nor GITEA_TOKEN environment variable set!" >&2; exit 1; } 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
OLD_PWD="$PWD" $SCRIPT_DIR/multibuild.sh
cd "$SCRIPT_DIR" || exit 1 BUILD_DIR=$SCRIPT_DIR/build
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR" && cd "$OLD_PWD"' EXIT OLD_PWD=$PWD
cd $SCRIPT_DIR
if [ ! -f "output/dropshell.amd64" ]; then
echo "output/dropshell.amd64 not found!" >&2
echo "Please run multibuild.sh first." >&2
exit 1
fi
if [ ! -f "output/dropshell.arm64" ]; then
echo "output/dropshell.arm64 not found!" >&2
echo "Please run multibuild.sh first." >&2
exit 1
fi
TAG=$("$SCRIPT_DIR/output/dropshell.amd64" --version)
[ -z "$TAG" ] && echo "Failed to get version from dropshell.amd64" >&2 && exit 1
ARCH=$(uname -m)
TAG=$("$SCRIPT_DIR/output/dropshell.${ARCH}" --version)
[ -z "$TAG" ] && echo "Failed to get version from dropshell.${ARCH}" >&2 && exit 1
echo "Publishing dropshell version $TAG" echo "Publishing dropshell version $TAG"
# make sure we've commited.
git add "$SCRIPT_DIR/../" && git commit -m "dropshell release $TAG" && git push
function die() {
echo "$@" >&2 # 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 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"
# Create release
RELEASE_DATA=$(cat <<EOF
{
"tag_name": "$TAG",
"name": "$TAG",
"body": "dropshell release $TAG",
"draft": false,
"prerelease": false
} }
EOF
)
# Function to find file in specified locations RELEASE_ID=$(curl -s -X POST "$API_URL/releases" \
find_file() { -H "Content-Type: application/json" \
local filename="$1" -H "Authorization: token $TOKEN" \
shift # remove filename from args -d "$RELEASE_DATA" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
local locations=("$@") # grab the rest of the args as locations
for loc in "${locations[@]}"; do
if [ -f "$loc/$filename" ]; then
echo "$loc/$filename"
return 0 # Found the file, return success
fi
done
echo "" # Return empty string if not found
return 1
}
curl -L -s -o "${TEMP_DIR}/sos" "https://getbin.xyz/sos" || die "Failed to download sos"
chmod +x "${TEMP_DIR}/sos"
if [ -z "$RELEASE_ID" ]; then
echo "Failed to create release on Gitea." >&2
exit 1
fi
# Upload binaries and install.sh # Upload binaries and install.sh
for FILE in dropshell.x86_64 dropshell.aarch64 dropshell-install.sh dropshell-server-autosetup.sh; do for FILE in dropshell.amd64 dropshell.arm64 install.sh server_autosetup.sh; do
# Pass the locations directly to the find_file function if [ -f "output/$FILE" ]; then
filetoupload=$(find_file "$FILE" "output" "../" ".") filetoupload="output/$FILE"
[ -z "$filetoupload" ] && die "File $FILE not found in expected locations!" elif [ -f "../$FILE" ]; then
filetoupload="../$FILE"
elif [ -f "$FILE" ]; then
filetoupload="$FILE"
else
echo "File $FILE not found!" >&2
continue
fi
"${TEMP_DIR}/sos" upload getbin.xyz "$filetoupload" "$FILE:latest" "$FILE:TAG" # 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 done
echo "Published dropshell $TAG to getbin.xyz" echo "Published dropshell version $TAG to $REPO_URL (tag $TAG) with binaries."
cd $OLD_PWD

View File

@ -5,7 +5,7 @@
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include <libassert/assert.hpp> #include <assert.hpp>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
@ -14,7 +14,9 @@ namespace autocomplete {
const std::set<std::string> system_commands_noargs = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"}; const std::set<std::string> system_commands_noargs = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"};
const std::set<std::string> system_commands_always_available = {"help","edit"}; const std::set<std::string> system_commands_always_available = {"help","edit"};
const std::set<std::string> system_commands_require_config = {"server","templates","create-service","create-template","create-server","ssh","list"}; const std::set<std::string> system_commands_require_config = {"server","templates","create-service","create-template","create-server","ssh","list"};
const std::set<std::string> system_commands_hidden = {"_allservicesstatus"}; const std::set<std::string> system_commands_hidden = {"nuke","_allservicesstatus"};
const std::set<std::string> service_commands_require_config = {"ssh","edit","nuke","_allservicesstatus"};
void merge_commands(std::set<std::string> &commands, const std::set<std::string> &new_commands) void merge_commands(std::set<std::string> &commands, const std::set<std::string> &new_commands)
{ {

View File

@ -5,7 +5,7 @@
#include <filesystem> #include <filesystem>
#include "utils/output.hpp" #include "utils/output.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#include "config.hpp" #include "config.hpp"
@ -53,7 +53,7 @@ namespace dropshell
namespace shared_commands namespace shared_commands
{ {
bool backupdata_service(const ServerConfig &server_env, const std::string &service) bool backupdata_service(const server_config &server_env, const std::string &service)
{ {
ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name()); ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
std::string server = server_env.get_server_name(); std::string server = server_env.get_server_name();
@ -84,7 +84,7 @@ namespace dropshell
remote_command_script_file, remote_command_script_file,
remotefile(server, user).service_env(service)}, user)) remotefile(server, user).service_env(service)}, user))
{ {
error << "Required service directories not found on remote server" << std::endl; error << "Error: Required service directories not found on remote server" << std::endl;
info << "Is the service installed?" << std::endl; info << "Is the service installed?" << std::endl;
return false; return false;
} }
@ -103,7 +103,7 @@ namespace dropshell
std::string local_backups_dir = localpath::backups(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty()) if (local_backups_dir.empty())
{ {
error << "Local backups directory not found" << std::endl; error << "Error: Local backups directory not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl; info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; return false;
} }

View File

@ -1,7 +1,7 @@
#include "command_registry.hpp" #include "command_registry.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell { namespace dropshell {

View File

@ -20,15 +20,6 @@ void CommandRegistry::register_command(const CommandInfo& info) {
const CommandInfo* CommandRegistry::find_command(const std::string& name) const { const CommandInfo* CommandRegistry::find_command(const std::string& name) const {
auto it = command_map_.find(name); auto it = command_map_.find(name);
if (it != command_map_.end()) return it->second.get(); if (it != command_map_.end()) return it->second.get();
// go deep now.
for (const auto& cmd : all_commands_) {
if (cmd->names.size() > 0) {
for (const auto& altname : cmd->names) {
if (name == altname) return cmd.get();
}
}
}
return nullptr; return nullptr;
} }

View File

@ -10,7 +10,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell { namespace dropshell {

View File

@ -3,7 +3,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "templates.hpp" #include "templates.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "services.hpp" #include "services.hpp"
@ -103,12 +103,7 @@ namespace dropshell
if (server_name.empty() || template_name.empty() || service_name.empty()) if (server_name.empty() || template_name.empty() || service_name.empty())
return false; return false;
if (!legal_service_name(service_name)) { server_config server_info(server_name);
error << "Service name contains illegal characters: " << service_name << std::endl;
return false;
}
ServerConfig server_info(server_name);
if (!server_info.is_valid()) if (!server_info.is_valid())
{ {
error << "Server " << server_name << " is not valid" << std::endl; error << "Server " << server_name << " is not valid" << std::endl;
@ -199,10 +194,10 @@ namespace dropshell
info << "Setting SSH_USER to " << sshuser << " in the " << filenames::service_env << " file" << std::endl; info << "Setting SSH_USER to " << sshuser << " in the " << filenames::service_env << " file" << std::endl;
{ // edit the service.env file to set the SSH_USER. { // edit the service.env file to set the SSH_USER.
std::string source_service_env = tinfo.local_template_path() / "config" / filenames::service_env; std::string template_service_env_file = tinfo.local_template_path() / "config" / filenames::service_env;
ASSERT(std::filesystem::exists(source_service_env), "Template service env file not found: " + source_service_env); ASSERT(std::filesystem::exists(template_service_env_file), "Template service env file not found: " + template_service_env_file);
std::ifstream template_service_env_file_in(source_service_env); std::ifstream template_service_env_file_in(template_service_env_file);
std::ofstream service_env_file_out(localfile::service_env(server_name, service_name)); std::ofstream service_env_file_out(localfile::template_info_env(server_name, service_name));
std::string line; std::string line;
while (std::getline(template_service_env_file_in, line)) while (std::getline(template_service_env_file_in, line))
{ {

View File

@ -4,7 +4,7 @@
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "version.hpp" #include "version.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "templates.hpp" #include "templates.hpp"
#include <unistd.h> #include <unistd.h>

View File

@ -8,21 +8,21 @@
#include "templates.hpp" #include "templates.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell namespace dropshell
{ {
int destroy_handler(const CommandContext &ctx); int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> destroy_name_list = {"destroy", "nuke", "nuke-service","erase","destroy-service"}; static std::vector<std::string> nuke_name_list = {"destroy","nuke"};
// Static registration // Static registration
struct DestroyCommandRegister struct NukeCommandRegister
{ {
DestroyCommandRegister() NukeCommandRegister()
{ {
CommandRegistry::instance().register_command({destroy_name_list, CommandRegistry::instance().register_command({nuke_name_list,
destroy_handler, nuke_handler,
shared_commands::std_autocomplete, shared_commands::std_autocomplete,
false, // hidden false, // hidden
true, // requires_config true, // requires_config
@ -33,7 +33,7 @@ namespace dropshell
"Destroy a service on a server. Erases everything, both local and remote!", "Destroy a service on a server. Erases everything, both local and remote!",
// heredoc // heredoc
R"( R"(
Destroy a service. Nuke a service.
Examples: Examples:
destroy SERVER SERVICE destroy the given service on the given server. destroy SERVER SERVICE destroy the given service on the given server.
@ -45,64 +45,42 @@ namespace dropshell
Use with caution! Use with caution!
)"}); )"});
} }
} destroy_command_register; } nuke_command_register;
namespace shared_commands namespace shared_commands
{ {
bool destroy_service(const std::string &server, const std::string &service) bool nuke_service(const std::string &server, const std::string &service)
{ {
ServerConfig server_env(server); server_config server_env(server);
// step 1 - destroy on remote server. // step 1 - nuke on remote server.
if (server_env.is_valid()) if (server_env.is_valid())
{ {
std::string user = server_env.get_user_for_service(service); // returns empty string if no user found.
if (user.empty())
{
warning << "No user found for service " << service << " on " << server << std::endl;
for (auto sshuser : server_env.get_users())
{
if (server_env.check_remote_dir_exists(remotepath(server, sshuser.user).service(service), sshuser.user))
{
info << "Found a remote service directory here: " << remotepath(server, sshuser.user).service(service) << std::endl;
info << "Deleting it as user " << sshuser.user << std::endl;
user = sshuser.user;
break;
}
}
}
if (user.empty())
warning << "No remote service directory found for " << service << " on " << server << std::endl;
else
{ // user is not empty.
LocalServiceInfo service_info; LocalServiceInfo service_info;
service_info = get_service_info(server, service);
bool service_valid = SIvalid(service_info);
if (!service_valid)
warning << "No valid service definition found for " << service << std::endl;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Invalid service: " << service << std::endl;
std::string user = server_env.get_user_for_service(service);
if (server_env.check_remote_dir_exists(remotepath(server, user).service(service), user)) if (server_env.check_remote_dir_exists(remotepath(server, user).service(service), user))
{ {
// run the destroy script on the remote server if it exists. // run the nuke script on the remote server if it exists.
// otherwise just uninstall. // otherwise just uninstall.
if (service_valid) if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
{ {
if (gTemplateManager().template_command_exists(service_info.template_name, "destroy")) info << "Running nuke script for " << service << " on " << server << std::endl;
{ if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
info << "Running destroy script for " << service << " on " << server << std::endl; warning << "Failed to run nuke script: " << service << std::endl;
if (!server_env.run_remote_template_command(service, "destroy", {}, false, {}))
warning << "Failed to run destroy script: " << service << std::endl;
} }
else else
{ {
info << "No destroy script found for " << service << " on " << server << std::endl; info << "No nuke script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl; info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl; warning << "Failed to uninstall service: " << service << std::endl;
} }
}
// Remove the service directory from the server, running in a docker container as root. // Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath(server, user).service(service), true, user)) if (server_env.remove_remote_dir(remotepath(server, user).service(service), true, user))
@ -114,36 +92,33 @@ namespace dropshell
warning << "Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
} }
else else
warning << "No remote service directory found for " << service << " on "<< server << std::endl; warning << "Service not found on remote server: " << remotepath(server, user).service(service) << std::endl;
} // user is not empty. }
} // server_env is valid.
else else
error << "No valid local server information for server " << server << std::endl; warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - destroy the local service directory, if it exists. // step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service); std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path)) if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{ {
warning << "No local service directory found for " << service << " on " << server << std::endl; warning << "Local service directory not found: " << local_service_path << std::endl;
} }
else else
{ {
auto itemsdeleted = std::filesystem::remove_all(local_service_path); auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0) if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl; error << "Failed to remove local service directory" << std::endl;
else
info << "Local service directory removed: " << local_service_path << std::endl;
} }
info << "Finished destroying service " << service << " on server " << server << std::endl; info << "Nuked service " << service << " on server " << server << std::endl;
return true; return true;
} }
} // namespace shared_commands } // namespace shared_commands
int destroy_handler(const CommandContext &ctx) int nuke_handler(const CommandContext &ctx)
{ {
ASSERT(ctx.args.size() == 2, "Usage: destroy SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")"); ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration."); ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0); std::string server = safearg(ctx.args, 0);
@ -166,14 +141,14 @@ namespace dropshell
if (entry.is_directory() && entry.path().filename().string().find(".") != 0) if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{ {
std::string service_name = entry.path().filename().string(); std::string service_name = entry.path().filename().string();
rval |= (shared_commands::destroy_service(server, service_name) ? 0 : 1); rval |= (shared_commands::nuke_service(server, service_name) ? 0 : 1);
} }
} }
return rval; return rval;
} }
else else
{ {
return (shared_commands::destroy_service(server, service) ? 0 : 1); return (shared_commands::nuke_service(server, service) ? 0 : 1);
} }
} }

View File

@ -9,7 +9,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell { namespace dropshell {
@ -94,11 +94,11 @@ int edit_config()
std::string config_file = localfile::dropshell_json(); std::string config_file = localfile::dropshell_json();
if (!edit_file(config_file, false) || !std::filesystem::exists(config_file)) if (!edit_file(config_file, false) || !std::filesystem::exists(config_file))
return return_die("Failed to edit config file."); return die("Error: Failed to edit config file.");
gConfig().load_config(); gConfig().load_config();
if (!gConfig().is_config_set()) if (!gConfig().is_config_set())
return return_die("Failed to load and parse edited config file!"); return die("Error: Failed to load and parse edited config file!");
gConfig().save_config(true); gConfig().save_config(true);
@ -112,7 +112,7 @@ int edit_config()
int edit_server(const std::string &server_name) int edit_server(const std::string &server_name)
{ {
if (localpath::server(server_name).empty()) { if (localpath::server(server_name).empty()) {
error << "Server not found: " << server_name << std::endl; std::cerr << "Error: Server not found: " << server_name << std::endl;
return -1; return -1;
} }
@ -130,28 +130,6 @@ int edit_server(const std::string &server_name)
return 0; return 0;
} }
void list_directory(std::string dir, std::string msg)
{
bool first=true;
std::vector<std::string> directories;
for (const auto &file : std::filesystem::directory_iterator(dir))
{
if (first)
{
if (!msg.empty())
info << msg << std::endl;
first=false;
}
if (std::filesystem::is_directory(file.path()))
directories.push_back(file.path());
else
info << " " << file.path() << std::endl;
}
for (const auto &dir : directories)
list_directory(dir, "");
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// edit service config // edit service config
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -165,14 +143,7 @@ int edit_service_config(const std::string &server, const std::string &service)
} }
if (edit_file(config_file, true) && std::filesystem::exists(config_file)) if (edit_file(config_file, true) && std::filesystem::exists(config_file))
info << "Successfully edited service config file at " << config_file << std::endl; info << "To apply your changes, run:\n dropshell install " + server + " " + service << std::endl;
std::string service_dir = localpath::service(server, service);
list_directory(service_dir, "You may wish to edit the other files in " + service_dir);
info << "Then to apply your changes, run:" << std::endl;
info << " dropshell uninstall " + server + " " + service << std::endl;
info << " dropshell install " + server + " " + service << std::endl;
return 0; return 0;
} }

View File

@ -1,83 +0,0 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "version.hpp"
#include "hash.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include <libassert/assert.hpp>
namespace dropshell {
void hash_autocomplete(const CommandContext& ctx);
int hash_handler(const CommandContext& ctx);
static std::vector<std::string> hash_name_list={"hash"};
// Static registration
struct HashCommandRegister {
HashCommandRegister() {
CommandRegistry::instance().register_command({
hash_name_list,
hash_handler,
hash_autocomplete,
false, // hidden
false, // requires_config
false, // requires_install
0, // min_args (after command)
1, // max_args (after command)
"hash [FILE|DIRECTORY]",
"Hash a file or directory.",
// heredoc
R"(
Hash a file or directory recursively.
)"
});
}
} hash_command_register;
void hash_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 0) {
// list all files and directories in the current directory
for (const auto& entry : std::filesystem::directory_iterator(".")) {
rawout << entry.path().string() << std::endl;
}
}
return;
}
int hash_handler(const CommandContext& ctx) {
std::filesystem::path path = safearg(ctx.args, 0);
if (path.empty())
path=std::filesystem::current_path();
if (!std::filesystem::exists(path))
{
error << "Does not exist: " << path.string() << std::endl;
return 1;
}
if (std::filesystem::is_directory(path))
{
// hash the directory recursively
uint64_t hash = hash_directory_recursive(path.string());
std::cout << hash << std::endl;
}
else
{
// hash the file
uint64_t hash = hash_file(path.string());
std::cout << hash << std::endl;
}
return 0;
}
} // namespace dropshell

View File

@ -10,7 +10,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell { namespace dropshell {
@ -141,7 +141,7 @@ int help_handler(const CommandContext& ctx) {
info << std::endl; info << std::endl;
show_command("install"); show_command("install");
show_command("uninstall"); show_command("uninstall");
show_command("destroy"); show_command("nuke");
info << std::endl; info << std::endl;
show_command("start"); show_command("start");
show_command("stop"); show_command("stop");

View File

@ -16,9 +16,8 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "servers.hpp" #include "servers.hpp"
#include <sys/stat.h>
namespace dropshell namespace dropshell
{ {
@ -64,34 +63,12 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND // install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool install_service(const ServerConfig &server_env, const std::string &service) bool install_service(const server_config &server_env, const std::string &service)
{ {
std::string server = server_env.get_server_name(); std::string server = server_env.get_server_name();
LocalServiceInfo service_info = get_service_info(server_env.get_server_name(), service); LocalServiceInfo service_info = get_service_info(server_env.get_server_name(), service);
if (!SIvalid(service_info)) if (!SIvalid(service_info) || !service_info.service_template_hash_match)
{
error << "Failed to install - service information not valid." << std::endl;
return false;
}
if (!server_env.is_valid())
return false; // should never hit this.
std::string user = service_info.user;
std::string remote_service_path = remotepath(server,user).service(service);
ASSERT(!remote_service_path.empty(), "Install_Service: Remote service path is empty for " + service + " on " + server);
ASSERT(!user.empty(), "Install_Service: User is empty for " + service + " on " + server);
if (server_env.check_remote_dir_exists(remote_service_path, user))
{ // uninstall the old service before we update the config or template!
info << "Service " << service << " is already installed on " << server << std::endl;
shared_commands::uninstall_service(server_env, service);
}
if (!service_info.service_template_hash_match)
{ {
warning << "Service " << service << " is using an old template. Updating. " << std::endl; warning << "Service " << service << " is using an old template. Updating. " << std::endl;
if (!merge_updated_service_template(server_env.get_server_name(), service)) if (!merge_updated_service_template(server_env.get_server_name(), service))
@ -110,6 +87,9 @@ namespace dropshell
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server); maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
if (!server_env.is_valid())
return false; // should never hit this.
// Check if template exists // Check if template exists
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name); template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
if (!tinfo.is_set()) if (!tinfo.is_set())
@ -122,6 +102,8 @@ namespace dropshell
} }
// Create service directory // Create service directory
std::string user = server_env.get_user_for_service(service);
std::string remote_service_path = remotepath(server,user).service(service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path); std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Silent)) if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Silent))
{ {
@ -129,6 +111,14 @@ namespace dropshell
return false; return false;
} }
// Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
// Copy template files // Copy template files
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_template(service) << "/" << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_template(service) << "/" << std::endl;
@ -152,13 +142,7 @@ namespace dropshell
// Run install script // Run install script
{ {
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl; info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, false, {});
shared_commands::cRemoteTempFolder remote_temp_folder(server_env, user);
if (!server_env.run_remote_template_command(service, "install", {}, false, {{"TEMP_DIR", remote_temp_folder.path()}}))
{
error << "Failed to run install script on " << server << std::endl;
return false;
}
} }
// print health tick // print health tick
@ -192,107 +176,49 @@ namespace dropshell
return trim(result); return trim(result);
} }
int configure_autocomplete()
{
debug << "Ensuring dropshell autocomplete is registered in ~/.bashrc..." << std::endl;
std::filesystem::path bashrc = localpath::current_user_home() +"/.bashrc";
std::string autocomplete_script = R"(
#---DROPSHELL AUTOCOMPLETE START---
_dropshell_completions() {
local cur
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
# call dropshell to get the list of possiblities for the current argument. Supply all previous arguments.
local completions=($(dropshell autocomplete "${COMP_WORDS[@]:1:${COMP_CWORD}-1}"))
COMPREPLY=( $(compgen -W "${completions[*]}" -- ${cur}) )
return 0
}
# Register the completion function
complete -F _dropshell_completions dropshell
complete -F _dropshell_completions ds
#---DROPSHELL AUTOCOMPLETE END---
)";
file_replace_or_add_segment(bashrc.string(), autocomplete_script);
return 0;
}
int configure_localbin()
{
debug << "Ensuring ~/.local/bin is in the ~/.bashrc path..." << std::endl;
std::filesystem::path bashrc = localpath::current_user_home() +"/.bashrc";
std::filesystem::path localbin = localpath::current_user_home() + "/.local/bin";
std::filesystem::create_directories(localbin);
// check if already in path
const char* env_p = std::getenv("PATH");
if (env_p) {
std::string path_str = env_p;
if (path_str.find(localbin.string()) == std::string::npos) {
std::string pathstr="#---DROPSHELL PATH START---\nexport PATH=\""+localbin.string()+":$PATH\"\n#---DROPSHELL PATH END---\n";
file_replace_or_add_segment(bashrc.string(), pathstr);
}
}
return 0;
}
int update_dropshell() int update_dropshell()
{ {
maketitle("Updating dropshell on this computer..."); maketitle("Updating dropshell on this computer...");
configure_localbin();
configure_autocomplete();
// determine path to this executable // determine path to this executable
std::filesystem::path exe_path = std::filesystem::canonical("/proc/self/exe"); std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
std::filesystem::path parent_path = exe_path.parent_path(); std::filesystem::path parent_path = dropshell_path.parent_path();
// determine the architecture of the system // determine the architecture of the system
std::string arch = shared_commands::get_arch(); std::string arch = shared_commands::get_arch();
std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch; std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch;
// check that the user that owns the exe is the current user this process is running as. // download new version, preserve permissions and ownership
struct stat st; std::string bash_script;
if (stat(exe_path.c_str(), &st) != 0) { bash_script += "docker run --rm -v " + parent_path.string() + ":/target";
error << "Failed to stat dropshell executable: " << strerror(errno) << std::endl; bash_script += " gitea.jde.nz/public/debian-curl:latest";
return -1; bash_script += " sh -c \"";
} bash_script += " curl -fsSL " + url + " -o /target/dropshell_temp &&";
bash_script += " chmod --reference=/target/dropshell /target/dropshell_temp &&";
bash_script += " chown --reference=/target/dropshell /target/dropshell_temp";
bash_script += "\"";
uid_t current_uid = getuid(); std::string cmd = "bash -c '" + bash_script + "'";
if (st.st_uid != current_uid) { int rval = system(cmd.c_str());
warning << "Current user does not own the dropshell executable. Please run as the owner to update." << std::endl; if (rval != 0)
return -1;
}
shared_commands::cLocalTempFolder local_temp_folder;
std::filesystem::path temp_file = local_temp_folder.path() / "dropshell";
bool download_okay = download_file(url, temp_file);
if (!download_okay)
{ {
error << "Failed to download new version of dropshell." << std::endl; std::cerr << "Failed to download new version of dropshell." << std::endl;
return -1; return -1;
} }
// make executable
chmod(temp_file.c_str(), 0755);
// check if the new version is the same as the old version // check if the new version is the same as the old version
uint64_t new_hash = hash_file(temp_file); uint64_t new_hash = hash_file(parent_path / "dropshell_temp");
uint64_t old_hash = hash_file(exe_path); uint64_t old_hash = hash_file(parent_path / "dropshell");
if (new_hash == old_hash) if (new_hash == old_hash)
{ {
info << "Confirmed dropshell is the latest version." << std::endl; std::cout << "Confirmed dropshell is the latest version." << std::endl;
return 0; return 0;
} }
std::string runvercmd = exe_path.string() + " version"; std::string runvercmd = (parent_path / "dropshell").string() + " version";
std::string currentver = _exec(runvercmd.c_str()); std::string currentver = _exec(runvercmd.c_str());
runvercmd = temp_file.string() + " version"; runvercmd = (parent_path / "dropshell_temp").string() + " version";
std::string newver = _exec(runvercmd.c_str()); std::string newver = _exec(runvercmd.c_str());
if (currentver >= newver) if (currentver >= newver)
@ -302,15 +228,21 @@ complete -F _dropshell_completions ds
return 0; return 0;
} }
// move the new version to the old version. return 0;
std::filesystem::rename(exe_path, exe_path.parent_path() / "dropshell.old");
std::filesystem::rename(temp_file, exe_path);
// remove the old version. std::string bash_script_2 = "docker run --rm -v " + parent_path.string() + ":/target gitea.jde.nz/public/debian-curl:latest " +
std::filesystem::remove(exe_path.parent_path() / "dropshell.old"); "sh -c \"mv /target/dropshell_temp /target/dropshell\"";
rval = system(bash_script_2.c_str());
if (rval != 0)
{
error << "Failed to install new version of dropshell." << std::endl;
return -1;
}
info << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl;
// execute the new version // execute the new version
execlp("bash", "bash", "-c", (exe_path.parent_path() / "dropshell").string() + "install", (char *)nullptr); execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr);
error << "Failed to execute new version of dropshell." << std::endl; error << "Failed to execute new version of dropshell." << std::endl;
return -1; return -1;
} }
@ -339,7 +271,7 @@ complete -F _dropshell_completions ds
return 0; return 0;
} }
int install_server(const ServerConfig &server) int install_server(const server_config &server)
{ {
// install the dropshell agent on the given server. // install the dropshell agent on the given server.
maketitle("Installing dropshell agent on " + server.get_server_name(), sColour::INFO); maketitle("Installing dropshell agent on " + server.get_server_name(), sColour::INFO);
@ -349,7 +281,7 @@ complete -F _dropshell_completions ds
info << "Installing agent for user " << user.user << " on " << server.get_server_name() << std::endl; info << "Installing agent for user " << user.user << " on " << server.get_server_name() << std::endl;
std::string agent_path = remotepath(server.get_server_name(),user.user).agent(); std::string agent_path = remotepath(server.get_server_name(),user.user).agent();
ASSERT(agent_path == user.dir+"/agent", "Remote agent path does not match user directory for "+user.user+"@" + server.get_server_name() + " : " + agent_path + " != " + user.dir); ASSERT(agent_path == user.dir+"/agent", "Agent path does not match user directory for "+user.user+"@" + server.get_server_name() + " : " + agent_path + " != " + user.dir);
ASSERT(!agent_path.empty(), "Agent path is empty for " + user.user + "@" + server.get_server_name()); ASSERT(!agent_path.empty(), "Agent path is empty for " + user.user + "@" + server.get_server_name());
// now create the agent. // now create the agent.
@ -363,7 +295,7 @@ complete -F _dropshell_completions ds
bool okay = execute_ssh_command(server.get_SSH_INFO(user.user), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr); bool okay = execute_ssh_command(server.get_SSH_INFO(user.user), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay) if (!okay)
{ {
error << "Failed to install remote agent on " << server.get_server_name() << std::endl; error << "ERROR: Failed to install remote agent on " << server.get_server_name() << std::endl;
return 1; return 1;
} }
@ -389,7 +321,7 @@ complete -F _dropshell_completions ds
return rval; return rval;
// install the dropshell agent on all servers. // install the dropshell agent on all servers.
std::vector<ServerConfig> servers = get_configured_servers(); std::vector<server_config> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
rval = install_server(server); rval = install_server(server);
@ -432,7 +364,7 @@ complete -F _dropshell_completions ds
return 1; return 1;
} }
ServerConfig server_env(server); server_config server_env(server);
ASSERT(server_env.is_valid(), "Invalid server environment for " + server); ASSERT(server_env.is_valid(), "Invalid server environment for " + server);
if (safearg(ctx.args, 1) == "all") if (safearg(ctx.args, 1) == "all")
{ {

View File

@ -14,7 +14,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
namespace dropshell { namespace dropshell {
@ -83,61 +83,44 @@ void list_servers() {
tableprint tp("All DropShell Servers"); tableprint tp("All DropShell Servers");
tp.add_row({"Name", "Address", "User", "Health", "Ports"}); tp.add_row({"Name", "Address", "User", "Health", "Ports"});
info << "Checking "<<servers.size() << " servers: " << std::flush;
typedef std::map<std::string, shared_commands::ServiceStatus> tServiceStatusMap;
std::vector<tServiceStatusMap> service_status_maps;
typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair;
std::vector<server_user_pair> server_user_pairs;
for (const auto& server : servers)
for (const auto& user : server.get_users())
server_user_pairs.push_back({server, user});
// mutex for the tableprint
std::mutex tp_mutex;
info << "Checking "<<server_user_pairs.size() << " agents: " << std::flush;
int checked = 0; int checked = 0;
transwarp::parallel exec{servers.size()};
auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const server_config& server) {
transwarp::parallel exec{server_user_pairs.size()}; server_config server_env(server.get_server_name());
auto task = transwarp::for_each(exec, server_user_pairs.begin(), server_user_pairs.end(), [&](const server_user_pair& sup) {
ServerConfig server_env(sup.server.get_server_name());
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Invalid server environment for " << sup.server.get_server_name() << std::endl; error << "Invalid server environment for " << server.get_server_name() << std::endl;
return; return;
} }
std::string serviceticks = ""; int first=true;
std::string ports_used_str = ""; for (const auto &user : server_env.get_users())
{
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server.get_server_name(),user.user);
std::set<int> ports_used; std::set<int> ports_used;
std::string serviceticks = "";
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(sup.server.get_server_name(),sup.user.user);
for (const auto& [service_name, service_status] : status) { for (const auto& [service_name, service_status] : status) {
ports_used.insert(service_status.ports.begin(), service_status.ports.end()); ports_used.insert(service_status.ports.begin(), service_status.ports.end());
serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; serviceticks += shared_commands::HealthStatus2String(service_status.health) + " ";
} }
std::string ports_used_str = "";
for (const auto& port : ports_used) for (const auto& port : ports_used)
ports_used_str += std::to_string(port) + " "; ports_used_str += std::to_string(port) + " ";
// critical section
{ tp.add_row({(first ? server.get_server_name() : ""), (first ? server.get_SSH_HOST() : ""), user.user, serviceticks, ports_used_str});
std::lock_guard<std::mutex> lock(tp_mutex); first = false;
tp.add_row({sup.server.get_server_name(), sup.server.get_SSH_HOST(), sup.user.user, serviceticks, ports_used_str}); }
++checked; ++checked;
// print out a tick character for each server checked. // print out a tick character for each server checked.
info << checked << "" << std::flush; info << checked << "" << std::flush;
}
}); });
task->wait(); task->wait();
info << std::endl << std::endl; info << std::endl << std::endl;
tp.sort({0,2});
tp.print(); tp.print();
} }
@ -145,9 +128,9 @@ void list_servers() {
void show_server_details(const std::string& server_name) { void show_server_details(const std::string& server_name) {
ServerConfig env(server_name); server_config env(server_name);
if (!env.is_valid()) { if (!env.is_valid()) {
error << "Invalid server environment file: " << server_name << std::endl; error << "Error: Invalid server environment file: " << server_name << std::endl;
return; return;
} }

View File

@ -5,7 +5,7 @@
#include <filesystem> #include <filesystem>
#include "utils/output.hpp" #include "utils/output.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#include "config.hpp" #include "config.hpp"
@ -60,7 +60,7 @@ namespace dropshell
std::string local_backups_dir = localpath::backups(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{ {
error << "Local backups directory not found: " << local_backups_dir << std::endl; error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return {}; return {};
} }
@ -92,7 +92,7 @@ namespace dropshell
std::string service = ctx.args[1]; std::string service = ctx.args[1];
std::string backup_arg = ctx.args[2]; std::string backup_arg = ctx.args[2];
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
@ -105,16 +105,6 @@ namespace dropshell
return 1; return 1;
} }
if (!gTemplateManager().template_command_exists(service_info.template_name, "backup") ||
!gTemplateManager().template_command_exists(service_info.template_name, "restore"))
{
info << service << " has no data to restore" << std::endl;
debug << "(no backup or restore script for " << service_info.template_name << ")" << std::endl;
return 0; // nothing to back up.
}
std::optional<shared_commands::cBackupFileName> backup_details; std::optional<shared_commands::cBackupFileName> backup_details;
if (backup_arg == "latest") if (backup_arg == "latest")
{ // special case. { // special case.
@ -150,19 +140,19 @@ namespace dropshell
std::string local_backups_dir = localpath::backups(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{ {
error << "Local backups directory not found: " << local_backups_dir << std::endl; error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return 1; return 1;
} }
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_details->get_filename()).string(); std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_details->get_filename()).string();
if (!std::filesystem::exists(local_backup_file_path)) if (!std::filesystem::exists(local_backup_file_path))
{ {
error << "Backup file not found at " << local_backup_file_path << std::endl; error << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return 1; return 1;
} }
if (backup_details->get_template_name() != service_info.template_name) if (backup_details->get_template_name() != service_info.template_name)
{ {
error << "Backup template does not match service template. Can't restore." << std::endl; error << "Error: Backup template does not match service template. Can't restore." << std::endl;
info << "Backup template: " << backup_details->get_template_name() << std::endl; info << "Backup template: " << backup_details->get_template_name() << std::endl;
info << "Service template: " << service_info.template_name << std::endl; info << "Service template: " << service_info.template_name << std::endl;
return 1; return 1;
@ -185,9 +175,9 @@ namespace dropshell
info << "Backup complete." << std::endl; info << "Backup complete." << std::endl;
} }
{ // Destroy the old service { // nuke the old service
info << "2) Destroying old service..." << std::endl; info << "2) Nuking old service..." << std::endl;
if (!shared_commands::destroy_service(server, service)) if (!shared_commands::nuke_service(server, service))
return 1; return 1;
} }
@ -199,7 +189,7 @@ namespace dropshell
{ // installing fresh service { // installing fresh service
info << "4) Install of fresh service..." << std::endl; info << "4) Install of fresh service..." << std::endl;
ServerConfig server_env(server); server_config server_env(server);
if (!shared_commands::install_service(server_env, service)) if (!shared_commands::install_service(server_env, service))
return 1; return 1;
} }

View File

@ -1,5 +1,5 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "directories.hpp" #include "directories.hpp"
@ -21,7 +21,7 @@ namespace dropshell
if (ctx.args.size() == 0) if (ctx.args.size() == 0)
{ // just the command, no args yet. { // just the command, no args yet.
// list servers // list servers
std::vector<ServerConfig> servers = get_configured_servers(); std::vector<server_config> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
rawout << server.get_server_name() << std::endl; rawout << server.get_server_name() << std::endl;
@ -54,7 +54,7 @@ namespace dropshell
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
const ServerConfig &server_env, const server_config &server_env,
bool silent, bool silent,
std::string user) std::string user)
{ {
@ -75,9 +75,9 @@ namespace dropshell
// determine the architecture of the system // determine the architecture of the system
std::string arch; std::string arch;
#ifdef __aarch64__ #ifdef __aarch64__
arch = "aarch64"; arch = "arm64";
#elif __x86_64__ #elif __x86_64__
arch = "x86_64"; arch = "amd64";
#endif #endif
return arch; return arch;
} }
@ -85,7 +85,7 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS // cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const ServerConfig &server_env, std::string user) : cRemoteTempFolder::cRemoteTempFolder(const server_config &server_env, std::string user) :
mServerEnv(server_env), mUser(user) mServerEnv(server_env), mUser(user)
{ {
std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10); std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10);
@ -107,26 +107,10 @@ namespace dropshell
return mPath; return mPath;
} }
cLocalTempFolder::cLocalTempFolder()
{
mPath = std::filesystem::temp_directory_path() / random_alphanumeric_string(10);
std::filesystem::create_directories(mPath);
}
cLocalTempFolder::~cLocalTempFolder()
{
std::filesystem::remove_all(mPath);
}
std::filesystem::path cLocalTempFolder::path() const
{
return mPath;
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND // get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env) std::map<std::string, ServiceStatus> get_all_services_status(const server_config & server_env)
{ {
std::map<std::string, ServiceStatus> status; std::map<std::string, ServiceStatus> status;
for (const auto& user : server_env.get_users()) { for (const auto& user : server_env.get_users()) {
@ -135,7 +119,7 @@ namespace dropshell
return status; return status;
} }
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user) std::map<std::string, ServiceStatus> get_all_services_status(const server_config & server_env, std::string user)
{ {
std::map<std::string, ServiceStatus> status; std::map<std::string, ServiceStatus> status;
std::string server_name = server_env.get_server_name(); std::string server_name = server_env.get_server_name();
@ -232,7 +216,7 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service) HealthStatus is_healthy(const std::string &server, const std::string &service)
{ {
ServerConfig env(server); server_config env(server);
if (!env.is_valid()) if (!env.is_valid())
{ {
error << "Server service not initialized" << std::endl; error << "Server service not initialized" << std::endl;
@ -325,7 +309,7 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// scp_file_to_remote : SHARED COMMAND // scp_file_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user) bool scp_file_to_remote(const server_config &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user)
{ {
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
@ -340,7 +324,7 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// scp_file_from_remote : SHARED COMMAND // scp_file_from_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user) bool scp_file_from_remote(const server_config &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user)
{ {
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {

View File

@ -1,8 +1,6 @@
#ifndef SHARED_COMMANDS_HPP #ifndef SHARED_COMMANDS_HPP
#define SHARED_COMMANDS_HPP #define SHARED_COMMANDS_HPP
#include <filesystem>
#include "servers.hpp" #include "servers.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#include "servers.hpp" #include "servers.hpp"
@ -33,36 +31,26 @@ namespace dropshell
class cRemoteTempFolder class cRemoteTempFolder
{ {
public: public:
cRemoteTempFolder(const ServerConfig &server_env, std::string user); // create a temp folder on the remote server cRemoteTempFolder(const server_config &server_env, std::string user); // create a temp folder on the remote server
~cRemoteTempFolder(); // delete the temp folder on the remote server ~cRemoteTempFolder(); // delete the temp folder on the remote server
std::string path() const; // get the path to the temp folder on the remote server std::string path() const; // get the path to the temp folder on the remote server
private: private:
std::string mPath; std::string mPath;
const ServerConfig &mServerEnv; const server_config &mServerEnv;
std::string mUser; std::string mUser;
}; };
class cLocalTempFolder
{
public:
cLocalTempFolder(); // create a temp folder on the local machine
~cLocalTempFolder(); // delete the temp folder on the local machine
std::filesystem::path path() const; // get the path to the temp folder on the local machine
private:
std::filesystem::path mPath;
};
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
const ServerConfig &server_env, const server_config &server_env,
bool silent, bool silent,
std::string user); std::string user);
std::string get_arch(); std::string get_arch();
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env); std::map<std::string, ServiceStatus> get_all_services_status(const server_config & server_env);
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user); std::map<std::string, ServiceStatus> get_all_services_status(const server_config & server_env, std::string user);
std::string healthtick(const std::string &server, const std::string &service); std::string healthtick(const std::string &server, const std::string &service);
std::string HealthStatus2String(HealthStatus status); std::string HealthStatus2String(HealthStatus status);
@ -93,20 +81,20 @@ namespace dropshell
std::string mDatetime; std::string mDatetime;
}; };
bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user); bool scp_file_to_remote(const server_config &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user);
bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user); bool scp_file_from_remote(const server_config &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user);
// defined in backupdata.cpp, used by restoredata.cpp. // defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const ServerConfig &server_env, const std::string& service); bool backupdata_service(const server_config &server_env, const std::string& service);
// defined in uninstall.cpp // defined in uninstall.cpp
bool uninstall_service(const ServerConfig &server_env, const std::string &service); bool uninstall_service(const server_config &server_env, const std::string &service);
// defined in destroy.cpp // defined in nuke.cpp
bool destroy_service(const std::string &server, const std::string &service); bool nuke_service(const std::string &server, const std::string &service);
// defined in install.cpp // defined in install.cpp
bool install_service(const ServerConfig &server_env, const std::string &service); bool install_service(const server_config &server_env, const std::string &service);
// defined in create-service.cpp // defined in create-service.cpp
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override=""); bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override="");

View File

@ -7,7 +7,7 @@
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
#include <libassert/assert.hpp> #include "assert.hpp"
namespace dropshell namespace dropshell
{ {
@ -42,7 +42,7 @@ namespace dropshell
bool ssh_into_server(const std::string &server, std::string user) bool ssh_into_server(const std::string &server, std::string user)
{ {
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
@ -54,19 +54,13 @@ namespace dropshell
bool ssh_into_service(const std::string &server, const std::string &service) bool ssh_into_service(const std::string &server, const std::string &service)
{ {
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
if (!legal_service_name(service))
{
error << "Service name contains illegal characters: " << service << std::endl;
return false;
}
LocalServiceInfo sinfo = get_service_info(server, service); LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo)) if (!SIvalid(sinfo))
{ {
@ -115,7 +109,7 @@ namespace dropshell
server = arg1; server = arg1;
// get the first user from the server.env file, and ssh in as that user. // get the first user from the server.env file, and ssh in as that user.
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;

View File

@ -44,19 +44,13 @@ namespace dropshell
bool start_service(const std::string &server, const std::string &service) bool start_service(const std::string &server, const std::string &service)
{ {
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
if (!legal_service_name(service))
{
error << "Service name contains illegal characters: " << service << std::endl;
return false;
}
// run the start script. // run the start script.
bool started = server_env.run_remote_template_command(service, "start", {}, false, {}); bool started = server_env.run_remote_template_command(service, "start", {}, false, {});
@ -73,7 +67,7 @@ namespace dropshell
{ {
if (ctx.args.size() < 2) if (ctx.args.size() < 2)
{ {
error << "Server name and service name are both required" << std::endl; std::cerr << "Error: Server name and service name are both required" << std::endl;
return 1; return 1;
} }

View File

@ -44,19 +44,13 @@ namespace dropshell
bool stop_service(const std::string &server, const std::string &service) bool stop_service(const std::string &server, const std::string &service)
{ {
ServerConfig server_env(server); server_config server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
if (!legal_service_name(service))
{
error << "Service name contains illegal characters: " << service << std::endl;
return false;
}
// run the stop script. // run the stop script.
bool stopped = server_env.run_remote_template_command(service, "stop", {}, false, {}); bool stopped = server_env.run_remote_template_command(service, "stop", {}, false, {});

View File

@ -3,7 +3,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "templates.hpp" #include "templates.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "services.hpp" #include "services.hpp"
@ -36,13 +36,13 @@ namespace dropshell
uninstall SERVER SERVICE Uninstall the given service on the given server. uninstall SERVER SERVICE Uninstall the given service on the given server.
uninstall SERVER all Uninstall all services on the given server. uninstall SERVER all Uninstall all services on the given server.
Update and reinstall the service with install, or delete all configuration and data with destroy. Update and reinstall the service with install, or delete all configuration and data with nuke.
)"}); )"});
} }
} uninstall_command_register; } uninstall_command_register;
namespace shared_commands { namespace shared_commands {
bool uninstall_service(const ServerConfig & server_env, const std::string &service) bool uninstall_service(const server_config & server_env, const std::string &service)
{ {
ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name()); ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
std::string server = server_env.get_server_name(); std::string server = server_env.get_server_name();

View File

@ -37,7 +37,7 @@ bool config::load_config() { // load json config file.
} }
catch (nlohmann::json::parse_error& ex) catch (nlohmann::json::parse_error& ex)
{ {
error << "Failed to parse config file: " << ex.what() << std::endl; std::cerr << "Error: Failed to parse config file: " << ex.what() << std::endl;
return false; return false;
} }
@ -69,7 +69,7 @@ bool config::save_config(bool create_aux_directories)
if (!mIsConfigSet) if (!mIsConfigSet)
{ {
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
std::string dropshell_base = homedir + "/.dropshell"; std::string dropshell_base = homedir + "/.local/dropshell_files";
mConfig["server_definition_paths"] = { mConfig["server_definition_paths"] = {
dropshell_base + "/servers" dropshell_base + "/servers"
@ -81,10 +81,6 @@ bool config::save_config(bool create_aux_directories)
"https://templates.dropshell.app" "https://templates.dropshell.app"
}; };
mConfig["template_upload_token"] = "SECRETTOKEN"; mConfig["template_upload_token"] = "SECRETTOKEN";
mConfig["backups_path"] = {
dropshell_base + "/backups"
};
} }
config_file << mConfig.dump(4); config_file << mConfig.dump(4);
@ -120,14 +116,14 @@ bool config::is_agent_installed()
return std::filesystem::exists(localfile::bb64()); return std::filesystem::exists(localfile::bb64());
} }
std::vector<tRegistryEntry> config::get_template_registry_urls() { std::vector<std::string> config::get_template_registry_urls() {
nlohmann::json template_registries = mConfig["template_registries"]; nlohmann::json template_registry_urls = mConfig["template_registry_URLs"];
std::vector<tRegistryEntry> registries; std::vector<std::string> urls;
for (auto &registry : template_registries) { for (auto &url : template_registry_urls) {
if (registry.is_object() && !registry.empty()) if (url.is_string() && !url.empty())
registries.push_back(tRegistryEntry(registry)); urls.push_back(url);
} }
return registries; return urls;
} }
std::vector<std::string> config::get_local_template_paths() std::vector<std::string> config::get_local_template_paths()
@ -167,43 +163,16 @@ std::string config::get_template_create_path()
return paths[0]; return paths[0];
} }
std::string config::get_backups_path() std::string config::get_template_upload_url()
{ {
nlohmann::json backups_path = mConfig["backups_path"]; std::vector<std::string> urls = get_template_registry_urls();
if (backups_path.empty()) if (urls.empty())
return "";
if (backups_path.is_string())
return backups_path;
warning << "backups_path is not a string: " << backups_path << std::endl;
return ""; return "";
return urls[0];
} }
dropshell::tRegistryEntry::tRegistryEntry(nlohmann::json json) std::string config::get_template_upload_token() {
{ return mConfig["template_upload_token"];
valid = false;
if (json.is_object() && !json.empty()) {
for (auto &[key, value] : json.items()) {
if (value.is_string() && !value.empty())
switch (switchhash(key.c_str())) {
case switchhash("name"):
name = value;
break;
case switchhash("url"):
url = value;
break;
case switchhash("token"):
token = value;
break;
default:
break;
}
}
valid = (!url.empty()&&!name.empty()); // token can be empty.
}
}
tRegistryEntry::~tRegistryEntry()
{
} }
} // namespace dropshell } // namespace dropshell

View File

@ -4,23 +4,10 @@
#include <vector> #include <vector>
#define JSON_INLINE_ALL #define JSON_INLINE_ALL
#include <nlohmann/json.hpp> #include "json.hpp"
namespace dropshell { namespace dropshell {
class tRegistryEntry {
public:
tRegistryEntry(nlohmann::json json);
~tRegistryEntry();
public:
std::string name;
std::string url;
std::string token;
bool valid;
};
class config { class config {
public: public:
config(); config();
@ -32,13 +19,14 @@ class config {
bool is_config_set() const; bool is_config_set() const;
static bool is_agent_installed(); static bool is_agent_installed();
std::vector<tRegistryEntry> get_template_registry_urls(); std::vector<std::string> get_template_registry_urls();
std::vector<std::string> get_local_template_paths(); std::vector<std::string> get_local_template_paths();
std::vector<std::string> get_local_server_definition_paths(); std::vector<std::string> get_local_server_definition_paths();
std::string get_server_create_path(); std::string get_server_create_path();
std::string get_template_create_path(); std::string get_template_create_path();
std::string get_backups_path(); std::string get_template_upload_url();
std::string get_template_upload_token();
private: private:
nlohmann::json mConfig; nlohmann::json mConfig;

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ _dropshell_completions() {
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
# call dropshell to get the list of possiblities for the current argument. Supply all previous arguments. # call dropshell to get the list of possiblities for the current argument. Supply all previous arguments.
mapfile -t completions < <(dropshell autocomplete "${COMP_WORDS[@]:1:${COMP_CWORD}-1}") local completions=($(dropshell autocomplete "${COMP_WORDS[@]:1:${COMP_CWORD}-1}"))
mapfile -t COMPREPLY < <(compgen -W "${completions[*]}" -- "$cur") COMPREPLY=( $(compgen -W "${completions[*]}" -- ${cur}) )
return 0 return 0
} }

View File

@ -16,7 +16,7 @@
#include <vector> #include <vector>
#include <iomanip> #include <iomanip>
#include <chrono> #include <chrono>
#include <libassert/assert.hpp> #include <assert.hpp>
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
namespace dropshell { namespace dropshell {
@ -80,7 +80,7 @@ int main(int argc, char* argv[]) {
} }
catch (const std::exception& e) { catch (const std::exception& e) {
error << "Uncaught Exception: " << e.what() << std::endl; std::cerr << "Error: " << e.what() << std::endl;
return 1; return 1;
} }
} }
@ -140,6 +140,115 @@ auto command_match = [](const std::string& cmd_list, int argc, char* argv[]) ->
} \ } \
} }
// int old_main(int argc, char* argv[]) {
// HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2)))
// HAPPYEXIT("version", printversion())
// BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
// ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
// try {
// // silently attempt to load the config file and templates.
// gConfig().load_config();
// if (gConfig().is_config_set())
// gTemplateManager().load_sources();
// std::string cmd = argv[1];
// // ------------------------------------------------------------
// // from here we require the config file to be loaded.
// if (!gConfig().is_config_set())
// return die("Please run 'dropshell edit' to set up the dropshell configuration.");
// const std::vector<std::string> & server_definition_paths = gConfig().get_local_server_definition_paths();
// if (server_definition_paths.size()>1) { // only show if there are multiple.
// std::cout << "Server definition paths: ";
// for (auto & dir : server_definition_paths)
// std::cout << "["<< dir << "] ";
// std::cout << std::endl;
// }
// if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0)
// gTemplateManager().print_sources();
// HAPPYEXIT("templates", gTemplateManager().list_templates());
// if (cmd == "create-template") {
// if (argc < 3) return die("Error: create-template requires a template name");
// return (gTemplateManager().create_template(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-server") {
// if (argc < 3) return die("Error: create-server requires a server name");
// return (create_server(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-service") {
// if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service");
// return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1;
// }
// if (cmd == "ssh" && argc < 4) {
// if (argc < 3) return die("Error: ssh requires a server name and optionally service name");
// service_runner::interactive_ssh(argv[2], "bash");
// return 0;
// }
// // handle running a command.
// std::set<std::string> commands;
// get_all_used_commands(commands);
// autocomplete::merge_commands(commands, autocomplete::service_commands_require_config); // handled by service_runner, but not in template_shell_commands.
// if (commands.count(cmd)) {
// std::set<std::string> safe_commands = {"nuke", "fullnuke"};
// if (safe_commands.count(cmd) && argc < 4)
// return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services.");
// // get all the services to run the command on.
// ServerAndServices server_and_services;
// if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services))
// return die("Error: "+cmd+" command requires server name and optionally service name");
// // run the command on each service.
// for (const auto& service_info : server_and_services.servicelist) {
// if (!SIvalid(service_info))
// std::cerr<<"Error: Unable to get service information."<<std::endl;
// else {
// service_runner runner(server_and_services.server_name, service_info.service_name);
// if (!runner.isValid())
// return die("Error: Failed to initialize service");
// std::vector<std::string> additional_args;
// for (int i=4; i<argc; i++)
// additional_args.push_back(argv[i]);
// if (!runner.run_command(cmd, additional_args))
// return die(cmd+" failed on service "+service_info.service_name);
// }
// }
// // success!
// return 0;
// }
// // Unknown command
// std::cerr << "Error: Unknown command '" << cmd << "'" << std::endl;
// std::cerr << "Valid commands: ";
// for (const auto& command : commands) {
// if (!command.empty() && command[0]!='_')
// std::cerr << command << " ";
// }
// std::cerr << std::endl;
// return 1;
// } catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return 1;
// }
// }
} // namespace dropshell } // namespace dropshell
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {

View File

@ -6,7 +6,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/execute.hpp" #include "utils/execute.hpp"
#include "output.hpp" #include "output.hpp"
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "config.hpp" #include "config.hpp"
#include <iostream> #include <iostream>
@ -23,7 +23,7 @@
namespace dropshell namespace dropshell
{ {
ServerConfig::ServerConfig(const std::string &server_name) : mValid(false), mServerName(server_name) server_config::server_config(const std::string &server_name) : mValid(false), mServerName(server_name)
{ {
if (server_name.empty()) if (server_name.empty())
return; return;
@ -104,28 +104,28 @@ namespace dropshell
catch (const std::exception &e) catch (const std::exception &e)
{ {
error << "Failed to parse " << server_json_path << std::endl; error << "Failed to parse " << server_json_path << std::endl;
error << "Exception: " << e.what() << std::endl; error << "Error: " << e.what() << std::endl;
mValid = false; mValid = false;
} }
} }
std::string ServerConfig::get_SSH_HOST() const std::string server_config::get_SSH_HOST() const
{ {
return get_variable("SSH_HOST"); return get_variable("SSH_HOST");
} }
std::string ServerConfig::get_SSH_PORT() const std::string server_config::get_SSH_PORT() const
{ {
return get_variable("SSH_PORT"); return get_variable("SSH_PORT");
} }
std::vector<UserConfig> ServerConfig::get_users() const std::vector<UserConfig> server_config::get_users() const
{ {
return mUsers; return mUsers;
} }
std::string ServerConfig::get_user_dir(const std::string &user) const std::string server_config::get_user_dir(const std::string &user) const
{ {
for (const auto &u : mUsers) for (const auto &u : mUsers)
{ {
@ -137,36 +137,28 @@ namespace dropshell
return ""; return "";
} }
std::string ServerConfig::get_server_name() const std::string server_config::get_server_name() const
{ {
return mServerName; return mServerName;
} }
std::string ServerConfig::get_user_for_service(const std::string &service) const std::string server_config::get_user_for_service(const std::string &service) const
{ {
return dropshell::get_user_for_service(mServerName, service); return dropshell::get_user_for_service(mServerName, service);
} }
std::string get_user_for_service(const std::string &server, const std::string &service) std::string get_user_for_service(const std::string &server, const std::string &service)
{ {
if (!legal_service_name(service))
{
error << "Service name contains illegal characters: " + service << std::endl;
return "";
}
auto services_info = get_server_services_info(server); auto services_info = get_server_services_info(server);
auto it = std::find_if(services_info.begin(), services_info.end(), auto it = std::find_if(services_info.begin(), services_info.end(),
[&service](const LocalServiceInfo &si) [&service](const LocalServiceInfo &si)
{ return si.service_name == service; }); { return si.service_name == service; });
if (it != services_info.end() && SIvalid(*it)) if (it != services_info.end() && SIvalid(*it))
return it->user; return it->user;
debug << "Couldn't find user for service \"" << service << "\" on server \"" << server << "\"" << std::endl;
return ""; return "";
} }
sSSHInfo ServerConfig::get_SSH_INFO(std::string user) const sSSHInfo server_config::get_SSH_INFO(std::string user) const
{ {
ASSERT(!user.empty(), "User is empty, cannot get SSH info."); ASSERT(!user.empty(), "User is empty, cannot get SSH info.");
// Find user in mUsers vector // Find user in mUsers vector
@ -177,24 +169,24 @@ namespace dropshell
return sSSHInfo(get_SSH_HOST(), it->user, get_SSH_PORT(), get_server_name(), it->dir); return sSSHInfo(get_SSH_HOST(), it->user, get_SSH_PORT(), get_server_name(), it->dir);
} }
bool ServerConfig::hasRootUser() const bool server_config::hasRootUser() const
{ {
auto it = std::find_if(mUsers.begin(), mUsers.end(),[](const UserConfig &u) auto it = std::find_if(mUsers.begin(), mUsers.end(),[](const UserConfig &u)
{ return u.user == "root"; }); { return u.user == "root"; });
return it != mUsers.end(); return it != mUsers.end();
} }
bool ServerConfig::hasDocker() const bool server_config::hasDocker() const
{ {
return get_variable("HAS_DOCKER") == "true"; return get_variable("HAS_DOCKER") == "true";
} }
bool ServerConfig::hasRootDocker() const bool server_config::hasRootDocker() const
{ {
return get_variable("DOCKER_ROOTLESS") == "false"; return get_variable("DOCKER_ROOTLESS") == "false";
} }
bool ServerConfig::hasUser(const std::string &user) const bool server_config::hasUser(const std::string &user) const
{ {
auto it = std::find_if(mUsers.begin(), mUsers.end(), auto it = std::find_if(mUsers.begin(), mUsers.end(),
[&user](const UserConfig &u) [&user](const UserConfig &u)
@ -202,38 +194,20 @@ namespace dropshell
return it != mUsers.end(); return it != mUsers.end();
} }
bool ServerConfig::check_remote_dir_exists(const std::string &dir_path, std::string user) const bool server_config::check_remote_dir_exists(const std::string &dir_path, std::string user) const
{ {
if (user.empty())
{
debug << "Can't check remote directory exists for " << dir_path << " as user is empty" << std::endl;
return false;
}
sCommand scommand("", "test -d " + quote(dir_path), {}); sCommand scommand("", "test -d " + quote(dir_path), {});
return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent);
} }
bool ServerConfig::check_remote_file_exists(const std::string &file_path, std::string user) const bool server_config::check_remote_file_exists(const std::string &file_path, std::string user) const
{ {
if (user.empty())
{
debug << "Can't check remote file exists for " << file_path << " as user is empty" << std::endl;
return false;
}
sCommand scommand("", "test -f " + quote(file_path), {}); sCommand scommand("", "test -f " + quote(file_path), {});
return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent);
} }
bool ServerConfig::check_remote_items_exist(const std::vector<std::string> &file_paths, std::string user) const bool server_config::check_remote_items_exist(const std::vector<std::string> &file_paths, std::string user) const
{ {
if (user.empty())
{
debug << "Can't check remote items exist as user is empty" << std::endl;
return false;
}
// convert file_paths to a single string, separated by spaces // convert file_paths to a single string, separated by spaces
std::string file_paths_str; std::string file_paths_str;
std::string file_names_str; std::string file_names_str;
@ -249,13 +223,13 @@ namespace dropshell
bool okay = execute_ssh_command(sshinfo, scommand, cMode::Silent); bool okay = execute_ssh_command(sshinfo, scommand, cMode::Silent);
if (!okay) if (!okay)
{ {
error << "Required items not found on remote server: " << file_names_str << std::endl; std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
return false; return false;
} }
return true; return true;
} }
bool ServerConfig::remove_remote_dir( bool server_config::remove_remote_dir(
const std::string &dir_path, bool silent, std::string user) const const std::string &dir_path, bool silent, std::string user) const
{ {
std::filesystem::path path(dir_path); std::filesystem::path path(dir_path);
@ -284,7 +258,7 @@ namespace dropshell
return execute_ssh_command(sshinfo, scommand, mode); return execute_ssh_command(sshinfo, scommand, mode);
} }
bool ServerConfig::run_remote_template_command( bool server_config::run_remote_template_command(
const std::string &service_name, const std::string &service_name,
const std::string &command, const std::string &command,
std::vector<std::string> args, std::vector<std::string> args,
@ -306,7 +280,7 @@ namespace dropshell
return execute_ssh_command(get_SSH_INFO(user), scommand.value(), mode); return execute_ssh_command(get_SSH_INFO(user), scommand.value(), mode);
} }
bool ServerConfig::run_remote_template_command_and_capture_output( bool server_config::run_remote_template_command_and_capture_output(
const std::string &service_name, const std::string &service_name,
const std::string &command, const std::string &command,
std::vector<std::string> args, std::vector<std::string> args,
@ -326,7 +300,7 @@ namespace dropshell
return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output); return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output);
} }
std::string ServerConfig::get_variable(const std::string &name) const std::string server_config::get_variable(const std::string &name) const
{ {
auto it = mVariables.find(name); auto it = mVariables.find(name);
if (it == mVariables.end()) if (it == mVariables.end())
@ -336,7 +310,7 @@ namespace dropshell
return it->second; return it->second;
} }
std::optional<sCommand> ServerConfig::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const std::optional<sCommand> server_config::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const
{ {
if (command.empty()) if (command.empty())
return std::nullopt; return std::nullopt;
@ -349,12 +323,10 @@ namespace dropshell
std::map<std::string, std::string> env_vars; std::map<std::string, std::string> env_vars;
if (!get_all_service_env_vars(mServerName, service_name, env_vars)) if (!get_all_service_env_vars(mServerName, service_name, env_vars))
{ {
error << "Failed to get all service env vars for " << service_name << std::endl; std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
return std::nullopt; return std::nullopt;
} }
env_vars["HOST_NAME"] = get_SSH_HOST();
std::string argstr = ""; std::string argstr = "";
for (const auto &arg : args) for (const auto &arg : args)
{ {
@ -368,15 +340,15 @@ namespace dropshell
if (sc.empty()) if (sc.empty())
{ {
error << "Failed to construct command for " << service_name << " " << command << std::endl; std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl;
return std::nullopt; return std::nullopt;
} }
return sc; return sc;
} }
std::vector<ServerConfig> get_configured_servers() std::vector<server_config> get_configured_servers()
{ {
std::vector<ServerConfig> servers; std::vector<server_config> servers;
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths(); std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty()) if (lsdp.empty())
@ -395,10 +367,10 @@ namespace dropshell
if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_') if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_')
continue; continue;
ServerConfig env(server_name); server_config env(server_name);
if (!env.is_valid()) if (!env.is_valid())
{ {
error << "Invalid server environment file: " << entry.path().string() << std::endl; std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
continue; continue;
} }
servers.push_back(env); servers.push_back(env);
@ -416,7 +388,7 @@ namespace dropshell
std::string server_existing_dir = localpath::server(server_name); std::string server_existing_dir = localpath::server(server_name);
if (!server_existing_dir.empty()) if (!server_existing_dir.empty())
{ {
error << "Server name already exists: " << server_name << std::endl; error << "Error: Server name already exists: " << server_name << std::endl;
info << "Current server path: " << server_existing_dir << std::endl; info << "Current server path: " << server_existing_dir << std::endl;
return false; return false;
} }
@ -425,7 +397,7 @@ namespace dropshell
auto lsdp = gConfig().get_local_server_definition_paths(); auto lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty() || lsdp[0].empty()) if (lsdp.empty() || lsdp[0].empty())
{ {
error << "Local server definition path not found" << std::endl; error << "Error: Local server definition path not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl; info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; return false;
} }
@ -444,9 +416,7 @@ namespace dropshell
server_env_file << " \"USER\": \"" << user << "\"," << std::endl; server_env_file << " \"USER\": \"" << user << "\"," << std::endl;
server_env_file << " \"DIR\": \"" << "/home/" + user << "/.dropshell\"" << std::endl; server_env_file << " \"DIR\": \"" << "/home/" + user << "/.dropshell\"" << std::endl;
server_env_file << " }" << std::endl; server_env_file << " }" << std::endl;
server_env_file << " ]," << std::endl; server_env_file << " ]" << std::endl;
server_env_file << " \"HAS_DOCKER\": \"true\"," << std::endl;
server_env_file << " \"DOCKER_ROOTLESS\": \"false\"" << std::endl;
server_env_file << "}" << std::endl; server_env_file << "}" << std::endl;
server_env_file.close(); server_env_file.close();
@ -460,7 +430,7 @@ namespace dropshell
void get_all_used_commands(std::set<std::string> &commands) void get_all_used_commands(std::set<std::string> &commands)
{ {
std::vector<ServerConfig> servers = get_configured_servers(); std::vector<server_config> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
auto services = get_server_services_info(server.get_server_name()); auto services = get_server_services_info(server.get_server_name());

View File

@ -32,10 +32,10 @@ namespace dropshell
// SSH_PORT // SSH_PORT
// the following replacements are made in the values: // the following replacements are made in the values:
// ${USER} -> the username of the user running dropshell // ${USER} -> the username of the user running dropshell
class ServerConfig class server_config
{ {
public: public:
ServerConfig(const std::string &server_name); server_config(const std::string &server_name);
bool is_valid() const { return mValid; } bool is_valid() const { return mValid; }
@ -85,11 +85,11 @@ namespace dropshell
std::map<std::string, std::string> mVariables; std::map<std::string, std::string> mVariables;
std::vector<UserConfig> mUsers; std::vector<UserConfig> mUsers;
bool mValid; bool mValid;
}; // class ServerConfig }; // class server_config
std::vector<ServerConfig> get_configured_servers(); std::vector<server_config> get_configured_servers();
std::string get_user_for_service(const std::string &server, const std::string &service); std::string get_user_for_service(const std::string &server, const std::string &service);

View File

@ -7,7 +7,7 @@
// #include <iomanip> // #include <iomanip>
// #include <filesystem> // #include <filesystem>
// #include <unistd.h> // #include <unistd.h>
// #include <libassert/assert.hpp> // #include "utils/assert.hpp"
// #include "config.hpp" // #include "config.hpp"
// #include "servers.hpp" // #include "servers.hpp"
@ -19,6 +19,74 @@
// #include "shared_commands.hpp" // #include "shared_commands.hpp"
// namespace dropshell {
// class service_runner {
// public:
// service_runner(const std::string& server_name, const std::string& service_name);
// bool isValid() const { return mValid; }
// // run a command over ssh, using the credentials from server.env (via server_env.hpp)
// // first check that the command corresponds to a valid .sh file in the service directory
// // then run the command, passing the {service_name}.env file as an argument
// // do a lot of checks, such as:
// // checking that we can ssh to the server.
// // checking whether the service directory exists on the server.
// // checking that the command exists in the service directory.
// // checking that the command is a valid .sh file.
// // checking that the {service_name}.env file exists in the service directory.
// bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={});
// // check health of service. Silent.
// // 1. run status.sh on the server
// // 2. return the output of the status.sh script
// //HealthStatus is_healthy();
// // std::string healthtick();
// // std::string healthmark();
// public:
// // backup and restore
// bool backup(bool silent=false);
// bool restore(std::string backup_file, bool silent=false);
// // nuke the service
// bool nuke(bool silent=false); // nukes all data for this service on the remote server
// bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles
// // launch an interactive ssh session on a server or service
// // replaces the current dropshell process with the ssh process
// bool interactive_ssh_service();
// bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false);
// bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
// public:
// // utility functions
// static std::string get_latest_backup_file(const std::string& server, const std::string& service);
// static bool interactive_ssh(const std::string & server_name, const std::string & command);
// // static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
// private:
// std::string mServer;
// server_config mServerEnv;
// LocalServiceInfo mServiceInfo;
// std::string mService;
// bool mValid;
// // Helper methods
// public:
// };
// } // namespace dropshell
// namespace fs = std::filesystem; // namespace fs = std::filesystem;
// namespace dropshell { // namespace dropshell {
@ -45,6 +113,47 @@
// bool service_runner::nuke(bool silent)
// {
// maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
// if (!mServerEnv.is_valid()) return false; // should never hit this.
// std::string remote_service_path = remotepath::service(mServer, mService);
// info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
// if (!silent) {
// info << "There's nothing left on the remote server." << std::endl;
// info << "You can remove the local files with:" << std::endl;
// info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
// }
// return true;
// }
// bool service_runner::fullnuke()
// {
// if (!nuke(true))
// {
// warning << "Nuke script failed, aborting." << std::endl;
// return false;
// }
// std::string local_service_path = mServiceInfo.local_service_path;
// if (local_service_path.empty() || !fs::exists(local_service_path)) {
// error << "Service directory not found: " << local_service_path << std::endl;
// return false;
// }
// std::string rm_cmd = "rm -rf " + quote(local_service_path);
// if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
// error << "Failed to remove service directory" << std::endl;
// return false;
// }
// return true;
// }
// // ------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------
// // Run a command on the service. // // Run a command on the service.
@ -60,6 +169,15 @@
// return false; // return false;
// } // }
// if (command == "fullnuke")
// return fullnuke();
// if (command == "nuke")
// {
// std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
// return nuke();
// }
// if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) { // if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
// std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl; // std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
// return true; // nothing to run. // return true; // nothing to run.
@ -114,4 +232,298 @@
// } // }
// bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
// std::string serverpath = localpath::server(server_name);
// if (serverpath.empty()) {
// std::cerr << "Error: Server not found: " << server_name << std::endl;
// return false;
// }
// server_config env(server_name);
// if (!env.is_valid()) {
// std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
// return false;
// }
// sCommand scommand("", "bash",{});
// return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
// }
// bool service_runner::interactive_ssh_service()
// {
// std::set<std::string> used_commands = get_used_commands(mServer, mService);
// if (used_commands.find("ssh") == used_commands.end()) {
// std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
// return false;
// }
// std::vector<std::string> args; // not passed through yet.
// return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {});
// }
// bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
// return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
// }
// bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : "");
// return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
// }
// bool service_runner::restore(std::string backup_file, bool silent)
// {
// if (backup_file.empty()) {
// std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
// return false;
// }
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (backup_file == "latest") {
// // get the latest backup file from the server
// backup_file = get_latest_backup_file(mServer, mService);
// }
// std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
// if (! std::filesystem::exists(local_backup_file_path)) {
// std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
// return false;
// }
// // split the backup filename into parts based on the magic string
// std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
// if (parts.size() != 4) {
// std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
// return false;
// }
// std::string backup_server_name = parts[0];
// std::string backup_template_name = parts[1];
// std::string backup_service_name = parts[2];
// std::string backup_datetime = parts[3];
// if (backup_template_name != mServiceInfo.template_name) {
// std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
// return false;
// }
// std::string nicedate = std::string(backup_datetime).substr(0, 10);
// std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl;
// std::cout << std::endl;
// std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// // run the restore script
// std::cout << "OK, here goes..." << std::endl;
// { // backup existing service
// maketitle("1) Backing up old service... ");
// if (!backup(true)) // silent=true
// {
// std::cerr << std::endl;
// std::cerr << "Error: Backup failed, restore aborted." << std::endl;
// std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl;
// std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
// return false;
// }
// std::cout << "Backup complete." << std::endl;
// }
// { // uninstall service, then nuke it.
// maketitle("2) Uninstalling old service...");
// // if (!uninstall(true))
// // return false;
// maketitle("3) Nuking old service...");
// // if (!nuke(true))
// // return false;
// }
// { // restore service from backup
// maketitle("4) Restoring service data from backup...");
// std::string remote_backups_dir = remotepath::backups(mServer);
// std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// // Copy backup file from local to server
// if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) {
// std::cerr << "Failed to copy backup file from local to server" << std::endl;
// return false;
// }
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}});
// } // dtor of remote_temp_folder will clean up the temp folder on the server
// // { // installing fresh service
// // maketitle("5) Non-destructive install of fresh service...");
// // if (!install_service(mServer, mService, true))
// // return false;
// // }
// bool healthy = false;
// {// healthcheck the service
// maketitle("6) Healthchecking service...");
// std::string green_tick = "\033[32m✓\033[0m";
// std::string red_cross = "\033[31m✗\033[0m";
// healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
// if (!silent)
// std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
// }
// return healthy;
// }
// // backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// // 1. run backup.sh on the server
// // 2. create a backup file with format server-service-datetime.tgz
// // 3. store it in the server's DROPSHELL_DIR/backups folder
// // 4. copy it to the local user_dir/backups folder
// // ------------------------------------------------------------------------------------------------
// // Backup the service.
// // ------------------------------------------------------------------------------------------------
// bool service_runner::backup(bool silent) {
// auto service_info = get_service_info(mServer, mService);
// if (service_info.local_service_path.empty()) {
// std::cerr << "Error: Service not found" << std::endl;
// return 1;
// }
// const std::string command = "backup";
// if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
// std::cout << "No backup script for " << service_info.template_name << std::endl;
// return true; // nothing to back up.
// }
// // Check if basic installed stuff is in place.
// std::string remote_service_template_path = remotepath::service_template(mServer, mService);
// std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
// std::string remote_service_config_path = remotepath::service_config(mServer, mService);
// if (!mServerEnv.check_remote_items_exist({
// remotepath::service(mServer, mService),
// remote_command_script_file,
// remotefile::service_env(mServer, mService)})
// )
// {
// std::cerr << "Error: Required service directories not found on remote server" << std::endl;
// std::cerr << "Is the service installed?" << std::endl;
// return false;
// }
// // Create backups directory on server if it doesn't exist
// std::string remote_backups_dir = remotepath::backups(mServer);
// if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
// std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
// if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
// std::cerr << "Failed to create backups directory on server" << std::endl;
// return false;
// }
// // Create backups directory locally if it doesn't exist
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (local_backups_dir.empty()) {
// std::cerr << "Error: Local backups directory not found" << std::endl;
// std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
// return false;
// }
// if (!std::filesystem::exists(local_backups_dir))
// std::filesystem::create_directories(local_backups_dir);
// // Get current datetime for backup filename
// auto now = std::chrono::system_clock::now();
// auto time = std::chrono::system_clock::to_time_t(now);
// std::stringstream datetime;
// datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
// // Construct backup filename
// shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name);
// if (!backup_filename_construction.is_valid()) {
// std::cerr << "Invalid backup filename" << std::endl;
// return false;
// }
// std::string backup_filename = backup_filename_construction.get_filename();
// std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
// std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string();
// // assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path.
// ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename");
// { // Run backup script
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) {
// std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
// return false;
// }
// // Copy backup file from server to local
// if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) {
// std::cerr << "Failed to copy backup file from server" << std::endl;
// return false;
// }
// } // dtor of remote_temp_folder will clean up the temp folder on the server
// if (!silent) {
// std::cout << "Backup created successfully. Restore with:"<<std::endl;
// std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
// }
// return true;
// }
// // Helper function to get the latest backup file for a given server and service
// std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) {
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
// std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
// return "";
// }
// // Get the template name for this service
// LocalServiceInfo info = get_service_info(server, service);
// if (info.template_name.empty()) {
// std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
// return "";
// }
// // Build the expected prefix for backup files
// std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
// std::string latest_file;
// std::string latest_datetime;
// std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
// for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
// if (!entry.is_regular_file()) continue;
// std::string filename = entry.path().filename().string();
// if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// // Extract the datetime part
// size_t dt_start = prefix.size();
// size_t dt_end = filename.find(".tgz", dt_start);
// if (dt_end == std::string::npos) continue;
// std::string datetime = filename.substr(dt_start, dt_end - dt_start);
// std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
// if (datetime > latest_datetime) {
// latest_datetime = datetime;
// latest_file = filename;
// }
// }
// }
// if (latest_file.empty()) {
// std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
// }
// std::cout << "Latest backup file: " << latest_file << std::endl;
// return latest_file;
// }
// } // namespace dropshell // } // namespace dropshell

View File

@ -4,7 +4,7 @@
#include "templates.hpp" #include "templates.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include <libassert/assert.hpp> #include "assert.hpp"
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>
@ -34,8 +34,8 @@ namespace dropshell
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths(); std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
if (local_server_definition_paths.empty()) if (local_server_definition_paths.empty())
{ {
error << "No local server definition paths found" << std::endl; std::cerr << "Error: No local server definition paths found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return services; return services;
} }
@ -80,9 +80,6 @@ namespace dropshell
if (server_name.empty() || service_name.empty()) if (server_name.empty() || service_name.empty())
return LocalServiceInfo(); return LocalServiceInfo();
if (!legal_service_name(service_name))
return LocalServiceInfo();
service.service_name = service_name; service.service_name = service_name;
service.local_service_path = localpath::service(server_name, service_name); service.local_service_path = localpath::service(server_name, service_name);
@ -92,7 +89,7 @@ namespace dropshell
// check the service directory exists. // check the service directory exists.
if (!fs::exists(service.local_service_path)) if (!fs::exists(service.local_service_path))
{ {
warning << "Service directory not found: " << service.local_service_path << std::endl; std::cerr << "Error: Service directory not found: " << service.local_service_path << std::endl;
return LocalServiceInfo(); return LocalServiceInfo();
} }
@ -161,7 +158,7 @@ namespace dropshell
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) if (service_info.local_template_path.empty())
{ {
error << "Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return commands; return commands;
} }
@ -186,7 +183,7 @@ namespace dropshell
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) if (service_info.local_template_path.empty())
{ {
error << "Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return backups; return backups;
} }
@ -214,7 +211,7 @@ namespace dropshell
if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name))) if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
{ {
error << "Service not found: " << service_name << " on server " << server_name << std::endl; std::cerr << "Error: Service not found: " << service_name << " on server " << server_name << std::endl;
return false; return false;
} }
@ -233,13 +230,6 @@ namespace dropshell
warning << "Expected environment file not found: " << file << std::endl; warning << "Expected environment file not found: " << file << std::endl;
}; };
// add in some simple variables first, as others below may depend on/use these in bash.
// if we change these, we also need to update agent/_allservicesstatus.sh
all_env_vars["SERVER"] = server_name;
all_env_vars["SERVICE"] = service_name;
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// Load environment files // Load environment files
load_env_file(localfile::service_env(server_name, service_name)); load_env_file(localfile::service_env(server_name, service_name));
load_env_file(localfile::template_info_env(server_name, service_name)); load_env_file(localfile::template_info_env(server_name, service_name));
@ -253,10 +243,13 @@ namespace dropshell
return false; return false;
} }
// more additional, these depend on others above. // add in some handy variables.
// if we change these, we also need to update agent/_allservicesstatus.sh
all_env_vars["CONFIG_PATH"] = remotepath(server_name, user).service_config(service_name); all_env_vars["CONFIG_PATH"] = remotepath(server_name, user).service_config(service_name);
all_env_vars["SERVER"] = server_name;
all_env_vars["SERVICE"] = service_name;
all_env_vars["AGENT_PATH"] = remotepath(server_name, user).agent(); all_env_vars["AGENT_PATH"] = remotepath(server_name, user).agent();
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// determine template name. // determine template name.
auto it = all_env_vars.find("TEMPLATE"); auto it = all_env_vars.find("TEMPLATE");
@ -272,7 +265,7 @@ namespace dropshell
template_info tinfo = gTemplateManager().get_template_info(it->second); template_info tinfo = gTemplateManager().get_template_info(it->second);
if (!tinfo.is_set()) if (!tinfo.is_set())
{ {
error << "Template '" << it->second << "' not found" << std::endl; std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
return false; return false;
} }

View File

@ -6,7 +6,7 @@
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <map> #include <map>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "utils/envmanager.hpp" #include "utils/envmanager.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
@ -163,7 +163,7 @@
ASSERT(mLoaded && mSources.size() > 0, "Template manager not loaded, or no template sources found."); ASSERT(mLoaded && mSources.size() > 0, "Template manager not loaded, or no template sources found.");
template_source_interface* source = get_source(template_name); template_source_interface* source = get_source(template_name);
if (!source) { if (!source) {
error << "Template '" << template_name << "' not found" << std::endl; std::cerr << "Error: Template '" << template_name << "' not found" << std::endl;
return false; return false;
} }
return source->template_command_exists(template_name, command); return source->template_command_exists(template_name, command);
@ -171,30 +171,25 @@
bool template_manager::create_template(const std::string &template_name) const bool template_manager::create_template(const std::string &template_name) const
{ {
if (!legal_service_name(template_name)) {
error << "Template name contains illegal characters: " << template_name << std::endl;
return false;
}
// 1. Create a new directory in the user templates directory // 1. Create a new directory in the user templates directory
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths(); std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
if (local_server_definition_paths.empty()) { if (local_server_definition_paths.empty()) {
error << "No local server definition paths found" << std::endl; std::cerr << "Error: No local server definition paths found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; return false;
} }
auto tinfo = get_template_info(template_name); auto info = get_template_info(template_name);
if (tinfo.is_set()) { if (info.is_set()) {
error << "Template '" << template_name << "' already exists at " << tinfo.locationID() << std::endl; std::cerr << "Error: Template '" << template_name << "' already exists at " << info.locationID() << std::endl;
return false; return false;
} }
auto local_template_paths = gConfig().get_local_template_paths(); auto local_template_paths = gConfig().get_local_template_paths();
if (local_template_paths.empty()) { if (local_template_paths.empty()) {
error << "No local template paths found" << std::endl; std::cerr << "Error: No local template paths found" << std::endl;
info << "Run 'dropshell edit' to add one to the DropShell config" << std::endl; std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
return false; return false;
} }
std::string new_template_path = local_template_paths[0] + "/" + template_name; std::string new_template_path = local_template_paths[0] + "/" + template_name;
@ -205,7 +200,7 @@
// 2. Copy the example template from the system templates directory // 2. Copy the example template from the system templates directory
auto example_info = gTemplateManager().get_template_info("example-nginx"); auto example_info = gTemplateManager().get_template_info("example-nginx");
if (!example_info.is_set()) { if (!example_info.is_set()) {
error << "Example template not found" << std::endl; std::cerr << "Error: Example template not found" << std::endl;
return false; return false;
} }
std::string example_template_path = example_info.local_template_path(); std::string example_template_path = example_info.local_template_path();
@ -227,7 +222,7 @@
std::string replacement_line = "TEMPLATE=" + template_name; std::string replacement_line = "TEMPLATE=" + template_name;
std::string service_env_path = new_template_path + "/config/" + filenames::template_info_env; std::string service_env_path = new_template_path + "/config/" + filenames::template_info_env;
if (!replace_line_in_file(service_env_path, search_string, replacement_line)) { if (!replace_line_in_file(service_env_path, search_string, replacement_line)) {
error << "Failed to replace TEMPLATE= line in the " << filenames::template_info_env <<" file" << std::endl; std::cerr << "Error: Failed to replace TEMPLATE= line in the " << filenames::template_info_env <<" file" << std::endl;
return false; return false;
} }
@ -283,7 +278,7 @@
bool template_manager::required_file(std::string path, std::string template_name) bool template_manager::required_file(std::string path, std::string template_name)
{ {
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
error << path << " file not found in template - REQUIRED." << template_name << std::endl; std::cerr << "Error: " << path << " file not found in template - REQUIRED." << template_name << std::endl;
return false; return false;
} }
return true; return true;
@ -328,7 +323,7 @@
std::filesystem::path path = template_path + "/" + file; std::filesystem::path path = template_path + "/" + file;
auto perms = std::filesystem::status(path).permissions(); auto perms = std::filesystem::status(path).permissions();
if ((perms & std::filesystem::perms::owner_exec) == std::filesystem::perms::none) if ((perms & std::filesystem::perms::owner_exec) == std::filesystem::perms::none)
error << file << " is not executable" << std::endl; std::cerr << "Error: " << file << " is not executable" << std::endl;
} }
} }
@ -352,18 +347,18 @@
// determine template name. // determine template name.
auto it = all_env_vars.find("TEMPLATE"); auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end()) { if (it == all_env_vars.end()) {
error << "TEMPLATE variable not found in " << template_path << std::endl; std::cerr << "Error: TEMPLATE variable not found in " << template_path << std::endl;
return false; return false;
} }
std::string env_template_name = it->second; std::string env_template_name = it->second;
if (env_template_name.empty()) { if (env_template_name.empty()) {
error << "TEMPLATE variable is empty in " << template_path << std::endl; std::cerr << "Error: TEMPLATE variable is empty in " << template_path << std::endl;
return false; return false;
} }
if (env_template_name != template_name) { if (env_template_name != template_name) {
error << "TEMPLATE variable is wrong in " << template_path << std::endl; std::cerr << "Error: TEMPLATE variable is wrong in " << template_path << std::endl;
return false; return false;
} }

View File

@ -4,9 +4,8 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include "config.hpp"
#define JSON_INLINE_ALL #define JSON_INLINE_ALL
#include <nlohmann/json.hpp> #include "json.hpp"
namespace dropshell { namespace dropshell {
@ -51,7 +50,7 @@ class template_source_interface {
class template_source_registry : public template_source_interface { class template_source_registry : public template_source_interface {
public: public:
template_source_registry(tRegistryEntry registry) : mRegistry(registry) {} template_source_registry(std::string URL) : mURL(URL) {}
~template_source_registry() {} ~template_source_registry() {}
@ -60,11 +59,11 @@ class template_source_registry : public template_source_interface {
template_info get_template_info(const std::string& template_name); template_info get_template_info(const std::string& template_name);
bool template_command_exists(const std::string& template_name,const std::string& command); bool template_command_exists(const std::string& template_name,const std::string& command);
std::string get_description() { return "Registry: " + mRegistry.name + " (" + mRegistry.url + ")"; } std::string get_description() { return "Registry: " + mURL; }
private: private:
std::filesystem::path get_cache_dir(); std::filesystem::path get_cache_dir();
private: private:
tRegistryEntry mRegistry; std::string mURL;
std::vector<nlohmann::json> mTemplates; // cached list. std::vector<nlohmann::json> mTemplates; // cached list.
}; };

View File

@ -0,0 +1,13 @@
#ifndef ASSERT_HPP
#define ASSERT_HPP
#include "output.hpp"
#define ASSERT(condition, message) \
if (!(condition)) { \
dropshell::error << "Assertion failed: " << message << std::endl; \
std::exit(1); \
}
#endif // ASSERT_HPP

View File

@ -7,27 +7,30 @@
#include <string> #include <string>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell namespace dropshell {
{
namespace localfile
{
std::string dropshell_json() namespace localfile {
{
return localpath::dropshell_dir() + "/" + filenames::dropshell_json; std::string dropshell_json() {
// Try ~/.config/dropshell/dropshell.json
std::string homedir = localpath::current_user_home();
if (!homedir.empty()) {
fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / filenames::dropshell_json;
return user_path.string();
}
return std::string();
} }
std::string server_json(const std::string &server_name) std::string server_json(const std::string &server_name) {
{
std::string serverpath = localpath::server(server_name); std::string serverpath = localpath::server(server_name);
return (serverpath.empty() ? "" : (fs::path(serverpath) / filenames::server_json).string()); return (serverpath.empty() ? "" : (fs::path(serverpath) / filenames::server_json).string());
} }
std::string service_env(const std::string &server_name, const std::string &service_name) std::string service_env(const std::string &server_name, const std::string &service_name) {
{
std::string servicepath = localpath::service(server_name, service_name); std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::service_env).string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::service_env).string());
} }
@ -48,20 +51,13 @@ namespace dropshell
return localpath::agent_local() + "/bb64"; return localpath::agent_local() + "/bb64";
} }
} // namespace localfile } // namespace localfile
// ------------------------------------------------------------------------------------------
namespace localpath // ------------------------------------------------------------------------------------------
{
std::string dropshell_dir() namespace localpath {
{ std::string server(const std::string &server_name) {
return current_user_home() + "/.dropshell";
}
std::string server(const std::string &server_name)
{
for (std::filesystem::path dir : gConfig().get_local_server_definition_paths()) for (std::filesystem::path dir : gConfig().get_local_server_definition_paths())
if (fs::exists(dir / server_name)) if (fs::exists(dir / server_name))
return dir / server_name; return dir / server_name;
@ -69,23 +65,28 @@ namespace dropshell
return ""; return "";
} }
std::string service(const std::string &server_name, const std::string &service_name) std::string service(const std::string &server_name, const std::string &service_name) {
{
std::string serverpath = localpath::server(server_name); std::string serverpath = localpath::server(server_name);
return ((serverpath.empty() || service_name.empty()) ? "" : (serverpath + "/" + service_name)); return ((serverpath.empty() || service_name.empty()) ? "" : (serverpath+"/"+service_name));
} }
std::string remote_versions(const std::string &server_name, const std::string &service_name)
{
std::string template_cache_path = localpath::template_cache();
return ((template_cache_path.empty() || service_name.empty()) ? "" :
(template_cache_path+"/remote_versions/"+service_name+".json"));
}
std::string agent_local() std::string agent_local()
{ {
return dropshell_dir() + "/agent-local"; return current_user_home()+"/.local/dropshell_agent/agent-local";
} }
std::string agent_remote() std::string agent_remote()
{ {
return dropshell_dir() + "/agent-remote"; return current_user_home() + "/.local/dropshell_agent/agent-remote";
} }
std::string current_user_home() std::string current_user_home()
{ {
char *homedir = std::getenv("HOME"); char * homedir = std::getenv("HOME");
if (homedir) if (homedir)
{ {
std::filesystem::path homedir_path(homedir); std::filesystem::path homedir_path(homedir);
@ -95,32 +96,37 @@ namespace dropshell
return std::string(); return std::string();
} }
std::string dropshell_files()
{
return current_user_home() + "/.local/dropshell_files";
return std::string();
}
std::string backups() std::string backups()
{ {
if (!gConfig().is_config_set()) return dropshell_files() + "/backups";
return "";
return gConfig().get_backups_path();
} }
std::string temp_files() std::string temp_files()
{ {
return dropshell_dir() + "/temp_files"; return dropshell_files() + "/temp_files";
} }
std::string template_cache() std::string template_cache()
{ {
return dropshell_dir() + "/template_cache"; return dropshell_files() + "template_cache";
} }
bool create_directories() bool create_directories()
{ {
std::vector<std::filesystem::path> paths = { std::vector<std::filesystem::path> paths = {
dropshell_dir(), dropshell_files(),
agent_local(), agent_local(),
agent_remote(), agent_remote(),
template_cache(), template_cache(),
backups(), backups(),
temp_files()}; temp_files()
};
for (auto &p : gConfig().get_local_server_definition_paths()) for (auto &p : gConfig().get_local_server_definition_paths())
paths.push_back(p); paths.push_back(p);
@ -133,7 +139,7 @@ namespace dropshell
return false; return false;
} }
} // namespace localpath } // namespace localpath
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
// remote paths // remote paths
@ -153,26 +159,21 @@ namespace dropshell
// |-- service.env (default service config) // |-- service.env (default service config)
// |-- (other config files for specific server&service) // |-- (other config files for specific server&service)
remotefile::remotefile(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {}
remotefile::remotefile(const std::string &server_name, const std::string &user) :
mServer_name(server_name), mUser(user) {}
std::string remotefile::service_env(const std::string &service_name) const std::string remotefile::service_env(const std::string &service_name) const
{ {
return remotepath(mServer_name, mUser).service_config(service_name) + "/" + filenames::service_env; return remotepath(mServer_name,mUser).service_config(service_name) + "/" + filenames::service_env;
} }
remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {} remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {}
std::string remotepath::DROPSHELL_DIR() const std::string remotepath::DROPSHELL_DIR() const
{ {
try return server_config(mServer_name).get_user_dir(mUser);
{
return ServerConfig(mServer_name).get_user_dir(mUser);
} catch (const std::exception &e)
{
error << "Failed to get remote dropshell directory for " << mServer_name << "@" << mUser << std::endl;
error << "Exception: " << e.what() << std::endl;
return "";
}
} }
std::string remotepath::services() const std::string remotepath::services() const
@ -217,21 +218,22 @@ namespace dropshell
return (dsp.empty() ? "" : (dsp + "/agent")); return (dsp.empty() ? "" : (dsp + "/agent"));
} }
// ------------------------------------------------------------------------------------------
// Utility functions
std::string get_parent(const std::filesystem::path path) // ------------------------------------------------------------------------------------------
{ // Utility functions
std::string get_parent(const std::filesystem::path path)
{
if (path.empty()) if (path.empty())
return std::string(); return std::string();
return path.parent_path().string(); return path.parent_path().string();
} }
std::string get_child(const std::filesystem::path path) std::string get_child(const std::filesystem::path path)
{ {
if (path.empty()) if (path.empty())
return std::string(); return std::string();
return path.filename().string(); return path.filename().string();
} }
} // namespace dropshell } // namespace dropshell

View File

@ -11,14 +11,19 @@ namespace dropshell {
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
// local user config directories // local user config directories
// ~/.dropshell // ~/.config/dropshell/dropshell.json
// |-- dropshell.json
// ~/.local/dropshell_agent
// |-- agent-local // |-- agent-local
// |-- agent-install.sh // |-- agent-install.sh
// |-- bb64 (only used locally, as it's for the local machine's architecture!) // |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- template_example // |-- template_example
// |-- agent-remote // |-- agent-remote
// |-- (remote agent files, including _allservicesstatus.sh) // |-- (remote agent files, including _allservicesstatus.sh)
// ~/.local/dropshell_files
// |-- backups
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// |-- temp_files // |-- temp_files
// |-- template_cache // |-- template_cache
// |-- templates // |-- templates
@ -30,10 +35,6 @@ namespace dropshell {
// | |-- .template_info.env // | |-- .template_info.env
// | |-- (...other service config files...) // | |-- (...other service config files...)
// backups_path
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// server_definition_path // server_definition_path
// |-- <server_name> // |-- <server_name>
// |-- server.json // |-- server.json
@ -52,6 +53,7 @@ namespace dropshell {
} // namespace filenames. } // namespace filenames.
namespace localfile { namespace localfile {
// ~/.config/dropshell/dropshell.json
std::string dropshell_json(); std::string dropshell_json();
std::string server_json(const std::string &server_name); std::string server_json(const std::string &server_name);
std::string service_env(const std::string &server_name, const std::string &service_name); std::string service_env(const std::string &server_name, const std::string &service_name);
@ -61,15 +63,16 @@ namespace dropshell {
} // namespace localfile } // namespace localfile
namespace localpath { namespace localpath {
std::string dropshell_dir();
std::string server(const std::string &server_name); std::string server(const std::string &server_name);
std::string service(const std::string &server_name, const std::string &service_name); std::string service(const std::string &server_name, const std::string &service_name);
std::string remote_versions(const std::string &server_name, const std::string &service_name);
std::string agent_local(); std::string agent_local();
std::string agent_remote(); std::string agent_remote();
std::string current_user_home(); std::string current_user_home();
std::string dropshell_files();
std::string backups(); std::string backups();
std::string temp_files(); std::string temp_files();
std::string template_cache(); std::string template_cache();

View File

@ -6,7 +6,7 @@
#include <string> #include <string>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <libassert/assert.hpp> #include "utils/assert.hpp"
#include "execute.hpp" #include "execute.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
@ -183,7 +183,7 @@ namespace dropshell
if (!rval && !hasFlag(mode, cMode::Silent)) if (!rval && !hasFlag(mode, cMode::Silent))
{ {
error << "Failed to execute ssh command:" << std::endl; error << "Error: Failed to execute ssh command:" << std::endl;
debug << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path) << std::endl; debug << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path) << std::endl;
} }
return rval; return rval;

View File

@ -3,8 +3,6 @@
#define XXH_INLINE_ALL #define XXH_INLINE_ALL
#include "contrib/xxhash.hpp" #include "contrib/xxhash.hpp"
#include "output.hpp"
#include <fstream> #include <fstream>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
@ -15,7 +13,7 @@ uint64_t hash_file(const std::string &path) {
// Create hash state // Create hash state
XXH64_state_t* const state = XXH64_createState(); XXH64_state_t* const state = XXH64_createState();
if (state == nullptr) { if (state == nullptr) {
error << "Failed to create hash state" << std::endl; std::cerr << "Failed to create hash state" << std::endl;
return 0; return 0;
} }
@ -26,7 +24,7 @@ uint64_t hash_file(const std::string &path) {
// Open file // Open file
std::ifstream file(path, std::ios::binary); std::ifstream file(path, std::ios::binary);
if (!file.is_open()) { if (!file.is_open()) {
error << "Failed to open file: " << path << std::endl; std::cerr << "Failed to open file: " << path << std::endl;
XXH64_freeState(state); XXH64_freeState(state);
return 0; return 0;
} }
@ -36,7 +34,7 @@ uint64_t hash_file(const std::string &path) {
char buffer[buffer_size]; char buffer[buffer_size];
while (file.read(buffer, buffer_size)) { while (file.read(buffer, buffer_size)) {
if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) { if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) {
error << "Failed to update hash" << std::endl; std::cerr << "Failed to update hash" << std::endl;
XXH64_freeState(state); XXH64_freeState(state);
return 0; return 0;
} }
@ -45,7 +43,7 @@ uint64_t hash_file(const std::string &path) {
// Handle any remaining bytes // Handle any remaining bytes
if (file.gcount() > 0) { if (file.gcount() > 0) {
if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) { if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) {
error << "Failed to update hash" << std::endl; std::cerr << "Failed to update hash" << std::endl;
XXH64_freeState(state); XXH64_freeState(state);
return 0; return 0;
} }
@ -61,14 +59,14 @@ uint64_t hash_directory_recursive(const std::string &path) {
// Create hash state // Create hash state
XXH64_state_t* const state = XXH64_createState(); XXH64_state_t* const state = XXH64_createState();
if (state == nullptr) { if (state == nullptr) {
error << "Failed to create hash state" << std::endl; std::cerr << "Failed to create hash state" << std::endl;
return 0; return 0;
} }
// Initialize state with seed 0 // Initialize state with seed 0
XXH64_hash_t const seed = 0; /* or any other value */ XXH64_hash_t const seed = 0; /* or any other value */
if (XXH64_reset(state, seed) == XXH_ERROR) { if (XXH64_reset(state, seed) == XXH_ERROR) {
error << "Failed to reset hash state" << std::endl; std::cerr << "Failed to reset hash state" << std::endl;
XXH64_freeState(state); XXH64_freeState(state);
return 0; return 0;
} }
@ -83,7 +81,7 @@ uint64_t hash_directory_recursive(const std::string &path) {
} }
} }
} catch (const std::filesystem::filesystem_error& e) { } catch (const std::filesystem::filesystem_error& e) {
error << "Filesystem error: " << e.what() << std::endl; std::cerr << "Filesystem error: " << e.what() << std::endl;
XXH64_freeState(state); XXH64_freeState(state);
return 0; return 0;
} }
@ -96,7 +94,7 @@ uint64_t hash_directory_recursive(const std::string &path) {
uint64_t hash_path(const std::string &path) { uint64_t hash_path(const std::string &path) {
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
error << "Path does not exist: " << path << std::endl; std::cerr << "Path does not exist: " << path << std::endl;
return 0; return 0;
} }
@ -105,28 +103,28 @@ uint64_t hash_path(const std::string &path) {
} else if (std::filesystem::is_regular_file(path)) { } else if (std::filesystem::is_regular_file(path)) {
return hash_file(path); return hash_file(path);
} else { } else {
error << "Path is neither a file nor a directory: " << path << std::endl; std::cerr << "Path is neither a file nor a directory: " << path << std::endl;
return 0; return 0;
} }
} }
void hash_demo(const std::string & path) void hash_demo(const std::string & path)
{ {
info << "Hashing path: " << path << std::endl; std::cout << "Hashing path: " << path << std::endl;
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
XXH64_hash_t hash = hash_path(path); XXH64_hash_t hash = hash_path(path);
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
info << "Hash: " << hash << " (took " << duration.count() << "ms)" << std::endl; std::cout << "Hash: " << hash << " (took " << duration.count() << "ms)" << std::endl;
} }
int hash_demo_raw(const std::string & path) int hash_demo_raw(const std::string & path)
{ {
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
info << 0 <<std::endl; return 1; std::cout << 0 <<std::endl; return 1;
} }
XXH64_hash_t hash = hash_path(path); XXH64_hash_t hash = hash_path(path);
info << hash << std::endl; std::cout << hash << std::endl;
return 0; return 0;
} }

25578
source/src/utils/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_DIAGNOSTIC_POSITIONS
#define JSON_DIAGNOSTIC_POSITIONS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_DIAGNOSTIC_POSITIONS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View File

@ -147,27 +147,6 @@ void tableprint::set_title(const std::string title) {
this->title = title; this->title = title;
} }
// gives the columns to sort by, starting at 0.
void tableprint::sort(std::vector<int> sort_columns)
{
// Skip header row and sort remaining rows
if (rows.size() <= 1) return; // Only header or empty table
// Create a custom comparator that compares rows based on the specified columns
auto comparator = [this, &sort_columns](const std::vector<std::string>& a, const std::vector<std::string>& b) {
for (int col : sort_columns) {
if (col >= 0 && col < a.size() && col < b.size()) {
int cmp = a[col].compare(b[col]);
if (cmp != 0) return cmp < 0;
}
}
return false; // Equal rows maintain original order
};
// Sort rows starting from index 1 (after header)
std::sort(rows.begin() + 1, rows.end(), comparator);
}
void tableprint::add_row(const std::vector<std::string>& row) { void tableprint::add_row(const std::vector<std::string>& row) {
std::vector<std::string> trimmed_row; std::vector<std::string> trimmed_row;
for (const auto& cell : row) { for (const auto& cell : row) {

View File

@ -16,7 +16,6 @@ class tableprint {
void add_row(const std::vector<std::string>& row); void add_row(const std::vector<std::string>& row);
void print(); void print();
void set_title(const std::string title); void set_title(const std::string title);
void sort(std::vector<int> sort_columns);
private: private:
std::vector<std::vector<std::string>> rows; std::vector<std::vector<std::string>> rows;
std::string title; std::string title;

View File

@ -1,8 +1,4 @@
#include "utils.hpp" #include "utils.hpp"
#include "httplib.hpp"
#include <nlohmann/json.hpp>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <fstream> #include <fstream>
@ -14,7 +10,6 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <cctype> #include <cctype>
#include <sstream>
namespace dropshell { namespace dropshell {
@ -40,7 +35,7 @@ bool replace_line_in_file(const std::string& file_path, const std::string& searc
std::string line; std::string line;
if (!input_file.is_open()) { if (!input_file.is_open()) {
error << "Unable to open file: " << file_path << std::endl; std::cerr << "Error: Unable to open file: " << file_path << std::endl;
return false; return false;
} }
@ -55,7 +50,7 @@ bool replace_line_in_file(const std::string& file_path, const std::string& searc
std::ofstream output_file(file_path); std::ofstream output_file(file_path);
if (!output_file.is_open()) if (!output_file.is_open())
{ {
error << "Unable to open file: " << file_path << std::endl; std::cerr << "Error: Unable to open file: " << file_path << std::endl;
return false; return false;
} }
for (const auto& modified_line : file_lines) for (const auto& modified_line : file_lines)
@ -156,7 +151,7 @@ int str2int(const std::string &str)
try { try {
return std::stoi(str); return std::stoi(str);
} catch (const std::exception& e) { } catch (const std::exception& e) {
error << "Invalid integer string: [" << str << "]" << std::endl; std::cerr << "Error: Invalid integer string: [" << str << "]" << std::endl;
return 0; return 0;
} }
} }
@ -312,9 +307,8 @@ std::string requote(std::string str) {
} }
int return_die(const std::string & msg) { int die(const std::string & msg) {
error << "Fatal error:" << std::endl; std::cerr << msg << std::endl;
error << msg << std::endl;
return 1; return 1;
} }
@ -451,223 +445,4 @@ std::string tolower(const std::string& str) {
return result; return result;
} }
// Common utility function to make HTTP requests
struct HttpResult {
bool success;
int status;
std::string body;
std::string error;
};
HttpResult make_http_request(const std::string& url) {
try {
// Parse the URL to get host and path
std::string host;
std::string path;
size_t protocol_end = url.find("://");
if (protocol_end != std::string::npos) {
size_t host_start = protocol_end + 3;
size_t path_start = url.find('/', host_start);
if (path_start != std::string::npos) {
host = url.substr(host_start, path_start - host_start);
path = url.substr(path_start);
} else {
host = url.substr(host_start);
path = "/";
}
} else {
return {false, 0, "", "Invalid URL format"};
}
// Create HTTP client
httplib::Client cli(host);
cli.set_connection_timeout(10); // 10 second timeout
// Make GET request
auto res = cli.Get(path);
if (!res) {
return {false, 0, "", "Failed to connect to server"};
}
if (res->status != 200) {
return {false, res->status, "", "HTTP request failed with status " + std::to_string(res->status)};
}
return {true, res->status, res->body, ""};
} catch (const std::exception& e) {
return {false, 0, "", std::string("Exception: ") + e.what()};
}
}
bool download_file(const std::string &url, const std::string &destination) {
auto result = make_http_request(url);
if (!result.success) {
warning << "Failed to download file from URL: " << url << std::endl;
return false;
}
try {
std::ofstream out_file(destination, std::ios::binary);
if (!out_file) {
warning << "Failed to open file for writing: " << destination << std::endl;
return false;
}
out_file.write(result.body.c_str(), result.body.size());
out_file.close();
return true;
} catch (const std::exception& e) {
warning << "Failed to download file from URL: " << url << std::endl;
warning << "Exception: " << e.what() << std::endl;
return false;
}
}
nlohmann::json get_json_from_url(const std::string &url) {
auto result = make_http_request(url);
if (!result.success) {
warning << "Failed to get JSON from URL: " << url << std::endl;
return nlohmann::json();
}
try {
return nlohmann::json::parse(result.body);
} catch (const nlohmann::json::parse_error& e) {
warning << "Failed to parse JSON from URL: " << url << std::endl;
warning << "JSON: " << result.body << std::endl;
return nlohmann::json();
}
}
std::string get_string_from_url(const std::string &url) {
auto result = make_http_request(url);
if (!result.success) {
warning << "Failed to get string from URL: " << url << std::endl;
return std::string();
}
return result.body;
}
bool match_line(const std::string &line, const std::string &pattern) {
return trim(line) == trim(pattern);
}
// replace or append a block of text to a file, matching first and last lines if replacing.
// edits file in-place.
bool file_replace_or_add_segment(std::string filepath, std::string segment)
{
// Create a backup of the original file
std::string backup_path = filepath + ".bak";
try {
std::filesystem::copy_file(filepath, backup_path, std::filesystem::copy_options::overwrite_existing);
} catch (const std::exception& e) {
std::cerr << "Error creating backup file: " << e.what() << std::endl;
return false;
}
// Handle empty segment
if (segment.empty()) {
error << "Empty segment provided" << std::endl;
return false;
}
// split the segment into lines
std::vector<std::string> segment_lines = split(segment, "\n");
// remove empty lines
segment_lines.erase(std::remove_if(segment_lines.begin(), segment_lines.end(), [](const std::string& line) {
return trim(line).empty();
}), segment_lines.end());
// remove any lines that are just whitespace
segment_lines.erase(std::remove_if(segment_lines.begin(), segment_lines.end(), [](const std::string& line) { return trim(line).empty(); }), segment_lines.end());
// check that the segment has at least two lines
if (segment_lines.size() < 2) {
error << "Segment must contain at least two non-empty lines" << std::endl;
return false;
}
// Read the entire file into memory
std::ifstream input_file(filepath);
if (!input_file.is_open()) {
error << "Unable to open file: " << filepath << std::endl;
return false;
}
std::vector<std::string> file_lines;
std::string line;
while (std::getline(input_file, line)) {
file_lines.push_back(line);
}
input_file.close();
// Store original file size for verification
size_t original_size = file_lines.size();
if (original_size == 0) {
warning << "File is empty" << std::endl;
}
// Try to find the matching block
bool found_match = false;
for (size_t i = 0; i < file_lines.size(); i++) {
if (match_line(file_lines[i], segment_lines[0])) {
// Found potential start, look for end
for (size_t j = i + 1; j < file_lines.size(); j++) {
if (match_line(file_lines[j], segment_lines[segment_lines.size() - 1])) {
// Found matching block, replace it
file_lines.erase(file_lines.begin() + i, file_lines.begin() + j + 1);
file_lines.insert(file_lines.begin() + i, segment_lines.begin(), segment_lines.end());
found_match = true;
break;
}
}
if (found_match) break;
}
}
// If no match found, append the segment
if (!found_match) {
file_lines.insert(file_lines.end(), segment_lines.begin(), segment_lines.end());
}
// Write back to file
std::ofstream output_file(filepath);
if (!output_file.is_open()) {
error << "Unable to open file for writing: " << filepath << std::endl;
return false;
}
for (const auto& line : file_lines) {
output_file << line << "\n";
}
output_file.close();
// If everything succeeded, remove the backup
try {
std::filesystem::remove(backup_path);
} catch (const std::exception& e) {
warning << "Could not remove backup file: " << e.what() << std::endl;
}
return true;
}
bool legal_service_name(const std::string &service_name) {
static bool initialized = false;
static bool legal_chars[256] = {false}; // Initialize all to false
// One-time initialization
if (!initialized) {
// Set true for valid characters
for (unsigned char c : "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"._-") {
legal_chars[c] = true;
}
initialized = true;
}
return std::all_of(service_name.begin(), service_name.end(),
[](unsigned char c) { return legal_chars[c]; });
}
} // namespace dropshell } // namespace dropshell

View File

@ -5,7 +5,6 @@
#include <map> #include <map>
#include "output.hpp" #include "output.hpp"
#include <nlohmann/json.hpp>
namespace dropshell { namespace dropshell {
@ -45,7 +44,7 @@ int count_substring(const std::string &substring, const std::string &text);
std::string random_alphanumeric_string(int length); std::string random_alphanumeric_string(int length);
int return_die(const std::string & msg); int die(const std::string & msg);
std::string safearg(int argc, char *argv[], int index); std::string safearg(int argc, char *argv[], int index);
std::string safearg(const std::vector<std::string> & args, int index); std::string safearg(const std::vector<std::string> & args, int index);
@ -62,20 +61,4 @@ std::string get_line_wrap(std::string & src, int maxchars);
std::string tolower(const std::string& str); std::string tolower(const std::string& str);
bool download_file(const std::string& url, const std::string& destination);
nlohmann::json get_json_from_url(const std::string& url);
std::string get_string_from_url(const std::string& url);
// replace or append a block of text to a file, matching first and last lines if replacing.
bool file_replace_or_add_segment(std::string filepath, std::string segment);
constexpr unsigned int switchhash(const char *s, int off = 0)
{
return !s[off] ? 5381 : (switchhash(s, off + 1) * 33) ^ s[off];
}
bool legal_service_name(const std::string & service_name);
} // namespace dropshell } // namespace dropshell

View File

@ -1,12 +0,0 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ARCH=$(uname -m)
PREV_DIR=$(pwd)
trap 'cd "$PREV_DIR"' EXIT
"$SCRIPT_DIR/output/dropshell.${ARCH}" hash "${SCRIPT_DIR}/test.sh"
"$SCRIPT_DIR/output/dropshell.${ARCH}" help