mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-03 17:27:06 +00:00
237 lines
8.1 KiB
Bash
Executable File
237 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh — install livesync-cli as a systemd service
|
|
#
|
|
# Usage:
|
|
# install.sh [--user] [--system] [--vault <path>] [--interval <N>]
|
|
#
|
|
# Defaults: user install, prompts for vault path if not supplied.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../../.." && pwd)"
|
|
CLI_DIR="$REPO_ROOT/src/apps/cli"
|
|
SERVICE_TEMPLATE="$SCRIPT_DIR/livesync-cli.service"
|
|
|
|
# ── Argument parsing ────────────────────────────────────────────────────────
|
|
INSTALL_MODE="user"
|
|
VAULT_PATH=""
|
|
INTERVAL=""
|
|
FORCE=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--user)
|
|
INSTALL_MODE="user"
|
|
shift
|
|
;;
|
|
--system)
|
|
INSTALL_MODE="system"
|
|
shift
|
|
;;
|
|
--vault)
|
|
if [[ -z "${2:-}" ]]; then
|
|
echo "Error: --vault requires a path argument" >&2
|
|
exit 1
|
|
fi
|
|
VAULT_PATH="$2"
|
|
shift 2
|
|
;;
|
|
--interval)
|
|
if [[ -z "${2:-}" ]]; then
|
|
echo "Error: --interval requires a numeric argument" >&2
|
|
exit 1
|
|
fi
|
|
INTERVAL="$2"
|
|
if ! [[ "$INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "Error: --interval requires a positive integer, got '$INTERVAL'" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--force|-f)
|
|
FORCE=1
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
cat <<EOF
|
|
Usage: install.sh [--user|--system] [--vault <path>] [--interval <N>] [--force]
|
|
|
|
--user Install as a user systemd service (default, ~/.config/systemd/user/)
|
|
--system Install as a system systemd service (/etc/systemd/system/)
|
|
--vault Path to the vault directory (prompted if omitted)
|
|
--interval Poll CouchDB every N seconds instead of using the _changes feed
|
|
--force Overwrite existing service unit without prompting
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Error: Unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ── Vault path ──────────────────────────────────────────────────────────────
|
|
if [[ -z "$VAULT_PATH" ]]; then
|
|
if [ ! -t 0 ]; then
|
|
echo "Error: --vault is required in non-interactive mode" >&2
|
|
exit 1
|
|
fi
|
|
printf 'Vault path: '
|
|
read -r VAULT_PATH
|
|
fi
|
|
|
|
_orig_vault="$VAULT_PATH"
|
|
if ! VAULT_PATH="$(cd -- "$VAULT_PATH" 2>/dev/null && pwd)"; then
|
|
echo "Error: vault directory does not exist: $_orig_vault" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[INFO] Vault: $VAULT_PATH"
|
|
echo "[INFO] Install mode: $INSTALL_MODE"
|
|
|
|
# ── Build ────────────────────────────────────────────────────────────────────
|
|
echo "[INFO] Building CLI from $REPO_ROOT..."
|
|
(cd "$REPO_ROOT" && npm install --silent)
|
|
(cd "$CLI_DIR" && npm run build)
|
|
|
|
BUILT_CJS="$CLI_DIR/dist/index.cjs"
|
|
if [[ ! -f "$BUILT_CJS" ]]; then
|
|
echo "Error: build output not found: $BUILT_CJS" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── Install binary ───────────────────────────────────────────────────────────
|
|
if [[ "$INSTALL_MODE" == "user" ]]; then
|
|
BIN_DIR="$HOME/.local/bin"
|
|
LIB_DIR="$HOME/.local/lib/livesync-cli"
|
|
UNIT_DIR="$HOME/.config/systemd/user"
|
|
SYSTEMCTL_FLAGS="--user"
|
|
else
|
|
BIN_DIR="/usr/local/bin"
|
|
LIB_DIR="/usr/local/lib/livesync-cli"
|
|
UNIT_DIR="/etc/systemd/system"
|
|
SYSTEMCTL_FLAGS=""
|
|
fi
|
|
|
|
LIB_PARENT="$(dirname -- "$LIB_DIR")"
|
|
mkdir -p "$BIN_DIR" "$LIB_PARENT"
|
|
|
|
LIVESYNC_BIN="$BIN_DIR/livesync-cli"
|
|
LIVESYNC_JS="$LIB_DIR/dist/index.cjs"
|
|
|
|
# Build a complete runtime payload before replacing any previous installation.
|
|
# The Vite output contains hashed sibling chunks, while some Node dependencies
|
|
# deliberately remain external and must be installed next to the bundle.
|
|
PAYLOAD_STAGING="$(mktemp -d "$LIB_PARENT/.livesync-cli.install.XXXXXX")"
|
|
cleanup_payload() {
|
|
if [[ -n "$PAYLOAD_STAGING" ]] && [[ -e "$PAYLOAD_STAGING" ]]; then
|
|
rm -rf -- "$PAYLOAD_STAGING"
|
|
fi
|
|
}
|
|
trap cleanup_payload EXIT
|
|
|
|
cp "$CLI_DIR/package.json" "$PAYLOAD_STAGING/package.json"
|
|
npm install --omit=dev --no-audit --no-fund --prefix "$PAYLOAD_STAGING"
|
|
cp -R "$CLI_DIR/dist" "$PAYLOAD_STAGING/dist"
|
|
|
|
if ! node "$PAYLOAD_STAGING/dist/index.cjs" --help >/dev/null; then
|
|
echo "Error: installed CLI failed its start-up check" >&2
|
|
exit 1
|
|
fi
|
|
|
|
PAYLOAD_BACKUP=""
|
|
if [[ -e "$LIB_DIR" ]] || [[ -L "$LIB_DIR" ]]; then
|
|
PAYLOAD_BACKUP="$(mktemp -d "$LIB_PARENT/.livesync-cli.backup.XXXXXX")"
|
|
rmdir "$PAYLOAD_BACKUP"
|
|
mv -- "$LIB_DIR" "$PAYLOAD_BACKUP"
|
|
fi
|
|
|
|
if ! mv -- "$PAYLOAD_STAGING" "$LIB_DIR"; then
|
|
if [[ -n "$PAYLOAD_BACKUP" ]]; then
|
|
mv -- "$PAYLOAD_BACKUP" "$LIB_DIR"
|
|
fi
|
|
echo "Error: failed to install the CLI files at $LIB_DIR" >&2
|
|
exit 1
|
|
fi
|
|
PAYLOAD_STAGING=""
|
|
|
|
if [[ -n "$PAYLOAD_BACKUP" ]]; then
|
|
rm -rf -- "$PAYLOAD_BACKUP"
|
|
fi
|
|
trap - EXIT
|
|
|
|
# Write a bash wrapper that invokes Node.js on the installed payload.
|
|
cat > "$LIVESYNC_BIN" <<WRAPPER
|
|
#!/usr/bin/env bash
|
|
exec node "$LIVESYNC_JS" "\$@"
|
|
WRAPPER
|
|
chmod +x "$LIVESYNC_BIN"
|
|
echo "[INFO] Installed CLI files: $LIB_DIR"
|
|
echo "[INFO] Installed binary: $LIVESYNC_BIN"
|
|
|
|
# ── Write systemd unit ───────────────────────────────────────────────────────
|
|
mkdir -p "$UNIT_DIR"
|
|
UNIT_PATH="$UNIT_DIR/livesync-cli.service"
|
|
|
|
EXEC_START="\"$LIVESYNC_BIN\" \"$VAULT_PATH\""
|
|
if [[ -n "$INTERVAL" ]]; then
|
|
EXEC_START="\"$LIVESYNC_BIN\" \"$VAULT_PATH\" --interval $INTERVAL"
|
|
fi
|
|
|
|
# Check for existing service and offer to overwrite.
|
|
if [[ -f "$UNIT_PATH" ]] && [[ "$FORCE" -eq 0 ]]; then
|
|
if [ ! -t 0 ]; then
|
|
echo "Error: service unit already exists at $UNIT_PATH; use --force to overwrite" >&2
|
|
exit 1
|
|
fi
|
|
printf 'Service unit already exists at %s. Overwrite? [y/N]: ' "$UNIT_PATH"
|
|
read -r CONFIRM
|
|
case "$CONFIRM" in
|
|
[yY]|[yY][eE][sS]) : ;;
|
|
*)
|
|
echo "[INFO] Aborted. Existing unit left in place."
|
|
exit 0
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# In awk gsub(), '&' in the replacement means "matched text"; escape any literal '&'
|
|
# in path variables before passing them as awk replacement strings.
|
|
AWK_BIN="${LIVESYNC_BIN//&/\\&}"
|
|
AWK_VAULT="${VAULT_PATH//&/\\&}"
|
|
awk -v bin="$AWK_BIN" -v vault="$AWK_VAULT" -v exec_start="ExecStart=$EXEC_START" \
|
|
'/^ExecStart=/ { print exec_start; next } {gsub("LIVESYNC_BIN", bin); gsub("LIVESYNC_VAULT_PATH", vault); print}' \
|
|
"$SERVICE_TEMPLATE" > "$UNIT_PATH"
|
|
|
|
echo "[INFO] Installed unit: $UNIT_PATH"
|
|
|
|
# ── Enable service ───────────────────────────────────────────────────────────
|
|
if ! command -v systemctl >/dev/null 2>&1; then
|
|
echo "[WARN] systemctl not found — skipping service activation"
|
|
echo "[INFO] To enable manually, copy $UNIT_PATH to the correct systemd directory and run:"
|
|
echo " systemctl $SYSTEMCTL_FLAGS daemon-reload"
|
|
echo " systemctl $SYSTEMCTL_FLAGS enable --now livesync-cli"
|
|
exit 0
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
systemctl $SYSTEMCTL_FLAGS daemon-reload
|
|
# shellcheck disable=SC2086
|
|
systemctl $SYSTEMCTL_FLAGS enable --now livesync-cli
|
|
|
|
sleep 1
|
|
# shellcheck disable=SC2086
|
|
if ! systemctl $SYSTEMCTL_FLAGS is-active --quiet livesync-cli; then
|
|
echo "Error: livesync-cli service did not remain active after startup." >&2
|
|
# shellcheck disable=SC2086
|
|
systemctl $SYSTEMCTL_FLAGS status livesync-cli --no-pager || true
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "[Done] livesync-cli service installed and started."
|
|
echo ""
|
|
# shellcheck disable=SC2086
|
|
systemctl $SYSTEMCTL_FLAGS status livesync-cli --no-pager || true
|