#!/usr/bin/env bash set -euo pipefail # Default config for: # curl -fsSL https://punchsky.cn/install_efka.sh | bash # # Fill these values before publishing this script at https://punchsky.cn/install_efka.sh. 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 programs here before publishing this script. # Format: # INSTALL_=( # "service_name" # "download_url" # "start_command" # "stop_command" # "service_user" # "service_group" # ) # # EFKA_ENV is rendered into the systemd service file as Environment= lines, # using the [prod] section from efka/env.file. Directory creation is driven by # those service env values. EFKA_DIR_ENV_KEYS lists env keys whose values are # directories that must be created if they do not exist. # # Use {install_dir}, {work_dir} and {service_name} in commands and directories. # service_user and service_group are optional. INSTALL_EFKA=( "efka" "https://punchsky.cn/down/efka-0.1.0.tar.gz" "{install_dir}/bin/efka foreground" "{install_dir}/bin/efka stop" "" "" ) EFKA_ENV=( "EFKA_DETS_DIR=/var/lib/efka/dets/" "EFKA_IOT_HOST=localhost" "EFKA_AUTH_UUID=qbxmjyzrkpntfgswaevodhluicqzxplkm" "EFKA_AUTH_TOKEN=zpxlkvmqwnbghytrujsdieofazxcvbnm" "EFKA_DOCKER_ROOT_DIR=/var/lib/efka/docker/" "EFKA_MNESIA_DIR=/var/lib/efka/mnesia" ) EFKA_DIR_ENV_KEYS=( "EFKA_DETS_DIR" "EFKA_DOCKER_ROOT_DIR" "EFKA_MNESIA_DIR" ) TEMP_FILES=() INSTALLED_SERVICES=() usage() { cat <<'EOF' Usage: curl -fsSL https://punchsky.cn/install_efka.sh | bash curl -fsSL https://punchsky.cn/install_efka.sh | 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_EFKA EFKA_ENV EFKA_DIR_ENV_KEYS Package format: INSTALL_=( "service_name" "download_url" "start_command" "stop_command" "service_user" "service_group" ) Placeholders: {install_dir} will be replaced with the extracted package directory. {work_dir} will be replaced with the root work directory. {service_name} will be replaced with the service name. Example: curl -fsSL https://punchsky.cn/install_efka.sh | 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 } archive_name_from_url() { local download_url="$1" local clean_url="$download_url" clean_url="${clean_url%%\?*}" clean_url="${clean_url%%#*}" basename "$clean_url" } 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" require_install_config INSTALL_EFKA } 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" local env_line 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" } require_install_config() { local config_name="$1" local config_count local service_name local download_url local start_command eval "config_count=\${#${config_name}[@]}" eval "service_name=\"\${${config_name}[0]:-}\"" eval "download_url=\"\${${config_name}[1]:-}\"" eval "start_command=\"\${${config_name}[2]:-}\"" [[ "$config_count" -ge 4 ]] || fail "Invalid $config_name config. Expected service name, URL and commands" [[ -n "$service_name" ]] || fail "Invalid $config_name config: service name is empty" [[ -n "$download_url" ]] || fail "Invalid $config_name config: download URL is empty" [[ -n "$start_command" ]] || fail "Invalid $config_name config: start command is empty" } replace_placeholders() { local value="$1" local install_dir="$2" local service_name="$3" value="${value//\{install_dir\}/$install_dir}" value="${value//\{work_dir\}/$WORK_DIR}" value="${value//\{service_name\}/$service_name}" printf '%s\n' "$value" } validate_environment_line() { local env_line="$1" local env_key="${env_line%%=*}" [[ "$env_line" == *=* ]] || fail "Invalid environment line: $env_line" [[ "$env_key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || fail "Invalid environment key: $env_key" } service_env_lines() { local service_name="$1" local env_array_name="" local env_count local index local env_line case "$service_name" in efka) env_array_name="EFKA_ENV" ;; *) return 0 ;; esac eval "env_count=\${#${env_array_name}[@]}" for ((index=0; index/dev/null local archive_path="${WORK_DIR}/${archive_name}" local install_dir="${WORK_DIR}/${service_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_placeholders "$start_command" "$install_dir" "$service_name")" stop_command="$(replace_placeholders "$stop_command" "$install_dir" "$service_name")" log "Downloading $download_url" wget --tries=3 --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 "Removing downloaded archive: $archive_path" run_as_root rm -f "$archive_path" log "Setting owner for install directory: ${service_user}:${service_group}" run_as_root chown -R "${service_user}:${service_group}" "$install_dir" create_dependency_dirs_from_env "$service_name" "$install_dir" "$service_user" "$service_group" write_service_file "$service_file" "$service_name" "$service_user" "$service_group" "$install_dir" "$start_command" "$stop_command" INSTALLED_SERVICES+=("$service_name") } install_program_from_config() { local config_name="$1" local service_name local download_url local start_command local stop_command local service_user local service_group eval "service_name=\"\${${config_name}[0]:-}\"" eval "download_url=\"\${${config_name}[1]:-}\"" eval "start_command=\"\${${config_name}[2]:-}\"" eval "stop_command=\"\${${config_name}[3]:-}\"" eval "service_user=\"\${${config_name}[4]:-}\"" eval "service_group=\"\${${config_name}[5]:-}\"" install_program \ "$service_name" \ "$download_url" \ "$start_command" \ "$stop_command" \ "$service_user" \ "$service_group" } install_programs() { install_program_from_config INSTALL_EFKA } reload_systemd() { log "Reloading systemd" run_as_root systemctl daemon-reload } enable_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" set +u done set -u } main() { trap cleanup EXIT parse_args "$@" validate_args require_command wget require_root_runner require_command tar require_command systemctl prepare_work_dir install_programs reload_systemd enable_installed_services log "EFKA installation completed" } main "$@"