This commit is contained in:
parent
068ef34709
commit
8827ea5a42
@ -1,90 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner VERSION 1.0)
|
||||
project(runner_demo)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Include directories
|
||||
include_directories(include)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
# Find required packages
|
||||
find_package(nlohmann_json QUIET)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
nlohmann_json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG v3.11.2
|
||||
)
|
||||
FetchContent_MakeAvailable(nlohmann_json)
|
||||
endif()
|
||||
add_library(runner_lib runner.cpp)
|
||||
target_include_directories(runner_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Try to find libssh using different methods
|
||||
find_package(libssh QUIET)
|
||||
if(NOT libssh_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(LIBSSH libssh QUIET)
|
||||
endif()
|
||||
|
||||
if(NOT LIBSSH_FOUND)
|
||||
# Try to find manually if pkg-config failed too
|
||||
find_path(LIBSSH_INCLUDE_DIR
|
||||
NAMES libssh/libssh.h
|
||||
PATHS /usr/include /usr/local/include
|
||||
)
|
||||
|
||||
find_library(LIBSSH_LIBRARY
|
||||
NAMES ssh libssh
|
||||
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu
|
||||
)
|
||||
|
||||
if(LIBSSH_INCLUDE_DIR AND LIBSSH_LIBRARY)
|
||||
set(LIBSSH_FOUND TRUE)
|
||||
set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY})
|
||||
set(LIBSSH_INCLUDE_DIRS ${LIBSSH_INCLUDE_DIR})
|
||||
message(STATUS "Found libssh: ${LIBSSH_LIBRARY}")
|
||||
else()
|
||||
message(FATAL_ERROR "libssh not found. Please install libssh-dev package.\nOn Ubuntu/Debian: sudo apt install libssh-dev\nOn CentOS/RHEL: sudo yum install libssh-devel\nOn macOS: brew install libssh")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Print libssh information for debugging
|
||||
message(STATUS "LIBSSH_FOUND: ${LIBSSH_FOUND}")
|
||||
message(STATUS "LIBSSH_LIBRARIES: ${LIBSSH_LIBRARIES}")
|
||||
message(STATUS "LIBSSH_INCLUDE_DIRS: ${LIBSSH_INCLUDE_DIRS}")
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# Library target
|
||||
add_library(dropshell_runner STATIC
|
||||
src/runner.cpp
|
||||
src/base64.cpp
|
||||
)
|
||||
|
||||
target_include_directories(dropshell_runner PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${LIBSSH_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Link with libssh
|
||||
if(libssh_FOUND)
|
||||
# libssh was found using the find_package method
|
||||
target_link_libraries(dropshell_runner PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
ssh
|
||||
Threads::Threads
|
||||
)
|
||||
else()
|
||||
# libssh was found using pkg-config or manual search
|
||||
target_link_libraries(dropshell_runner PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
${LIBSSH_LIBRARIES}
|
||||
Threads::Threads
|
||||
)
|
||||
endif()
|
||||
|
||||
# Executable target
|
||||
add_executable(runner src/main.cpp)
|
||||
target_link_libraries(runner PRIVATE dropshell_runner)
|
||||
add_executable(runner runner_demo.cpp)
|
||||
target_link_libraries(runner runner_lib OpenSSL::SSL OpenSSL::Crypto)
|
254
runner/README.md
254
runner/README.md
@ -7,207 +7,77 @@ use:
|
||||
|
||||
BASE64COMMAND is Base64 encoded json. The json format is as described below. The exit code is that of the command run, or -1 if the command couldn't be run.
|
||||
|
||||
The c++ library used, which is contained in this codebase, has two simple functions:
|
||||
bool runner(nlohmann::json run_json); // no output capture
|
||||
bool runner(nlohmann::json run_json, std::string & output); // with output capture.
|
||||
|
||||
## JSON Specification
|
||||
|
||||
```json
|
||||
The c++ library used, which is contained in this codebase, has one simple function:
|
||||
|
||||
typedef struct sSSHInfo {
|
||||
std::string host;
|
||||
std::string user;
|
||||
std::string port;
|
||||
} sSSHInfo;
|
||||
|
||||
|
||||
static int execute_cmd(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
sSSHInfo * sshinfo = nullptr,
|
||||
std::string* output = nullptr,
|
||||
);
|
||||
|
||||
|
||||
If SSH information is provided, the command will be executed on the remote server. Otherwise on the local machine.
|
||||
|
||||
If output is provided, the output of the command is captured.
|
||||
|
||||
If interactive is true, then an interactive session is created - e.g. for running nano to remotely edit a file, or sshing into a docker container from the remote host.
|
||||
|
||||
If silent is true, then all output is suppressed.
|
||||
|
||||
Before the command is run, the current directory is changed to working_dir on the remote host (or local host if no ssh info provided).
|
||||
|
||||
|
||||
## BASE64COMMAND JSON Format
|
||||
|
||||
The BASE64COMMAND argument should be a Base64-encoded JSON object with the following fields:
|
||||
|
||||
- `command` (string, required): The command to execute (e.g., "ls").
|
||||
- `args` (array of strings, optional): Arguments to pass to the command (e.g., ["-l", "/tmp"]).
|
||||
- `working_dir` (string, optional): Directory to change to before running the command.
|
||||
- `env` (object, optional): Environment variables to set (e.g., {"FOO": "bar"}).
|
||||
- `silent` (boolean, optional): If true, suppress all output. Default: false.
|
||||
- `interactive` (boolean, optional): If true, run the command interactively. Default: false.
|
||||
- `sshinfo` (object, optional): If present, run the command on a remote host (not implemented in demo):
|
||||
- `host` (string): Hostname or IP.
|
||||
- `user` (string): Username.
|
||||
- `port` (string): SSH port.
|
||||
|
||||
### Example JSON
|
||||
|
||||
```
|
||||
{
|
||||
"ssh": { // Optional: SSH connection information
|
||||
"host": "hostname", // Remote host to connect to
|
||||
"port": 22, // Port number (default: 22)
|
||||
"user": "username", // Username for SSH connection
|
||||
"key": "path/to/key" // Path to SSH key file or "auto" to use current user's key
|
||||
},
|
||||
"working_directory": "/path", // Optional: Directory to change to before executing the command
|
||||
"env": { // Optional: Environment variables
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
"command": "command_name", // Required: Command to execute
|
||||
"args": ["arg1", "arg2"], // Optional: Command arguments
|
||||
"options": { // Optional: Execution options
|
||||
"silent": false, // Suppress all terminal output (default: false)
|
||||
"interactive": false // Hook up TTY for interactive sessions (default: false)
|
||||
}
|
||||
"command": "ls",
|
||||
"args": ["-l", "/tmp"],
|
||||
"working_dir": "/",
|
||||
"env": {"FOO": "bar"},
|
||||
"silent": false,
|
||||
"interactive": false
|
||||
}
|
||||
```
|
||||
|
||||
If SSH information is provided, the command will be executed on the remote server.
|
||||
To use, encode the JSON as a single line, then base64 encode it:
|
||||
|
||||
## Build Instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.10 or newer
|
||||
- C++17 compatible compiler
|
||||
- libssh development libraries
|
||||
- jq (for the helper scripts)
|
||||
|
||||
#### Installing Dependencies
|
||||
|
||||
##### Quick Installation (Ubuntu/Debian)
|
||||
|
||||
For Ubuntu/Debian systems, you can use the provided installation script:
|
||||
|
||||
```bash
|
||||
sudo ./install_deps.sh
|
||||
```
|
||||
echo -n '{"command":"ls","args":["-l","/tmp"],"working_dir":"/","env":{"FOO":"bar"},"silent":false,"interactive":false}' | base64
|
||||
```
|
||||
|
||||
This will install all required dependencies (cmake, g++, libssh-dev, jq).
|
||||
Then run:
|
||||
|
||||
##### Manual Installation
|
||||
|
||||
###### Ubuntu/Debian
|
||||
|
||||
```bash
|
||||
sudo apt-get install cmake g++ libssh-dev jq
|
||||
```
|
||||
./build/runner <BASE64COMMAND>
|
||||
```
|
||||
|
||||
###### CentOS/RHEL
|
||||
|
||||
```bash
|
||||
sudo yum install cmake gcc-c++ libssh-devel jq
|
||||
```
|
||||
|
||||
###### macOS
|
||||
|
||||
```bash
|
||||
brew install cmake libssh jq
|
||||
```
|
||||
|
||||
###### Windows
|
||||
|
||||
Using vcpkg:
|
||||
|
||||
```bash
|
||||
vcpkg install libssh nlohmann-json
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
To build the project, you can use the provided build script:
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
The executable will be created at `build/runner`.
|
||||
|
||||
### Testing
|
||||
|
||||
A simple test script is included to verify the functionality:
|
||||
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
This will run basic tests for command execution, environment variables, silent mode, and return codes.
|
||||
|
||||
If you have SSH configured on your local machine and want to test the SSH functionality:
|
||||
|
||||
```bash
|
||||
ENABLE_SSH_TEST=1 ./test.sh
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If CMake cannot find libssh, you can:
|
||||
|
||||
1. Run the libssh finder script to locate your installation:
|
||||
```bash
|
||||
./examples/find_libssh.sh
|
||||
```
|
||||
|
||||
2. Specify its location manually:
|
||||
```bash
|
||||
cmake -DCMAKE_PREFIX_PATH=/path/to/libssh/installation ..
|
||||
```
|
||||
|
||||
3. Or set the libssh_DIR environment variable:
|
||||
```bash
|
||||
export libssh_DIR=/path/to/libssh/installation
|
||||
cmake ..
|
||||
```
|
||||
|
||||
4. If the problem persists, specify the library and include paths directly:
|
||||
```bash
|
||||
cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include ..
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Running a local command
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for the 'ls -l' command
|
||||
JSON='{"command":"ls","args":["-l"]}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running a command with environment variables
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration with environment variables
|
||||
JSON='{"command":"echo","args":["$GREETING"],"env":{"GREETING":"Hello, World!"}}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running a command on a remote server via SSH
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for a remote command
|
||||
JSON='{"ssh":{"host":"example.com","user":"username","key":"auto"},"command":"hostname"}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running an interactive command
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for an interactive command
|
||||
JSON='{"command":"vim","options":{"interactive":true}}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Using the helper script
|
||||
|
||||
The `run.sh` script simplifies testing by handling the JSON validation and Base64 encoding:
|
||||
|
||||
```bash
|
||||
# Run with a JSON file
|
||||
./run.sh examples/local_command.json
|
||||
|
||||
# Run with a JSON string
|
||||
./run.sh '{"command":"echo","args":["Hello World"]}'
|
||||
```
|
||||
|
105
runner/build.sh
105
runner/build.sh
@ -1,78 +1,39 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Don't use set -e because we want to handle errors ourselves
|
||||
# set -e
|
||||
|
||||
echo "Building Runner - Dropshell Command Execution Library"
|
||||
echo "===================================================="
|
||||
|
||||
# Create build directory if it doesn't exist
|
||||
mkdir -p build || {
|
||||
echo "ERROR: Failed to create build directory"
|
||||
# Check for cmake
|
||||
if ! command -v cmake &> /dev/null; then
|
||||
echo "Error: cmake is not installed. Please install cmake." >&2
|
||||
exit 1
|
||||
}
|
||||
cd build
|
||||
fi
|
||||
|
||||
# Check if pkg-config is available and can find libssh
|
||||
if command -v pkg-config &> /dev/null && pkg-config --exists libssh; then
|
||||
# If libssh is found through pkg-config, use it
|
||||
LIBSSH_PREFIX=$(pkg-config --variable=prefix libssh)
|
||||
echo "Found libssh through pkg-config at: $LIBSSH_PREFIX"
|
||||
CMAKE_ARGS="-DCMAKE_PREFIX_PATH=$LIBSSH_PREFIX"
|
||||
# Check for g++
|
||||
if ! command -v g++ &> /dev/null; then
|
||||
echo "Error: g++ is not installed. Please install g++." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for OpenSSL
|
||||
if ! pkg-config --exists openssl; then
|
||||
echo "Error: OpenSSL development libraries not found. Please install libssl-dev." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for nlohmann_json
|
||||
if ! pkg-config --exists nlohmann_json; then
|
||||
echo "Warning: nlohmann_json not found via pkg-config. Make sure it is installed or available to CMake." >&2
|
||||
fi
|
||||
|
||||
BUILD_DIR=build
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
if [ -f runner ]; then
|
||||
echo "Build successful. Run ./build/runner BASE64COMMAND to test."
|
||||
else
|
||||
# Otherwise, let CMake try to find it
|
||||
CMAKE_ARGS=""
|
||||
|
||||
# Check if libssh-dev/libssh-devel is installed
|
||||
if [ ! -f "/usr/include/libssh/libssh.h" ] && [ ! -f "/usr/local/include/libssh/libssh.h" ]; then
|
||||
echo "WARNING: libssh development headers not found in standard locations."
|
||||
echo "You may need to install the libssh development package:"
|
||||
echo " - Ubuntu/Debian: sudo apt install libssh-dev"
|
||||
echo " - CentOS/RHEL: sudo yum install libssh-devel"
|
||||
echo " - macOS: brew install libssh"
|
||||
echo ""
|
||||
echo "Continuing build anyway, but it may fail..."
|
||||
echo ""
|
||||
|
||||
# Offer to run the find_libssh.sh helper script
|
||||
if [ -x "../examples/find_libssh.sh" ]; then
|
||||
echo "Would you like to run the libssh finder helper script? (y/n)"
|
||||
read -r answer
|
||||
if [[ "$answer" =~ ^[Yy] ]]; then
|
||||
cd ..
|
||||
./examples/find_libssh.sh
|
||||
cd build
|
||||
echo ""
|
||||
echo "Continuing with build..."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure and build with error handling
|
||||
echo "Running cmake with args: $CMAKE_ARGS"
|
||||
if ! cmake $CMAKE_ARGS ..; then
|
||||
echo ""
|
||||
echo "ERROR: CMake configuration failed."
|
||||
echo "This is likely due to missing dependencies. Please check the error message above."
|
||||
echo "For libssh issues, try:"
|
||||
echo " 1. Install libssh development package: sudo apt install libssh-dev"
|
||||
echo " 2. Run our helper script: ./examples/find_libssh.sh"
|
||||
echo " 3. If you know where libssh is installed, specify it with:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include .."
|
||||
echo "Build failed. Check the output above for errors."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! make -j$(nproc); then
|
||||
echo ""
|
||||
echo "ERROR: Build failed."
|
||||
echo "Please check the error messages above for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Build complete. Binary is at build/runner"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " ./run.sh examples/local_command.json # Run a local command"
|
||||
echo " ./run.sh examples/env_vars.json # Run with environment variables"
|
||||
fi
|
@ -1 +0,0 @@
|
||||
{"command":"bash","args":["-c","echo Hello $NAME, the current directory is $PWD"],"env":{"NAME":"Runner","CUSTOM_VAR":"This is a custom environment variable"}}
|
@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script to find libssh installation on your system
|
||||
|
||||
echo "Looking for libssh installation..."
|
||||
echo
|
||||
|
||||
# Check pkg-config path
|
||||
if command -v pkg-config &> /dev/null; then
|
||||
echo "Checking pkg-config for libssh:"
|
||||
if pkg-config --exists libssh; then
|
||||
echo " - Found libssh with pkg-config"
|
||||
echo " - Version: $(pkg-config --modversion libssh)"
|
||||
echo " - Include path: $(pkg-config --variable=includedir libssh)"
|
||||
echo " - Library path: $(pkg-config --variable=libdir libssh)"
|
||||
echo
|
||||
echo "To use this in cmake:"
|
||||
echo " cmake -DCMAKE_PREFIX_PATH=$(pkg-config --variable=prefix libssh) .."
|
||||
echo
|
||||
else
|
||||
echo " - libssh not found with pkg-config"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo "pkg-config not found on your system"
|
||||
echo
|
||||
fi
|
||||
|
||||
# Check common include paths
|
||||
for DIR in /usr/include /usr/local/include /opt/local/include; do
|
||||
if [ -d "$DIR" ] && [ -f "$DIR/libssh/libssh.h" ]; then
|
||||
echo "Found libssh headers at: $DIR/libssh/libssh.h"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check common library paths
|
||||
LIB_PATHS=()
|
||||
for DIR in /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/local/lib64 /opt/local/lib; do
|
||||
if [ -d "$DIR" ]; then
|
||||
LIBS=$(find "$DIR" -name "libssh.so*" -o -name "libssh.dylib" -o -name "libssh.a" 2>/dev/null)
|
||||
if [ -n "$LIBS" ]; then
|
||||
echo "Found libssh libraries in $DIR:"
|
||||
echo "$LIBS" | sed 's/^/ - /'
|
||||
LIB_DIR="$DIR"
|
||||
LIB_PATHS+=("$DIR")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [ ${#LIB_PATHS[@]} -gt 0 ]; then
|
||||
echo "To build with the detected libssh installation, you can use:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=${LIB_PATHS[0]}/libssh.so -DLIBSSH_INCLUDE_DIR=/usr/include .."
|
||||
echo
|
||||
echo "Or set environment variables:"
|
||||
echo " export LIBSSH_LIBRARY=${LIB_PATHS[0]}/libssh.so"
|
||||
echo " export LIBSSH_INCLUDE_DIR=/usr/include"
|
||||
echo " cmake .."
|
||||
else
|
||||
echo "Could not find libssh on your system."
|
||||
echo "Please install it with your package manager:"
|
||||
echo " - Ubuntu/Debian: sudo apt install libssh-dev"
|
||||
echo " - CentOS/RHEL: sudo yum install libssh-devel"
|
||||
echo " - macOS: brew install libssh"
|
||||
fi
|
@ -1 +0,0 @@
|
||||
{"command":"bash","options":{"interactive":true}}
|
@ -1 +0,0 @@
|
||||
{"command":"ls","args":["-la"],"options":{"silent":false}}
|
@ -1 +0,0 @@
|
||||
{"ssh":{"host":"localhost","key":"auto"},"command":"echo","args":["Hello from SSH on localhost!"]}
|
@ -1 +0,0 @@
|
||||
{"command":"ls","args":["-la"],"options":{"silent":true}}
|
@ -1 +0,0 @@
|
||||
{"ssh":{"host":"localhost","port":22,"user":"USERNAME","key":"auto"},"command":"hostname","options":{"interactive":false}}
|
@ -1 +0,0 @@
|
||||
{"ssh":{"host":"localhost","user":"USERNAME","key":"auto"},"command":"pwd","working_directory":"/tmp"}
|
@ -1 +0,0 @@
|
||||
{"command":"pwd","working_directory":"/tmp"}
|
@ -1,2 +0,0 @@
|
||||
lkjdfslka
|
||||
|
@ -1,13 +0,0 @@
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Decode a Base64 encoded string to its original form
|
||||
* @param encoded The Base64 encoded string
|
||||
* @return The decoded string
|
||||
*/
|
||||
std::string base64_decode(const std::string& encoded);
|
||||
|
||||
#endif // BASE64_H
|
@ -1,104 +0,0 @@
|
||||
#ifndef DROPSHELL_RUNNER_H
|
||||
#define DROPSHELL_RUNNER_H
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
/**
|
||||
* Runner library for executing commands locally or remotely via SSH
|
||||
*/
|
||||
class Runner {
|
||||
public:
|
||||
/**
|
||||
* Execute a command according to the specification in the JSON
|
||||
* @param run_json JSON specification for the command
|
||||
* @return true if command executed successfully, false otherwise
|
||||
*/
|
||||
static bool run(const nlohmann::json& run_json);
|
||||
|
||||
/**
|
||||
* Execute a command and capture its output
|
||||
* @param run_json JSON specification for the command
|
||||
* @param output Reference to string where output will be stored
|
||||
* @return true if command executed successfully, false otherwise
|
||||
*/
|
||||
static bool run(const nlohmann::json& run_json, std::string& output);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Execute a command locally
|
||||
* @param command Command to execute
|
||||
* @param args Command arguments
|
||||
* @param working_dir Working directory to change to before execution
|
||||
* @param env Environment variables
|
||||
* @param silent Whether to suppress output
|
||||
* @param interactive Whether to enable interactive mode
|
||||
* @param output Reference to string where output will be stored (if capturing)
|
||||
* @param capture_output Whether to capture output
|
||||
* @return exit code of the command, or -1 if execution failed
|
||||
*/
|
||||
static int execute_local(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output = nullptr,
|
||||
bool capture_output = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Execute a command remotely via SSH
|
||||
* @param ssh_config SSH configuration
|
||||
* @param command Command to execute
|
||||
* @param args Command arguments
|
||||
* @param working_dir Working directory to change to on the remote server
|
||||
* @param env Environment variables
|
||||
* @param silent Whether to suppress output
|
||||
* @param interactive Whether to enable interactive mode
|
||||
* @param output Reference to string where output will be stored (if capturing)
|
||||
* @param capture_output Whether to capture output
|
||||
* @return exit code of the command, or -1 if execution failed
|
||||
*/
|
||||
static int execute_ssh(
|
||||
const nlohmann::json& ssh_config,
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output = nullptr,
|
||||
bool capture_output = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Parse command specification from JSON
|
||||
* @param run_json JSON specification
|
||||
* @param command Output parameter for command
|
||||
* @param args Output parameter for command arguments
|
||||
* @param working_dir Output parameter for working directory
|
||||
* @param env Output parameter for environment variables
|
||||
* @param silent Output parameter for silent option
|
||||
* @param interactive Output parameter for interactive option
|
||||
* @return true if parsing was successful, false otherwise
|
||||
*/
|
||||
static bool parse_json(
|
||||
const nlohmann::json& run_json,
|
||||
std::string& command,
|
||||
std::vector<std::string>& args,
|
||||
std::string& working_dir,
|
||||
std::map<std::string, std::string>& env,
|
||||
bool& silent,
|
||||
bool& interactive
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace dropshell
|
||||
|
||||
#endif // DROPSHELL_RUNNER_H
|
@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install dependencies script for Runner
|
||||
# This script is for Ubuntu/Debian systems
|
||||
|
||||
echo "Installing dependencies for Runner - Dropshell Command Execution Library"
|
||||
echo "======================================================================"
|
||||
echo
|
||||
|
||||
# Check if running as root or with sudo
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run this script with sudo:"
|
||||
echo " sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updating package lists..."
|
||||
apt-get update
|
||||
|
||||
echo "Installing required packages:"
|
||||
echo " - build-essential (C++ compiler and build tools)"
|
||||
echo " - cmake (build system)"
|
||||
echo " - libssh-dev (SSH client library)"
|
||||
echo " - jq (JSON parsing for helper scripts)"
|
||||
echo
|
||||
|
||||
apt-get install -y build-essential cmake libssh-dev jq
|
||||
|
||||
# Check if installation was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo
|
||||
echo "Dependencies installed successfully!"
|
||||
echo
|
||||
echo "You can now build the project with:"
|
||||
echo " ./build.sh"
|
||||
echo
|
||||
else
|
||||
echo
|
||||
echo "Error: Failed to install dependencies."
|
||||
echo "Please check the error messages above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify libssh installation
|
||||
if [ -f "/usr/include/libssh/libssh.h" ]; then
|
||||
echo "Verified: libssh development headers are installed."
|
||||
|
||||
# Find libssh shared library
|
||||
LIB=$(find /usr/lib -name "libssh.so*" | head -1)
|
||||
if [ -n "$LIB" ]; then
|
||||
echo "Verified: libssh shared library is installed at $LIB"
|
||||
else
|
||||
echo "Warning: Could not find libssh shared library."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "You're all set! Build the project with ./build.sh"
|
||||
else
|
||||
echo "Warning: Could not verify libssh installation."
|
||||
echo "The package might have been installed in a non-standard location."
|
||||
echo "You may need to manually specify the location when building:"
|
||||
echo
|
||||
echo " cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include .."
|
||||
fi
|
20
runner/jt.sh
20
runner/jt.sh
@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# JSON=$(cat <<'EOF'
|
||||
# {"command":"nano","args":["-w","./hello.txt"]}
|
||||
# EOF
|
||||
# )
|
||||
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"nano","args":["-w","./hello.txt"],"options":{"interactive":true}}
|
||||
EOF
|
||||
)
|
||||
|
||||
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command..."
|
||||
build/runner "$BASE64"
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Running minimal libssh test to diagnose linking issues"
|
||||
echo "===================================================="
|
||||
|
||||
# Create test directory
|
||||
TESTDIR="ssh_test_tmp"
|
||||
mkdir -p $TESTDIR
|
||||
cd $TESTDIR
|
||||
|
||||
# Create minimal C program that uses libssh
|
||||
cat > test.c << 'EOF'
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
int main() {
|
||||
ssh_session session = ssh_new();
|
||||
if (session == NULL) {
|
||||
fprintf(stderr, "Failed to create SSH session\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Successfully created SSH session\n");
|
||||
|
||||
ssh_free(session);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Created minimal test program. Attempting to compile..."
|
||||
|
||||
# Check pkg-config first
|
||||
if command -v pkg-config &> /dev/null && pkg-config --exists libssh; then
|
||||
CFLAGS=$(pkg-config --cflags libssh)
|
||||
LIBS=$(pkg-config --libs libssh)
|
||||
|
||||
echo "Found libssh with pkg-config:"
|
||||
echo " CFLAGS: $CFLAGS"
|
||||
echo " LIBS: $LIBS"
|
||||
echo
|
||||
|
||||
echo "Compiling with pkg-config flags..."
|
||||
gcc -o test_pkg test.c $CFLAGS $LIBS
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Compilation successful!"
|
||||
echo "Running the test program:"
|
||||
./test_pkg
|
||||
else
|
||||
echo "Compilation failed with pkg-config flags."
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
# Try with simple -lssh flag
|
||||
echo "Compiling with simple -lssh flag..."
|
||||
gcc -o test_simple test.c -lssh
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Compilation successful with -lssh!"
|
||||
echo "Running the test program:"
|
||||
./test_simple
|
||||
else
|
||||
echo "Compilation failed with simple -lssh flag."
|
||||
echo "This indicates your libssh installation might not be in the standard location."
|
||||
fi
|
||||
echo
|
||||
|
||||
# Try to find libssh manually
|
||||
echo "Searching for libssh.so in common locations..."
|
||||
LIBSSH_PATHS=$(find /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu -name "libssh.so*" 2>/dev/null)
|
||||
|
||||
if [ -n "$LIBSSH_PATHS" ]; then
|
||||
echo "Found libssh in the following locations:"
|
||||
echo "$LIBSSH_PATHS" | sed 's/^/ - /'
|
||||
|
||||
# Extract directory of first result
|
||||
LIBSSH_DIR=$(dirname $(echo "$LIBSSH_PATHS" | head -1))
|
||||
echo
|
||||
echo "For CMake, you can try:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=$LIBSSH_DIR/libssh.so -DLIBSSH_INCLUDE_DIR=/usr/include .."
|
||||
else
|
||||
echo "Could not find libssh.so."
|
||||
echo "Consider installing libssh-dev package:"
|
||||
echo " sudo apt install libssh-dev"
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
cd ..
|
||||
echo
|
||||
echo "Cleaning up..."
|
||||
rm -rf $TESTDIR
|
||||
|
||||
echo
|
||||
echo "Test completed. Use this information to troubleshoot your libssh configuration."
|
25578
runner/nlohmann/json.hpp
Normal file
25578
runner/nlohmann/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
187
runner/nlohmann/json_fwd.hpp
Normal file
187
runner/nlohmann/json_fwd.hpp
Normal 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_
|
@ -1,83 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check if jq is installed
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required but not installed."
|
||||
echo "Install it with: sudo apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to print usage
|
||||
usage() {
|
||||
echo "Usage: $0 <json_file>"
|
||||
echo " $0 'json_string'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if argument is provided
|
||||
if [ $# -ne 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
JSON=""
|
||||
|
||||
# Process and clean up JSON input to handle any whitespace or formatting issues
|
||||
process_json() {
|
||||
local input="$1"
|
||||
# Use 'tr' to remove any trailing whitespace and control characters
|
||||
# Then use jq to validate and normalize the JSON structure
|
||||
echo "$input" | tr -d '\r' | sed 's/[[:space:]]*$//' | jq -c '.'
|
||||
}
|
||||
|
||||
# Check if the argument is a file or a JSON string
|
||||
if [ -f "$1" ]; then
|
||||
# Validate JSON file
|
||||
if ! jq . "$1" > /dev/null 2>&1; then
|
||||
echo "Error: Invalid JSON in file $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read file content and process it
|
||||
FILE_CONTENT=$(cat "$1")
|
||||
JSON=$(process_json "$FILE_CONTENT")
|
||||
else
|
||||
# Check if it's a valid JSON string
|
||||
if ! echo "$1" | jq . > /dev/null 2>&1; then
|
||||
echo "Error: Invalid JSON string"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process the JSON string
|
||||
JSON=$(process_json "$1")
|
||||
fi
|
||||
|
||||
# Double-check that we have valid JSON (defensive programming)
|
||||
if ! echo "$JSON" | jq . > /dev/null 2>&1; then
|
||||
echo "Error: Something went wrong processing the JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Base64 encode the JSON, ensuring no trailing newlines
|
||||
BASE64=$(echo -n "$JSON" | base64 | tr -d '\n')
|
||||
|
||||
# Ensure the binary exists
|
||||
if [ ! -f "build/runner" ]; then
|
||||
echo "Building the project first..."
|
||||
./build.sh
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
echo "Running command with JSON:"
|
||||
echo "$JSON" | jq .
|
||||
echo
|
||||
echo "Base64 encoded: $BASE64"
|
||||
echo
|
||||
|
||||
# Execute the runner with the processed base64 input
|
||||
build/runner "$BASE64"
|
||||
EXIT_CODE=$?
|
||||
|
||||
echo
|
||||
echo "Command exited with code: $EXIT_CODE"
|
||||
exit $EXIT_CODE
|
93
runner/runner.cpp
Normal file
93
runner/runner.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include "runner.h"
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
int execute_cmd(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
sSSHInfo* sshinfo,
|
||||
std::string* output
|
||||
) {
|
||||
// SSH execution is not implemented in this demo
|
||||
if (sshinfo) {
|
||||
std::cerr << "Remote SSH execution is not implemented in this demo.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pipefd[2];
|
||||
if (output && pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("fork");
|
||||
return -1;
|
||||
}
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
if (!working_dir.empty()) {
|
||||
if (chdir(working_dir.c_str()) != 0) {
|
||||
perror("chdir");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
// Set environment variables
|
||||
for (const auto& kv : env) {
|
||||
setenv(kv.first.c_str(), kv.second.c_str(), 1);
|
||||
}
|
||||
if (output) {
|
||||
close(pipefd[0]);
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
} else if (silent) {
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
if (!interactive) {
|
||||
// Detach from terminal if not interactive
|
||||
setsid();
|
||||
}
|
||||
std::vector<char*> argv;
|
||||
argv.push_back(const_cast<char*>(command.c_str()));
|
||||
for (const auto& arg : args) {
|
||||
argv.push_back(const_cast<char*>(arg.c_str()));
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
execvp(command.c_str(), argv.data());
|
||||
perror("execvp");
|
||||
exit(-1);
|
||||
} else {
|
||||
// Parent process
|
||||
if (output) {
|
||||
close(pipefd[1]);
|
||||
std::ostringstream oss;
|
||||
char buf[4096];
|
||||
ssize_t n;
|
||||
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
|
||||
oss.write(buf, n);
|
||||
}
|
||||
close(pipefd[0]);
|
||||
*output = oss.str();
|
||||
}
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
21
runner/runner.h
Normal file
21
runner/runner.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
struct sSSHInfo {
|
||||
std::string host;
|
||||
std::string user;
|
||||
std::string port;
|
||||
};
|
||||
|
||||
int execute_cmd(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
sSSHInfo* sshinfo = nullptr,
|
||||
std::string* output = nullptr
|
||||
);
|
63
runner/runner_demo.cpp
Normal file
63
runner/runner_demo.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "runner.h"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string base64_decode(const std::string& encoded) {
|
||||
BIO* bio, *b64;
|
||||
int decodeLen = (encoded.length() * 3) / 4;
|
||||
std::string decoded(decodeLen, '\0');
|
||||
bio = BIO_new_mem_buf(encoded.data(), encoded.length());
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bio = BIO_push(b64, bio);
|
||||
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
|
||||
int len = BIO_read(bio, &decoded[0], encoded.length());
|
||||
decoded.resize(len > 0 ? len : 0);
|
||||
BIO_free_all(bio);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: runner BASE64COMMAND\n";
|
||||
return -1;
|
||||
}
|
||||
std::string decoded = base64_decode(argv[1]);
|
||||
json j;
|
||||
try {
|
||||
j = json::parse(decoded);
|
||||
} catch (...) {
|
||||
std::cerr << "Invalid JSON in decoded command\n";
|
||||
return -1;
|
||||
}
|
||||
std::string command = j.value("command", "");
|
||||
std::vector<std::string> args = j.value("args", std::vector<std::string>{});
|
||||
std::string working_dir = j.value("working_dir", "");
|
||||
std::map<std::string, std::string> env = j.value("env", std::map<std::string, std::string>{});
|
||||
bool silent = j.value("silent", false);
|
||||
bool interactive = j.value("interactive", false);
|
||||
sSSHInfo* sshinfo = nullptr;
|
||||
sSSHInfo ssh;
|
||||
if (j.contains("sshinfo")) {
|
||||
ssh.host = j["sshinfo"].value("host", "");
|
||||
ssh.user = j["sshinfo"].value("user", "");
|
||||
ssh.port = j["sshinfo"].value("port", "");
|
||||
sshinfo = &ssh;
|
||||
}
|
||||
std::string output;
|
||||
int ret = execute_cmd(command, args, working_dir, env, silent, interactive, sshinfo, &output);
|
||||
if (!silent && !output.empty()) {
|
||||
std::cout << output;
|
||||
}
|
||||
return ret;
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
|
||||
std::string base64_decode(const std::string& encoded) {
|
||||
const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
size_t in_len = encoded.size();
|
||||
size_t i = 0;
|
||||
size_t in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string decoded;
|
||||
|
||||
while (in_ < in_len && encoded[in_] != '=' &&
|
||||
(std::isalnum(encoded[in_]) || encoded[in_] == '+' || encoded[in_] == '/')) {
|
||||
char_array_4[i++] = encoded[in_++];
|
||||
if (i == 4) {
|
||||
// Translate values in char_array_4 from base64 alphabet to indices
|
||||
for (i = 0; i < 4; i++) {
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
}
|
||||
|
||||
// Decode to original bytes
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
decoded += char_array_3[i];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any remaining bytes
|
||||
if (i) {
|
||||
// Fill remaining positions with zeros
|
||||
for (size_t j = i; j < 4; j++) {
|
||||
char_array_4[j] = 0;
|
||||
}
|
||||
|
||||
// Convert to indices
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
}
|
||||
|
||||
// Decode remaining bytes
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
// Only add valid bytes based on how many characters we had
|
||||
for (size_t j = 0; j < i - 1; j++) {
|
||||
decoded += char_array_3[j];
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
#include "runner.h"
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
void print_usage() {
|
||||
std::cerr << "Usage: runner BASE64COMMAND" << std::endl;
|
||||
std::cerr << " where BASE64COMMAND is a Base64 encoded JSON string" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string base64_command = argv[1];
|
||||
std::string json_string;
|
||||
|
||||
try {
|
||||
// Decode Base64
|
||||
json_string = base64_decode(base64_command);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error decoding Base64: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
nlohmann::json run_json;
|
||||
try {
|
||||
run_json = nlohmann::json::parse(json_string);
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute command
|
||||
bool success = dropshell::Runner::run(run_json);
|
||||
|
||||
// Return the exit code
|
||||
return success ? 0 : 1;
|
||||
}
|
@ -1,513 +0,0 @@
|
||||
#include "runner.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
bool Runner::run(const nlohmann::json& run_json) {
|
||||
std::string command;
|
||||
std::vector<std::string> args;
|
||||
std::string working_dir;
|
||||
std::map<std::string, std::string> env;
|
||||
bool silent, interactive;
|
||||
|
||||
if (!parse_json(run_json, command, args, working_dir, env, silent, interactive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int exit_code;
|
||||
if (run_json.contains("ssh")) {
|
||||
exit_code = execute_ssh(run_json["ssh"], command, args, working_dir, env, silent, interactive);
|
||||
} else {
|
||||
exit_code = execute_local(command, args, working_dir, env, silent, interactive);
|
||||
}
|
||||
|
||||
return exit_code == 0;
|
||||
}
|
||||
|
||||
bool Runner::run(const nlohmann::json& run_json, std::string& output) {
|
||||
std::string command;
|
||||
std::vector<std::string> args;
|
||||
std::string working_dir;
|
||||
std::map<std::string, std::string> env;
|
||||
bool silent, interactive;
|
||||
|
||||
if (!parse_json(run_json, command, args, working_dir, env, silent, interactive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int exit_code;
|
||||
if (run_json.contains("ssh")) {
|
||||
exit_code = execute_ssh(run_json["ssh"], command, args, working_dir, env, silent, interactive, &output, true);
|
||||
} else {
|
||||
exit_code = execute_local(command, args, working_dir, env, silent, interactive, &output, true);
|
||||
}
|
||||
|
||||
return exit_code == 0;
|
||||
}
|
||||
|
||||
bool Runner::parse_json(
|
||||
const nlohmann::json& run_json,
|
||||
std::string& command,
|
||||
std::vector<std::string>& args,
|
||||
std::string& working_dir,
|
||||
std::map<std::string, std::string>& env,
|
||||
bool& silent,
|
||||
bool& interactive
|
||||
) {
|
||||
try {
|
||||
// Command is required
|
||||
if (!run_json.contains("command") || !run_json["command"].is_string()) {
|
||||
std::cerr << "Error: 'command' field is required and must be a string" << std::endl;
|
||||
return false;
|
||||
}
|
||||
command = run_json["command"];
|
||||
|
||||
// Args are optional
|
||||
args.clear();
|
||||
if (run_json.contains("args")) {
|
||||
if (!run_json["args"].is_array()) {
|
||||
std::cerr << "Error: 'args' field must be an array" << std::endl;
|
||||
return false;
|
||||
}
|
||||
for (const auto& arg : run_json["args"]) {
|
||||
if (!arg.is_string()) {
|
||||
std::cerr << "Error: All arguments must be strings" << std::endl;
|
||||
return false;
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Working directory is optional
|
||||
working_dir = "";
|
||||
if (run_json.contains("working_directory")) {
|
||||
if (!run_json["working_directory"].is_string()) {
|
||||
std::cerr << "Error: 'working_directory' field must be a string" << std::endl;
|
||||
return false;
|
||||
}
|
||||
working_dir = run_json["working_directory"];
|
||||
}
|
||||
|
||||
// Environment variables are optional
|
||||
env.clear();
|
||||
if (run_json.contains("env")) {
|
||||
if (!run_json["env"].is_object()) {
|
||||
std::cerr << "Error: 'env' field must be an object" << std::endl;
|
||||
return false;
|
||||
}
|
||||
for (auto it = run_json["env"].begin(); it != run_json["env"].end(); ++it) {
|
||||
if (!it.value().is_string()) {
|
||||
std::cerr << "Error: All environment variable values must be strings" << std::endl;
|
||||
return false;
|
||||
}
|
||||
env[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
// Options are optional
|
||||
silent = false;
|
||||
interactive = false;
|
||||
if (run_json.contains("options")) {
|
||||
if (!run_json["options"].is_object()) {
|
||||
std::cerr << "Error: 'options' field must be an object" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (run_json["options"].contains("silent")) {
|
||||
if (!run_json["options"]["silent"].is_boolean()) {
|
||||
std::cerr << "Error: 'silent' option must be a boolean" << std::endl;
|
||||
return false;
|
||||
}
|
||||
silent = run_json["options"]["silent"];
|
||||
}
|
||||
|
||||
if (run_json["options"].contains("interactive")) {
|
||||
if (!run_json["options"]["interactive"].is_boolean()) {
|
||||
std::cerr << "Error: 'interactive' option must be a boolean" << std::endl;
|
||||
return false;
|
||||
}
|
||||
interactive = run_json["options"]["interactive"];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int Runner::execute_local(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output,
|
||||
bool capture_output
|
||||
) {
|
||||
int pipefd[2];
|
||||
if (capture_output && pipe(pipefd) == -1) {
|
||||
std::cerr << "Error creating pipe: " << strerror(errno) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
std::cerr << "Fork failed: " << strerror(errno) << std::endl;
|
||||
if (capture_output) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
|
||||
// Set up output redirection if needed
|
||||
if (capture_output) {
|
||||
close(pipefd[0]); // Close read end
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
} else if (silent) {
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
}
|
||||
|
||||
// Change to working directory if specified
|
||||
if (!working_dir.empty()) {
|
||||
if (chdir(working_dir.c_str()) != 0) {
|
||||
std::cerr << "Error changing to directory " << working_dir << ": " << strerror(errno) << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
for (const auto& [key, value] : env) {
|
||||
setenv(key.c_str(), value.c_str(), 1);
|
||||
}
|
||||
|
||||
// Prepare arguments
|
||||
std::vector<char*> c_args;
|
||||
c_args.push_back(const_cast<char*>(command.c_str()));
|
||||
for (const auto& arg : args) {
|
||||
c_args.push_back(const_cast<char*>(arg.c_str()));
|
||||
}
|
||||
c_args.push_back(nullptr);
|
||||
|
||||
// Execute command
|
||||
execvp(command.c_str(), c_args.data());
|
||||
|
||||
// If exec fails
|
||||
std::cerr << "exec failed: " << strerror(errno) << std::endl;
|
||||
exit(1);
|
||||
} else {
|
||||
// Parent process
|
||||
if (capture_output) {
|
||||
close(pipefd[1]); // Close write end
|
||||
|
||||
std::array<char, 4096> buffer;
|
||||
std::ostringstream oss;
|
||||
|
||||
ssize_t bytes_read;
|
||||
while ((bytes_read = read(pipefd[0], buffer.data(), buffer.size() - 1)) > 0) {
|
||||
buffer[bytes_read] = '\0';
|
||||
oss << buffer.data();
|
||||
|
||||
if (!silent) {
|
||||
std::cout << buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
close(pipefd[0]);
|
||||
|
||||
if (output) {
|
||||
*output = oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string find_ssh_key_for_user() {
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (!home_dir) {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) {
|
||||
home_dir = pw->pw_dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (!home_dir) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Common SSH key locations
|
||||
std::vector<std::string> key_paths = {
|
||||
std::string(home_dir) + "/.ssh/id_rsa",
|
||||
std::string(home_dir) + "/.ssh/id_ed25519",
|
||||
std::string(home_dir) + "/.ssh/id_ecdsa",
|
||||
std::string(home_dir) + "/.ssh/id_dsa"
|
||||
};
|
||||
|
||||
for (const auto& path : key_paths) {
|
||||
if (access(path.c_str(), F_OK) == 0) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
int Runner::execute_ssh(
|
||||
const nlohmann::json& ssh_config,
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::string& working_dir,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output,
|
||||
bool capture_output
|
||||
) {
|
||||
if (!ssh_config.contains("host") || !ssh_config["host"].is_string()) {
|
||||
std::cerr << "Error: SSH configuration requires 'host' field" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
std::string host = ssh_config["host"];
|
||||
|
||||
int port = 22;
|
||||
if (ssh_config.contains("port")) {
|
||||
if (ssh_config["port"].is_number_integer()) {
|
||||
port = ssh_config["port"];
|
||||
} else {
|
||||
std::cerr << "Error: SSH 'port' must be an integer" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string user = "";
|
||||
if (ssh_config.contains("user") && ssh_config["user"].is_string()) {
|
||||
user = ssh_config["user"];
|
||||
} else {
|
||||
// Get current username as default
|
||||
char username[256];
|
||||
if (getlogin_r(username, sizeof(username)) == 0) {
|
||||
user = username;
|
||||
} else {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) {
|
||||
user = pw->pw_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string key_path = "";
|
||||
if (ssh_config.contains("key") && ssh_config["key"].is_string()) {
|
||||
std::string key = ssh_config["key"];
|
||||
if (key == "auto") {
|
||||
key_path = find_ssh_key_for_user();
|
||||
if (key_path.empty()) {
|
||||
std::cerr << "Error: Could not find SSH key automatically" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
key_path = key;
|
||||
}
|
||||
} else {
|
||||
key_path = find_ssh_key_for_user();
|
||||
}
|
||||
|
||||
// Initialize SSH session
|
||||
ssh_session session = ssh_new();
|
||||
if (session == nullptr) {
|
||||
std::cerr << "Error: Failed to create SSH session" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set SSH options
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, host.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, user.c_str());
|
||||
|
||||
// Set pseudo-terminal flag (equivalent to ssh -tt)
|
||||
int flag = 1;
|
||||
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &flag);
|
||||
|
||||
// Connect to server
|
||||
int rc = ssh_connect(session);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error connecting to " << host << ": " << ssh_get_error(session) << std::endl;
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Authenticate with key
|
||||
if (!key_path.empty()) {
|
||||
rc = ssh_userauth_publickey_auto(session, nullptr, key_path.empty() ? nullptr : key_path.c_str());
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
std::cerr << "Error authenticating with key: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Try default authentication methods
|
||||
rc = ssh_userauth_publickey_auto(session, nullptr, nullptr);
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
std::cerr << "Error authenticating: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare command
|
||||
std::ostringstream cmd_stream;
|
||||
|
||||
// Add environment variables
|
||||
for (const auto& [key, value] : env) {
|
||||
cmd_stream << "export " << key << "=\"" << value << "\"; ";
|
||||
}
|
||||
|
||||
// Add cd command if working_directory is specified
|
||||
if (!working_dir.empty()) {
|
||||
cmd_stream << "cd \"" << working_dir << "\" && ";
|
||||
}
|
||||
|
||||
// Add command and args
|
||||
cmd_stream << command;
|
||||
for (const auto& arg : args) {
|
||||
cmd_stream << " " << arg;
|
||||
}
|
||||
|
||||
std::string full_command = cmd_stream.str();
|
||||
|
||||
int exit_status = -1;
|
||||
ssh_channel channel = ssh_channel_new(session);
|
||||
if (channel == nullptr) {
|
||||
std::cerr << "Error creating SSH channel: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error opening SSH session: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
// Request a pseudo-terminal for interactive commands with specific term type
|
||||
const char* term_type = "xterm-256color";
|
||||
|
||||
// Force PTY allocation first with basic settings
|
||||
rc = ssh_channel_request_pty(channel);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error requesting basic PTY: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Then set the size and term type
|
||||
rc = ssh_channel_request_pty_size(channel, term_type, 80, 24);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Warning: Could not set PTY size, but continuing anyway" << std::endl;
|
||||
}
|
||||
|
||||
// Set up terminal mode to be more raw (important for full-screen apps)
|
||||
if (env.find("TERM") == env.end()) {
|
||||
// Add TERM environment if not present
|
||||
cmd_stream << "export TERM=\"" << term_type << "\"; ";
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
rc = ssh_channel_request_exec(channel, full_command.c_str());
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error executing command: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read command output
|
||||
char buffer[4096];
|
||||
int nbytes;
|
||||
std::ostringstream oss;
|
||||
|
||||
// Read from stdout
|
||||
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
|
||||
if (capture_output) {
|
||||
oss.write(buffer, nbytes);
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
std::cout.write(buffer, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Read from stderr if needed
|
||||
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
|
||||
if (capture_output) {
|
||||
oss.write(buffer, nbytes);
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
std::cerr.write(buffer, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (capture_output && output) {
|
||||
*output = oss.str();
|
||||
}
|
||||
|
||||
// Get exit status
|
||||
ssh_channel_send_eof(channel);
|
||||
exit_status = ssh_channel_get_exit_status(channel);
|
||||
|
||||
// Clean up
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
148
runner/test.sh
148
runner/test.sh
@ -1,148 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Simple test script to verify that the runner works properly
|
||||
|
||||
echo "Testing Runner - Dropshell Command Execution Library"
|
||||
echo "=================================================="
|
||||
echo
|
||||
|
||||
# Ensure the binary is built
|
||||
if [ ! -f "build/runner" ]; then
|
||||
echo "Building the project first..."
|
||||
./build.sh
|
||||
fi
|
||||
|
||||
# Run a simple echo command
|
||||
echo "Test 1: Basic command execution"
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"echo","args":["Hello from runner test!"]}
|
||||
EOF
|
||||
)
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command..."
|
||||
build/runner "$BASE64"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Test 1 passed"
|
||||
else
|
||||
echo "❌ Test 1 failed"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test with environment variables
|
||||
echo "Test 2: Environment variables"
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"bash","args":["-c","echo Value of TEST_VAR: $TEST_VAR"],"env":{"TEST_VAR":"This is a test environment variable"}}
|
||||
EOF
|
||||
)
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command..."
|
||||
build/runner "$BASE64"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Test 2 passed"
|
||||
else
|
||||
echo "❌ Test 2 failed"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test silent mode
|
||||
echo "Test 3: Silent mode"
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"echo","args":["This should not be displayed"],"options":{"silent":true}}
|
||||
EOF
|
||||
)
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command (no output expected)..."
|
||||
OUTPUT=$(build/runner "$BASE64")
|
||||
if [ $? -eq 0 ] && [ -z "$OUTPUT" ]; then
|
||||
echo "✅ Test 3 passed"
|
||||
else
|
||||
echo "❌ Test 3 failed, unexpected output: '$OUTPUT'"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test return code handling
|
||||
echo "Test 4: Error return code"
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"false"}
|
||||
EOF
|
||||
)
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command (should fail)..."
|
||||
set +e # Temporarily disable exiting on error
|
||||
build/runner "$BASE64"
|
||||
RC=$?
|
||||
set -e # Re-enable exiting on error
|
||||
if [ $RC -ne 0 ]; then
|
||||
echo "✅ Test 4 passed (command correctly reported failure, exit code: $RC)"
|
||||
else
|
||||
echo "❌ Test 4 failed (command should have returned non-zero)"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test working directory feature
|
||||
echo "Test 5: Working directory"
|
||||
JSON=$(cat <<'EOF'
|
||||
{"command":"pwd","working_directory":"/tmp"}
|
||||
EOF
|
||||
)
|
||||
BASE64=$(echo -n "$JSON" | base64 -w0)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command (should show /tmp)..."
|
||||
OUTPUT=$(build/runner "$BASE64")
|
||||
if [ $? -eq 0 ] && [[ "$OUTPUT" == "/tmp"* ]]; then
|
||||
echo "✅ Test 5 passed (command ran in the correct directory)"
|
||||
else
|
||||
echo "❌ Test 5 failed, unexpected output: '$OUTPUT'"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Optional SSH test (disabled by default)
|
||||
echo "Test 6: SSH to localhost (make sure SSH server is running and you can connect to localhost)"
|
||||
if [ -f "examples/local_ssh.json" ]; then
|
||||
echo "Using examples/local_ssh.json for the test..."
|
||||
./run.sh examples/local_ssh.json || {
|
||||
echo "❌ Test 6 failed"
|
||||
echo "Note: This test requires SSH server running on localhost and proper key-based authentication."
|
||||
echo "Check your SSH configuration or disable this test."
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Test 6 passed"
|
||||
else
|
||||
echo "❌ Test 6 failed: examples/local_ssh.json not found"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "Test 7: SSH with working directory (make sure SSH server is running and you can connect to localhost)"
|
||||
if [ -f "examples/ssh_working_dir.json" ]; then
|
||||
echo "Using examples/ssh_working_dir.json for the test..."
|
||||
OUTPUT=$(./run.sh examples/ssh_working_dir.json | grep "/tmp")
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Test 7 failed, command did not run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$OUTPUT" == *"/tmp"* ]]; then
|
||||
echo "✅ Test 7 passed (SSH command ran in the correct directory)"
|
||||
else
|
||||
echo "❌ Test 7 failed, directory was not changed correctly"
|
||||
echo "Note: This test requires SSH server running on localhost and proper key-based authentication."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Test 7 failed: examples/ssh_working_dir.json not found"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "All tests passed! 🎉"
|
Loading…
x
Reference in New Issue
Block a user