#!/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 |