From 48d498bf89dc44f8e51fcfcbe9880df2d594475b Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Fri, 29 May 2026 19:24:07 +0800 Subject: [PATCH] init shell --- install_service.sh | 340 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100755 install_service.sh diff --git a/install_service.sh b/install_service.sh new file mode 100755 index 0000000..a326834 --- /dev/null +++ b/install_service.sh @@ -0,0 +1,340 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Default config for: +# curl -fsSL https://punchsky.com/install | bash +# +# Fill these values before publishing this script at https://punchsky.com/install. +WORK_DIR="${WORK_DIR:-/opt/app}" +CURRENT_USER="${SUDO_USER:-$(id -un)}" +SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}" +SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" + +# Configure multiple programs here before publishing this script. +# Format: +# "service_name|download_url|start_command|stop_command|service_user|service_group" +# +# Use {install_dir} in commands to reference the extracted package directory. +# stop_command, service_user and service_group are optional. +INSTALL_ITEMS=( + "iot|https://punchsky.net/down/iot-0.1.0.tar.gz|{install_dir}/bin/iot foreground|{install_dir}/bin/iot stop" + "efka|https://punchsky.net/down/efka-0.1.0.tar.gz|{install_dir}/bin/efka foreground|{install_dir}/bin/efka stop" +) +TEMP_FILES=() +INSTALLED_SERVICES=() + +usage() { + cat <<'EOF' +Usage: + curl -fsSL https://punchsky.com/install | bash + + curl -fsSL https://punchsky.com/install | bash -s -- \ + -w + +Config: + Configure programs inside this script before publishing it. + +Options: + -w, --work-dir Working directory used for installation. + --user Default service user. Default: current user. + --group Default service group. Default: current user's primary group. + -h, --help Show this help. + +Array config inside script: + INSTALL_ITEMS=( + "iot|https://punchsky.net/down/iot-0.1.0.tar.gz|{install_dir}/bin/iot foreground|{install_dir}/bin/iot stop" + "efka|https://punchsky.net/down/efka-0.1.0.tar.gz|{install_dir}/bin/efka foreground|{install_dir}/bin/efka stop" + ) + +Package format: + "service_name|download_url|start_command|stop_command|service_user|service_group" + +Command placeholder: + {install_dir} will be replaced with the extracted package directory. + +Example: + curl -fsSL https://punchsky.com/install | bash +EOF +} + +log() { + printf '[install] %s\n' "$*" +} + +cleanup() { + local file + + set +u + for file in "${TEMP_FILES[@]}"; do + [[ -n "$file" ]] && rm -f "$file" + done + set -u +} + +fail() { + printf '[install][error] %s\n' "$*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1" +} + +require_option_value() { + local option="$1" + local value="${2:-}" + + [[ -n "$value" ]] || fail "$option requires a value" +} + +require_root_runner() { + if [[ "${EUID}" -ne 0 ]]; then + require_command sudo + fi +} + +run_as_root() { + if [[ "${EUID}" -eq 0 ]]; then + "$@" + else + sudo "$@" + fi +} + +strip_archive_suffix() { + local filename="$1" + + case "$filename" in + *.tar.gz) printf '%s\n' "${filename%.tar.gz}" ;; + *.tgz) printf '%s\n' "${filename%.tgz}" ;; + *) fail "Unsupported archive suffix: $filename. Expected .tar.gz or .tgz" ;; + esac +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -w|--work-dir) + require_option_value "$1" "${2:-}" + WORK_DIR="${2:-}" + shift 2 + ;; + --user) + require_option_value "$1" "${2:-}" + SERVICE_USER="${2:-}" + shift 2 + ;; + --group) + require_option_value "$1" "${2:-}" + SERVICE_GROUP="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "Unknown argument: $1" + ;; + esac + done +} + +validate_args() { + [[ -n "$WORK_DIR" ]] || fail "--work-dir is required" + [[ "$WORK_DIR" = /* ]] || fail "--work-dir must be an absolute path" + + set +u + local item_count="${#INSTALL_ITEMS[@]}" + set -u + + [[ "$item_count" -gt 0 ]] || fail "No programs configured. Fill INSTALL_ITEMS inside this script" +} + +prepare_work_dir() { + log "Creating root work directory: $WORK_DIR" + run_as_root install -d -m 0755 "$WORK_DIR" +} + +render_service_file() { + local service_name="$1" + local service_user="$2" + local service_group="$3" + local install_dir="$4" + local start_command="$5" + local stop_command="$6" + + cat </dev/null +} + +validate_install_item() { + local service_name="$1" + local download_url="$2" + local start_command="$3" + + [[ -n "$service_name" ]] || fail "Invalid package config: service name is empty" + [[ -n "$download_url" ]] || fail "Invalid package config for $service_name: download URL is empty" + [[ -n "$start_command" ]] || fail "Invalid package config for $service_name: start command is empty" + [[ "$service_name" =~ ^[a-zA-Z0-9_.@-]+$ ]] || fail "Invalid service name: $service_name" +} + +replace_install_dir_placeholder() { + local command_template="$1" + local install_dir="$2" + + printf '%s\n' "${command_template//\{install_dir\}/$install_dir}" +} + +install_program() { + local service_name="$1" + local download_url="$2" + local start_command="$3" + local stop_command="$4" + local service_user="$5" + local service_group="$6" + local clean_url="$download_url" + local archive_name + + validate_install_item "$service_name" "$download_url" "$start_command" + + clean_url="${clean_url%%\?*}" + clean_url="${clean_url%%#*}" + archive_name="$(basename "$clean_url")" + [[ -n "$archive_name" ]] || fail "Cannot parse archive filename from URL: $download_url" + + local app_dir_name + app_dir_name="$(strip_archive_suffix "$archive_name")" + + local archive_path="${WORK_DIR}/${archive_name}" + local install_dir="${WORK_DIR}/${app_dir_name}" + local service_file="/etc/systemd/system/${service_name}.service" + local temp_archive + temp_archive="$(mktemp)" + TEMP_FILES+=("$temp_archive") + + service_user="${service_user:-$SERVICE_USER}" + service_group="${service_group:-$SERVICE_GROUP}" + start_command="$(replace_install_dir_placeholder "$start_command" "$install_dir")" + stop_command="$(replace_install_dir_placeholder "$stop_command" "$install_dir")" + + log "Downloading $download_url" + curl -fL --retry 3 --connect-timeout 20 -o "$temp_archive" "$download_url" + run_as_root install -m 0644 "$temp_archive" "$archive_path" + + log "Creating install directory: $install_dir" + run_as_root install -d -m 0755 "$install_dir" + + log "Extracting archive to $install_dir" + run_as_root tar -zxvf "$archive_path" -C "$install_dir" + + log "Setting owner for install directory: ${service_user}:${service_group}" + run_as_root chown -R "${service_user}:${service_group}" "$install_dir" + + write_service_file "$service_file" "$service_name" "$service_user" "$service_group" "$install_dir" "$start_command" "$stop_command" + INSTALLED_SERVICES+=("$service_name") +} + +install_programs() { + local item + local service_name + local download_url + local start_command + local stop_command + local service_user + local service_group + + set +u + for item in "${INSTALL_ITEMS[@]}"; do + set -u + IFS='|' read -r service_name download_url start_command stop_command service_user service_group <<< "$item" + install_program "$service_name" "$download_url" "$start_command" "$stop_command" "$service_user" "$service_group" + set +u + done + set -u +} + +reload_systemd() { + log "Reloading systemd" + run_as_root systemctl daemon-reload +} + +enable_and_start_installed_services() { + local service_name + + set +u + for service_name in "${INSTALLED_SERVICES[@]}"; do + set -u + log "Enabling service on boot: ${service_name}.service" + run_as_root systemctl enable "${service_name}.service" + + log "Starting service: ${service_name}.service" + run_as_root systemctl start "${service_name}.service" + set +u + done + set -u +} + +main() { + trap cleanup EXIT + + parse_args "$@" + validate_args + + require_command curl + require_root_runner + require_command tar + require_command systemctl + + prepare_work_dir + install_programs + reload_systemd + enable_and_start_installed_services + + log "Installation completed" +} + +main "$@"