Compare commits
3
Commits
e545a27e9a
...
294960f44d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
294960f44d | ||
|
|
51f2474db6 | ||
|
|
d0138b742d |
@@ -20,7 +20,7 @@ Single script, three automatic modes:
|
||||
bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"
|
||||
```
|
||||
|
||||
The script automatically creates an Alpine 3.23 LXC with Docker and act_runner.
|
||||
The script automatically creates an Alpine LXC (template auto-detected from `pveam available`) with Docker and act_runner.
|
||||
|
||||
#### Customization
|
||||
|
||||
@@ -34,6 +34,7 @@ CTID=120 HOSTNAME=runner-02 CORES=4 RAM=4096 bash -c "$(curl -fsSL https://gitea
|
||||
|----------|---------|-------------|
|
||||
| `CTID` | auto | Container ID |
|
||||
| `RUNNER_HOSTNAME` | `gitea-runner` | LXC Hostname |
|
||||
| `TEMPLATE` | auto-detected | Alpine template; auto-detected from `pveam available` |
|
||||
| `CORES` | `2` | CPU cores |
|
||||
| `RAM` | `2048` | RAM in MiB |
|
||||
| `DISK` | `8` | Disk in GB |
|
||||
@@ -59,7 +60,7 @@ From inside the LXC:
|
||||
curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh | bash
|
||||
```
|
||||
|
||||
The script detects that act_runner is already installed and switches to update mode automatically.
|
||||
The script detects that act_runner is already installed and switches to update mode automatically. Re-running from the Proxmox host does the same, plus refreshes the LXC's Alpine packages first (`apk update && apk upgrade`).
|
||||
|
||||
### Architecture
|
||||
|
||||
|
||||
+102
-31
@@ -8,14 +8,15 @@ set -euo pipefail
|
||||
|
||||
# --- Config (override via environment) ---
|
||||
CTID="${CTID:-}"
|
||||
HOSTNAME="${RUNNER_HOSTNAME:-gitea-runner}"
|
||||
TEMPLATE="${TEMPLATE:-alpine-3.23-default_20260116_amd64.tar.xz}"
|
||||
HOSTNAME_LXC="${RUNNER_HOSTNAME:-gitea-runner}"
|
||||
TEMPLATE="${TEMPLATE:-}" # auto-detected when empty
|
||||
STORAGE="${STORAGE:-local-lvm}"
|
||||
TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}"
|
||||
CORES="${CORES:-2}"
|
||||
RAM="${RAM:-2048}"
|
||||
DISK="${DISK:-8}"
|
||||
BRIDGE="${BRIDGE:-vmbr0}"
|
||||
LXC_TAG="${LXC_TAG:-gitea-runner}" # stable identifier for the container
|
||||
SCRIPT_URL="https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh"
|
||||
GITEA_HOSTNAME="${GITEA_HOSTNAME:-gitea.taila5ad8.ts.net}"
|
||||
GITEA_API="https://gitea.com/api/v1/repos/gitea/act_runner/releases"
|
||||
@@ -31,6 +32,33 @@ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
require_root() {
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
log_error "This script must be run as root (current uid: $(id -u))."
|
||||
log_error "On Proxmox, launch it from the host shell or via the Web UI shell, both of which run as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Load shared helpers (lib/common.sh: detect_latest_alpine_template,
|
||||
# enable_tty1_autologin, find_existing_lxc, refresh_os_packages).
|
||||
#
|
||||
# Same reasoning as openbao/install.sh: a local checkout has the file
|
||||
# on disk right next to us, but the documented curl one-liner (host or
|
||||
# piped into `pct exec` inside the LXC) has no BASH_SOURCE path worth
|
||||
# trusting, so fall back to fetching lib/common.sh over HTTP next to
|
||||
# SCRIPT_URL. The LXC already needs outbound network to curl this very
|
||||
# script and to download the act_runner binary, so this adds no new
|
||||
# failure mode.
|
||||
# ============================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd || true)"
|
||||
if [[ -n "$SCRIPT_DIR" && -f "${SCRIPT_DIR}/../lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/common.sh"
|
||||
else
|
||||
source <(curl -fsSL "$(dirname "$(dirname "$SCRIPT_URL")")/lib/common.sh")
|
||||
fi
|
||||
|
||||
# --- Helpers ---
|
||||
get_latest_release() {
|
||||
local release
|
||||
@@ -67,12 +95,34 @@ download_runner() {
|
||||
echo "$release" > "$VERSION_FILE"
|
||||
}
|
||||
|
||||
# Inject the script into the container and execute it in the requested mode.
|
||||
# Forwards the runtime configuration the inner invocation needs to reproduce
|
||||
# what the user requested on the host (mirrors openbao/install.sh's helper
|
||||
# of the same name).
|
||||
exec_in_lxc() {
|
||||
local ctid="$1"
|
||||
local mode="$2" # --install or --update
|
||||
|
||||
pct exec "$ctid" -- sh -c "apk add --no-cache bash curl jq > /dev/null 2>&1"
|
||||
curl -fsSL "$SCRIPT_URL" \
|
||||
| pct exec "$ctid" -- env \
|
||||
SCRIPT_URL="$SCRIPT_URL" \
|
||||
GITEA_HOSTNAME="$GITEA_HOSTNAME" \
|
||||
bash -s -- "$mode"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MODE 1: Proxmox host — create LXC container
|
||||
# ============================================================
|
||||
create_lxc() {
|
||||
log_info "=== Gitea Act Runner — LXC Creation ==="
|
||||
|
||||
if [[ -z "$TEMPLATE" ]]; then
|
||||
TEMPLATE=$(detect_latest_alpine_template)
|
||||
else
|
||||
log_info "Using user-provided template: $TEMPLATE"
|
||||
fi
|
||||
|
||||
# Auto-select next CTID if not specified
|
||||
if [[ -z "$CTID" ]]; then
|
||||
CTID=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null \
|
||||
@@ -86,16 +136,16 @@ create_lxc() {
|
||||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
|
||||
fi
|
||||
|
||||
log_info "Creating LXC $CTID ($HOSTNAME)..."
|
||||
log_info "Creating LXC $CTID ($HOSTNAME_LXC)..."
|
||||
pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" \
|
||||
--hostname "$HOSTNAME" \
|
||||
--hostname "$HOSTNAME_LXC" \
|
||||
--cores "$CORES" \
|
||||
--memory "$RAM" \
|
||||
--rootfs "${STORAGE}:${DISK}" \
|
||||
--net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \
|
||||
--unprivileged 1 \
|
||||
--features nesting=1,keyctl=1 \
|
||||
--tags "infra-script,cicd" \
|
||||
--tags "infra-script,${LXC_TAG}" \
|
||||
--start 0
|
||||
|
||||
log_info "Configuring LXC for Docker and Tailscale..."
|
||||
@@ -111,8 +161,7 @@ EOF
|
||||
sleep 5
|
||||
|
||||
log_info "Injecting install script into container..."
|
||||
pct exec "$CTID" -- sh -c "apk add --no-cache bash curl jq > /dev/null 2>&1"
|
||||
curl -fsSL "$SCRIPT_URL" | pct exec "$CTID" -- bash -s -- --install
|
||||
exec_in_lxc "$CTID" "--install"
|
||||
|
||||
local ip
|
||||
ip=$(pct exec "$CTID" -- ip -4 addr show eth0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1)
|
||||
@@ -122,7 +171,7 @@ EOF
|
||||
log_info "LXC $CTID created successfully!"
|
||||
log_info "========================================="
|
||||
echo ""
|
||||
echo " Hostname : $HOSTNAME"
|
||||
echo " Hostname : $HOSTNAME_LXC"
|
||||
echo " IP : ${ip:-pending}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
@@ -133,6 +182,28 @@ EOF
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MODE 1b: Proxmox host — update an existing LXC
|
||||
# ============================================================
|
||||
update_lxc() {
|
||||
local ctid="$1"
|
||||
log_info "=== Gitea Act Runner — updating existing LXC ${ctid} ==="
|
||||
|
||||
if ! pct status "$ctid" | grep -q running; then
|
||||
log_info "Starting LXC ${ctid}..."
|
||||
pct start "$ctid"
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
log_info "Refreshing Alpine packages inside LXC ${ctid}..."
|
||||
pct exec "$ctid" -- sh -c "apk update >/dev/null && apk upgrade >/dev/null"
|
||||
|
||||
log_info "Upgrading act_runner binary inside LXC ${ctid}..."
|
||||
exec_in_lxc "$ctid" "--update"
|
||||
|
||||
log_info "Update of LXC ${ctid} complete."
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MODE 2: Inside LXC — fresh install
|
||||
# ============================================================
|
||||
@@ -232,24 +303,7 @@ LOGROTATE
|
||||
|
||||
ln -sf /usr/sbin/logrotate /etc/periodic/daily/logrotate 2>/dev/null || true
|
||||
|
||||
log_info "Enabling console auto-login on tty1..."
|
||||
# Alpine ships busybox getty by default; agetty (from util-linux) is what
|
||||
# supports --autologin.
|
||||
apk add --no-cache agetty >/dev/null 2>&1 || apk add --no-cache util-linux >/dev/null
|
||||
|
||||
# Replace any existing tty1 entry, then append our autologin line. Doing it
|
||||
# in two steps (delete + append) is more robust than an in-place sed against
|
||||
# a pattern that may drift across Alpine releases.
|
||||
sed -i '/^tty1::/d' /etc/inittab
|
||||
echo 'tty1::respawn:/sbin/agetty --autologin root --noclear 38400 tty1' >> /etc/inittab
|
||||
|
||||
# Tell PID 1 to re-read /etc/inittab so the change takes effect without a reboot.
|
||||
kill -HUP 1 2>/dev/null || true
|
||||
|
||||
# Kick any getty/agetty still attached to tty1 so init respawns it *now* with
|
||||
# the new line — otherwise the first web-console session lands on the stale
|
||||
# process and the operator has to type `exit` once before autologin kicks in.
|
||||
pkill -KILL -f '(getty|agetty).*tty1' 2>/dev/null || true
|
||||
enable_tty1_autologin
|
||||
|
||||
log_info "Cleaning up..."
|
||||
rm -rf /var/cache/apk/*
|
||||
@@ -275,6 +329,8 @@ LOGROTATE
|
||||
update_runner() {
|
||||
log_info "=== Gitea Act Runner — Update ==="
|
||||
|
||||
refresh_os_packages
|
||||
|
||||
local release
|
||||
release=$(get_latest_release)
|
||||
|
||||
@@ -305,12 +361,27 @@ update_runner() {
|
||||
# Main — detect context
|
||||
# ============================================================
|
||||
main() {
|
||||
if [[ "${1:-}" == "--install" ]]; then
|
||||
# Explicitly called in install mode (from pct exec)
|
||||
install_runner
|
||||
elif command -v pct &> /dev/null; then
|
||||
case "${1:-}" in
|
||||
--install)
|
||||
install_runner
|
||||
return
|
||||
;;
|
||||
--update)
|
||||
update_runner
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
if command -v pct &> /dev/null; then
|
||||
# We're on the Proxmox host
|
||||
create_lxc
|
||||
require_root
|
||||
local existing=""
|
||||
if existing=$(find_existing_lxc); then
|
||||
log_info "Found existing gitea-runner LXC (CTID ${existing}, hostname/tag match) — switching to update mode."
|
||||
update_lxc "$existing"
|
||||
else
|
||||
create_lxc
|
||||
fi
|
||||
elif [[ -f /usr/local/bin/act_runner ]]; then
|
||||
# act_runner exists — update mode
|
||||
update_runner
|
||||
|
||||
+43
-4
@@ -26,7 +26,18 @@ set -euo pipefail
|
||||
# template if `pveam` is unavailable or returns nothing.
|
||||
# ============================================================
|
||||
detect_latest_alpine_template() {
|
||||
:
|
||||
local tmpl
|
||||
tmpl=$(pveam available --section system 2>/dev/null \
|
||||
| awk '/^system[[:space:]]+alpine-/ {print $2}' \
|
||||
| sort -V \
|
||||
| tail -n1)
|
||||
|
||||
if [[ -z "$tmpl" ]]; then
|
||||
log_warn "Could not query pveam; falling back to a known-good Alpine template."
|
||||
tmpl="alpine-3.22-default_20250617_amd64.tar.xz"
|
||||
fi
|
||||
log_info "Selected Alpine template: $tmpl"
|
||||
echo "$tmpl"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -34,7 +45,24 @@ detect_latest_alpine_template() {
|
||||
# Idempotent: safe to call on every install/update.
|
||||
# ============================================================
|
||||
enable_tty1_autologin() {
|
||||
:
|
||||
log_info "Enabling console auto-login on tty1..."
|
||||
# Alpine ships busybox getty by default; agetty (from util-linux) is what
|
||||
# supports --autologin.
|
||||
apk add --no-cache agetty >/dev/null 2>&1 || apk add --no-cache util-linux >/dev/null
|
||||
|
||||
# Replace any existing tty1 entry, then append our autologin line. Doing it
|
||||
# in two steps (delete + append) is more robust than an in-place sed against
|
||||
# a pattern that may drift across Alpine releases.
|
||||
sed -i '/^tty1::/d' /etc/inittab
|
||||
echo 'tty1::respawn:/sbin/agetty --autologin root --noclear 38400 tty1' >> /etc/inittab
|
||||
|
||||
# Tell PID 1 to re-read /etc/inittab so the change takes effect without a reboot.
|
||||
kill -HUP 1 2>/dev/null || true
|
||||
|
||||
# Kick any getty/agetty still attached to tty1 so init respawns it *now* with
|
||||
# the new line — otherwise the first web-console session lands on the stale
|
||||
# process and the operator has to type `exit` once before autologin kicks in.
|
||||
pkill -KILL -f '(getty|agetty).*tty1' 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -45,7 +73,17 @@ enable_tty1_autologin() {
|
||||
# find_existing_lxc already does).
|
||||
# ============================================================
|
||||
find_existing_lxc() {
|
||||
:
|
||||
local id host tags
|
||||
while read -r id _; do
|
||||
[[ -z "$id" || "$id" == "VMID" ]] && continue
|
||||
host=$(pct config "$id" 2>/dev/null | awk -F': ' '/^hostname:/ {print $2}' || true)
|
||||
tags=$(pct config "$id" 2>/dev/null | awk -F': ' '/^tags:/ {print $2}' || true)
|
||||
if [[ "$host" == "$HOSTNAME_LXC" ]] || [[ ",${tags//;/,}," == *",${LXC_TAG},"* ]]; then
|
||||
echo "$id"
|
||||
return 0
|
||||
fi
|
||||
done < <(pct list | awk 'NR>1 {print $1}')
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -53,5 +91,6 @@ find_existing_lxc() {
|
||||
# Callable both host-side (via pct exec) and inside the LXC.
|
||||
# ============================================================
|
||||
refresh_os_packages() {
|
||||
:
|
||||
log_info "Refreshing Alpine packages..."
|
||||
apk update >/dev/null && apk upgrade >/dev/null
|
||||
}
|
||||
|
||||
+3
-1
@@ -110,7 +110,9 @@ curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/feat/lxc-Open
|
||||
|
||||
The script auto-detects the presence of `/usr/local/bin/bao` and switches to
|
||||
update mode. The OpenRC service is stopped, the binary is swapped (the old one
|
||||
is kept as `bao.bak.<ts>`), then the service is restarted.
|
||||
is kept as `bao.bak.<ts>`), then the service is restarted. Re-running from the
|
||||
Proxmox host does the same, plus refreshes the LXC's Alpine packages first
|
||||
(`apk update && apk upgrade`).
|
||||
|
||||
### Architecture
|
||||
|
||||
|
||||
+28
-51
@@ -56,6 +56,29 @@ log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
|
||||
# ============================================================
|
||||
# Load shared helpers (lib/common.sh: detect_latest_alpine_template,
|
||||
# enable_tty1_autologin, find_existing_lxc, refresh_os_packages).
|
||||
#
|
||||
# This script runs in three different contexts, only one of which has a
|
||||
# real file on disk next to it:
|
||||
# - local checkout (`bash openbao/install.sh`) -> lib/common.sh
|
||||
# sits right there at ../lib/common.sh, source it straight from disk.
|
||||
# - Proxmox host, documented one-liner (`bash -c "$(curl ... )"`)
|
||||
# -> no checkout, no BASH_SOURCE path worth trusting.
|
||||
# - inside the LXC (exec_in_lxc does `curl ... | pct exec ... bash -s --`)
|
||||
# -> same story, script arrives on stdin.
|
||||
# For the latter two we fetch lib/common.sh over HTTP, next to SCRIPT_URL.
|
||||
# The LXC already needs outbound network to curl this very script and to
|
||||
# download the bao binary, so this adds no new failure mode.
|
||||
# ============================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd || true)"
|
||||
if [[ -n "$SCRIPT_DIR" && -f "${SCRIPT_DIR}/../lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/common.sh"
|
||||
else
|
||||
source <(curl -fsSL "$(dirname "$(dirname "$SCRIPT_URL")")/lib/common.sh")
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Generic helpers
|
||||
# ============================================================
|
||||
@@ -204,37 +227,6 @@ configure_tailscale_proxy() {
|
||||
# Proxmox-host helpers
|
||||
# ============================================================
|
||||
|
||||
# Detect newest Alpine LXC template available from the Proxmox repos.
|
||||
detect_latest_alpine_template() {
|
||||
local tmpl
|
||||
tmpl=$(pveam available --section system 2>/dev/null \
|
||||
| awk '/^system[[:space:]]+alpine-/ {print $2}' \
|
||||
| sort -V \
|
||||
| tail -n1)
|
||||
|
||||
if [[ -z "$tmpl" ]]; then
|
||||
log_warn "Could not query pveam; falling back to a known-good Alpine template."
|
||||
tmpl="alpine-3.22-default_20250617_amd64.tar.xz"
|
||||
fi
|
||||
log_info "Selected Alpine template: $tmpl"
|
||||
echo "$tmpl"
|
||||
}
|
||||
|
||||
# Find an existing LXC by tag or hostname. Echoes CTID, returns 1 if none.
|
||||
find_existing_lxc() {
|
||||
local id host tags
|
||||
while read -r id _; do
|
||||
[[ -z "$id" || "$id" == "VMID" ]] && continue
|
||||
host=$(pct config "$id" 2>/dev/null | awk -F': ' '/^hostname:/ {print $2}' || true)
|
||||
tags=$(pct config "$id" 2>/dev/null | awk -F': ' '/^tags:/ {print $2}' || true)
|
||||
if [[ "$host" == "$HOSTNAME_LXC" ]] || [[ ",${tags//;/,}," == *",${LXC_TAG},"* ]]; then
|
||||
echo "$id"
|
||||
return 0
|
||||
fi
|
||||
done < <(pct list | awk 'NR>1 {print $1}')
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_template_present() {
|
||||
local tmpl="$1"
|
||||
if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$tmpl"; then
|
||||
@@ -363,6 +355,9 @@ update_lxc() {
|
||||
fi
|
||||
|
||||
log_info "Refreshing Alpine packages inside LXC ${ctid}..."
|
||||
# refresh_os_packages() is a bash function local to this process; it can't
|
||||
# run over `pct exec ... sh -c` without shipping the function definition
|
||||
# into the container, so this call site stays inline rather than dedupe.
|
||||
pct exec "$ctid" -- sh -c "apk update >/dev/null && apk upgrade >/dev/null"
|
||||
|
||||
log_info "Upgrading bao binary inside LXC ${ctid}..."
|
||||
@@ -474,24 +469,7 @@ EOF
|
||||
log_info "Starting openbao service..."
|
||||
rc-service openbao start || log_warn "openbao failed to start — inspect /var/log/openbao.log"
|
||||
|
||||
log_info "Enabling console auto-login on tty1..."
|
||||
# Alpine ships busybox getty by default; agetty (from util-linux) is what
|
||||
# supports --autologin.
|
||||
apk add --no-cache agetty >/dev/null 2>&1 || apk add --no-cache util-linux >/dev/null
|
||||
|
||||
# Replace any existing tty1 entry, then append our autologin line. Doing it
|
||||
# in two steps (delete + append) is more robust than an in-place sed against
|
||||
# a pattern that may drift across Alpine releases.
|
||||
sed -i '/^tty1::/d' /etc/inittab
|
||||
echo 'tty1::respawn:/sbin/agetty --autologin root --noclear 38400 tty1' >> /etc/inittab
|
||||
|
||||
# Tell PID 1 to re-read /etc/inittab so the change takes effect without a reboot.
|
||||
kill -HUP 1 2>/dev/null || true
|
||||
|
||||
# Kick any getty/agetty still attached to tty1 so init respawns it *now* with
|
||||
# the new line — otherwise the first web-console session lands on the stale
|
||||
# process and the operator has to type `exit` once before autologin kicks in.
|
||||
pkill -KILL -f '(getty|agetty).*tty1' 2>/dev/null || true
|
||||
enable_tty1_autologin
|
||||
|
||||
configure_tailscale_proxy
|
||||
|
||||
@@ -563,8 +541,7 @@ MOTD
|
||||
# ============================================================
|
||||
update_inside_lxc() {
|
||||
log_info "=== OpenBao — update ==="
|
||||
apk update >/dev/null
|
||||
apk upgrade >/dev/null
|
||||
refresh_os_packages
|
||||
install_or_upgrade_bao
|
||||
configure_tailscale_proxy
|
||||
log_info "Update complete."
|
||||
|
||||
Reference in New Issue
Block a user