Update source/src/templates.cpp
This commit is contained in:
@@ -493,6 +493,25 @@
|
|||||||
return source->template_command_exists(template_name, command);
|
return source->template_command_exists(template_name, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to write a file with content
|
||||||
|
static bool write_template_file(const std::string& path, const std::string& content, bool executable = false) {
|
||||||
|
std::ofstream file(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
error << "Failed to create file: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file << content;
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (executable) {
|
||||||
|
std::filesystem::permissions(path,
|
||||||
|
std::filesystem::perms::owner_all |
|
||||||
|
std::filesystem::perms::group_read | std::filesystem::perms::group_exec |
|
||||||
|
std::filesystem::perms::others_read | std::filesystem::perms::others_exec);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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)) {
|
if (!legal_service_name(template_name)) {
|
||||||
@@ -500,21 +519,14 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Create a new directory in the user templates directory
|
// 1. Check template doesn't already exist
|
||||||
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
|
|
||||||
|
|
||||||
if (local_server_definition_paths.empty()) {
|
|
||||||
error << "No local server definition paths found" << std::endl;
|
|
||||||
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto tinfo = get_template_info(template_name, false);
|
auto tinfo = get_template_info(template_name, false);
|
||||||
if (tinfo.is_set()) {
|
if (tinfo.is_set()) {
|
||||||
error << "Template '" << template_name << "' already exists at " << tinfo.locationID() << std::endl;
|
error << "Template '" << template_name << "' already exists at " << tinfo.locationID() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Determine where to create the template
|
||||||
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;
|
error << "No local template paths found" << std::endl;
|
||||||
@@ -523,57 +535,199 @@
|
|||||||
}
|
}
|
||||||
std::string new_template_path = local_template_paths[0] + "/" + template_name;
|
std::string new_template_path = local_template_paths[0] + "/" + template_name;
|
||||||
|
|
||||||
// Create the new template directory
|
// 3. Create directory structure
|
||||||
std::filesystem::create_directories(new_template_path);
|
std::filesystem::create_directories(new_template_path + "/config");
|
||||||
|
|
||||||
// 2. Copy the example template from the system templates directory
|
// 4. Generate template files inline (self-contained, no external dependencies)
|
||||||
auto example_info = gTemplateManager().get_template_info("example-nginx");
|
|
||||||
if (!example_info.is_set()) {
|
|
||||||
error << "Example template not found" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::string example_template_path = example_info.local_template_path();
|
|
||||||
|
|
||||||
// Copy all files from example template to new template
|
// config/.template_info.env
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(example_template_path)) {
|
std::string template_info_env = R"TMPL(# Template identifier - MUST match the directory name
|
||||||
std::string relative_path = entry.path().string().substr(example_template_path.length());
|
TEMPLATE=)TMPL" + template_name + R"TMPL(
|
||||||
std::string target_path = new_template_path + relative_path;
|
|
||||||
|
|
||||||
if (entry.is_directory()) {
|
# Requirements
|
||||||
std::filesystem::create_directory(target_path);
|
REQUIRES_HOST_ROOT=false
|
||||||
} else {
|
REQUIRES_DOCKER=true
|
||||||
std::filesystem::copy_file(entry.path(), target_path);
|
REQUIRES_DOCKER_ROOT=false
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name>
|
# Docker image settings
|
||||||
std::string search_string = "TEMPLATE=";
|
IMAGE_REGISTRY="docker.io"
|
||||||
std::string replacement_line = "TEMPLATE=" + template_name;
|
IMAGE_REPO="nginx"
|
||||||
std::string service_env_path = new_template_path + "/config/" + filenames::template_info_env;
|
IMAGE_TAG="alpine"
|
||||||
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;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Print out the README.txt file and the path
|
# Volume definitions
|
||||||
std::string readme_path = new_template_path + "/README.txt";
|
DATA_VOLUME="${CONTAINER_NAME}_data"
|
||||||
if (std::filesystem::exists(readme_path)) {
|
)TMPL";
|
||||||
std::cout << "\nREADME contents:" << std::endl;
|
if (!write_template_file(new_template_path + "/config/" + filenames::template_info_env, template_info_env)) return false;
|
||||||
|
|
||||||
|
// config/service.env
|
||||||
|
std::string service_env = R"TMPL(# Service identification (REQUIRED)
|
||||||
|
CONTAINER_NAME=)TMPL" + template_name + R"TMPL(
|
||||||
|
|
||||||
|
# Server settings (REQUIRED by dropshell)
|
||||||
|
SSH_USER="root"
|
||||||
|
|
||||||
|
# Service-specific settings
|
||||||
|
HTTP_PORT=8080
|
||||||
|
)TMPL";
|
||||||
|
if (!write_template_file(new_template_path + "/config/" + filenames::service_env, service_env)) return false;
|
||||||
|
|
||||||
|
// install.sh
|
||||||
|
std::string install_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
_check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG"
|
||||||
|
|
||||||
|
# Check Docker is available
|
||||||
|
_check_docker_installed || _die "Docker test failed"
|
||||||
|
|
||||||
|
# Pull the Docker image
|
||||||
|
docker pull "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || _die "Failed to pull image"
|
||||||
|
|
||||||
|
# Stop any existing container
|
||||||
|
bash "$SCRIPT_DIR/stop.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove old container
|
||||||
|
_remove_container "$CONTAINER_NAME" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start the new container
|
||||||
|
bash "$SCRIPT_DIR/start.sh" || _die "Failed to start container"
|
||||||
|
|
||||||
|
echo "Installation of ${CONTAINER_NAME} complete"
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/install.sh", install_sh, true)) return false;
|
||||||
|
|
||||||
|
// uninstall.sh
|
||||||
|
std::string uninstall_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
_check_required_env_vars "CONTAINER_NAME"
|
||||||
|
|
||||||
|
# Stop the container
|
||||||
|
bash "$SCRIPT_DIR/stop.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove the container (but preserve data volumes!)
|
||||||
|
_remove_container "$CONTAINER_NAME" || _die "Failed to remove container"
|
||||||
|
|
||||||
|
# CRITICAL: Never remove data volumes in uninstall.sh!
|
||||||
|
# Data volumes must be preserved for potential reinstallation
|
||||||
|
# Only destroy.sh should remove volumes
|
||||||
|
|
||||||
|
echo "Uninstallation of ${CONTAINER_NAME} complete"
|
||||||
|
echo "Note: Data volumes have been preserved. To remove all data, use destroy.sh"
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/uninstall.sh", uninstall_sh, true)) return false;
|
||||||
|
|
||||||
|
// start.sh
|
||||||
|
std::string start_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
|
||||||
|
_check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "HTTP_PORT"
|
||||||
|
|
||||||
|
# Create data volume if it doesn't exist
|
||||||
|
docker volume create "$DATA_VOLUME" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start the container
|
||||||
|
docker run -d \
|
||||||
|
--name "$CONTAINER_NAME" \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p "${HTTP_PORT}:80" \
|
||||||
|
-v "${DATA_VOLUME}:/usr/share/nginx/html" \
|
||||||
|
"$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || _die "Failed to start container"
|
||||||
|
|
||||||
|
echo "Container ${CONTAINER_NAME} started on port ${HTTP_PORT}"
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/start.sh", start_sh, true)) return false;
|
||||||
|
|
||||||
|
// stop.sh
|
||||||
|
std::string stop_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
|
||||||
|
_check_required_env_vars "CONTAINER_NAME"
|
||||||
|
|
||||||
|
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Container ${CONTAINER_NAME} stopped"
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/stop.sh", stop_sh, true)) return false;
|
||||||
|
|
||||||
|
// status.sh (REQUIRED for dropshell list command)
|
||||||
|
std::string status_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
|
||||||
|
_check_required_env_vars "CONTAINER_NAME"
|
||||||
|
|
||||||
|
# Check if container is running
|
||||||
|
if docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
echo "Running"
|
||||||
|
else
|
||||||
|
echo "Stopped"
|
||||||
|
fi
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/status.sh", status_sh, true)) return false;
|
||||||
|
|
||||||
|
// logs.sh
|
||||||
|
std::string logs_sh = R"BASH(#!/bin/bash
|
||||||
|
source "${AGENT_PATH}/common.sh"
|
||||||
|
|
||||||
|
_check_required_env_vars "CONTAINER_NAME"
|
||||||
|
|
||||||
|
docker logs "$CONTAINER_NAME" "$@"
|
||||||
|
)BASH";
|
||||||
|
if (!write_template_file(new_template_path + "/logs.sh", logs_sh, true)) return false;
|
||||||
|
|
||||||
|
// README.txt
|
||||||
|
std::string readme = "Template: " + template_name + R"TMPL(
|
||||||
|
|
||||||
|
This template was created by 'dropshell create-template'.
|
||||||
|
|
||||||
|
QUICK START
|
||||||
|
-----------
|
||||||
|
1. Edit config/service.env to customize your deployment
|
||||||
|
2. Edit config/.template_info.env if you need different Docker settings
|
||||||
|
3. Modify the scripts as needed for your use case
|
||||||
|
4. Run 'dropshell validate <path>' to check for issues
|
||||||
|
|
||||||
|
REQUIRED FILES
|
||||||
|
--------------
|
||||||
|
- config/.template_info.env : Template metadata (don't change TEMPLATE=)
|
||||||
|
- config/service.env : Service configuration (edit this!)
|
||||||
|
- install.sh : Installation script
|
||||||
|
- uninstall.sh : Uninstallation script (preserves data)
|
||||||
|
- status.sh : Status check (required for 'dropshell list')
|
||||||
|
|
||||||
|
OPTIONAL FILES
|
||||||
|
--------------
|
||||||
|
- start.sh : Start the service
|
||||||
|
- stop.sh : Stop the service
|
||||||
|
- logs.sh : View logs
|
||||||
|
- backup.sh : Backup data
|
||||||
|
- restore.sh : Restore data
|
||||||
|
- destroy.sh : Remove service AND data (use with caution!)
|
||||||
|
|
||||||
|
BEST PRACTICES
|
||||||
|
--------------
|
||||||
|
1. Always source common.sh: source "${AGENT_PATH}/common.sh"
|
||||||
|
2. Check required vars: _check_required_env_vars "VAR1" "VAR2"
|
||||||
|
3. Handle errors: command || _die "Error message"
|
||||||
|
4. NEVER remove data volumes in uninstall.sh
|
||||||
|
5. Run 'dropshell validate' before publishing
|
||||||
|
|
||||||
|
For full documentation, see: dropshell help templates
|
||||||
|
)TMPL";
|
||||||
|
if (!write_template_file(new_template_path + "/README.txt", readme)) return false;
|
||||||
|
|
||||||
|
// 5. Print summary
|
||||||
|
std::cout << "\nTemplate '" << template_name << "' created at " << new_template_path << std::endl;
|
||||||
|
std::cout << std::string(60, '-') << std::endl;
|
||||||
|
std::cout << "Next steps:" << std::endl;
|
||||||
|
std::cout << " 1. Edit " << new_template_path << "/config/service.env" << std::endl;
|
||||||
|
std::cout << " 2. Customize the scripts for your application" << std::endl;
|
||||||
|
std::cout << " 3. Run: dropshell validate " << new_template_path << std::endl;
|
||||||
std::cout << std::string(60, '-') << std::endl;
|
std::cout << std::string(60, '-') << std::endl;
|
||||||
|
|
||||||
std::ifstream readme_file(readme_path);
|
|
||||||
if (readme_file.is_open()) {
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(readme_file, line)) {
|
|
||||||
std::cout << line << std::endl;
|
|
||||||
}
|
|
||||||
readme_file.close();
|
|
||||||
}
|
|
||||||
std::cout << std::string(60, '-') << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << std::endl;
|
|
||||||
std::cout << "Template '" << template_name << "' created at " << new_template_path << std::endl;
|
|
||||||
return test_template(new_template_path);
|
return test_template(new_template_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user