install.sh #1

  • //
  • p4lf/
  • dev/
  • install.sh
  • View
  • Commits
  • Open Download .zip Download (10 KB)
#!/usr/bin/env bash
# install.sh — P4 Log Feeder (p4lf) installer
#
# Downloads and installs the p4lf binary and supporting files for the
# current platform from the Perforce Workshop (workshop.perforce.com).
#
# Usage:
#   sudo bash install.sh [OPTIONS]
#
# Options:
#   -s <stream>   P4 stream to install from. Default: main
#                 Examples: main, dev, r1.0
#   -d <dir>      Installation directory. Default: /p4/common/site/log_feeder
#   -n            Dry run: show what would be done without making changes.
#   -h            Show this help message.
#
# This script MUST be run as root. It will exit immediately if it is not.
#
# What this script does:
#   1. Detects the current platform (Linux amd64/arm64).
#   2. Downloads the p4lf binary, example config, and systemd unit file
#      from workshop.perforce.com.
#   3. Installs the binary to <install_dir>/p4lf.
#   4. Installs p4lf.cfg.example (never overwrites an existing p4lf.cfg).
#   5. Installs/updates /etc/systemd/system/p4lf.service if the new file
#      differs from what is already on disk; runs 'systemctl daemon-reload'
#      if updated.
#   6. Prints next-step instructions.
#
# NOTE: Starting and stopping the p4lf service requires root (or a user
# with appropriate sudo permissions). See job P4LF-2 for future work to
# allow the 'perforce' OS user to manage the service directly.
#
# $Id$
# $Change$
# $DateTime$

set -euo pipefail

# ── Constants ─────────────────────────────────────────────────────────────────

readonly WORKSHOP_BASE="https://workshop.perforce.com/download/p4lf"
readonly SYSTEMD_UNIT_PATH="/etc/systemd/system/p4lf.service"
readonly DEFAULT_INSTALL_DIR="/p4/common/site/log_feeder"
readonly DEFAULT_STREAM="main"

# ── Defaults ──────────────────────────────────────────────────────────────────

STREAM="$DEFAULT_STREAM"
INSTALL_DIR="$DEFAULT_INSTALL_DIR"
DRY_RUN=false

# ── Helpers ───────────────────────────────────────────────────────────────────

msg()  { echo "install.sh: $*"; }
warn() { echo "install.sh: WARNING: $*" >&2; }
die()  { echo "install.sh: ERROR: $*" >&2; exit 1; }

usage() {
   cat <<EOF
Usage: sudo bash install.sh [OPTIONS]

Options:
  -s <stream>   Stream to install from (default: main). Examples: main, r1.0, dev
  -d <dir>      Installation directory (default: $DEFAULT_INSTALL_DIR)
  -n            Dry run: show what would be done without making changes
  -h            Show this help

This script must be run as root.
EOF
}

# download_file <url> <dest>
# Downloads <url> to <dest>, or prints the command in dry-run mode.
download_file() {
   local url="$1"
   local dest="$2"
   if [[ "$DRY_RUN" == true ]]; then
      msg "[dry-run] curl -fsSL '$url' -o '$dest'"
      return 0
   fi
   msg "Downloading $(basename "$dest") ..."
   curl -fsSL "$url" -o "$dest" || die "Failed to download $url"
}

# ── Argument parsing ───────────────────────────────────────────────────────────

while getopts ":s:d:nh" opt; do
   case "$opt" in
      s) STREAM="$OPTARG" ;;
      d) INSTALL_DIR="$OPTARG" ;;
      n) DRY_RUN=true ;;
      h) usage; exit 0 ;;
      :) die "Option -$OPTARG requires an argument." ;;
      \?) die "Unknown option: -$OPTARG" ;;
   esac
done

# ── Root check ────────────────────────────────────────────────────────────────

if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
   die "This script must be run as root. Try: sudo bash $0 $*"
fi

# ── Platform detection ────────────────────────────────────────────────────────

