first commit

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-06 23:03:38 +02:00
commit 3d6c49df65
16 files changed
+1137

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
SHA=${1:?commit SHA required}
SHORT_SHA=${SHA:0:12}
BUILD_ID="$(date -u '+%Y%m%dT%H%M%SZ')-${SHORT_SHA}"
WORK="$DATA_DIR/work/$BUILD_ID"
SRC="$WORK/source"
PKG="$WORK/package"
OUT="$WORK/out"
REPOSITORY="$DATA_DIR/repository"
APTLY_CONF="$DATA_DIR/state/aptly.conf"
cleanup() {
if [[ "${KEEP_WORKSPACE:-0}" != "1" ]]; then
rm -rf "$WORK"
else
log "workspace kept: $WORK"
fi
}
trap cleanup EXIT
mkdir -p "$SRC" "$PKG" "$OUT"
log "cloning source"
git clone --quiet --no-tags --single-branch --branch "$SOURCE_BRANCH" "$SOURCE_REPO" "$SRC"
git -C "$SRC" checkout --quiet --detach "$SHA"
ACTUAL_SHA=$(git -C "$SRC" rev-parse HEAD)
[[ "$ACTUAL_SHA" == "$SHA" ]] || fatal "checkout SHA mismatch"
cd "$SRC"
META_JSON=$(cargo metadata --format-version 1 --no-deps)
export META_JSON SRC
readarray -t META < <(python3 <<'PY'
import json, os
m=json.loads(os.environ['META_JSON'])
src=os.path.realpath(os.environ['SRC'])
packages=m.get('packages', [])
chosen=None
for p in packages:
if os.path.realpath(os.path.dirname(p['manifest_path'])) == src:
chosen=p; break
if chosen is None:
defaults=set(m.get('workspace_default_members') or [])
for p in packages:
if p['id'] in defaults:
chosen=p; break
if chosen is None and packages:
chosen=packages[0]
if chosen is None:
raise SystemExit('no Cargo package found')
bin_targets=[t for t in chosen.get('targets', []) if 'bin' in t.get('kind', [])]
if not bin_targets:
raise SystemExit('no binary target found')
values=[
chosen.get('name') or '',
chosen.get('version') or '',
bin_targets[0].get('name') or '',
chosen.get('description') or '',
chosen.get('license') or '',
chosen.get('homepage') or '',
chosen.get('repository') or '',
]
for v in values:
print(str(v).replace('\n',' '))
PY
)
CARGO_NAME=${META[0]}
VERSION=${META[1]}
BINARY_NAME=${META[2]}
DESCRIPTION=${META[3]}
LICENSE=${META[4]}
CARGO_HOMEPAGE=${META[5]}
CARGO_REPOSITORY=${META[6]}
[[ -n "$VERSION" && -n "$BINARY_NAME" ]] || fatal "invalid Cargo metadata"
PACKAGE_NAME=${PACKAGE_NAME:-}
MAINTAINER=${PACKAGE_MAINTAINER:-}
CONTROL_HOMEPAGE=""
if [[ -r "$SRC/debian/control" ]]; then
[[ -n "$PACKAGE_NAME" ]] || PACKAGE_NAME=$(read_first_field "$SRC/debian/control" Package || true)
[[ -n "$MAINTAINER" ]] || MAINTAINER=$(read_first_field "$SRC/debian/control" Maintainer || true)
CONTROL_HOMEPAGE=$(read_first_field "$SRC/debian/control" Homepage || true)
fi
PACKAGE_NAME=${PACKAGE_NAME:-${CARGO_NAME//-/}}
MAINTAINER=${MAINTAINER:-Rust Package Builder <packages@localhost>}
DESCRIPTION=${DESCRIPTION:-$PACKAGE_NAME}
LICENSE=${LICENSE:-Proprietary}
HOMEPAGE=${PACKAGE_HOMEPAGE:-${CARGO_HOMEPAGE:-${CARGO_REPOSITORY:-${CONTROL_HOMEPAGE:-$SOURCE_REPO}}}}
REQUESTED_PACKAGE_RELEASE=$PACKAGE_RELEASE
if [[ "$REQUESTED_PACKAGE_RELEASE" == "auto" ]]; then
LAST_VERSION=""
LAST_PACKAGE_RELEASE=0
[[ -r "$DATA_DIR/state/last_version" ]] && LAST_VERSION=$(cat "$DATA_DIR/state/last_version")
[[ -r "$DATA_DIR/state/last_package_release" ]] && LAST_PACKAGE_RELEASE=$(cat "$DATA_DIR/state/last_package_release")
if [[ "$LAST_VERSION" == "$VERSION" && "$LAST_PACKAGE_RELEASE" =~ ^[0-9]+$ ]]; then
PACKAGE_RELEASE=$((LAST_PACKAGE_RELEASE + 1))
else
PACKAGE_RELEASE=1
fi
else
PACKAGE_RELEASE=$REQUESTED_PACKAGE_RELEASE
fi
export PACKAGE_NAME VERSION BINARY_NAME DESCRIPTION LICENSE HOMEPAGE MAINTAINER PACKAGE_RELEASE
log "building version $VERSION ($PACKAGE_NAME / $BINARY_NAME)"
log "cargo build started"
if [[ -f Cargo.lock ]]; then
cargo build --release --locked
else
cargo build --release
fi
log "cargo build completed"
BINARY_PATH="$CARGO_TARGET_DIR/release/$BINARY_NAME"
[[ -x "$BINARY_PATH" ]] || fatal "built binary not found: $BINARY_PATH"
MAX_GLIBC_FOUND=$(objdump -T "$BINARY_PATH" 2>/dev/null | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sed 's/^GLIBC_//' | sort -V | tail -n1 || true)
if [[ -n "$MAX_GLIBC_FOUND" ]]; then
if [[ "$(printf '%s\n%s\n' "$MAX_GLIBC_VERSION" "$MAX_GLIBC_FOUND" | sort -V | tail -n1)" != "$MAX_GLIBC_VERSION" ]]; then
fatal "binary requires GLIBC_$MAX_GLIBC_FOUND, target maximum is GLIBC_$MAX_GLIBC_VERSION"
fi
log "glibc compatibility check: <= $MAX_GLIBC_FOUND"
fi
/opt/rust-package-builder/scripts/prepare-package.sh "$SRC" "$PKG" "$BINARY_PATH"
DEB_FILE="$OUT/${PACKAGE_NAME}_${VERSION}-${PACKAGE_RELEASE}_amd64.deb"
RPM_FILE="$OUT/${PACKAGE_NAME}-${VERSION}-${PACKAGE_RELEASE}.x86_64.rpm"
log "creating deb package"
nfpm package --config "$PKG/nfpm-deb.yaml" --packager deb --target "$DEB_FILE"
log "creating rpm package"
nfpm package --config "$PKG/nfpm-rpm.yaml" --packager rpm --target "$RPM_FILE"
ar t "$DEB_FILE" | grep '^_gpgorigin$' >/dev/null || fatal "DEB package does not contain nFPM GPG origin signature"
rpm -qpi "$RPM_FILE" | awk -F: '/^Signature/ && $0 !~ /\(none\)/ {ok=1} END{exit !ok}' || fatal "RPM package is not signed"
log "package signatures verified"
cp -f "$DEB_FILE" "$DATA_DIR/packages/deb/"
cp -f "$RPM_FILE" "$DATA_DIR/packages/rpm/x86_64/"
log "updating aptly internal repository"
aptly -config="$APTLY_CONF" repo add -force-replace package-builder "$DEB_FILE" >/dev/null
SNAPSHOT="release-${VERSION}-${SHORT_SHA}-$(date +%s)"
aptly -config="$APTLY_CONF" snapshot create "$SNAPSHOT" from repo package-builder >/dev/null
APTLY_SIGN_ARGS=(-batch -gpg-key="$GPG_KEY_ID")
if [[ -r "$GPG_PASSPHRASE_FILE" ]]; then
APTLY_SIGN_ARGS+=(-passphrase-file="$GPG_PASSPHRASE_FILE")
fi
if aptly -config="$APTLY_CONF" publish list -raw | awk -v d="$REPO_DISTRIBUTION" '$2 == d {found=1} END{exit !found}'; then
aptly -config="$APTLY_CONF" publish switch \
"${APTLY_SIGN_ARGS[@]}" \
"$REPO_DISTRIBUTION" . "$SNAPSHOT" >/dev/null
else
aptly -config="$APTLY_CONF" publish snapshot \
"${APTLY_SIGN_ARGS[@]}" \
-distribution="$REPO_DISTRIBUTION" \
-component="$REPO_COMPONENT" \
-architectures=amd64 \
"$SNAPSHOT" >/dev/null
fi
APT_PUBLIC="$DATA_DIR/aptly/public"
[[ -f "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/InRelease" ]] || fatal "aptly did not create InRelease"
[[ -f "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release.gpg" ]] || fatal "aptly did not create Release.gpg"
gpg --batch --verify \
"$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release.gpg" \
"$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release" >/dev/null 2>&1 || fatal "APT Release signature verification failed"
RELEASE_DIR="$REPOSITORY/releases/$BUILD_ID"
mkdir -p "$RELEASE_DIR/apt" "$RELEASE_DIR/rpm/x86_64"
CURRENT_TARGET=""
if [[ -L "$REPOSITORY/current" ]]; then
CURRENT_LINK=$(readlink "$REPOSITORY/current")
CURRENT_TARGET="$REPOSITORY/$CURRENT_LINK"
fi
log "staging apt repository"
if [[ -n "$CURRENT_TARGET" && -d "$CURRENT_TARGET/apt" ]]; then
rsync -a --delete --link-dest="$CURRENT_TARGET/apt"/ "$APT_PUBLIC"/ "$RELEASE_DIR/apt"/
else
rsync -a --delete "$APT_PUBLIC"/ "$RELEASE_DIR/apt"/
fi
log "updating rpm repository"
if [[ -n "$CURRENT_TARGET" && -d "$CURRENT_TARGET/rpm" ]]; then
rsync -a --delete --link-dest="$CURRENT_TARGET/rpm"/ "$DATA_DIR/packages/rpm"/ "$RELEASE_DIR/rpm"/
else
rsync -a --delete "$DATA_DIR/packages/rpm"/ "$RELEASE_DIR/rpm"/
fi
rm -rf "$RELEASE_DIR/rpm/x86_64/repodata"
createrepo_c --update "$RELEASE_DIR/rpm/x86_64" >/dev/null
GPG_SIGN_ARGS=(--batch --yes --pinentry-mode loopback)
if [[ -r "$GPG_PASSPHRASE_FILE" ]]; then
GPG_SIGN_ARGS+=(--passphrase-file "$GPG_PASSPHRASE_FILE")
fi
gpg "${GPG_SIGN_ARGS[@]}" --armor --detach-sign \
--local-user "$GPG_KEY_ID" \
--output "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" \
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml"
gpg --batch --verify \
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" \
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" >/dev/null 2>&1 || fatal "RPM metadata signature verification failed"
gpg --batch --armor --export "$GPG_KEY_ID" > "$RELEASE_DIR/repo-key.gpg"
[[ -s "$RELEASE_DIR/repo-key.gpg" ]] || fatal "public GPG key export failed"
/opt/rust-package-builder/scripts/generate-index.sh "$RELEASE_DIR/index.html" "$PACKAGE_NAME"
# Validate all externally visible endpoints before the atomic switch.
[[ -s "$RELEASE_DIR/index.html" ]]
[[ -f "$RELEASE_DIR/apt/dists/$REPO_DISTRIBUTION/InRelease" ]]
[[ -f "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" ]]
log "publishing repository atomically"
rm -f "$REPOSITORY/current.new"
ln -s "releases/$BUILD_ID" "$REPOSITORY/current.new"
mv -Tf "$REPOSITORY/current.new" "$REPOSITORY/current"
printf '%s\n' "$SHA" | atomic_write "$DATA_DIR/state/last_commit"
printf '%s\n' "$VERSION" | atomic_write "$DATA_DIR/state/last_version"
printf '%s\n' "$PACKAGE_RELEASE" | atomic_write "$DATA_DIR/state/last_package_release"
printf '%s\n' "$BUILD_ID" | atomic_write "$DATA_DIR/state/last_release"
log "build completed: version=$VERSION package_release=$PACKAGE_RELEASE commit=$SHA release=$BUILD_ID"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
STATE_FILE="$DATA_DIR/state/last_commit"
log "checking repository"
REMOTE_SHA=$(git ls-remote "$SOURCE_REPO" "refs/heads/$SOURCE_BRANCH" | awk 'NR == 1 {print $1}')
[[ "$REMOTE_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || fatal "cannot resolve branch $SOURCE_BRANCH from $SOURCE_REPO"
LAST_SHA=""
[[ -r "$STATE_FILE" ]] && LAST_SHA=$(cat "$STATE_FILE")
if [[ "$REMOTE_SHA" == "$LAST_SHA" ]]; then
log "no changes: $REMOTE_SHA"
exit 0
fi
log "new commit detected: $REMOTE_SHA"
/opt/rust-package-builder/scripts/build-release.sh "$REMOTE_SHA"
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
: "${SOURCE_REPO:?SOURCE_REPO is required}"
SOURCE_BRANCH=${SOURCE_BRANCH:-master}
: "${REPOSITORY_URL:?REPOSITORY_URL is required}"
POLL_INTERVAL=${POLL_INTERVAL:-300}
PACKAGE_RELEASE=${PACKAGE_RELEASE:-auto}
[[ "$POLL_INTERVAL" =~ ^[0-9]+$ ]] || fatal "POLL_INTERVAL must be an integer"
(( POLL_INTERVAL >= 1 )) || fatal "POLL_INTERVAL must be >= 1"
[[ "$PACKAGE_RELEASE" == "auto" || "$PACKAGE_RELEASE" =~ ^[1-9][0-9]*$ ]] || fatal "PACKAGE_RELEASE must be auto or a positive integer"
export DATA_DIR=${DATA_DIR:-/var/lib/package-builder}
export SOURCE_REPO SOURCE_BRANCH REPOSITORY_URL POLL_INTERVAL
export REPO_DISTRIBUTION=${REPO_DISTRIBUTION:-stable}
export REPO_COMPONENT=${REPO_COMPONENT:-main}
export PACKAGE_RELEASE
export MAX_GLIBC_VERSION=${MAX_GLIBC_VERSION:-2.34}
export GPG_PRIVATE_KEY_FILE=${GPG_PRIVATE_KEY_FILE:-/run/secrets/gpg/private-key.asc}
export GPG_PASSPHRASE_FILE=${GPG_PASSPHRASE_FILE:-/run/secrets/gpg/passphrase}
export GNUPGHOME=${GNUPGHOME:-/run/package-builder/gnupg}
export CARGO_HOME="$DATA_DIR/cargo"
export CARGO_TARGET_DIR="$DATA_DIR/cargo/target"
for cmd in git cargo rustc nfpm aptly createrepo_c gpg rsync python3 python3.11 objdump ar rpm; do
require_cmd "$cmd"
done
mkdir -p \
"$DATA_DIR/repository/releases" \
"$DATA_DIR/state" \
"$DATA_DIR/cargo" \
"$DATA_DIR/work" \
"$DATA_DIR/packages/rpm/x86_64" \
"$DATA_DIR/packages/deb"
mkdir -p "$GNUPGHOME"
chmod 0700 "$GNUPGHOME"
[[ -r "$GPG_PRIVATE_KEY_FILE" ]] || fatal "GPG private key not found: $GPG_PRIVATE_KEY_FILE"
log "importing GPG signing key"
gpg --batch --import "$GPG_PRIVATE_KEY_FILE" >/dev/null 2>&1 || fatal "cannot import GPG key"
if [[ -z "${GPG_KEY_ID:-}" ]]; then
GPG_KEY_ID=$(gpg --batch --with-colons --list-secret-keys | awk -F: '/^sec:/ && !seen_sec {seen_sec=1; next} seen_sec && /^fpr:/ && first=="" {first=$10} END {print first}')
fi
[[ -n "$GPG_KEY_ID" ]] || fatal "cannot determine GPG key fingerprint"
export GPG_KEY_ID
if [[ -r "$GPG_PASSPHRASE_FILE" ]]; then
NFPM_PASSPHRASE=$(cat "$GPG_PASSPHRASE_FILE")
export NFPM_PASSPHRASE
else
unset NFPM_PASSPHRASE || true
fi
/opt/rust-package-builder/scripts/init-storage.sh
log "builder started: branch=$SOURCE_BRANCH interval=${POLL_INTERVAL}s"
while true; do
if ! /opt/rust-package-builder/scripts/check-and-build.sh; then
log "ERROR: build cycle failed; published repository was not switched"
fi
sleep "$POLL_INTERVAL"
done
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
OUT=${1:?output file required}
PACKAGE=${2:?package name required}
URL=$(trim_trailing_slash "$REPOSITORY_URL")
export OUT PACKAGE URL REPO_DISTRIBUTION REPO_COMPONENT
python3 <<'PY_END'
import html, os
out=os.environ['OUT']
package=os.environ['PACKAGE']
url=os.environ['URL']
dist=os.environ['REPO_DISTRIBUTION']
component=os.environ['REPO_COMPONENT']
h=html.escape
content=f'''<!doctype html>
<html lang="pl">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{h(package)} repository</title></head>
<body>
<h1>{h(package)}</h1>
<h2>Debian / Ubuntu</h2>
<pre>curl -fsSL {h(url)}/repo-key.gpg \\
| sudo gpg --dearmor -o /usr/share/keyrings/{h(package)}.gpg
echo "deb [signed-by=/usr/share/keyrings/{h(package)}.gpg] {h(url)}/apt {h(dist)} {h(component)}" \\
| sudo tee /etc/apt/sources.list.d/{h(package)}.list
sudo apt update
sudo apt install {h(package)}</pre>
<h2>RHEL / Rocky / Alma / Fedora</h2>
<pre>sudo rpm --import {h(url)}/repo-key.gpg
sudo tee /etc/yum.repos.d/{h(package)}.repo &lt;&lt;'REPO'
[{h(package)}]
name={h(package)} Repository
baseurl={h(url)}/rpm/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey={h(url)}/repo-key.gpg
REPO
sudo dnf install {h(package)}</pre>
</body>
</html>
'''
with open(out, 'w', encoding='utf-8') as f:
f.write(content)
PY_END
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
APTLY_CONF="$DATA_DIR/state/aptly.conf"
APTLY_ROOT="$DATA_DIR/aptly"
REPOSITORY="$DATA_DIR/repository"
mkdir -p "$APTLY_ROOT" "$REPOSITORY/releases"
cat > "$APTLY_CONF" <<JSON
{
"rootDir": "$APTLY_ROOT",
"architectures": ["amd64"],
"dependencyFollowSuggests": false,
"dependencyFollowRecommends": false,
"dependencyFollowAllVariants": false,
"dependencyFollowSource": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"skipLegacyPool": true
}
JSON
export APTLY_CONF
if ! aptly -config="$APTLY_CONF" repo list -raw | grep -Fx package-builder >/dev/null; then
log "creating aptly local repository"
aptly -config="$APTLY_CONF" repo create \
-distribution="$REPO_DISTRIBUTION" \
-component="$REPO_COMPONENT" \
package-builder >/dev/null
fi
for item in apt rpm repo-key.gpg index.html; do
if [[ -e "$REPOSITORY/$item" && ! -L "$REPOSITORY/$item" ]]; then
fatal "$REPOSITORY/$item exists and is not a symlink; refusing to overwrite"
fi
done
ln -sfn current/apt "$REPOSITORY/apt"
ln -sfn current/rpm "$REPOSITORY/rpm"
ln -sfn current/repo-key.gpg "$REPOSITORY/repo-key.gpg"
ln -sfn current/index.html "$REPOSITORY/index.html"
Executable
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -Eeuo pipefail
log() {
printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"
}
fatal() {
log "ERROR: $*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fatal "required command not found: $1"
}
trim_trailing_slash() {
local value=$1
while [[ "$value" == */ ]]; do value=${value%/}; done
printf '%s' "$value"
}
yaml_quote() {
python3 - "$1" <<'PY'
import json, sys
print(json.dumps(sys.argv[1]))
PY
}
read_first_field() {
local file=$1 key=$2
awk -v key="$key" 'BEGIN{IGNORECASE=1} $0 ~ "^" key ":[[:space:]]*" {sub(/^[^:]+:[[:space:]]*/, ""); print; exit}' "$file"
}
atomic_write() {
local path=$1
local tmp="${path}.tmp.$$"
cat > "$tmp"
mv -f "$tmp" "$path"
}
+227
View File
@@ -0,0 +1,227 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
SRC=${1:?source path required}
PKG=${2:?package workspace required}
BINARY_PATH=${3:?binary path required}
rm -rf "$PKG"
mkdir -p "$PKG/payload" "$PKG/scripts"
install -D -m 0755 "$BINARY_PATH" "$PKG/payload/usr/bin/$BINARY_NAME"
SERVICE_SRC="$SRC/debian/${PACKAGE_NAME}.service"
ENV_SRC="$SRC/debian/${PACKAGE_NAME}.environment"
LOGROTATE_SRC="$SRC/debian/${PACKAGE_NAME}.logrotate"
TMPFILES_SRC="$SRC/debian/${PACKAGE_NAME}.tmpfiles"
POSTINST_SRC="$SRC/debian/${PACKAGE_NAME}.postinst"
if [[ -r "$SERVICE_SRC" ]]; then
install -D -m 0644 "$SERVICE_SRC" "$PKG/payload/usr/lib/systemd/system/${PACKAGE_NAME}.service"
else
mkdir -p "$PKG/payload/usr/lib/systemd/system"
cat > "$PKG/payload/usr/lib/systemd/system/${PACKAGE_NAME}.service" <<UNIT
[Unit]
Description=$DESCRIPTION
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/$BINARY_NAME
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
UNIT
fi
if [[ -r "$ENV_SRC" ]]; then
install -D -m 0640 "$ENV_SRC" "$PKG/payload/etc/${PACKAGE_NAME}/environment"
fi
if [[ -r "$LOGROTATE_SRC" ]]; then
install -D -m 0644 "$LOGROTATE_SRC" "$PKG/payload/etc/logrotate.d/${PACKAGE_NAME}"
fi
if [[ -r "$TMPFILES_SRC" ]]; then
install -D -m 0644 "$TMPFILES_SRC" "$PKG/payload/usr/lib/tmpfiles.d/${PACKAGE_NAME}.conf"
fi
# Preserve optional project-owned monitoring assets used by RustProxy's existing Debian package.
if [[ -r "$SRC/grafana/rustproxy-overview.json" ]]; then
install -D -m 0644 "$SRC/grafana/rustproxy-overview.json" "$PKG/payload/usr/share/${PACKAGE_NAME}/grafana/rustproxy-overview.json"
fi
if [[ -r "$SRC/grafana/README.md" ]]; then
install -D -m 0644 "$SRC/grafana/README.md" "$PKG/payload/usr/share/doc/${PACKAGE_NAME}/grafana/README.md"
fi
cat > "$PKG/scripts/deb-postinstall.sh" <<'SCRIPT'
#!/bin/sh
set -e
SCRIPT
if [[ -r "$POSTINST_SRC" ]]; then
sed -e '/^#![[:space:]]*\/bin\/sh/d' \
-e '/^[[:space:]]*#DEBHELPER#[[:space:]]*$/d' \
-e '/^[[:space:]]*exit[[:space:]]\+0[[:space:]]*$/d' \
"$POSTINST_SRC" >> "$PKG/scripts/deb-postinstall.sh"
else
cat >> "$PKG/scripts/deb-postinstall.sh" <<SCRIPT
if ! getent group "$PACKAGE_NAME" >/dev/null 2>&1; then
addgroup --system "$PACKAGE_NAME" >/dev/null
fi
if ! getent passwd "$PACKAGE_NAME" >/dev/null 2>&1; then
adduser --system --ingroup "$PACKAGE_NAME" --home "/var/lib/$PACKAGE_NAME" --no-create-home \\
--disabled-password --shell /usr/sbin/nologin "$PACKAGE_NAME" >/dev/null
fi
install -d -o "$PACKAGE_NAME" -g "$PACKAGE_NAME" -m 0750 "/var/lib/$PACKAGE_NAME" "/var/log/$PACKAGE_NAME"
SCRIPT
fi
cat >> "$PKG/scripts/deb-postinstall.sh" <<SCRIPT
systemctl daemon-reload >/dev/null 2>&1 || true
systemctl enable "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
systemctl restart "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
exit 0
SCRIPT
cat > "$PKG/scripts/deb-preremove.sh" <<SCRIPT
#!/bin/sh
set -e
case "\${1:-}" in
remove|deconfigure) systemctl stop "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true ;;
esac
exit 0
SCRIPT
cat > "$PKG/scripts/deb-postremove.sh" <<SCRIPT
#!/bin/sh
set -e
systemctl daemon-reload >/dev/null 2>&1 || true
exit 0
SCRIPT
cat > "$PKG/scripts/rpm-postinstall.sh" <<SCRIPT
#!/bin/sh
set -e
if ! getent group "$PACKAGE_NAME" >/dev/null 2>&1; then
groupadd --system "$PACKAGE_NAME"
fi
if ! getent passwd "$PACKAGE_NAME" >/dev/null 2>&1; then
useradd --system --gid "$PACKAGE_NAME" --home-dir "/var/lib/$PACKAGE_NAME" --no-create-home --shell /sbin/nologin "$PACKAGE_NAME"
fi
install -d -o "$PACKAGE_NAME" -g "$PACKAGE_NAME" -m 0750 "/var/lib/$PACKAGE_NAME" "/var/log/$PACKAGE_NAME"
systemd-tmpfiles --create "/usr/lib/tmpfiles.d/${PACKAGE_NAME}.conf" >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
systemctl enable "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
systemctl restart "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
exit 0
SCRIPT
cat > "$PKG/scripts/rpm-preremove.sh" <<SCRIPT
#!/bin/sh
set -e
if [ "\${1:-1}" -eq 0 ]; then
systemctl stop "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
systemctl disable "${PACKAGE_NAME}.service" >/dev/null 2>&1 || true
fi
exit 0
SCRIPT
cat > "$PKG/scripts/rpm-postremove.sh" <<SCRIPT
#!/bin/sh
set -e
systemctl daemon-reload >/dev/null 2>&1 || true
exit 0
SCRIPT
chmod 0755 "$PKG/scripts"/*.sh
# Build nFPM contents dynamically from project-provided package assets.
CONTENTS_FILE="$PKG/contents.yaml"
: > "$CONTENTS_FILE"
add_content() {
local src=$1 dst=$2 mode=$3 type=${4:-}
{
printf ' - src: %s\n' "$(yaml_quote "$src")"
printf ' dst: %s\n' "$(yaml_quote "$dst")"
[[ -n "$type" ]] && printf ' type: %s\n' "$(yaml_quote "$type")"
printf ' file_info:\n mode: %s\n' "$mode"
} >> "$CONTENTS_FILE"
}
add_content "$PKG/payload/usr/bin/$BINARY_NAME" "/usr/bin/$BINARY_NAME" 0755
add_content "$PKG/payload/usr/lib/systemd/system/${PACKAGE_NAME}.service" "/usr/lib/systemd/system/${PACKAGE_NAME}.service" 0644
[[ -f "$PKG/payload/etc/${PACKAGE_NAME}/environment" ]] && add_content "$PKG/payload/etc/${PACKAGE_NAME}/environment" "/etc/${PACKAGE_NAME}/environment" 0640 'config|noreplace'
[[ -f "$PKG/payload/etc/logrotate.d/${PACKAGE_NAME}" ]] && add_content "$PKG/payload/etc/logrotate.d/${PACKAGE_NAME}" "/etc/logrotate.d/${PACKAGE_NAME}" 0644 'config|noreplace'
[[ -f "$PKG/payload/usr/lib/tmpfiles.d/${PACKAGE_NAME}.conf" ]] && add_content "$PKG/payload/usr/lib/tmpfiles.d/${PACKAGE_NAME}.conf" "/usr/lib/tmpfiles.d/${PACKAGE_NAME}.conf" 0644
[[ -f "$PKG/payload/usr/share/${PACKAGE_NAME}/grafana/rustproxy-overview.json" ]] && add_content "$PKG/payload/usr/share/${PACKAGE_NAME}/grafana/rustproxy-overview.json" "/usr/share/${PACKAGE_NAME}/grafana/rustproxy-overview.json" 0644
[[ -f "$PKG/payload/usr/share/doc/${PACKAGE_NAME}/grafana/README.md" ]] && add_content "$PKG/payload/usr/share/doc/${PACKAGE_NAME}/grafana/README.md" "/usr/share/doc/${PACKAGE_NAME}/grafana/README.md" 0644
NAME_Q=$(yaml_quote "$PACKAGE_NAME")
VERSION_Q=$(yaml_quote "$VERSION")
MAINTAINER_Q=$(yaml_quote "$MAINTAINER")
DESC_Q=$(yaml_quote "$DESCRIPTION")
LICENSE_Q=$(yaml_quote "$LICENSE")
HOMEPAGE_Q=$(yaml_quote "$HOMEPAGE")
GPG_KEY_Q=$(yaml_quote "$GPG_PRIVATE_KEY_FILE")
cat > "$PKG/nfpm-deb.yaml" <<YAML
name: $NAME_Q
arch: amd64
platform: linux
version: $VERSION_Q
release: "$PACKAGE_RELEASE"
section: net
priority: optional
maintainer: $MAINTAINER_Q
description: $DESC_Q
license: $LICENSE_Q
homepage: $HOMEPAGE_Q
contents:
$(cat "$CONTENTS_FILE")
depends:
- adduser
- ca-certificates
- gzip
- logrotate
- systemd
scripts:
postinstall: "$PKG/scripts/deb-postinstall.sh"
preremove: "$PKG/scripts/deb-preremove.sh"
postremove: "$PKG/scripts/deb-postremove.sh"
deb:
compression: xz
signature:
key_file: $GPG_KEY_Q
type: origin
YAML
cat > "$PKG/nfpm-rpm.yaml" <<YAML
name: $NAME_Q
arch: amd64
platform: linux
version: $VERSION_Q
release: "$PACKAGE_RELEASE"
section: net
priority: optional
maintainer: $MAINTAINER_Q
description: $DESC_Q
license: $LICENSE_Q
homepage: $HOMEPAGE_Q
vendor: $NAME_Q
contents:
$(cat "$CONTENTS_FILE")
depends:
- ca-certificates
- gzip
- logrotate
- shadow-utils
- systemd
scripts:
postinstall: "$PKG/scripts/rpm-postinstall.sh"
preremove: "$PKG/scripts/rpm-preremove.sh"
postremove: "$PKG/scripts/rpm-postremove.sh"
rpm:
compression: zstd
signature:
key_file: $GPG_KEY_Q
YAML