diff --git a/templates/simple-object-storage/README.txt b/templates/simple-object-storage/README.txt new file mode 100644 index 0000000..7041ee9 --- /dev/null +++ b/templates/simple-object-storage/README.txt @@ -0,0 +1 @@ +simple-object-storage diff --git a/templates/simple-object-storage/_common.sh b/templates/simple-object-storage/_common.sh new file mode 100644 index 0000000..8c9d4e1 --- /dev/null +++ b/templates/simple-object-storage/_common.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +# COMMON FUNCTIONS +# JDE +# 2025-04-25 + +# This file is not required if you write your own template. + + +# Print error message and exit with code 1 +# Usage: die "error message" +die() { + echo -e "\033[91mError: $1\033[0m" + exit 1 +} + +grey_start() { + echo -e -n "\033[90m" +} + +grey_end() { + echo -e -n "\033[0m" +} + +create_and_start_container() { + if [ -z "$1" ] || [ -z "$2" ]; then + die "Template error: create_and_start_container " + fi + + local run_cmd="$1" + local container_name="$2" + + if _is_container_exists $container_name; then + _is_container_running $container_name && return 0 + _start_container $container_name + else + grey_start + $run_cmd + grey_end + fi + + if ! _is_container_running $container_name; then + die "Container ${container_name} failed to start" + fi + + ID=$(_get_container_id $container_name) + echo "Container ${container_name} is running with ID ${ID}" +} + +function create_folder() { + local folder="$1" + if [ -d "$folder" ]; then + return 0 + fi + if ! mkdir -p "$folder"; then + die "Failed to create folder: $folder" + fi + chmod 777 "$folder" + echo "Folder created: $folder" +} + +# Check if docker is installed +_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 +} + +# Check if a container exists +_is_container_exists() { + if ! docker ps -a --format "{{.Names}}" | grep -q "^$1$"; then + return 1 + fi + return 0 +} + +# Check if a container is running +_is_container_running() { + if ! docker ps --format "{{.Names}}" | grep -q "^$1$"; then + return 1 + fi + return 0 +} + +# get contianer ID +_get_container_id() { + docker ps --format "{{.ID}}" --filter "name=$1" +} + +# get container status +_get_container_status() { + docker ps --format "{{.Status}}" --filter "name=$1" +} + +# start container that exists +_start_container() { + _is_container_exists $1 || return 1 + _is_container_running $1 && return 0 + docker start $1 +} + +# stop container that exists +_stop_container() { + _is_container_running $1 || return 0; + docker stop $1 +} + +# remove container that exists +_remove_container() { + _stop_container $1 + _is_container_exists $1 || return 0; + docker rm $1 +} + +# get container logs +_get_container_logs() { + if ! _is_container_exists $1; then + echo "Container $1 does not exist" + return 1 + fi + + docker logs $1 +} + +check_required_env_vars() { + local required_vars=("$@") + for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + die "Required environment variable $var is not set in your service.env file" + fi + done +} + +function _root_remove_tree() { + local to_remove="$1" + parent=$(dirname "$to_remove") + abs_parent=$(realpath "$parent") + child=$(basename "$to_remove") + docker run --rm -v "$abs_parent":/data alpine rm -rf "/data/$child" +} diff --git a/templates/simple-object-storage/_default.env b/templates/simple-object-storage/_default.env new file mode 100644 index 0000000..8b9198b --- /dev/null +++ b/templates/simple-object-storage/_default.env @@ -0,0 +1,12 @@ +# Service settings specific to this server + +# Image settings +IMAGE_REGISTRY="gitea.jde.nz" +IMAGE_REPO="simple-object-storage" +IMAGE_TAG="latest" + +# Container settings +CONTAINER_NAME="simple-object-storage" + +# Volume settings +VOLUME_NAME="simple-object-storage" diff --git a/templates/simple-object-storage/backup.sh b/templates/simple-object-storage/backup.sh new file mode 100644 index 0000000..dec0092 --- /dev/null +++ b/templates/simple-object-storage/backup.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# BACKUP SCRIPT +# The backup script is OPTIONAL. +# It is used to backup the service on the server. +# It is called with one argument: the path to the destination backup file. +# If the backup file already exists, the script should exit with a message. + +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" "VOLUME_NAME" + + +# HOT backup is fine for simple-object-storage + + +# Get backup file path from first argument +BACKUP_FILE="$1" +if [ -z "$BACKUP_FILE" ]; then + die "Backup file path not provided" +fi + +TEMP_DIR=$2 +if [ -z "$TEMP_DIR" ]; then + die "Temporary directory not provided" +fi + +# Check if backup file already exists +if [ -f "$BACKUP_FILE" ]; then + die "Backup file $BACKUP_FILE already exists" +fi + +# Create backup of data folder +echo "Creating backup of $VOLUME_NAME..." + +docker run --rm -v ${VOLUME_NAME}:/data -v ${TEMP_DIR}:/tempdir alpine sh -c "\ +tar zcvf /tempdir/backup.tar.gz -C /data ." + +cp ${TEMP_DIR}/backup.tar.gz $BACKUP_FILE +# dropshell cleans up temp dir after script finishes + + +echo "Backup created successfully: $BACKUP_FILE" diff --git a/templates/simple-object-storage/example/.template_info.env b/templates/simple-object-storage/example/.template_info.env new file mode 100644 index 0000000..1c2ba83 --- /dev/null +++ b/templates/simple-object-storage/example/.template_info.env @@ -0,0 +1,2 @@ +# Template to use - always required! +TEMPLATE=simple-object-storage diff --git a/templates/simple-object-storage/example/service.env b/templates/simple-object-storage/example/service.env new file mode 100644 index 0000000..229c30f --- /dev/null +++ b/templates/simple-object-storage/example/service.env @@ -0,0 +1,7 @@ +HOST_PORT=8123 + +CONTAINER_NAME="simple-object-storage" + +VOLUME_NAME="simple-object-storage" + +WRITE_TOKENS="fizzle1,fizzle2,fizzle3" diff --git a/templates/simple-object-storage/install.sh b/templates/simple-object-storage/install.sh new file mode 100644 index 0000000..ef1c97b --- /dev/null +++ b/templates/simple-object-storage/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# INSTALL SCRIPT +# The install script is required for all templates. +# It is used to install the service on the server. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" + +# Required environment variables +check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "VOLUME_NAME" + +# Create volume if it doesn't exist +if ! docker volume ls | grep -q "^${VOLUME_NAME} "; then + echo "Volume ${VOLUME_NAME} does not exist, creating..." + docker volume create "${VOLUME_NAME}" +fi + + +# Test Docker +_check_docker_installed || die "Docker test failed, aborting installation..." + + +# check can pull image on remote host and exit if fails +docker pull "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || die "Failed to pull image $IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" + +# remove and restart, as the env may have changed. +bash ./stop.sh || die "Failed to stop container ${CONTAINER_NAME}" +_remove_container $CONTAINER_NAME || die "Failed to remove container ${CONTAINER_NAME}" +bash ./start.sh || die "Failed to start container ${CONTAINER_NAME}" + +echo "Installation of ${CONTAINER_NAME} complete" +echo "You can access the service at http://${SERVER}:${HOST_PORT}" diff --git a/templates/simple-object-storage/logs.sh b/templates/simple-object-storage/logs.sh new file mode 100644 index 0000000..209b57b --- /dev/null +++ b/templates/simple-object-storage/logs.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# LOGS SCRIPT +# The logs script is OPTIONAL. +# It is used to return the logs of the service. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" + +# Required environment variables +check_required_env_vars "CONTAINER_NAME" + +echo "Container ${CONTAINER_NAME} logs:" +grey_start +docker logs "${CONTAINER_NAME}" +grey_end diff --git a/templates/simple-object-storage/ports.sh b/templates/simple-object-storage/ports.sh new file mode 100644 index 0000000..e88f668 --- /dev/null +++ b/templates/simple-object-storage/ports.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# PORT SCRIPT +# The port script is OPTIONAL. +# It is used to return the ports used by the service. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" + +# Required environment variables +# check_required_env_vars "HOST_PORT" + +echo $HOST_PORT diff --git a/templates/simple-object-storage/restore.sh b/templates/simple-object-storage/restore.sh new file mode 100644 index 0000000..1c6c4b8 --- /dev/null +++ b/templates/simple-object-storage/restore.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# RESTORE SCRIPT +# The restore script is OPTIONAL. +# It is used to restore the service on the server from a backup file. +# It is called with one argument: the path to the backup file. + +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" "VOLUME_NAME" + +# Get backup file path from first argument +BACKUP_FILE="$1" +if [ -z "$BACKUP_FILE" ]; then + die "Backup file path not provided" +fi + +TEMP_DIR=$2 +if [ -z "$TEMP_DIR" ]; then + die "Temporary directory not provided" +fi + +# Check if backup file already exists +if [ ! -f "$BACKUP_FILE" ]; then + die "Backup file $BACKUP_FILE does not exist" +fi + +# # Stop container before backup +bash ./uninstall.sh || die "Failed to uninstall service before restore" + +# Remove existing data folder +echo "Deleting ALL data in $VOLUME_NAME." + +docker run --rm -v ${VOLUME_NAME}:/data alpine sh -c "\ +rm -rf /data/*" + +echo "Restoring data from $BACKUP_FILE to $VOLUME_NAME." +docker run --rm \ + -v ${VOLUME_NAME}:/data \ + -v ${BACKUP_FILE}:/backup.tar.gz \ + alpine sh -c "\ + tar xzvf /backup.tar.gz -C /data --strip-components=1" + +# reinstall service - ensure everything is latest. +bash ./install.sh || die "Failed to reinstall service after restore" + +echo "Restore complete! Service is running again on port $HOST_PORT with restored content." diff --git a/templates/simple-object-storage/start.sh b/templates/simple-object-storage/start.sh new file mode 100644 index 0000000..9582fb6 --- /dev/null +++ b/templates/simple-object-storage/start.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# START SCRIPT +# The start script is required for all templates. +# It is used to start the service on the server. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" "HOST_PORT" "VOLUME_NAME" + +# check volume exists. +if ! docker volume ls | grep -q "^${VOLUME_NAME} "; then + die "Docker volume ${VOLUME_NAME} does not exist" +fi + +# heredoc for config file. Have to use double quotes to substitute variables. +docker run --rm -v ${VOLUME_NAME}:/data alpine sh -c "\ +cat < /data/sos_config.json +{ + \"write_tokens\": [ + ${WRITE_TOKENS} + ], + \"object_store_path\": \"/data/storage\", + \"host\": \"0.0.0.0\", + \"port\": ${HOST_PORT} +} +EOF +" + +DOCKER_RUN_CMD="docker run -d \ + --restart unless-stopped \ + --name ${CONTAINER_NAME} \ + -p ${HOST_PORT}:80 \ + -v ${VOLUME_NAME}:/data \ + ${IMAGE_REGISTRY}/${IMAGE_REPO}:${IMAGE_TAG}" + + +if ! create_and_start_container "$DOCKER_RUN_CMD" "$CONTAINER_NAME"; then + die "Failed to start container ${CONTAINER_NAME}" +fi + +# Check if the container is running +if ! _is_container_running "$CONTAINER_NAME"; then + die "Container ${CONTAINER_NAME} is not running" +fi + +echo "Container ${CONTAINER_NAME} started" diff --git a/templates/simple-object-storage/status.sh b/templates/simple-object-storage/status.sh new file mode 100644 index 0000000..5018f97 --- /dev/null +++ b/templates/simple-object-storage/status.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# STATUS SCRIPT +# The status script is OPTIONAL. +# It is used to return the status of the service (0 is healthy, 1 is unhealthy). +# It is called with the path to the server specific env file as an argument. + + +# This is an example of a status script that checks if the service is running. +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" + +# check if the service is running +_is_container_running $CONTAINER_NAME || die "Service is not running - did not find container $CONTAINER_NAME." + +# check if the service is healthy +curl -s -X GET http://localhost:${HOST_PORT}/status | jq -e '.result == "success"' \ + || die "Service is not healthy - did not get OK response from /status endpoint." + +echo "Service is healthy" +exit 0 diff --git a/templates/simple-object-storage/stop.sh b/templates/simple-object-storage/stop.sh new file mode 100644 index 0000000..e1cc8e9 --- /dev/null +++ b/templates/simple-object-storage/stop.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# STOP SCRIPT +# The stop script is required for all templates. +# It is used to stop the service on the server. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" + +_stop_container $CONTAINER_NAME || die "Failed to stop container ${CONTAINER_NAME}" + +echo "Container ${CONTAINER_NAME} stopped" diff --git a/templates/simple-object-storage/uninstall.sh b/templates/simple-object-storage/uninstall.sh new file mode 100644 index 0000000..5a59244 --- /dev/null +++ b/templates/simple-object-storage/uninstall.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# UNINSTALL SCRIPT +# The uninstall script is required for all templates. +# It is used to uninstall the service from the server. +# It is called with the path to the server specific env file as an argument. + +source "$(dirname "$0")/_common.sh" +check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "VOLUME_NAME" + +_remove_container $CONTAINER_NAME || die "Failed to remove container ${CONTAINER_NAME}" +_is_container_running && die "Couldn't stop existing container" +_is_container_exists && die "Couldn't remove existing container" + +# remove the image +docker rmi "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || echo "Failed to remove image $IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" + +echo "Uninstallation of ${CONTAINER_NAME} complete." +echo "Data volume ${VOLUME_NAME} still in place."