OS="$(uname -s)"
ARCH="$(uname -m)"

case "$OS" in
   Linux)
      case "$ARCH" in
         x86_64)  PLATFORM="linux-amd64" ;;
         aarch64) PLATFORM="linux-arm64" ;;
         *) die "Unsupported Linux architecture: $ARCH (supported: x86_64, aarch64)" ;;
      esac
      ;;
   Darwin)
      # macOS installs are development/test only; not for production.
      case "$ARCH" in
         arm64)   PLATFORM="darwin-arm64" ;;
         x86_64)  PLATFORM="darwin-amd64" ;;
         *) die "Unsupported macOS architecture: $ARCH" ;;
      esac
      warn "Installing macOS build. This is intended for development/testing only."
      ;;
   *)
      die "Unsupported OS: $OS (supported: Linux)"
      ;;
esac

BINARY_NAME="p4lf-${PLATFORM}"
BASE_URL="${WORKSHOP_BASE}/${STREAM}"

msg "Platform:        $OS/$ARCH  →  $BINARY_NAME"
msg "Stream:          //p4lf/$STREAM"
msg "Install dir:     $INSTALL_DIR"
[[ "$DRY_RUN" == true ]] && msg "Mode:            DRY RUN (no changes will be made)"
echo ""

# ── Preflight checks ──────────────────────────────────────────────────────────

command -v curl >/dev/null 2>&1 || die "curl is required but not found in PATH."

if [[ "$DRY_RUN" == false ]]; then
   mkdir -p "$INSTALL_DIR" || die "Cannot create install directory $INSTALL_DIR"
fi

# ── Download phase ────────────────────────────────────────────────────────────

TMPDIR_WORK="$(mktemp -d /tmp/p4lf-install.XXXXXX)"
trap 'rm -rf "$TMPDIR_WORK"' EXIT

BINARY_TMP="$TMPDIR_WORK/p4lf"
CFG_EXAMPLE_TMP="$TMPDIR_WORK/p4lf.cfg.example"
SERVICE_TMP="$TMPDIR_WORK/p4lf.service"

download_file "${BASE_URL}/bin/${BINARY_NAME}"    "$BINARY_TMP"
download_file "${BASE_URL}/p4lf.cfg.example"      "$CFG_EXAMPLE_TMP"
download_file "${BASE_URL}/p4lf.service"          "$SERVICE_TMP"

# ── Install binary ────────────────────────────────────────────────────────────

BINARY_DEST="$INSTALL_DIR/p4lf"

if [[ "$DRY_RUN" == true ]]; then
   msg "[dry-run] install -m 0755 $BINARY_TMP $BINARY_DEST"
else
   install -m 0755 "$BINARY_TMP" "$BINARY_DEST"
   msg "Installed binary → $BINARY_DEST"
   VERSION_OUT="$("$BINARY_DEST" -version 2>&1 || true)"
   msg "Version: $VERSION_OUT"
fi

# ── Install example config (never overwrite existing config) ──────────────────

CFG_EXAMPLE_DEST="$INSTALL_DIR/p4lf.cfg.example"
CFG_DEST="$INSTALL_DIR/p4lf.cfg"

if [[ "$DRY_RUN" == true ]]; then
   msg "[dry-run] install -m 0640 $CFG_EXAMPLE_TMP $CFG_EXAMPLE_DEST"
   [[ ! -f "$CFG_DEST" ]] && msg "[dry-run] install -m 0640 $CFG_EXAMPLE_TMP $CFG_DEST  (first install)"
else
   install -m 0640 "$CFG_EXAMPLE_TMP" "$CFG_EXAMPLE_DEST"
   msg "Installed config example → $CFG_EXAMPLE_DEST"

   if [[ ! -f "$CFG_DEST" ]]; then
      install -m 0640 "$CFG_EXAMPLE_TMP" "$CFG_DEST"
      msg "Installed default config → $CFG_DEST  (EDIT THIS FILE BEFORE STARTING THE SERVICE)"
   else
      msg "Existing config preserved → $CFG_DEST"
   fi
