init shell
This commit is contained in:
commit
48d498bf89
340
install_service.sh
Executable file
340
install_service.sh
Executable file
@ -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 <work_dir>
|
||||||
|
|
||||||
|
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 <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=${service_name} service
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${service_user}
|
||||||
|
Group=${service_group}
|
||||||
|
WorkingDirectory=${install_dir}
|
||||||
|
|
||||||
|
ExecStart=${start_command}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [[ -n "$stop_command" ]]; then
|
||||||
|
printf 'ExecStop=%s\n' "$stop_command"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_service_file() {
|
||||||
|
local service_file="$1"
|
||||||
|
local service_name="$2"
|
||||||
|
local service_user="$3"
|
||||||
|
local service_group="$4"
|
||||||
|
local install_dir="$5"
|
||||||
|
local start_command="$6"
|
||||||
|
local stop_command="$7"
|
||||||
|
|
||||||
|
if [[ -f "$service_file" ]]; then
|
||||||
|
local backup_file="${service_file}.bak.$(date +%Y%m%d%H%M%S)"
|
||||||
|
log "Backing up existing service file: $backup_file"
|
||||||
|
run_as_root cp "$service_file" "$backup_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Writing systemd service file: $service_file"
|
||||||
|
render_service_file "$service_name" "$service_user" "$service_group" "$install_dir" "$start_command" "$stop_command" | run_as_root tee "$service_file" >/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 "$@"
|
||||||
Loading…
x
Reference in New Issue
Block a user