拆分独立的安装程序
This commit is contained in:
parent
6bde2b4262
commit
c9c48edd42
@ -2,9 +2,9 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Default config for:
|
# Default config for:
|
||||||
# curl -fsSL https://punchsky.com/install | bash
|
# curl -fsSL https://punchsky.cn/install_efka.sh | bash
|
||||||
#
|
#
|
||||||
# Fill these values before publishing this script at https://punchsky.com/install.
|
# Fill these values before publishing this script at https://punchsky.cn/install_efka.sh.
|
||||||
WORK_DIR="${WORK_DIR:-/opt/app}"
|
WORK_DIR="${WORK_DIR:-/opt/app}"
|
||||||
CURRENT_USER="${SUDO_USER:-$(id -un)}"
|
CURRENT_USER="${SUDO_USER:-$(id -un)}"
|
||||||
SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}"
|
SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}"
|
||||||
@ -21,37 +21,14 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
|
|||||||
# "service_group"
|
# "service_group"
|
||||||
# )
|
# )
|
||||||
#
|
#
|
||||||
# IOT_ENV and EFKA_ENV are rendered into the corresponding systemd service
|
# EFKA_ENV is rendered into the systemd service file as Environment= lines,
|
||||||
# file as Environment= lines, using the [prod] sections from iot/env.file and
|
# using the [prod] section from efka/env.file. Directory creation is driven by
|
||||||
# efka/env.file. Directory creation is driven by those service env values.
|
# those service env values. EFKA_DIR_ENV_KEYS lists env keys whose values are
|
||||||
# IOT_DIR_ENV_KEYS and EFKA_DIR_ENV_KEYS list env keys whose values are
|
|
||||||
# directories that must be created if they do not exist.
|
# directories that must be created if they do not exist.
|
||||||
#
|
#
|
||||||
# Use {install_dir}, {work_dir} and {service_name} in commands and directories.
|
# Use {install_dir}, {work_dir} and {service_name} in commands and directories.
|
||||||
# service_user and service_group are optional.
|
# service_user and service_group are optional.
|
||||||
|
|
||||||
INSTALL_IOT=(
|
|
||||||
"iot"
|
|
||||||
"https://punchsky.cn/down/iot-0.1.0.tar.gz"
|
|
||||||
"{install_dir}/bin/iot foreground"
|
|
||||||
"{install_dir}/bin/iot stop"
|
|
||||||
""
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
IOT_ENV=(
|
|
||||||
"IOT_API_URL=http://127.0.0.1/api/v1"
|
|
||||||
"ENDPOINT_ROOT_DIR=/var/lib/endpoint/database/"
|
|
||||||
"ENDPOINT_LOG_PATH=/var/lib/endpoint/endpoint_log"
|
|
||||||
"IOT_MNESIA_DIR=/var/lib/iot/mnesia/"
|
|
||||||
)
|
|
||||||
|
|
||||||
IOT_DIR_ENV_KEYS=(
|
|
||||||
"ENDPOINT_ROOT_DIR"
|
|
||||||
"ENDPOINT_LOG_PATH"
|
|
||||||
"IOT_MNESIA_DIR"
|
|
||||||
)
|
|
||||||
|
|
||||||
INSTALL_EFKA=(
|
INSTALL_EFKA=(
|
||||||
"efka"
|
"efka"
|
||||||
"https://punchsky.cn/down/efka-0.1.0.tar.gz"
|
"https://punchsky.cn/down/efka-0.1.0.tar.gz"
|
||||||
@ -82,9 +59,9 @@ INSTALLED_SERVICES=()
|
|||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage:
|
Usage:
|
||||||
curl -fsSL https://punchsky.cn/install | bash
|
curl -fsSL https://punchsky.cn/install_efka.sh | bash
|
||||||
|
|
||||||
curl -fsSL https://punchsky.cn/install | bash -s -- \
|
curl -fsSL https://punchsky.cn/install_efka.sh | bash -s -- \
|
||||||
-w <work_dir>
|
-w <work_dir>
|
||||||
|
|
||||||
Config:
|
Config:
|
||||||
@ -97,11 +74,8 @@ Options:
|
|||||||
-h, --help Show this help.
|
-h, --help Show this help.
|
||||||
|
|
||||||
Array config inside script:
|
Array config inside script:
|
||||||
INSTALL_IOT
|
|
||||||
INSTALL_EFKA
|
INSTALL_EFKA
|
||||||
IOT_ENV
|
|
||||||
EFKA_ENV
|
EFKA_ENV
|
||||||
IOT_DIR_ENV_KEYS
|
|
||||||
EFKA_DIR_ENV_KEYS
|
EFKA_DIR_ENV_KEYS
|
||||||
|
|
||||||
Package format:
|
Package format:
|
||||||
@ -120,7 +94,7 @@ Placeholders:
|
|||||||
{service_name} will be replaced with the service name.
|
{service_name} will be replaced with the service name.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
curl -fsSL https://punchsky.cn/install | bash
|
curl -fsSL https://punchsky.cn/install_efka.sh | bash
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +185,6 @@ validate_args() {
|
|||||||
[[ -n "$WORK_DIR" ]] || fail "--work-dir is required"
|
[[ -n "$WORK_DIR" ]] || fail "--work-dir is required"
|
||||||
[[ "$WORK_DIR" = /* ]] || fail "--work-dir must be an absolute path"
|
[[ "$WORK_DIR" = /* ]] || fail "--work-dir must be an absolute path"
|
||||||
|
|
||||||
require_install_config INSTALL_IOT
|
|
||||||
require_install_config INSTALL_EFKA
|
require_install_config INSTALL_EFKA
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,9 +312,6 @@ service_env_lines() {
|
|||||||
local env_line
|
local env_line
|
||||||
|
|
||||||
case "$service_name" in
|
case "$service_name" in
|
||||||
iot)
|
|
||||||
env_array_name="IOT_ENV"
|
|
||||||
;;
|
|
||||||
efka)
|
efka)
|
||||||
env_array_name="EFKA_ENV"
|
env_array_name="EFKA_ENV"
|
||||||
;;
|
;;
|
||||||
@ -365,9 +335,6 @@ service_dir_env_keys() {
|
|||||||
local env_key
|
local env_key
|
||||||
|
|
||||||
case "$service_name" in
|
case "$service_name" in
|
||||||
iot)
|
|
||||||
dir_keys_array_name="IOT_DIR_ENV_KEYS"
|
|
||||||
;;
|
|
||||||
efka)
|
efka)
|
||||||
dir_keys_array_name="EFKA_DIR_ENV_KEYS"
|
dir_keys_array_name="EFKA_DIR_ENV_KEYS"
|
||||||
;;
|
;;
|
||||||
@ -497,7 +464,6 @@ install_program_from_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_programs() {
|
install_programs() {
|
||||||
install_program_from_config INSTALL_IOT
|
|
||||||
install_program_from_config INSTALL_EFKA
|
install_program_from_config INSTALL_EFKA
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +501,7 @@ main() {
|
|||||||
reload_systemd
|
reload_systemd
|
||||||
enable_installed_services
|
enable_installed_services
|
||||||
|
|
||||||
log "Installation completed"
|
log "EFKA installation completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
505
install_iot.sh
Executable file
505
install_iot.sh
Executable file
@ -0,0 +1,505 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Default config for:
|
||||||
|
# curl -fsSL https://punchsky.cn/install_iot.sh | bash
|
||||||
|
#
|
||||||
|
# Fill these values before publishing this script at https://punchsky.cn/install_iot.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_<APP>=(
|
||||||
|
# "service_name"
|
||||||
|
# "download_url"
|
||||||
|
# "start_command"
|
||||||
|
# "stop_command"
|
||||||
|
# "service_user"
|
||||||
|
# "service_group"
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# IOT_ENV is rendered into the systemd service file as Environment= lines,
|
||||||
|
# using the [prod] section from iot/env.file. Directory creation is driven by
|
||||||
|
# those service env values. IOT_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_IOT=(
|
||||||
|
"iot"
|
||||||
|
"https://punchsky.cn/down/iot-0.1.0.tar.gz"
|
||||||
|
"{install_dir}/bin/iot foreground"
|
||||||
|
"{install_dir}/bin/iot stop"
|
||||||
|
""
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
IOT_ENV=(
|
||||||
|
"IOT_API_URL=http://127.0.0.1/api/v1"
|
||||||
|
"ENDPOINT_ROOT_DIR=/var/lib/endpoint/database/"
|
||||||
|
"ENDPOINT_LOG_PATH=/var/lib/endpoint/endpoint_log"
|
||||||
|
"IOT_MNESIA_DIR=/var/lib/iot/mnesia/"
|
||||||
|
)
|
||||||
|
|
||||||
|
IOT_DIR_ENV_KEYS=(
|
||||||
|
"ENDPOINT_ROOT_DIR"
|
||||||
|
"ENDPOINT_LOG_PATH"
|
||||||
|
"IOT_MNESIA_DIR"
|
||||||
|
)
|
||||||
|
|
||||||
|
TEMP_FILES=()
|
||||||
|
INSTALLED_SERVICES=()
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
curl -fsSL https://punchsky.cn/install_iot.sh | bash
|
||||||
|
|
||||||
|
curl -fsSL https://punchsky.cn/install_iot.sh | 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_IOT
|
||||||
|
IOT_ENV
|
||||||
|
IOT_DIR_ENV_KEYS
|
||||||
|
|
||||||
|
Package format:
|
||||||
|
INSTALL_<APP>=(
|
||||||
|
"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_iot.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
|
||||||
|
}
|
||||||
|
|
||||||
|
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_IOT
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <<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}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
while IFS= read -r env_line; do
|
||||||
|
[[ -n "$env_line" ]] || continue
|
||||||
|
validate_environment_line "$env_line"
|
||||||
|
printf 'Environment="%s"\n' "$env_line"
|
||||||
|
done < <(service_env_lines "$service_name")
|
||||||
|
|
||||||
|
printf 'ExecStart=%s\n' "$start_command"
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
iot)
|
||||||
|
env_array_name="IOT_ENV"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
eval "env_count=\${#${env_array_name}[@]}"
|
||||||
|
for ((index=0; index<env_count; index++)); do
|
||||||
|
eval "env_line=\"\${${env_array_name}[$index]}\""
|
||||||
|
printf '%s\n' "$env_line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
service_dir_env_keys() {
|
||||||
|
local service_name="$1"
|
||||||
|
local dir_keys_array_name=""
|
||||||
|
local dir_keys_count
|
||||||
|
local index
|
||||||
|
local env_key
|
||||||
|
|
||||||
|
case "$service_name" in
|
||||||
|
iot)
|
||||||
|
dir_keys_array_name="IOT_DIR_ENV_KEYS"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
eval "dir_keys_count=\${#${dir_keys_array_name}[@]}"
|
||||||
|
for ((index=0; index<dir_keys_count; index++)); do
|
||||||
|
eval "env_key=\"\${${dir_keys_array_name}[$index]}\""
|
||||||
|
printf '%s\n' "$env_key"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
env_value_for_key() {
|
||||||
|
local service_name="$1"
|
||||||
|
local env_key="$2"
|
||||||
|
local env_line
|
||||||
|
local value
|
||||||
|
|
||||||
|
while IFS= read -r env_line; do
|
||||||
|
[[ "$env_line" == "${env_key}="* ]] || continue
|
||||||
|
value="${env_line#*=}"
|
||||||
|
printf '%s\n' "$value"
|
||||||
|
return 0
|
||||||
|
done < <(service_env_lines "$service_name")
|
||||||
|
|
||||||
|
fail "Missing env key $env_key for $service_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_dependency_dirs_from_env() {
|
||||||
|
local service_name="$1"
|
||||||
|
local install_dir="$2"
|
||||||
|
local service_user="$3"
|
||||||
|
local service_group="$4"
|
||||||
|
local env_key
|
||||||
|
local dependency_dir
|
||||||
|
|
||||||
|
while IFS= read -r env_key; do
|
||||||
|
[[ -n "$env_key" ]] || continue
|
||||||
|
dependency_dir="$(env_value_for_key "$service_name" "$env_key")"
|
||||||
|
dependency_dir="$(replace_placeholders "$dependency_dir" "$install_dir" "$service_name")"
|
||||||
|
|
||||||
|
[[ "$dependency_dir" = /* ]] || fail "Dependency directory must be an absolute path: $dependency_dir"
|
||||||
|
|
||||||
|
log "Creating dependency directory from $env_key: $dependency_dir"
|
||||||
|
run_as_root install -d -m 0755 "$dependency_dir"
|
||||||
|
run_as_root chown -R "${service_user}:${service_group}" "$dependency_dir"
|
||||||
|
done < <(service_dir_env_keys "$service_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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 "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_IOT
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "IOT installation completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@ -1,12 +1,15 @@
|
|||||||
# install_service.sh 规则与参考原则
|
# install service 脚本规则与参考原则
|
||||||
|
|
||||||
本文档说明 `shell/install_service.sh` 的配置规则、安装流程和后续维护原则。
|
本文档说明 `shell/install_iot.sh`、`shell/install_efka.sh` 的配置规则、安装流程和后续维护原则。
|
||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
|
|
||||||
`install_service.sh` 用于自动安装并通过 systemd 管理 `iot` 和 `efka` 服务。
|
安装逻辑已经按服务拆分:
|
||||||
|
|
||||||
脚本负责:
|
- `install_iot.sh`: 只安装并配置 `iot` 服务。
|
||||||
|
- `install_efka.sh`: 只安装并配置 `efka` 服务。
|
||||||
|
|
||||||
|
服务脚本负责:
|
||||||
|
|
||||||
- 下载服务发布包。
|
- 下载服务发布包。
|
||||||
- 解压到 `WORK_DIR`。
|
- 解压到 `WORK_DIR`。
|
||||||
@ -14,15 +17,36 @@
|
|||||||
- 生成 `/etc/systemd/system/<service>.service`。
|
- 生成 `/etc/systemd/system/<service>.service`。
|
||||||
- 将服务环境变量写入对应 `.service` 文件。
|
- 将服务环境变量写入对应 `.service` 文件。
|
||||||
- 执行 `systemctl daemon-reload`。
|
- 执行 `systemctl daemon-reload`。
|
||||||
- 设置服务开机启动并启动服务。
|
- 设置服务开机启动。
|
||||||
|
|
||||||
|
## 入口规则
|
||||||
|
|
||||||
|
只安装 iot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://punchsky.cn/install_iot.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
只安装 efka:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://punchsky.cn/install_efka.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
本地执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install_iot.sh
|
||||||
|
sudo ./install_efka.sh
|
||||||
|
```
|
||||||
|
|
||||||
## 顶部配置规则
|
## 顶部配置规则
|
||||||
|
|
||||||
脚本顶部是主要维护区域。后续修改下载包、启动命令、环境变量、目录变量时,应优先修改顶部配置。
|
服务配置只维护在对应拆分脚本顶部。
|
||||||
|
|
||||||
### INSTALL_IOT / INSTALL_EFKA
|
### INSTALL_IOT / INSTALL_EFKA
|
||||||
|
|
||||||
`INSTALL_IOT` 和 `INSTALL_EFKA` 分别配置服务安装信息,不再合并成统一的 `INSTALL_ITEMS`。
|
`install_iot.sh` 维护 `INSTALL_IOT`,`install_efka.sh` 维护 `INSTALL_EFKA`。
|
||||||
|
|
||||||
格式:
|
格式:
|
||||||
|
|
||||||
@ -56,40 +80,16 @@ INSTALL_<APP>=(
|
|||||||
|
|
||||||
`IOT_ENV` 和 `EFKA_ENV` 配置对应项目的生产环境变量,来源参考各项目 `env.file` 中的 `[prod]` 配置。
|
`IOT_ENV` 和 `EFKA_ENV` 配置对应项目的生产环境变量,来源参考各项目 `env.file` 中的 `[prod]` 配置。
|
||||||
|
|
||||||
格式:
|
这些变量不会写入 `/etc/default/*`,也不会使用 `EnvironmentFile=`。脚本会直接将它们写入对应 systemd service:
|
||||||
|
|
||||||
```bash
|
|
||||||
IOT_ENV=(
|
|
||||||
"KEY=value"
|
|
||||||
)
|
|
||||||
|
|
||||||
EFKA_ENV=(
|
|
||||||
"KEY=value"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
这些变量不会写入 `/etc/default/*`,也不会使用 `EnvironmentFile=`。
|
|
||||||
|
|
||||||
脚本会直接将它们写入对应 systemd service:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
Environment="KEY=value"
|
Environment="KEY=value"
|
||||||
```
|
```
|
||||||
|
|
||||||
这样环境变量由 systemd unit 统一管理,可通过 `systemctl cat <service>` 查看最终配置。
|
|
||||||
|
|
||||||
### IOT_DIR_ENV_KEYS / EFKA_DIR_ENV_KEYS
|
### IOT_DIR_ENV_KEYS / EFKA_DIR_ENV_KEYS
|
||||||
|
|
||||||
`IOT_DIR_ENV_KEYS` 和 `EFKA_DIR_ENV_KEYS` 声明哪些环境变量的值是目录。
|
`IOT_DIR_ENV_KEYS` 和 `EFKA_DIR_ENV_KEYS` 声明哪些环境变量的值是目录。
|
||||||
|
|
||||||
格式:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IOT_DIR_ENV_KEYS=(
|
|
||||||
"ENDPOINT_ROOT_DIR"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
安装时脚本会:
|
安装时脚本会:
|
||||||
|
|
||||||
- 从对应 `*_ENV` 中读取变量值。
|
- 从对应 `*_ENV` 中读取变量值。
|
||||||
@ -112,15 +112,15 @@ IOT_DIR_ENV_KEYS=(
|
|||||||
|
|
||||||
## 安装流程
|
## 安装流程
|
||||||
|
|
||||||
脚本执行顺序:
|
单个服务脚本执行顺序:
|
||||||
|
|
||||||
1. 解析命令行参数。
|
1. 解析命令行参数。
|
||||||
2. 校验 `WORK_DIR` 和服务配置。
|
2. 校验 `WORK_DIR` 和服务配置。
|
||||||
3. 检查依赖命令:`wget`、`tar`、`systemctl`。
|
3. 检查依赖命令:`wget`、`tar`、`systemctl`。
|
||||||
4. 创建 `WORK_DIR`。
|
4. 创建 `WORK_DIR`。
|
||||||
5. 依次安装 `iot` 和 `efka`。
|
5. 安装当前服务。
|
||||||
6. 重新加载 systemd。
|
6. 重新加载 systemd。
|
||||||
7. enable 并 start 已安装服务。
|
7. enable 已安装服务。
|
||||||
|
|
||||||
单个服务安装流程:
|
单个服务安装流程:
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ IOT_DIR_ENV_KEYS=(
|
|||||||
7. 设置安装目录 owner。
|
7. 设置安装目录 owner。
|
||||||
8. 根据 `*_DIR_ENV_KEYS` 创建数据目录。
|
8. 根据 `*_DIR_ENV_KEYS` 创建数据目录。
|
||||||
9. 生成 systemd service 文件。
|
9. 生成 systemd service 文件。
|
||||||
10. 记录服务名,后续统一 enable/start。
|
10. 记录服务名,后续统一 enable。
|
||||||
|
|
||||||
## systemd service 生成规则
|
## systemd service 生成规则
|
||||||
|
|
||||||
@ -160,12 +160,6 @@ service 文件包含:
|
|||||||
|
|
||||||
只有明确列入 `IOT_DIR_ENV_KEYS` 或 `EFKA_DIR_ENV_KEYS` 的环境变量才会作为目录创建。
|
只有明确列入 `IOT_DIR_ENV_KEYS` 或 `EFKA_DIR_ENV_KEYS` 的环境变量才会作为目录创建。
|
||||||
|
|
||||||
这样做的原因:
|
|
||||||
|
|
||||||
- 避免误把 URL、token、host 等普通变量当成路径。
|
|
||||||
- 新增目录型变量时行为明确。
|
|
||||||
- 目录创建逻辑和环境变量配置保持关联。
|
|
||||||
|
|
||||||
新增目录型环境变量时,需要同时修改两处:
|
新增目录型环境变量时,需要同时修改两处:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -199,7 +193,8 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
|
|||||||
可通过参数覆盖:
|
可通过参数覆盖:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install_service.sh --user app --group app
|
./install_iot.sh --user app --group app
|
||||||
|
./install_efka.sh --user app --group app
|
||||||
```
|
```
|
||||||
|
|
||||||
## 占位符原则
|
## 占位符原则
|
||||||
@ -217,19 +212,13 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
|
|||||||
- `{work_dir}`
|
- `{work_dir}`
|
||||||
- `{service_name}`
|
- `{service_name}`
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
"LOG_DIR={work_dir}/var/log/{service_name}"
|
|
||||||
```
|
|
||||||
|
|
||||||
如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。
|
如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。
|
||||||
|
|
||||||
## 修改和扩展原则
|
## 修改和扩展原则
|
||||||
|
|
||||||
### 修改环境变量
|
### 修改环境变量
|
||||||
|
|
||||||
只修改顶部对应数组:
|
只修改对应服务脚本顶部数组:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
IOT_ENV=(
|
IOT_ENV=(
|
||||||
@ -237,7 +226,7 @@ IOT_ENV=(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
修改后重新执行安装脚本,会重新生成 service 文件。
|
修改后重新执行对应服务脚本,会重新生成 service 文件。
|
||||||
|
|
||||||
### 新增目录
|
### 新增目录
|
||||||
|
|
||||||
@ -248,7 +237,7 @@ IOT_ENV=(
|
|||||||
|
|
||||||
### 修改下载包
|
### 修改下载包
|
||||||
|
|
||||||
只修改:
|
只修改对应服务脚本:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
INSTALL_IOT=(
|
INSTALL_IOT=(
|
||||||
@ -265,15 +254,15 @@ INSTALL_IOT=(
|
|||||||
|
|
||||||
### 新增服务
|
### 新增服务
|
||||||
|
|
||||||
新增服务时应保持当前结构:
|
新增服务时应按现有拆分结构创建新的 `install_<app>.sh`:
|
||||||
|
|
||||||
1. 新增 `INSTALL_<APP>`。
|
1. 新增 `INSTALL_<APP>`。
|
||||||
2. 新增 `<APP>_ENV`。
|
2. 新增 `<APP>_ENV`。
|
||||||
3. 新增 `<APP>_DIR_ENV_KEYS`。
|
3. 新增 `<APP>_DIR_ENV_KEYS`。
|
||||||
4. 在 `validate_args` 中增加配置校验。
|
4. 在 `validate_args` 中校验对应配置。
|
||||||
5. 在 `service_env_lines` 中映射服务名到 env 数组。
|
5. 在 `service_env_lines` 中映射服务名到 env 数组。
|
||||||
6. 在 `service_dir_env_keys` 中映射服务名到目录 key 数组。
|
6. 在 `service_dir_env_keys` 中映射服务名到目录 key 数组。
|
||||||
7. 在 `install_programs` 中调用 `install_program_from_config`。
|
7. 在 `install_programs` 中调用对应配置。
|
||||||
|
|
||||||
## 兼容性原则
|
## 兼容性原则
|
||||||
|
|
||||||
@ -286,13 +275,15 @@ INSTALL_IOT=(
|
|||||||
修改脚本后至少执行:
|
修改脚本后至少执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash -n shell/install_service.sh
|
bash -n shell/install_iot.sh
|
||||||
|
bash -n shell/install_efka.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
建议额外验证 service 渲染结果:
|
建议额外验证 service 渲染结果:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash -c "$(sed '$d' shell/install_service.sh); render_service_file iot app app /opt/app/iot-0.1.0 '/opt/app/iot-0.1.0/bin/iot foreground' '/opt/app/iot-0.1.0/bin/iot stop'"
|
bash -c "$(sed '$d' shell/install_iot.sh); render_service_file iot app app /opt/app/iot-0.1.0 '/opt/app/iot-0.1.0/bin/iot foreground' '/opt/app/iot-0.1.0/bin/iot stop'"
|
||||||
|
bash -c "$(sed '$d' shell/install_efka.sh); render_service_file efka app app /opt/app/efka-0.1.0 '/opt/app/efka-0.1.0/bin/efka foreground' '/opt/app/efka-0.1.0/bin/efka stop'"
|
||||||
```
|
```
|
||||||
|
|
||||||
如果目标机器有 `systemd-analyze`,可进一步验证生成后的 unit 文件:
|
如果目标机器有 `systemd-analyze`,可进一步验证生成后的 unit 文件:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
WORK_DIR="${WORK_DIR:-/opt/app}"
|
WORK_DIR="${WORK_DIR:-/opt/app}"
|
||||||
PURGE_DATA="${PURGE_DATA:-0}"
|
PURGE_DATA="${PURGE_DATA:-0}"
|
||||||
|
|
||||||
# Keep this list in sync with install_service.sh.
|
# Keep this list in sync with install_iot.sh and install_efka.sh.
|
||||||
# Format:
|
# Format:
|
||||||
# "service_name|download_url|start_command|stop_command|dependency_dirs|service_user|service_group"
|
# "service_name|download_url|start_command|stop_command|dependency_dirs|service_user|service_group"
|
||||||
INSTALL_ITEMS=(
|
INSTALL_ITEMS=(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user