fi

# ── Install/update systemd unit (Linux only) ──────────────────────────────────

if [[ "$OS" == "Linux" ]]; then
   RELOAD_NEEDED=false

   if [[ "$DRY_RUN" == true ]]; then
      if [[ -f "$SYSTEMD_UNIT_PATH" ]]; then
         if ! diff -q "$SYSTEMD_UNIT_PATH" "$SERVICE_TMP" >/dev/null 2>&1; then
            msg "[dry-run] Unit file differs from $SYSTEMD_UNIT_PATH; would update + daemon-reload"
         else
            msg "[dry-run] Unit file $SYSTEMD_UNIT_PATH is already up to date"
         fi
      else
         msg "[dry-run] install -m 0644 $SERVICE_TMP $SYSTEMD_UNIT_PATH  (new install)"
         msg "[dry-run] systemctl daemon-reload"
      fi
   else
      if [[ -f "$SYSTEMD_UNIT_PATH" ]]; then
         if ! diff -q "$SYSTEMD_UNIT_PATH" "$SERVICE_TMP" >/dev/null 2>&1; then
            install -m 0644 "$SERVICE_TMP" "$SYSTEMD_UNIT_PATH"
            msg "Updated systemd unit → $SYSTEMD_UNIT_PATH"
            RELOAD_NEEDED=true
         else
            msg "Systemd unit is already up to date → $SYSTEMD_UNIT_PATH"
         fi
      else
         install -m 0644 "$SERVICE_TMP" "$SYSTEMD_UNIT_PATH"
         msg "Installed systemd unit → $SYSTEMD_UNIT_PATH"
         RELOAD_NEEDED=true
      fi

      if [[ "$RELOAD_NEEDED" == true ]]; then
         systemctl daemon-reload
         msg "Ran: systemctl daemon-reload"
      fi
   fi
fi

# ── Summary ───────────────────────────────────────────────────────────────────

echo ""
if [[ "$DRY_RUN" == true ]]; then
   msg "Dry run complete. No files were changed."
else
   msg "Installation complete."
   echo ""
   echo "  Next steps:"
   if [[ ! -f "$CFG_DEST" ]] || grep -q '^#P4LogFile' "$CFG_DEST" 2>/dev/null; then
      echo "  1. Edit $CFG_DEST"
      echo "     — Set P4LogFile to the path of your Perforce server log"
      echo "     — Review and adjust other settings as needed"
      echo "  2. systemctl enable p4lf"
      echo "  3. systemctl start p4lf"
      echo "  4. systemctl status p4lf"
   else
      echo "  1. systemctl enable p4lf   (if not already enabled)"
      echo "  2. systemctl restart p4lf"
      echo "  3. systemctl status p4lf"
   fi
   echo ""
   echo "  NOTE: Starting/stopping the p4lf service requires root."
   echo "        See job P4LF-2 for future work on least-privilege service management."
fi
# Change User Description Committed
#1 32834 C. Thomas Tyler Add install.sh and document release/promotion workflow.

install.sh (type text+x):
- Must be run as root (bails immediately if not)
- Detects platform (Linux amd64/arm64, macOS arm64/amd64)
- Downloads binary, p4lf.cfg.example, and p4lf.service via curl -fsSL
  from https://workshop.perforce.com/download/p4lf/<stream>/
- Installs binary to /p4/common/site/log_feeder/p4lf (or custom -d dir)
- Installs p4lf.cfg.example; never overwrites an existing p4lf.cfg
- Installs/updates /etc/systemd/system/p4lf.service only if changed;
  runs 'systemctl daemon-reload' if updated
- Supports -n (dry run), -s <stream>, -d <dir>, -h options
- References P4LF-2 in output (future: perforce user service management)

Makefile:
- release target now includes install.sh in dist/ tarballs
- Added promote section documenting dev->main->rX.Y copy-up workflow,
  correct workshop.perforce.com download URLs, and how to add platform
  binaries in a release stream