diff --git a/shlink/README.txt b/shlink/README.txt new file mode 100644 index 0000000..bcd05cf --- /dev/null +++ b/shlink/README.txt @@ -0,0 +1,76 @@ +Shlink - URL Shortener +====================== + +Shlink is a self-hosted URL shortener that allows you to create and manage +short URLs with tracking and analytics. + +Website: https://shlink.io/ + +Configuration +------------- + +Required settings in service.env: + + DEFAULT_DOMAIN - The domain for your short URLs (e.g., s.example.com) + IS_HTTPS_ENABLED - Set to "true" if using HTTPS (recommended) + DB_PASSWORD - MariaDB user password (change from default!) + DB_ROOT_PASSWORD - MariaDB root password (change from default!) + +Optional settings: + + GEOLITE_LICENSE_KEY - For IP geolocation features + Get a free key at: https://www.maxmind.com/en/geolite2/signup + + INITIAL_API_KEY - Pre-set an API key on first install + If not set, generate one after install (see below) + +Ports +----- + + HTTP_PORT (default: 8080) - Shlink API and redirect endpoint + +Setup +----- + +1. Configure your service.env with your domain and secure passwords + +2. Install the service: + dropshell install shlink + +3. Generate an API key (if INITIAL_API_KEY not set): + docker exec -it shlink shlink api-key:generate + +4. Point your short domain (e.g., s.example.com) to your server + +5. Optionally put behind a reverse proxy (Caddy, nginx) for HTTPS + +Usage +----- + +Create a short URL via API: + curl -X POST https://s.example.com/rest/v3/short-urls \ + -H "X-Api-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"longUrl": "https://example.com/very/long/url"}' + +Or use the Shlink web client: + https://app.shlink.io/ + +CLI commands (run inside container): + docker exec -it shlink shlink short-url:create https://example.com + docker exec -it shlink shlink short-url:list + docker exec -it shlink shlink api-key:list + +Database +-------- + +This template uses MariaDB for data persistence. The database is automatically +backed up and restored with the dropshell backup/restore commands. + +Data is stored in: ${DATA_PATH}/mariadb + +More Information +---------------- + +Documentation: https://shlink.io/documentation/ +API Reference: https://shlink.io/documentation/api-docs/ diff --git a/shlink/_volumes.sh b/shlink/_volumes.sh new file mode 100755 index 0000000..46de603 --- /dev/null +++ b/shlink/_volumes.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Define volume items for shlink container +# These are used across backup, restore, create, and destroy operations + +get_shlink_volumes() { + echo "path:data:${DATA_PATH}" +} diff --git a/shlink/backup.sh b/shlink/backup.sh new file mode 100755 index 0000000..5e6cc5e --- /dev/null +++ b/shlink/backup.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_volumes.sh" +_check_required_env_vars "CONTAINER_NAME" "DATA_PATH" "DB_NAME" "DB_USER" "DB_PASSWORD" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" + +# Dump MariaDB database before backup +echo "Dumping MariaDB database..." +docker exec ${CONTAINER_NAME}_db mariadb-dump -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "${DATA_PATH}/database.sql" || _die "Failed to dump database" + +# Stop containers for consistent backup +docker compose stop + +# Backup using dropshell's backup system +# shellcheck disable=SC2046 +backup_items $(get_shlink_volumes) || _die "Failed to create backup" + +# Clean up database dump (it's now in the backup) +rm -f "${DATA_PATH}/database.sql" + +# Restart containers +docker compose start + +echo "Backup created successfully" diff --git a/shlink/config/service.env b/shlink/config/service.env new file mode 100644 index 0000000..71c2317 --- /dev/null +++ b/shlink/config/service.env @@ -0,0 +1,34 @@ +# Service settings specific to this server +CONTAINER_NAME=shlink + +# Server Settings +SSH_USER="root" + +# Data path - stores MariaDB data +DATA_PATH="/home/dropshell/shlink" + +# Shlink port (web interface) +HTTP_PORT=8080 + +# Short URL domain (REQUIRED - change this!) +# This is the domain that will be used for short URLs +DEFAULT_DOMAIN="s.example.com" + +# Set to true if your domain uses HTTPS +IS_HTTPS_ENABLED=true + +# GeoLite2 license key for IP geolocation (optional but recommended) +# Get a free key at: https://www.maxmind.com/en/geolite2/signup +GEOLITE_LICENSE_KEY="" + +# Initial API key (optional - can also generate via CLI after install) +# If not set, generate with: docker exec -it shlink shlink api-key:generate +INITIAL_API_KEY="" + +# MariaDB database settings +DB_NAME="shlink" +DB_USER="shlink" +DB_PASSWORD="changeme_shlink_password" +DB_ROOT_PASSWORD="changeme_root_password" + +TEMPLATE=shlink diff --git a/shlink/destroy.sh b/shlink/destroy.sh new file mode 100755 index 0000000..7ed6d76 --- /dev/null +++ b/shlink/destroy.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_volumes.sh" +_check_required_env_vars "CONTAINER_NAME" "DATA_PATH" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" + +# Stop and remove containers +docker compose down + +# Destroy data +# shellcheck disable=SC2046 +destroy_items $(get_shlink_volumes) || _die "Failed to destroy data" + +echo "Destroyed ${CONTAINER_NAME} and all data" diff --git a/shlink/docker-compose.yml b/shlink/docker-compose.yml new file mode 100644 index 0000000..c92006a --- /dev/null +++ b/shlink/docker-compose.yml @@ -0,0 +1,39 @@ +services: + db: + image: ${DB_IMAGE_REGISTRY}/${DB_IMAGE_REPO}:${DB_IMAGE_TAG} + container_name: ${CONTAINER_NAME}_db + restart: unless-stopped + environment: + MARIADB_DATABASE: ${DB_NAME} + MARIADB_USER: ${DB_USER} + MARIADB_PASSWORD: ${DB_PASSWORD} + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - ${DATA_PATH}/mariadb:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + shlink: + image: ${IMAGE_REGISTRY}/${IMAGE_REPO}:${IMAGE_TAG} + container_name: ${CONTAINER_NAME} + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + DEFAULT_DOMAIN: ${DEFAULT_DOMAIN} + IS_HTTPS_ENABLED: ${IS_HTTPS_ENABLED} + GEOLITE_LICENSE_KEY: ${GEOLITE_LICENSE_KEY} + INITIAL_API_KEY: ${INITIAL_API_KEY} + DB_DRIVER: maria + DB_NAME: ${DB_NAME} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_HOST: ${CONTAINER_NAME}_db + DB_PORT: 3306 + ports: + - "${HTTP_PORT}:8080" diff --git a/shlink/install.sh b/shlink/install.sh new file mode 100755 index 0000000..bd633c5 --- /dev/null +++ b/shlink/install.sh @@ -0,0 +1,31 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_check_required_env_vars "CONTAINER_NAME" "DATA_PATH" "DEFAULT_DOMAIN" "DB_NAME" "DB_USER" "DB_PASSWORD" "DB_ROOT_PASSWORD" + +_check_docker_installed || _die "Docker test failed, aborting installation..." + +# Create data directory +mkdir -p "${DATA_PATH}/mariadb" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" + +# Pull images +docker compose pull || _die "Failed to pull images" + +# Stop existing containers +docker compose down 2>/dev/null || true + +# Start containers +docker compose up -d || _die "Failed to start containers" + +echo "Installation of ${CONTAINER_NAME} complete" +echo "Access Shlink at http://localhost:${HTTP_PORT}" +echo "" +echo "To generate an API key, run:" +echo " docker exec -it ${CONTAINER_NAME} shlink api-key:generate" diff --git a/shlink/logs.sh b/shlink/logs.sh new file mode 100755 index 0000000..09bd032 --- /dev/null +++ b/shlink/logs.sh @@ -0,0 +1,12 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_check_required_env_vars "CONTAINER_NAME" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" +docker compose logs --tail=100 -f diff --git a/shlink/restore.sh b/shlink/restore.sh new file mode 100755 index 0000000..d32b450 --- /dev/null +++ b/shlink/restore.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_volumes.sh" +_check_required_env_vars "CONTAINER_NAME" "DATA_PATH" "DB_NAME" "DB_USER" "DB_PASSWORD" "DB_ROOT_PASSWORD" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" + +# Stop and remove containers before restore +docker compose down + +# Restore files using dropshell's restore system +# shellcheck disable=SC2046 +restore_items $(get_shlink_volumes) || _die "Failed to restore data from backup file" + +# Start database container only +docker compose up -d db +echo "Waiting for database to be ready..." +sleep 10 + +# Restore database if dump exists +if [ -f "${DATA_PATH}/database.sql" ]; then + echo "Restoring MariaDB database..." + # Drop and recreate database + docker exec ${CONTAINER_NAME}_db mariadb -u root -p"$DB_ROOT_PASSWORD" -e "DROP DATABASE IF EXISTS ${DB_NAME}; CREATE DATABASE ${DB_NAME};" + # Restore from dump + docker exec -i ${CONTAINER_NAME}_db mariadb -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < "${DATA_PATH}/database.sql" || _die "Failed to restore database" + # Clean up dump file + rm -f "${DATA_PATH}/database.sql" + echo "Database restored successfully" +else + echo "Warning: No database dump found in backup" +fi + +# Start all containers +docker compose up -d + +echo "Restore complete! Service is running." diff --git a/shlink/start.sh b/shlink/start.sh new file mode 100755 index 0000000..2d8243b --- /dev/null +++ b/shlink/start.sh @@ -0,0 +1,14 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_check_required_env_vars "CONTAINER_NAME" "DATA_PATH" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" +docker compose up -d || _die "Failed to start containers" + +echo "Container ${CONTAINER_NAME} started" diff --git a/shlink/status.sh b/shlink/status.sh new file mode 100755 index 0000000..9da95a9 --- /dev/null +++ b/shlink/status.sh @@ -0,0 +1,15 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +# Check if both containers are running +shlink_running=$(_is_container_running "$CONTAINER_NAME" && echo "yes" || echo "no") +db_running=$(_is_container_running "${CONTAINER_NAME}_db" && echo "yes" || echo "no") + +if [ "$shlink_running" = "yes" ] && [ "$db_running" = "yes" ]; then + echo "Running" +elif [ "$shlink_running" = "no" ] && [ "$db_running" = "no" ]; then + echo "Stopped" +else + echo "Error" +fi diff --git a/shlink/stop.sh b/shlink/stop.sh new file mode 100755 index 0000000..48ce9a1 --- /dev/null +++ b/shlink/stop.sh @@ -0,0 +1,14 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_check_required_env_vars "CONTAINER_NAME" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" +docker compose stop || _die "Failed to stop containers" + +echo "Container ${CONTAINER_NAME} stopped" diff --git a/shlink/template_info.env b/shlink/template_info.env new file mode 100644 index 0000000..2c2f31c --- /dev/null +++ b/shlink/template_info.env @@ -0,0 +1,21 @@ +# DO NOT EDIT THIS FILE FOR YOUR SERVICE! +# This file is replaced from the template whenever there is an update. +# Edit the service.env file to make changes. + +# Template to use - always required! +TEMPLATE=shlink +REQUIRES_HOST_ROOT=false +REQUIRES_DOCKER=true +REQUIRES_DOCKER_ROOT=false + +# Image settings +IMAGE_REGISTRY="docker.io" +IMAGE_REPO="shlinkio/shlink" +IMAGE_TAG="stable" + +# MariaDB image settings +DB_IMAGE_REGISTRY="docker.io" +DB_IMAGE_REPO="library/mariadb" +DB_IMAGE_TAG="11" + +# DATA_PATH must be set in service.env - stores database and any persistent data diff --git a/shlink/uninstall.sh b/shlink/uninstall.sh new file mode 100755 index 0000000..79ebed6 --- /dev/null +++ b/shlink/uninstall.sh @@ -0,0 +1,16 @@ +#!/bin/bash +source "${AGENT_PATH}/common.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_check_required_env_vars "CONTAINER_NAME" + +# Export variables for docker compose +export CONTAINER_NAME DATA_PATH HTTP_PORT DEFAULT_DOMAIN IS_HTTPS_ENABLED GEOLITE_LICENSE_KEY INITIAL_API_KEY +export DB_NAME DB_USER DB_PASSWORD DB_ROOT_PASSWORD +export IMAGE_REGISTRY IMAGE_REPO IMAGE_TAG DB_IMAGE_REGISTRY DB_IMAGE_REPO DB_IMAGE_TAG + +cd "$SCRIPT_DIR" || _die "Failed to change to script directory" + +# Stop and remove containers (but preserve data) +docker compose down || _die "Failed to stop containers" + +echo "Uninstalled ${CONTAINER_NAME} (data preserved in ${DATA_PATH})"