mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 16:43:05 +02:00
feat: libre translate setup script
This commit is contained in:
parent
bb4b39f3d9
commit
652341cc73
381
libre_translate.sh
Executable file
381
libre_translate.sh
Executable file
@ -0,0 +1,381 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# LibreTranslate full setup script (Docker-based)
|
||||
# Features:
|
||||
# - Installs Docker if missing (optional --no-docker-install)
|
||||
# - Pulls libretranslate image (tag configurable)
|
||||
# - Creates persistent data + cache directories
|
||||
# - Optionally pre-downloads language models
|
||||
# - Generates or accepts an API key; can disable auth
|
||||
# - (Removed) systemd service setup – now always ephemeral
|
||||
# - Health check + sample translation
|
||||
# - Uninstall mode removes container, image, service, and data (optional keep data)
|
||||
# - Idempotent: safe to re-run for upgrades (will pull newer image)
|
||||
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
VERSION="1.0.0"
|
||||
|
||||
# Defaults
|
||||
IMAGE="libretranslate/libretranslate"
|
||||
TAG="latest"
|
||||
SERVICE_NAME="libretranslate"
|
||||
DOCKER_INSTALL=1
|
||||
# Systemd removed – always run ephemeral container
|
||||
API_KEY=""
|
||||
GENERATE_API_KEY=1
|
||||
DISABLE_API_KEY=0
|
||||
PORT=5000
|
||||
HOST=0.0.0.0
|
||||
DATA_DIR="/var/lib/libretranslate"
|
||||
CACHE_DIR="${DATA_DIR}/cache"
|
||||
CONFIG_DIR="/etc/libretranslate"
|
||||
ENV_FILE="${CONFIG_DIR}/libretranslate.env"
|
||||
PULL_ONLY=0
|
||||
PRELOAD_LANGS=""
|
||||
UNINSTALL=0
|
||||
KEEP_DATA=0
|
||||
HEALTH_TIMEOUT=15
|
||||
EXTRA_ENV=()
|
||||
NO_COLOR=0
|
||||
KEEP_ALIVE=0
|
||||
RUN_COMMAND=()
|
||||
DEBUG=0
|
||||
|
||||
# Colors
|
||||
if [[ -t 1 && ${NO_COLOR} -eq 0 ]]; then
|
||||
GREEN="\e[32m"; YELLOW="\e[33m"; RED="\e[31m"; BLUE="\e[34m"; BOLD="\e[1m"; RESET="\e[0m"
|
||||
else
|
||||
GREEN=""; YELLOW=""; RED=""; BLUE=""; BOLD=""; RESET=""
|
||||
fi
|
||||
|
||||
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
err() { echo -e "${RED}[ERR ]${RESET} $*" >&2; }
|
||||
success() { echo -e "${GREEN}[OK ]${RESET} $*"; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${SCRIPT_NAME} v${VERSION}
|
||||
Setup or uninstall a self-hosted LibreTranslate instance via Docker.
|
||||
|
||||
Usage: ${SCRIPT_NAME} [options]
|
||||
|
||||
Primary actions:
|
||||
--uninstall Remove service, container, image (data kept unless --purge or --no-keep-data)
|
||||
--pull-only Only pull/update image & exit
|
||||
|
||||
Install behavior options:
|
||||
--image NAME Docker image (default: ${IMAGE})
|
||||
--tag TAG Docker tag (default: ${TAG})
|
||||
--port N Host port to expose (default: ${PORT})
|
||||
--host IP Bind host (default: ${HOST})
|
||||
--data-dir PATH Persistent data directory (default: ${DATA_DIR})
|
||||
--cache-dir PATH Models cache dir (default: ${CACHE_DIR})
|
||||
--no-docker-install Do not attempt to install Docker
|
||||
(systemd support removed; container is ephemeral)
|
||||
--keep-alive Keep running (tail logs) until Ctrl-C
|
||||
-- Treat remaining arguments as a command to run after service is healthy; service stops when command exits
|
||||
--api-key KEY Use specified API key
|
||||
--generate-api-key Force generate new random key (default if none provided)
|
||||
--disable-api-key Disable key requirement (open instance)
|
||||
--preload-langs CSV Pre-download language models (e.g. en,es,fr,de)
|
||||
--env K=V Extra environment variable (repeatable)
|
||||
--health-timeout SEC Wait time for health check (default: ${HEALTH_TIMEOUT})
|
||||
--debug Verbose output (do not suppress curl errors; follow logs on failure)
|
||||
--no-color Disable colored output
|
||||
|
||||
Uninstall options:
|
||||
--purge Remove data directory (implies --uninstall)
|
||||
--keep-data Keep data on uninstall (default)
|
||||
|
||||
Misc:
|
||||
-h, --help Show this help
|
||||
-v, --version Show version
|
||||
|
||||
Examples:
|
||||
${SCRIPT_NAME} --preload-langs en,es,fr --env LT_LOAD_ONLY=en,es,fr
|
||||
${SCRIPT_NAME} --api-key mysecret123 --port 8080
|
||||
${SCRIPT_NAME} --uninstall --purge
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_api_key() {
|
||||
# Avoid SIGPIPE issues under set -o pipefail by capturing output first
|
||||
local key
|
||||
key=$(head -c 256 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 40 || true)
|
||||
if [[ -z $key || ${#key} -lt 40 ]]; then
|
||||
# Fallback using openssl if available
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
key=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40 || true)
|
||||
fi
|
||||
fi
|
||||
if [[ -z $key || ${#key} -lt 20 ]]; then
|
||||
# Last resort static warning key (should not happen)
|
||||
key="LT$(date +%s)$$RANDOM"
|
||||
fi
|
||||
printf '%s' "$key"
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || { err "Required command '$1' not found"; return 1; }
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--image) IMAGE="$2"; shift 2;;
|
||||
--tag) TAG="$2"; shift 2;;
|
||||
--port) PORT="$2"; shift 2;;
|
||||
--host) HOST="$2"; shift 2;;
|
||||
--data-dir) DATA_DIR="$2"; CACHE_DIR="${DATA_DIR}/cache"; shift 2;;
|
||||
--cache-dir) CACHE_DIR="$2"; shift 2;;
|
||||
--no-docker-install) DOCKER_INSTALL=0; shift;;
|
||||
--keep-alive) KEEP_ALIVE=1; shift;;
|
||||
--) shift; RUN_COMMAND=("$@"); break;;
|
||||
--api-key) API_KEY="$2"; GENERATE_API_KEY=0; shift 2;;
|
||||
--generate-api-key) GENERATE_API_KEY=1; shift;;
|
||||
--disable-api-key) DISABLE_API_KEY=1; shift;;
|
||||
--preload-langs) PRELOAD_LANGS="$2"; shift 2;;
|
||||
--env) EXTRA_ENV+=("$2"); shift 2;;
|
||||
--pull-only) PULL_ONLY=1; shift;;
|
||||
--uninstall) UNINSTALL=1; shift;;
|
||||
--purge) UNINSTALL=1; KEEP_DATA=0; shift;;
|
||||
--keep-data) KEEP_DATA=1; shift;;
|
||||
--health-timeout) HEALTH_TIMEOUT="$2"; shift 2;;
|
||||
--no-color) NO_COLOR=1; shift;;
|
||||
--debug) DEBUG=1; shift;;
|
||||
-h|--help) usage; exit 0;;
|
||||
-v|--version) echo "${VERSION}"; exit 0;;
|
||||
*) err "Unknown argument: $1"; usage; exit 1;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
ensure_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
err "This script must run as root (or via sudo)."; exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_docker() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
log "Docker already installed"
|
||||
return 0
|
||||
fi
|
||||
if [[ ${DOCKER_INSTALL} -eq 0 ]]; then
|
||||
err "Docker is not installed and --no-docker-install specified."; exit 1
|
||||
fi
|
||||
log "Installing Docker..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update -y
|
||||
apt-get install -y ca-certificates curl gnupg
|
||||
install -d -m 0755 /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(. /etc/os-release; echo "$VERSION_CODENAME") stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -y
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
else
|
||||
err "Unsupported package manager. Please install Docker manually."; exit 1
|
||||
fi
|
||||
# Attempt to start docker daemon if dockerd exists and systemctl available; otherwise rely on user
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
(systemctl enable --now docker 2>/dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
|
||||
else
|
||||
warn "Docker installed; please ensure docker daemon is running"
|
||||
fi
|
||||
}
|
||||
|
||||
pull_image() {
|
||||
log "Pulling image ${IMAGE}:${TAG}"
|
||||
docker pull "${IMAGE}:${TAG}"
|
||||
success "Image pulled"
|
||||
}
|
||||
|
||||
detect_container_user() {
|
||||
# Determine uid/gid of configured user inside image so host dirs can be chowned
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
local uid gid
|
||||
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 2>/dev/null || echo "")
|
||||
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2>/dev/null || echo "")
|
||||
if [[ -n $uid && -n $gid ]]; then
|
||||
CONTAINER_UID=$uid
|
||||
CONTAINER_GID=$gid
|
||||
fi
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${CACHE_DIR}"
|
||||
detect_container_user
|
||||
if [[ -n ${CONTAINER_UID:-} && -n ${CONTAINER_GID:-} ]]; then
|
||||
if command -v stat >/dev/null 2>&1; then
|
||||
for d in "${DATA_DIR}" "${CACHE_DIR}"; do
|
||||
if [[ -d $d ]]; then
|
||||
CUR_UID=$(stat -c %u "$d" 2>/dev/null || echo -1)
|
||||
if [[ ${CUR_UID} -ne ${CONTAINER_UID} ]]; then
|
||||
chown ${CONTAINER_UID}:${CONTAINER_GID} "$d" 2>/dev/null || warn "Unable to chown $d to ${CONTAINER_UID}:${CONTAINER_GID}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
if [[ ${DISABLE_API_KEY} -eq 1 ]]; then
|
||||
API_KEY_LINE="LT_NO_API_KEY=true"
|
||||
else
|
||||
if [[ -z ${API_KEY} && ${GENERATE_API_KEY} -eq 1 ]]; then
|
||||
API_KEY=$(gen_api_key)
|
||||
GENERATED=1
|
||||
else
|
||||
GENERATED=0
|
||||
fi
|
||||
API_KEY_LINE="LT_API_KEYS=${API_KEY}"
|
||||
fi
|
||||
|
||||
{ echo "# LibreTranslate environment file"; echo "# Generated $(date -u +%Y-%m-%dT%H:%M:%SZ)"; echo "${API_KEY_LINE}";
|
||||
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}";
|
||||
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done; } > "${ENV_FILE}.tmp"
|
||||
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
|
||||
chmod 600 "${ENV_FILE}"
|
||||
success "Environment file written: ${ENV_FILE}"
|
||||
}
|
||||
|
||||
start_container_ephemeral() {
|
||||
log "Starting ephemeral container..."
|
||||
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1 || true
|
||||
docker run -d --name "${SERVICE_NAME}" \
|
||||
--env-file "${ENV_FILE}" \
|
||||
-v "${DATA_DIR}:/home/libretranslate/.local/share/argos-translate" \
|
||||
-v "${CACHE_DIR}:/app/cache" \
|
||||
-p "${PORT}:${PORT}" \
|
||||
"${IMAGE}:${TAG}" \
|
||||
--host 0.0.0.0 --port ${PORT}
|
||||
success "Container started (ephemeral)"
|
||||
echo
|
||||
echo "Endpoint (pending readiness): http://$(hostname -I | awk '{print $1}'):${PORT}"
|
||||
echo "Waiting for health..."
|
||||
}
|
||||
|
||||
health_check() {
|
||||
local start=$(date +%s)
|
||||
local url="http://127.0.0.1:${PORT}/languages"
|
||||
local attempt=0
|
||||
while true; do
|
||||
attempt=$((attempt+1))
|
||||
if curl ${DEBUG:+-v} -fsS "$url" >/dev/null 2>&1; then
|
||||
success "Service healthy (attempt $attempt)"
|
||||
return 0
|
||||
else
|
||||
[[ $DEBUG -eq 1 ]] && log "Health attempt $attempt failed"
|
||||
fi
|
||||
if (( $(date +%s) - start > HEALTH_TIMEOUT )); then
|
||||
err "Health check failed after ${HEALTH_TIMEOUT}s (attempts: $attempt)"
|
||||
docker logs --tail 200 "${SERVICE_NAME}" || true
|
||||
return 1
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
}
|
||||
|
||||
sample_request() {
|
||||
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
||||
local key="${API_KEY}"
|
||||
else
|
||||
local key=""
|
||||
fi
|
||||
log "Performing sample translation (en->es)..."
|
||||
local DATA='{"q":"Hello world","source":"en","target":"es","format":"text"}'
|
||||
if [[ -n $key ]]; then
|
||||
curl -fsS -H "Content-Type: application/json" -H "Authorization: ${key}" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
||||
else
|
||||
curl -fsS -H "Content-Type: application/json" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
uninstall_all() {
|
||||
log "Uninstalling LibreTranslate (ephemeral mode)..."
|
||||
docker rm -f "${SERVICE_NAME}" 2>/dev/null || true
|
||||
docker rmi "${IMAGE}:${TAG}" 2>/dev/null || true
|
||||
if [[ ${KEEP_DATA} -eq 0 ]]; then
|
||||
rm -rf "${DATA_DIR}" "${CONFIG_DIR}" || true
|
||||
success "Data directories removed"
|
||||
else
|
||||
log "Data kept in ${DATA_DIR} and ${CONFIG_DIR}"
|
||||
fi
|
||||
success "Uninstall complete"
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
ensure_root
|
||||
|
||||
if [[ ${UNINSTALL} -eq 1 ]]; then
|
||||
uninstall_all
|
||||
fi
|
||||
|
||||
install_docker
|
||||
pull_image
|
||||
if [[ ${PULL_ONLY} -eq 1 ]]; then
|
||||
log "Pull-only requested, exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
write_env_file
|
||||
|
||||
# Always ephemeral now
|
||||
start_container_ephemeral
|
||||
|
||||
health_check
|
||||
sample_request || true
|
||||
|
||||
# If a command is provided, run it and then shutdown container
|
||||
if [[ ${#RUN_COMMAND[@]} -gt 0 ]]; then
|
||||
log "Running user command: ${RUN_COMMAND[*]}"
|
||||
set +e
|
||||
"${RUN_COMMAND[@]}"
|
||||
CMD_STATUS=$?
|
||||
set -e
|
||||
log "Command exited with status ${CMD_STATUS}; stopping container"
|
||||
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
|
||||
exit ${CMD_STATUS}
|
||||
fi
|
||||
|
||||
if [[ ${KEEP_ALIVE} -eq 1 ]]; then
|
||||
log "Tailing logs (Ctrl-C to stop and remove container)"
|
||||
trap 'log "Stopping container"; docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true; exit 0' INT TERM
|
||||
docker logs -f "${SERVICE_NAME}"
|
||||
log "Logs ended; stopping container"
|
||||
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
|
||||
else
|
||||
log "Ephemeral container left running in background (id: $(docker inspect --format '{{.Id}}' ${SERVICE_NAME} 2>/dev/null || echo unknown))"
|
||||
log "Stop manually with: docker stop ${SERVICE_NAME}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "${BOLD}LibreTranslate is ready.${RESET}"
|
||||
echo "Endpoint: http://$(hostname -I | awk '{print $1}'):${PORT}"
|
||||
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
||||
if [[ ${GENERATED:-0} -eq 1 ]]; then
|
||||
echo "Generated API key: ${API_KEY}"
|
||||
else
|
||||
echo "API key: ${API_KEY}"
|
||||
fi
|
||||
echo "Use header: Authorization: <API_KEY>"
|
||||
else
|
||||
echo "API key authentication DISABLED (public instance)."
|
||||
fi
|
||||
[[ -n ${PRELOAD_LANGS} ]] && echo "Preloaded languages requested: ${PRELOAD_LANGS}" || true
|
||||
echo "Environment file: ${ENV_FILE}"
|
||||
echo "Manage: docker logs -f ${SERVICE_NAME} | docker stop ${SERVICE_NAME}"
|
||||
echo "Uninstall: sudo ${SCRIPT_NAME} --uninstall"
|
||||
echo
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user