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