feat: add managed online updates
Signed Release / release (push) Successful in 9m24s

This commit is contained in:
Qiufeng
2026-08-04 20:05:50 +08:00
parent 4f2ea26e8e
commit d3892320dd
20 changed files with 1365 additions and 111 deletions
+97 -4
View File
@@ -8,6 +8,7 @@ INSTALL_ROOT="${ERP_INSTALL_ROOT:-}"
ALLOW_INSECURE="${ERP_UPDATE_ALLOW_INSECURE_HTTP:-0}"
NO_SERVICE="${ERP_INSTALL_NO_SERVICE:-0}"
TOKEN="${ERP_GITEA_TOKEN:-${OA_UPDATE_TOKEN:-}}"
PUBLIC_URL="${ERP_PUBLIC_URL:-}"
PUBLIC_KEY='-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAaErhcY8WZIZvPILmYnfjndVBAdOuWkvhaoHIWqNNdxI=
-----END PUBLIC KEY-----'
@@ -23,6 +24,7 @@ Usage: install.sh [options]
--repository O/R Release repository (default: awaioi/ERP)
--version VERSION Install one exact stable release
--install-root PATH Override installation directory
--public-url URL Public URL used for the one-time web setup link
--allow-insecure Development only: allow plain HTTP release URLs
--no-service Start without systemd/launchd; online update stays disabled
EOF
@@ -34,6 +36,7 @@ while [[ $# -gt 0 ]]; do
--repository) [[ $# -ge 2 ]] || fail '--repository requires a value'; REPOSITORY="$2"; shift 2 ;;
--version) [[ $# -ge 2 ]] || fail '--version requires a value'; REQUESTED_VERSION="$2"; shift 2 ;;
--install-root) [[ $# -ge 2 ]] || fail '--install-root requires a value'; INSTALL_ROOT="$2"; shift 2 ;;
--public-url) [[ $# -ge 2 ]] || fail '--public-url requires a value'; PUBLIC_URL="$2"; shift 2 ;;
--allow-insecure) ALLOW_INSECURE=1; shift ;;
--no-service) NO_SERVICE=1; shift ;;
-h|--help) usage; exit 0 ;;
@@ -519,6 +522,74 @@ detect_lan_address() {
printf '%s' "${address:-127.0.0.1}"
}
is_ip_address() {
python3 - "$1" <<'PY' >/dev/null 2>&1
import ipaddress, sys
ipaddress.ip_address(sys.argv[1])
PY
}
detect_public_address() {
local endpoint address
for endpoint in \
https://api.ipify.org \
https://ifconfig.me/ip \
https://icanhazip.com; do
address="$(curl --silent --show-error --fail --location \
--proto '=https' --proto-redir '=https' \
--connect-timeout 3 --max-time 5 "$endpoint" 2>/dev/null \
| tr -d '[:space:]' | head -c 128 || true)"
if [[ -n "$address" ]] && is_ip_address "$address"; then
printf '%s' "$address"
return 0
fi
done
return 1
}
normalize_public_url() {
python3 - "$1" <<'PY'
import sys
from urllib.parse import urlsplit, urlunsplit
value = sys.argv[1].strip()
try:
parsed = urlsplit(value)
port = parsed.port
except ValueError:
raise SystemExit("invalid public URL")
if parsed.scheme.lower() not in {"http", "https"} or not parsed.hostname:
raise SystemExit("public URL must use http or https")
if parsed.username is not None or parsed.password is not None:
raise SystemExit("public URL must not contain credentials")
if port is not None and not 1 <= port <= 65535:
raise SystemExit("invalid public URL port")
path = parsed.path or "/"
print(urlunsplit((parsed.scheme.lower(), parsed.netloc, path, parsed.query, parsed.fragment)))
PY
}
ip_setup_base_url() {
python3 - "$1" "$2" <<'PY'
import ipaddress, sys
address = ipaddress.ip_address(sys.argv[1])
host = f"[{address}]" if address.version == 6 else str(address)
print(f"http://{host}:{int(sys.argv[2])}/")
PY
}
append_setup_token() {
python3 - "$1" "$2" <<'PY'
import sys
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
parsed = urlsplit(sys.argv[1])
query = [(key, value) for key, value in parse_qsl(parsed.query, keep_blank_values=True) if key != "token"]
query.append(("token", sys.argv[2]))
print(urlunsplit((parsed.scheme, parsed.netloc, parsed.path or "/", urlencode(query), parsed.fragment)))
PY
}
main() {
detect_platform
validate_service_manager
@@ -531,9 +602,18 @@ main() {
fi
[[ -n "$GITEA_BASE_URL" ]] || fail 'Gitea URL is required; use --gitea-url or ERP_GITEA_BASE_URL'
validate_download_url "$GITEA_BASE_URL"
if [[ -n "$PUBLIC_URL" ]]; then
case "$PUBLIC_URL" in
http://*|https://*) ;;
*) fail 'public URL must start with http:// or https://' ;;
esac
fi
say "Detected $PLATFORM/$ARCH"
check_and_install_dependencies
if [[ -n "$PUBLIC_URL" ]]; then
PUBLIC_URL="$(normalize_public_url "$PUBLIC_URL")" || fail 'invalid public URL'
fi
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kaidi-erp-install.XXXXXX")"
create_curl_config
say 'Downloading and verifying the signed release...'
@@ -545,11 +625,24 @@ main() {
start_service
wait_for_installer
local port="${ERP_SERVER_PORT:-8091}" address
address="$(detect_lan_address)"
local port="${ERP_SERVER_PORT:-8091}" lan_address public_address setup_base local_base lan_base
lan_address="$(detect_lan_address)"
local_base="http://127.0.0.1:${port}/"
lan_base="$(ip_setup_base_url "$lan_address" "$port")"
if [[ -n "$PUBLIC_URL" ]]; then
setup_base="$PUBLIC_URL"
elif public_address="$(detect_public_address)"; then
setup_base="$(ip_setup_base_url "$public_address" "$port")"
else
setup_base="$lan_base"
say 'Public IP detection was unavailable; using the LAN address'
fi
say "Kaidi ERP $VERSION installer is running"
say "Setup URL: http://${address}:${port}/?token=${SETUP_TOKEN}"
say "Local URL: http://127.0.0.1:${port}/?token=${SETUP_TOKEN}"
say "Setup URL: $(append_setup_token "$setup_base" "$SETUP_TOKEN")"
say "Local URL: $(append_setup_token "$local_base" "$SETUP_TOKEN")"
if [[ "$lan_address" != "127.0.0.1" && "$lan_base" != "$setup_base" ]]; then
say "LAN URL: $(append_setup_token "$lan_base" "$SETUP_TOKEN")"
fi
say 'Complete PostgreSQL and administrator setup in the browser. The installer will remove itself after the formal service is healthy.'
}