v0.0.1 release
This commit is contained in:
+170
-33
@@ -1,23 +1,30 @@
|
||||
#!/bin/bash
|
||||
# QUptime installer.
|
||||
#
|
||||
# Downloads the latest released `qu` binary from the Gitea release
|
||||
# page, verifies it against the published SHA256SUMS, installs it to
|
||||
# /usr/local/bin, and (on systemd hosts) drops in a hardened
|
||||
# quptime.service that matches the unit documented in
|
||||
# docs/deployment/systemd.md. Idempotent — re-running upgrades the
|
||||
# binary and refreshes the unit without touching the data directory.
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_BIN="/usr/local/bin/qu"
|
||||
SERVICE_FILE="/etc/systemd/system/qu-serve.service"
|
||||
SERVICE_USER="${SUDO_USER:-$(whoami)}"
|
||||
SERVICE_GROUP="$(id -gn "$SERVICE_USER" 2>/dev/null || echo root)"
|
||||
SERVICE_FILE="/etc/systemd/system/quptime.service"
|
||||
SERVICE_NAME="$(basename "$SERVICE_FILE")"
|
||||
SERVICE_USER="quptime"
|
||||
SERVICE_GROUP="quptime"
|
||||
DATA_DIR="/etc/quptime"
|
||||
REPO_API="https://git.cer.sh/api/v1/repos/axodouble/quptime/releases/latest"
|
||||
RELEASE_BASE="https://git.cer.sh/axodouble/quptime/releases/download"
|
||||
|
||||
fail() {
|
||||
echo "Error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo_cmd() {
|
||||
echo -e "\033[90m> $1\033[0m"
|
||||
eval "$1"
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" > /dev/null 2>&1 || fail "$1 is not installed. Please install $1 and try again."
|
||||
command -v "$1" >/dev/null 2>&1 || fail "$1 is not installed. Please install $1 and try again."
|
||||
}
|
||||
|
||||
write_completion() {
|
||||
@@ -31,52 +38,182 @@ write_completion() {
|
||||
return 1
|
||||
}
|
||||
|
||||
require_command jq
|
||||
require_command curl
|
||||
require_command jq
|
||||
require_command sha256sum
|
||||
require_command install
|
||||
require_command mktemp
|
||||
|
||||
# --- target architecture ------------------------------------------------
|
||||
case "$(uname -m)" in
|
||||
x86_64) ARCH=amd64 ;;
|
||||
aarch64|arm64) ARCH=arm64 ;;
|
||||
*) fail "unsupported architecture: $(uname -m). Pre-built binaries are published for amd64 and arm64 only — build from source for other platforms." ;;
|
||||
esac
|
||||
|
||||
if [ ! -w "$(dirname "$INSTALL_BIN")" ]; then
|
||||
fail "You are not allowed to write to $(dirname "$INSTALL_BIN"). Run this script with sudo or install qu manually."
|
||||
fail "Cannot write to $(dirname "$INSTALL_BIN"). Run this script with sudo, or set INSTALL_BIN to a writable location."
|
||||
fi
|
||||
|
||||
RELEASE=$(curl -s https://git.cer.sh/api/v1/repos/axodouble/quptime/releases/latest | jq -r '.tag_name')
|
||||
# --- latest release tag -------------------------------------------------
|
||||
RELEASE=$(curl -fsSL "$REPO_API" | jq -r '.tag_name')
|
||||
[ -n "$RELEASE" ] && [ "$RELEASE" != "null" ] \
|
||||
|| fail "could not determine the latest release tag from $REPO_API"
|
||||
|
||||
echo_cmd "curl -L -o '$INSTALL_BIN' 'https://git.cer.sh/axodouble/quptime/releases/download/${RELEASE}/qu-${RELEASE}-linux-amd64'"
|
||||
echo_cmd "chmod +x '$INSTALL_BIN'"
|
||||
echo "> qu has been installed to $INSTALL_BIN"
|
||||
BINARY_NAME="qu-${RELEASE}-linux-${ARCH}"
|
||||
BINARY_URL="${RELEASE_BASE}/${RELEASE}/${BINARY_NAME}"
|
||||
SUMS_URL="${RELEASE_BASE}/${RELEASE}/SHA256SUMS"
|
||||
|
||||
# --- download + verify --------------------------------------------------
|
||||
# Stage in a temp dir so a failed verification never leaves a partial
|
||||
# or unverified binary on disk.
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
echo "> downloading $BINARY_NAME"
|
||||
curl -fsSL --proto '=https' --tlsv1.2 -o "$TMPDIR/$BINARY_NAME" "$BINARY_URL"
|
||||
echo "> downloading SHA256SUMS"
|
||||
curl -fsSL --proto '=https' --tlsv1.2 -o "$TMPDIR/SHA256SUMS" "$SUMS_URL"
|
||||
|
||||
echo "> verifying checksum"
|
||||
# Pull just our binary's entry so sha256sum -c doesn't fail on the
|
||||
# arches we didn't download.
|
||||
(
|
||||
cd "$TMPDIR"
|
||||
if ! grep -E "[[:space:]]\\*?${BINARY_NAME}\$" SHA256SUMS > expected.sum; then
|
||||
fail "no entry for $BINARY_NAME in published SHA256SUMS — refusing to install"
|
||||
fi
|
||||
if ! sha256sum -c expected.sum >/dev/null 2>&1; then
|
||||
echo "expected: $(awk '{print $1}' expected.sum)"
|
||||
echo "actual: $(sha256sum "$BINARY_NAME" | awk '{print $1}')"
|
||||
fail "checksum mismatch for $BINARY_NAME — refusing to install"
|
||||
fi
|
||||
)
|
||||
echo "> checksum OK"
|
||||
|
||||
install -m 0755 "$TMPDIR/$BINARY_NAME" "$INSTALL_BIN"
|
||||
echo "> qu ${RELEASE} installed to $INSTALL_BIN"
|
||||
|
||||
# --- shell completions --------------------------------------------------
|
||||
if "$INSTALL_BIN" --help 2>/dev/null | grep -q "completion"; then
|
||||
write_completion bash /usr/share/bash-completion/completions/qu \
|
||||
|| write_completion bash /etc/bash_completion.d/qu || true
|
||||
write_completion zsh /usr/share/zsh/site-functions/_qu || true
|
||||
write_completion fish /usr/share/fish/vendor_completions.d/qu.fish || true
|
||||
|| write_completion bash /etc/bash_completion.d/qu \
|
||||
|| true
|
||||
write_completion zsh /usr/share/zsh/site-functions/_qu || true
|
||||
write_completion fish /usr/share/fish/vendor_completions.d/qu.fish || true
|
||||
else
|
||||
echo "> qu does not expose completion support; skipping shell completion installation."
|
||||
fi
|
||||
|
||||
if ! command -v systemctl > /dev/null 2>&1; then
|
||||
echo "> Warning: systemd is not available on this system. qu serve will not be automatically started on boot."
|
||||
echo "Installation complete, before starting qu serve, make sure to run qu init and read the documentation."
|
||||
# --- systemd unit -------------------------------------------------------
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
echo
|
||||
echo "> systemd is not available on this system. Installation stops here."
|
||||
echo "> Run \`qu serve\` manually (or wire it into the supervisor of your choice)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "> Creating systemd service file for qu serve..."
|
||||
cat > "$SERVICE_FILE" <<EOL
|
||||
# Dedicated service user. Hardened unit drops all capabilities and
|
||||
# locks the daemon down with ProtectSystem=strict, so it must run as
|
||||
# its own unprivileged account rather than the invoking sudo user.
|
||||
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
echo "> creating system user $SERVICE_USER"
|
||||
useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER"
|
||||
fi
|
||||
|
||||
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 "$DATA_DIR"
|
||||
|
||||
echo "> writing $SERVICE_FILE"
|
||||
cat > "$SERVICE_FILE" <<'EOF'
|
||||
[Unit]
|
||||
Description=QUptime Serve
|
||||
After=network.target
|
||||
Description=QUptime distributed uptime monitor
|
||||
Documentation=https://git.cer.sh/axodouble/quptime
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=$INSTALL_BIN serve
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/qu serve
|
||||
Restart=always
|
||||
User=$SERVICE_USER
|
||||
Group=$SERVICE_GROUP
|
||||
RestartSec=5s
|
||||
|
||||
User=quptime
|
||||
Group=quptime
|
||||
|
||||
# Where state lives. RuntimeDirectory creates /var/run/quptime/ each
|
||||
# boot owned by User:Group with mode 0750.
|
||||
Environment=QUPTIME_DIR=/etc/quptime
|
||||
RuntimeDirectory=quptime
|
||||
RuntimeDirectoryMode=0750
|
||||
ReadWritePaths=/etc/quptime /var/run/quptime
|
||||
|
||||
# Hardening. Comment out individual directives if a probe needs
|
||||
# something we've revoked.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
ProtectClock=true
|
||||
ProtectHostname=true
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
# Network access is required (we're a network monitor). Keep address
|
||||
# families minimal — AF_NETLINK is needed for some libc lookups.
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
|
||||
# If you need raw ICMP, *also* uncomment:
|
||||
# AmbientCapabilities=CAP_NET_RAW
|
||||
# CapabilityBoundingSet=CAP_NET_RAW
|
||||
# Otherwise drop all capabilities:
|
||||
CapabilityBoundingSet=
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
EOF
|
||||
|
||||
echo_cmd "systemctl daemon-reload"
|
||||
echo_cmd "systemctl enable $(basename "$SERVICE_FILE")"
|
||||
echo "> qu serve service has been created and enabled. You can start it with 'systemctl start $(basename "$SERVICE_FILE")'"
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME" >/dev/null
|
||||
echo "> ${SERVICE_NAME} installed and enabled (not yet started)"
|
||||
|
||||
echo "Installation complete, before starting qu serve, make sure to run qu init and read the documentation."
|
||||
cat <<EOF
|
||||
|
||||
Installation complete.
|
||||
|
||||
Next steps:
|
||||
|
||||
1. Initialise the node identity. Either:
|
||||
|
||||
a) Let \`qu serve\` auto-init from environment variables.
|
||||
Drop a systemd override like:
|
||||
|
||||
sudo systemctl edit ${SERVICE_NAME}
|
||||
[Service]
|
||||
Environment=QUPTIME_ADVERTISE=<this-host>:9901
|
||||
# On follower nodes, also set the shared join secret:
|
||||
# Environment=QUPTIME_CLUSTER_SECRET=<paste from first node>
|
||||
|
||||
b) Or run \`qu init\` once explicitly:
|
||||
|
||||
sudo -u ${SERVICE_USER} QUPTIME_DIR=${DATA_DIR} \\
|
||||
qu init --advertise <this-host>:9901
|
||||
|
||||
2. Start the service:
|
||||
|
||||
sudo systemctl start ${SERVICE_NAME}
|
||||
sudo -u ${SERVICE_USER} qu status
|
||||
|
||||
3. For ICMP checks, the daemon defaults to unprivileged UDP-mode
|
||||
pings — those need the ping_group_range sysctl widened to include
|
||||
the ${SERVICE_USER} GID, or grant CAP_NET_RAW in the unit. See
|
||||
docs/deployment/systemd.md for the recipes.
|
||||
|
||||
Full documentation: https://git.cer.sh/axodouble/quptime/src/branch/master/docs
|
||||
EOF
|
||||
|
||||
Reference in New Issue
Block a user