split builder and signer provision scripts for Gitea CI
This commit is contained in:
Executable
+264
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env bash
|
||||
# Provisions an Ubuntu 22.04 LTS or Debian 13 (Trixie) host as a Gitea Actions
|
||||
# runner for RustDesk desktop (.deb) builds. Idempotent: safe to re-run.
|
||||
#
|
||||
# Versions are pinned to .gitea/workflows/build-linux.yml. Bump them there and
|
||||
# here together.
|
||||
#
|
||||
# Build host vs. user host: the resulting .deb links against the host's glibc.
|
||||
# Build on the OLDEST distro your users have, otherwise the .deb won't install
|
||||
# on older systems.
|
||||
# - Ubuntu 22.04 build -> runs on Ubuntu 22.04+, Debian 12+, derivatives
|
||||
# - Debian 13 build -> runs on Debian 13+, Ubuntu 24.04+ only
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./provision.sh \
|
||||
# --gitea-url https://gitea.example.com \
|
||||
# --runner-token <token>
|
||||
#
|
||||
# All toolchains land in /opt and are readable by the gitea-runner user.
|
||||
# Service is installed as a systemd unit running as that user.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- pinned versions (mirror .gitea/workflows/build-linux.yml env block) ----
|
||||
RUST_VERSION="1.75.0"
|
||||
FLUTTER_VERSION="3.24.5" # used for `flutter build linux`
|
||||
FLUTTER_BRIDGE_VERSION="3.22.3" # used for `flutter pub get` + flutter_rust_bridge_codegen
|
||||
LLVM_VERSION="15.0.6"
|
||||
VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b"
|
||||
RUNNER_VERSION="0.2.11"
|
||||
|
||||
# ---- defaults ----
|
||||
RUNNER_NAME="$(hostname)-rustdesk"
|
||||
RUNNER_LABELS="" # auto-derived from /etc/os-release if empty
|
||||
SERVICE_USER="gitea-runner"
|
||||
GITEA_URL=""
|
||||
RUNNER_TOKEN=""
|
||||
|
||||
# ---- arg parse ----
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gitea-url) GITEA_URL="$2"; shift 2 ;;
|
||||
--runner-token) RUNNER_TOKEN="$2"; shift 2 ;;
|
||||
--runner-name) RUNNER_NAME="$2"; shift 2 ;;
|
||||
--runner-labels) RUNNER_LABELS="$2"; shift 2 ;;
|
||||
--service-user) SERVICE_USER="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
sed -n '2,18p' "$0"
|
||||
exit 0 ;;
|
||||
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ "$EUID" -eq 0 ]] || { echo "Run as root (use sudo)." >&2; exit 1; }
|
||||
[[ -n "$GITEA_URL" && -n "$RUNNER_TOKEN" ]] \
|
||||
|| { echo "Missing --gitea-url or --runner-token" >&2; exit 2; }
|
||||
|
||||
. /etc/os-release
|
||||
case "${ID}-${VERSION_ID:-}" in
|
||||
ubuntu-22.04) DISTRO_LABEL="ubuntu-22.04" ;;
|
||||
debian-13|debian-trixie) DISTRO_LABEL="debian-13" ;;
|
||||
*)
|
||||
echo "WARNING: tested only on Ubuntu 22.04 and Debian 13. You're on $PRETTY_NAME."
|
||||
echo "Package names may differ; build outputs may not run on user systems."
|
||||
DISTRO_LABEL="${ID}-${VERSION_ID:-unknown}"
|
||||
sleep 3 ;;
|
||||
esac
|
||||
|
||||
# If --runner-labels wasn't passed, derive a sensible default that includes the
|
||||
# detected distro so workflows can target a specific build host when needed.
|
||||
if [[ -z "$RUNNER_LABELS" ]]; then
|
||||
RUNNER_LABELS="${DISTRO_LABEL},self-hosted,X64,Linux"
|
||||
fi
|
||||
|
||||
log() { printf '\n==> %s\n' "$*"; }
|
||||
|
||||
# ---- 1. apt packages ----
|
||||
log "Installing apt packages"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential clang gcc g++ cmake ninja-build pkg-config nasm yasm \
|
||||
autoconf automake libtool libtool-bin \
|
||||
libclang-dev llvm-dev \
|
||||
libgtk-3-dev libayatana-appindicator3-dev \
|
||||
libasound2-dev libpulse-dev libpam0g-dev libssl-dev \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
libva-dev libxdo-dev libxfixes-dev \
|
||||
libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
|
||||
git curl wget zip unzip tar xz-utils ca-certificates \
|
||||
python3 python3-pip \
|
||||
rpm tree dpkg-dev sudo
|
||||
|
||||
# Node.js (act_runner spawns node for JS actions like actions/checkout)
|
||||
if ! command -v node >/dev/null; then
|
||||
log "Installing Node.js LTS"
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt-get install -y --no-install-recommends nodejs
|
||||
fi
|
||||
|
||||
# ---- 2. LLVM (binary tarball; libclang-15-dev was dropped from Debian 13) ----
|
||||
LLVM_DIR="/opt/llvm-${LLVM_VERSION}"
|
||||
if [[ ! -x "$LLVM_DIR/bin/clang" ]]; then
|
||||
log "Installing LLVM $LLVM_VERSION (binary tarball)"
|
||||
arch="$(uname -m)"
|
||||
case "$arch" in
|
||||
x86_64) llvm_arch="x86_64-linux-gnu-ubuntu-18.04" ;;
|
||||
aarch64) llvm_arch="aarch64-linux-gnu" ;;
|
||||
*) echo "Unsupported arch for LLVM tarball: $arch" >&2; exit 1 ;;
|
||||
esac
|
||||
tmp="$(mktemp -d)"
|
||||
curl -fsSL -o "$tmp/llvm.tar.xz" \
|
||||
"https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${llvm_arch}.tar.xz"
|
||||
mkdir -p "$LLVM_DIR"
|
||||
tar --strip-components=1 -xJf "$tmp/llvm.tar.xz" -C "$LLVM_DIR"
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
|
||||
# ---- 3. dedicated runner user ----
|
||||
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
log "Creating user $SERVICE_USER"
|
||||
useradd --system --create-home --shell /bin/bash "$SERVICE_USER"
|
||||
fi
|
||||
RUNNER_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
|
||||
|
||||
# ---- 4. Rust (machine-wide) ----
|
||||
export RUSTUP_HOME=/opt/rustup
|
||||
export CARGO_HOME=/opt/cargo
|
||||
mkdir -p "$RUSTUP_HOME" "$CARGO_HOME"
|
||||
|
||||
if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then
|
||||
log "Installing rustup at $RUSTUP_HOME / $CARGO_HOME"
|
||||
curl -fsSL https://sh.rustup.rs | RUSTUP_HOME="$RUSTUP_HOME" CARGO_HOME="$CARGO_HOME" \
|
||||
sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
|
||||
fi
|
||||
"$CARGO_HOME/bin/rustup" toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
|
||||
"$CARGO_HOME/bin/rustup" target add --toolchain "$RUST_VERSION" x86_64-unknown-linux-gnu
|
||||
"$CARGO_HOME/bin/rustup" default "$RUST_VERSION"
|
||||
|
||||
# ---- 5. Flutter (two SDKs: 3.24.5 for build, 3.22.3 for bridge gen) ----
|
||||
# Why two: the bridge codegen (flutter_rust_bridge_codegen 1.80.1 + freezed)
|
||||
# produces broken Dart output when run under newer Flutter SDKs on Linux.
|
||||
# Upstream's bridge.yml uses 3.22.3 specifically; we mirror that. The .deb
|
||||
# build itself uses 3.24.5.
|
||||
install_flutter() {
|
||||
local ver="$1" dir="$2"
|
||||
if [[ ! -x "$dir/bin/flutter" ]]; then
|
||||
log "Installing Flutter $ver -> $dir"
|
||||
local tmp; tmp="$(mktemp -d)"
|
||||
local parent; parent="$(dirname "$dir")"
|
||||
curl -fsSL -o "$tmp/flutter.tar.xz" \
|
||||
"https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${ver}-stable.tar.xz"
|
||||
# Tarball extracts into a top-level `flutter/` dir; rename to target.
|
||||
tar -xJf "$tmp/flutter.tar.xz" -C "$tmp"
|
||||
mkdir -p "$parent"
|
||||
mv "$tmp/flutter" "$dir"
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
"$dir/bin/flutter" config --no-analytics >/dev/null
|
||||
"$dir/bin/flutter" precache --linux >/dev/null
|
||||
}
|
||||
install_flutter "$FLUTTER_VERSION" /opt/flutter
|
||||
install_flutter "$FLUTTER_BRIDGE_VERSION" /opt/flutter-bridge
|
||||
FLUTTER_DIR=/opt/flutter
|
||||
|
||||
# ---- 6. vcpkg ----
|
||||
VCPKG_DIR=/opt/vcpkg
|
||||
if [[ ! -d "$VCPKG_DIR/.git" ]]; then
|
||||
log "Cloning vcpkg"
|
||||
git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR"
|
||||
fi
|
||||
git -C "$VCPKG_DIR" fetch --tags origin
|
||||
git -C "$VCPKG_DIR" -c advice.detachedHead=false checkout "$VCPKG_COMMIT"
|
||||
[[ -x "$VCPKG_DIR/vcpkg" ]] || "$VCPKG_DIR/bootstrap-vcpkg.sh" -disableMetrics
|
||||
|
||||
# vcpkg binary cache (file-backed -- same scheme as build-windows.yml)
|
||||
mkdir -p /var/cache/vcpkg
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/cache/vcpkg
|
||||
|
||||
# ---- 7. Permissions ----
|
||||
log "Setting up permissions for $SERVICE_USER"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$CARGO_HOME"
|
||||
# rustup state needs to be writable too -- toolchain installs touch it.
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$RUSTUP_HOME"
|
||||
# Flutter SDK: r/x is enough for builds, but `flutter pub get` writes to its
|
||||
# own cache subdir so we make it writable as well.
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$FLUTTER_DIR"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /opt/flutter-bridge
|
||||
# vcpkg: builds write under installed/, buildtrees/, etc.
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$VCPKG_DIR"
|
||||
# LLVM: read+execute is enough; we never write here at build time.
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$LLVM_DIR"
|
||||
# /opt/cargo-tools: workflow installs cargo-expand and flutter_rust_bridge_codegen
|
||||
# here via `cargo install --root`. Pre-create with the right owner so the first
|
||||
# job doesn't try to mkdir under root-owned /opt.
|
||||
mkdir -p /opt/cargo-tools
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /opt/cargo-tools
|
||||
|
||||
# git "dubious ownership": same fix as Windows. Trust system-wide.
|
||||
git config --system --add safe.directory '*' || true
|
||||
|
||||
# ---- 8. act_runner ----
|
||||
RUNNER_DIR=/var/lib/gitea-runner
|
||||
mkdir -p "$RUNNER_DIR"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$RUNNER_DIR"
|
||||
|
||||
if [[ ! -x "$RUNNER_DIR/act_runner" ]]; then
|
||||
log "Downloading act_runner $RUNNER_VERSION"
|
||||
curl -fsSL -o "$RUNNER_DIR/act_runner" \
|
||||
"https://gitea.com/gitea/act_runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-amd64"
|
||||
chmod +x "$RUNNER_DIR/act_runner"
|
||||
chown "$SERVICE_USER:$SERVICE_USER" "$RUNNER_DIR/act_runner"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$RUNNER_DIR/.runner" ]]; then
|
||||
log "Registering runner with $GITEA_URL"
|
||||
sudo -u "$SERVICE_USER" -H bash -c "
|
||||
cd '$RUNNER_DIR' && \
|
||||
./act_runner register --no-interactive \
|
||||
--instance '$GITEA_URL' \
|
||||
--token '$RUNNER_TOKEN' \
|
||||
--name '$RUNNER_NAME' \
|
||||
--labels '$RUNNER_LABELS'
|
||||
"
|
||||
fi
|
||||
|
||||
# ---- 9. systemd service ----
|
||||
log "Installing systemd unit"
|
||||
cat > /etc/systemd/system/gitea-act-runner.service <<EOF
|
||||
[Unit]
|
||||
Description=Gitea Actions runner (RustDesk)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${SERVICE_USER}
|
||||
WorkingDirectory=${RUNNER_DIR}
|
||||
ExecStart=${RUNNER_DIR}/act_runner daemon
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
# Toolchain locations -- needed because services don't inherit a login shell's PATH.
|
||||
Environment=RUSTUP_HOME=${RUSTUP_HOME}
|
||||
Environment=CARGO_HOME=${CARGO_HOME}
|
||||
Environment=VCPKG_ROOT=${VCPKG_DIR}
|
||||
Environment=LIBCLANG_PATH=${LLVM_DIR}/lib
|
||||
Environment=PATH=${CARGO_HOME}/bin:${FLUTTER_DIR}/bin:${LLVM_DIR}/bin:${VCPKG_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# Resource limits for builds
|
||||
LimitNOFILE=65535
|
||||
TasksMax=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable gitea-act-runner.service
|
||||
systemctl restart gitea-act-runner.service
|
||||
|
||||
log "Done."
|
||||
echo " Verify with: systemctl status gitea-act-runner"
|
||||
echo " Tail logs with: journalctl -u gitea-act-runner -f"
|
||||
echo " Runner should appear (online) at $GITEA_URL > Site Admin > Actions > Runners"
|
||||
Executable
+291
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env bash
|
||||
# Provisions a macOS host (Apple Silicon, macOS 14+) as a Gitea Actions runner
|
||||
# for RustDesk desktop (.dmg) builds. Idempotent: safe to re-run.
|
||||
#
|
||||
# Versions are pinned to .gitea/workflows/build-macos.yml. Bump them there and
|
||||
# here together.
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./provision.sh \
|
||||
# --gitea-url https://gitea.example.com \
|
||||
# --runner-token <token>
|
||||
#
|
||||
# Toolchains land in /opt/* (chowned to the runner user). Service is installed
|
||||
# as a LaunchDaemon running as that user.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- pinned versions (mirror .gitea/workflows/build-macos.yml env block) ----
|
||||
RUST_VERSION="1.81.0" # MAC_RUST_VERSION upstream (cidre crate needs >=1.81)
|
||||
FLUTTER_VERSION="3.24.5" # used for `flutter build macos`
|
||||
FLUTTER_BRIDGE_VERSION="3.22.3" # used for `flutter pub get` + flutter_rust_bridge_codegen
|
||||
VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b"
|
||||
NASM_VERSION="2.16.03" # 3.x has incompatible CLI; aom/dav1d need 2.x
|
||||
RUNNER_VERSION="0.2.11"
|
||||
|
||||
# ---- defaults ----
|
||||
RUNNER_NAME="$(hostname -s)-rustdesk"
|
||||
RUNNER_LABELS=""
|
||||
SERVICE_USER="gitea-runner"
|
||||
GITEA_URL=""
|
||||
RUNNER_TOKEN=""
|
||||
|
||||
# ---- arg parse ----
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gitea-url) GITEA_URL="$2"; shift 2 ;;
|
||||
--runner-token) RUNNER_TOKEN="$2"; shift 2 ;;
|
||||
--runner-name) RUNNER_NAME="$2"; shift 2 ;;
|
||||
--runner-labels) RUNNER_LABELS="$2"; shift 2 ;;
|
||||
--service-user) SERVICE_USER="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
sed -n '2,15p' "$0"
|
||||
exit 0 ;;
|
||||
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ "$EUID" -eq 0 ]] || { echo "Run as root (use sudo)." >&2; exit 1; }
|
||||
[[ -n "$GITEA_URL" && -n "$RUNNER_TOKEN" ]] \
|
||||
|| { echo "Missing --gitea-url or --runner-token" >&2; exit 2; }
|
||||
|
||||
# ---- arch + macOS version detection ----
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
arm64) HOMEBREW_PREFIX="/opt/homebrew"; ARCH_LABEL="ARM64" ;;
|
||||
x86_64) HOMEBREW_PREFIX="/usr/local"; ARCH_LABEL="X64" ;;
|
||||
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
OS_MAJOR="$(sw_vers -productVersion | cut -d. -f1)"
|
||||
[[ "$OS_MAJOR" -ge 14 ]] || {
|
||||
echo "WARNING: tested only on macOS 14+. You're on $(sw_vers -productVersion)."
|
||||
sleep 3
|
||||
}
|
||||
DISTRO_LABEL="macos-${OS_MAJOR}"
|
||||
|
||||
if [[ -z "$RUNNER_LABELS" ]]; then
|
||||
RUNNER_LABELS="${DISTRO_LABEL},self-hosted,${ARCH_LABEL},macOS"
|
||||
fi
|
||||
|
||||
log() { printf '\n==> %s\n' "$*"; }
|
||||
|
||||
# ---- 1. Xcode Command Line Tools ----
|
||||
log "Verifying Xcode Command Line Tools"
|
||||
if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
|
||||
echo "Xcode Command Line Tools not installed. Run:" >&2
|
||||
echo " xcode-select --install" >&2
|
||||
echo "Then re-run this script." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " $(xcode-select -p)"
|
||||
|
||||
# ---- 2. Homebrew (machine-wide) ----
|
||||
# Homebrew refuses to install under root (its installer aborts with
|
||||
# "Don't run this as root!"). It must be installed manually by a regular
|
||||
# user before this script runs.
|
||||
log "Verifying Homebrew"
|
||||
if [[ ! -x "$HOMEBREW_PREFIX/bin/brew" ]]; then
|
||||
echo "Homebrew not installed at $HOMEBREW_PREFIX." >&2
|
||||
echo "Install it as your regular user (NOT root), then re-run this script:" >&2
|
||||
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
export PATH="$HOMEBREW_PREFIX/bin:$PATH"
|
||||
echo " $(brew --version | head -1)"
|
||||
|
||||
# brew install must also run as a non-root user. Determine which user invoked
|
||||
# sudo so we can drop privileges for brew commands below.
|
||||
BREW_USER="${SUDO_USER:-}"
|
||||
if [[ -z "$BREW_USER" || "$BREW_USER" == "root" ]]; then
|
||||
echo "Could not determine the non-root user that ran sudo (SUDO_USER unset)." >&2
|
||||
echo "Re-run with: sudo ./provision.sh ..." >&2
|
||||
exit 1
|
||||
fi
|
||||
brew_as_user() { sudo -u "$BREW_USER" -H "$HOMEBREW_PREFIX/bin/brew" "$@"; }
|
||||
|
||||
# ---- 3. brew packages ----
|
||||
log "Installing brew packages"
|
||||
brew_pkgs=(node cocoapods llvm create-dmg pkg-config cmake ninja yasm autoconf automake libtool wget)
|
||||
for p in "${brew_pkgs[@]}"; do
|
||||
if brew_as_user list --versions "$p" >/dev/null 2>&1; then
|
||||
echo " $p (already installed)"
|
||||
else
|
||||
brew_as_user install "$p"
|
||||
fi
|
||||
done
|
||||
|
||||
# ---- 4. NASM 2.16.x (NOT brew's nasm 3.x; aom/dav1d need 2.x) ----
|
||||
if ! /usr/local/bin/nasm --version 2>/dev/null | grep -q "version $NASM_VERSION"; then
|
||||
log "Installing NASM $NASM_VERSION"
|
||||
tmp="$(mktemp -d)"
|
||||
curl -fsSL -o "$tmp/nasm.zip" \
|
||||
"https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/macosx/nasm-${NASM_VERSION}-macosx.zip"
|
||||
unzip -q "$tmp/nasm.zip" -d "$tmp"
|
||||
install -m 0755 "$tmp/nasm-${NASM_VERSION}/nasm" /usr/local/bin/nasm
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
/usr/local/bin/nasm --version | head -1
|
||||
|
||||
# ---- 5. dedicated runner user ----
|
||||
if ! /usr/bin/id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
log "Creating user $SERVICE_USER"
|
||||
# Find an unused UID >= 600
|
||||
uid=600
|
||||
while dscl . -list /Users UniqueID | awk '{print $2}' | grep -qx "$uid"; do
|
||||
uid=$((uid + 1))
|
||||
done
|
||||
dscl . -create "/Users/$SERVICE_USER"
|
||||
dscl . -create "/Users/$SERVICE_USER" UserShell /bin/bash
|
||||
dscl . -create "/Users/$SERVICE_USER" RealName "Gitea Runner"
|
||||
dscl . -create "/Users/$SERVICE_USER" UniqueID "$uid"
|
||||
dscl . -create "/Users/$SERVICE_USER" PrimaryGroupID 20
|
||||
dscl . -create "/Users/$SERVICE_USER" NFSHomeDirectory "/Users/$SERVICE_USER"
|
||||
mkdir -p "/Users/$SERVICE_USER"
|
||||
chown "$SERVICE_USER:staff" "/Users/$SERVICE_USER"
|
||||
fi
|
||||
RUNNER_HOME="/Users/$SERVICE_USER"
|
||||
|
||||
# ---- 6. Rust (machine-wide) ----
|
||||
export RUSTUP_HOME=/opt/rustup
|
||||
export CARGO_HOME=/opt/cargo
|
||||
mkdir -p "$RUSTUP_HOME" "$CARGO_HOME"
|
||||
|
||||
if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then
|
||||
log "Installing rustup at $RUSTUP_HOME / $CARGO_HOME"
|
||||
curl -fsSL https://sh.rustup.rs | RUSTUP_HOME="$RUSTUP_HOME" CARGO_HOME="$CARGO_HOME" \
|
||||
sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
|
||||
fi
|
||||
"$CARGO_HOME/bin/rustup" toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
|
||||
"$CARGO_HOME/bin/rustup" target add --toolchain "$RUST_VERSION" aarch64-apple-darwin x86_64-apple-darwin
|
||||
"$CARGO_HOME/bin/rustup" default "$RUST_VERSION"
|
||||
|
||||
# ---- 7. Flutter (two SDKs: 3.24.5 for build, 3.22.3 for bridge gen) ----
|
||||
# Same rationale as Linux: bridge codegen 1.80.1 + freezed produces broken Dart
|
||||
# under newer Flutter. Run codegen under 3.22.3, build under 3.24.5.
|
||||
install_flutter() {
|
||||
local ver="$1" dir="$2"
|
||||
if [[ ! -x "$dir/bin/flutter" ]]; then
|
||||
log "Installing Flutter $ver -> $dir"
|
||||
local tmp; tmp="$(mktemp -d)"
|
||||
local parent; parent="$(dirname "$dir")"
|
||||
# Flutter URL pattern differs between archs: Apple Silicon has an
|
||||
# `_arm64_` infix, Intel has no arch infix at all.
|
||||
local flutter_url_base="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos"
|
||||
local flutter_url
|
||||
case "$ARCH" in
|
||||
arm64) flutter_url="${flutter_url_base}_arm64_${ver}-stable.zip" ;;
|
||||
x86_64) flutter_url="${flutter_url_base}_${ver}-stable.zip" ;;
|
||||
esac
|
||||
curl -fsSL -o "$tmp/flutter.zip" "$flutter_url"
|
||||
mkdir -p "$parent"
|
||||
unzip -q "$tmp/flutter.zip" -d "$tmp"
|
||||
mv "$tmp/flutter" "$dir"
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
"$dir/bin/flutter" config --no-analytics >/dev/null
|
||||
"$dir/bin/flutter" precache --macos >/dev/null
|
||||
}
|
||||
install_flutter "$FLUTTER_VERSION" /opt/flutter
|
||||
install_flutter "$FLUTTER_BRIDGE_VERSION" /opt/flutter-bridge
|
||||
|
||||
# ---- 8. vcpkg ----
|
||||
VCPKG_DIR=/opt/vcpkg
|
||||
if [[ ! -d "$VCPKG_DIR/.git" ]]; then
|
||||
log "Cloning vcpkg"
|
||||
git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR"
|
||||
fi
|
||||
git -C "$VCPKG_DIR" fetch --tags origin
|
||||
git -C "$VCPKG_DIR" -c advice.detachedHead=false checkout "$VCPKG_COMMIT"
|
||||
[[ -x "$VCPKG_DIR/vcpkg" ]] || "$VCPKG_DIR/bootstrap-vcpkg.sh" -disableMetrics
|
||||
|
||||
mkdir -p /var/cache/vcpkg
|
||||
chown -R "$SERVICE_USER:staff" /var/cache/vcpkg
|
||||
|
||||
# ---- 9. Permissions ----
|
||||
log "Setting up permissions for $SERVICE_USER"
|
||||
chown -R "$SERVICE_USER:staff" "$CARGO_HOME" "$RUSTUP_HOME" \
|
||||
/opt/flutter /opt/flutter-bridge "$VCPKG_DIR"
|
||||
mkdir -p /opt/cargo-tools
|
||||
chown -R "$SERVICE_USER:staff" /opt/cargo-tools
|
||||
|
||||
git config --system --add safe.directory '*' || true
|
||||
|
||||
# ---- 10. act_runner ----
|
||||
RUNNER_DIR="/usr/local/var/gitea-runner"
|
||||
mkdir -p "$RUNNER_DIR"
|
||||
chown -R "$SERVICE_USER:staff" "$RUNNER_DIR"
|
||||
|
||||
if [[ ! -x "$RUNNER_DIR/act_runner" ]]; then
|
||||
log "Downloading act_runner $RUNNER_VERSION"
|
||||
case "$ARCH" in
|
||||
arm64) rarch="arm64" ;;
|
||||
x86_64) rarch="amd64" ;;
|
||||
esac
|
||||
curl -fsSL -o "$RUNNER_DIR/act_runner" \
|
||||
"https://gitea.com/gitea/act_runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-darwin-${rarch}"
|
||||
chmod +x "$RUNNER_DIR/act_runner"
|
||||
chown "$SERVICE_USER:staff" "$RUNNER_DIR/act_runner"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$RUNNER_DIR/.runner" ]]; then
|
||||
log "Registering runner with $GITEA_URL"
|
||||
sudo -u "$SERVICE_USER" -H bash -c "
|
||||
cd '$RUNNER_DIR' && \
|
||||
./act_runner register --no-interactive \
|
||||
--instance '$GITEA_URL' \
|
||||
--token '$RUNNER_TOKEN' \
|
||||
--name '$RUNNER_NAME' \
|
||||
--labels '$RUNNER_LABELS'
|
||||
"
|
||||
fi
|
||||
|
||||
# ---- 11. launchd service ----
|
||||
log "Installing LaunchDaemon"
|
||||
PLIST=/Library/LaunchDaemons/com.rustdesk.gitea-runner.plist
|
||||
cat > "$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.rustdesk.gitea-runner</string>
|
||||
<key>UserName</key>
|
||||
<string>${SERVICE_USER}</string>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>${RUNNER_DIR}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${RUNNER_DIR}/act_runner</string>
|
||||
<string>daemon</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>RUSTUP_HOME</key> <string>${RUSTUP_HOME}</string>
|
||||
<key>CARGO_HOME</key> <string>${CARGO_HOME}</string>
|
||||
<key>VCPKG_ROOT</key> <string>${VCPKG_DIR}</string>
|
||||
<key>HOMEBREW_PREFIX</key> <string>${HOMEBREW_PREFIX}</string>
|
||||
<key>PATH</key>
|
||||
<string>${CARGO_HOME}/bin:/opt/flutter/bin:/opt/cargo-tools/bin:${HOMEBREW_PREFIX}/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key> <true/>
|
||||
<key>KeepAlive</key> <true/>
|
||||
<key>StandardOutPath</key><string>${RUNNER_DIR}/stdout.log</string>
|
||||
<key>StandardErrorPath</key><string>${RUNNER_DIR}/stderr.log</string>
|
||||
<key>SoftResourceLimits</key>
|
||||
<dict>
|
||||
<key>NumberOfFiles</key> <integer>65535</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
chmod 0644 "$PLIST"
|
||||
|
||||
launchctl bootout system "$PLIST" 2>/dev/null || true
|
||||
launchctl bootstrap system "$PLIST"
|
||||
launchctl enable "system/com.rustdesk.gitea-runner"
|
||||
|
||||
log "Done."
|
||||
echo " Verify with: sudo launchctl print system/com.rustdesk.gitea-runner | head"
|
||||
echo " Tail logs with: tail -F $RUNNER_DIR/stderr.log"
|
||||
echo " Runner should appear (online) at $GITEA_URL > Site Admin > Actions > Runners"
|
||||
@@ -0,0 +1,324 @@
|
||||
# Provisions a Windows host (Windows 10/11 or Server 2019+) as a Gitea Actions
|
||||
# runner for RustDesk desktop builds. Idempotent: safe to re-run.
|
||||
#
|
||||
# Versions are pinned to .gitea/workflows/build-windows.yml. Bump them there and
|
||||
# here together.
|
||||
#
|
||||
# Usage (Administrator PowerShell):
|
||||
# Set-ExecutionPolicy -Scope Process Bypass -Force
|
||||
# .\provision.ps1 -GiteaUrl https://gitea.example.com -RunnerToken <token>
|
||||
#
|
||||
# By default the runner service is created under a dedicated local user
|
||||
# (`gitea-runner`) -- LocalSystem has been observed to break flutter pub get,
|
||||
# symlink creation, and git's "dubious ownership" check on this codebase. To
|
||||
# opt out, pass `-ServiceAccount LocalSystem` (not recommended).
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $GiteaUrl,
|
||||
[Parameter(Mandatory = $true)] [string] $RunnerToken,
|
||||
[string] $RunnerName = "$env:COMPUTERNAME-rustdesk",
|
||||
[string] $RunnerLabels = "windows-10,self-hosted,X64",
|
||||
[string] $RunnerVersion = "0.2.11",
|
||||
[string] $ServiceAccount = "gitea-runner",
|
||||
[SecureString] $ServiceAccountPassword
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# Must run elevated -- nearly every step writes Machine env, HKLM, or service config.
|
||||
$me = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
if (-not (New-Object Security.Principal.WindowsPrincipal $me).IsInRole(
|
||||
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this script in an elevated (Administrator) PowerShell session.'
|
||||
}
|
||||
|
||||
# --- pinned versions (mirror .gitea/workflows/build-windows.yml env block) ---
|
||||
$RUST_VERSION = '1.75.0'
|
||||
$RUST_NIGHTLY = 'nightly-2023-10-13'
|
||||
$LLVM_VERSION = '15.0.6'
|
||||
$FLUTTER_VERSION = '3.24.5'
|
||||
$VCPKG_COMMIT = '120deac3062162151622ca4860575a33844ba10b'
|
||||
|
||||
$ToolsRoot = 'C:\tools'
|
||||
New-Item -ItemType Directory -Force -Path $ToolsRoot | Out-Null
|
||||
|
||||
# Exact-segment-match version of PATH augmentation. Substring matching would
|
||||
# falsely find C:\bin when C:\binaries is on PATH.
|
||||
function Add-MachinePath([string]$Dir) {
|
||||
$cur = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
||||
$segments = $cur -split ';' | Where-Object { $_ }
|
||||
if ($segments -notcontains $Dir) {
|
||||
[Environment]::SetEnvironmentVariable('Path', "$cur;$Dir", 'Machine')
|
||||
}
|
||||
if (($env:Path -split ';') -notcontains $Dir) { $env:Path = "$env:Path;$Dir" }
|
||||
}
|
||||
|
||||
# --- 1. Chocolatey (used for git, python, nuget, 7zip, node, dotnet, ...) ---
|
||||
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||
Write-Host '==> Installing Chocolatey'
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
}
|
||||
|
||||
Write-Host '==> Installing base packages'
|
||||
# nodejs-lts: act_runner spawns Node to execute JavaScript actions.
|
||||
# powershell-core: workflows use `shell: pwsh` (PS 7), not the OS's PS 5.1.
|
||||
# dotnet-sdk: WiX 4 SDK-style projects (.wixproj) need it for the MSI build.
|
||||
choco install -y --no-progress `
|
||||
git python311 nuget.commandline 7zip cmake ninja `
|
||||
nodejs-lts powershell-core dotnet-sdk
|
||||
Add-MachinePath 'C:\Program Files\Git\cmd'
|
||||
Add-MachinePath 'C:\Program Files\Git\bin' # bash.exe + posix tools (sed, find, ...)
|
||||
Add-MachinePath 'C:\Python311'
|
||||
Add-MachinePath 'C:\Python311\Scripts'
|
||||
Add-MachinePath 'C:\Program Files\nodejs'
|
||||
Add-MachinePath 'C:\Program Files\PowerShell\7'
|
||||
Add-MachinePath 'C:\Program Files\dotnet'
|
||||
|
||||
# --- 2. Visual Studio 2022 Build Tools (MSVC v143 + Win10 SDK) ---
|
||||
# Use [Environment]::GetEnvironmentVariable to avoid the PowerShell parser quirk
|
||||
# that mis-tokenises `$env:ProgramFiles(x86)` as `$env:ProgramFiles` + `(x86)`.
|
||||
$pfx86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)')
|
||||
$vsInstaller = Join-Path $pfx86 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||
$vsPresent = (Test-Path $vsInstaller) -and `
|
||||
((& $vsInstaller -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) -ne $null)
|
||||
if (-not $vsPresent) {
|
||||
Write-Host '==> Installing VS 2022 Build Tools (this takes a while)'
|
||||
$vsBootstrapper = "$env:TEMP\vs_buildtools.exe"
|
||||
Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_buildtools.exe' -OutFile $vsBootstrapper
|
||||
$vsArgs = @(
|
||||
'--quiet','--wait','--norestart','--nocache',
|
||||
'--add','Microsoft.VisualStudio.Workload.VCTools',
|
||||
'--add','Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
||||
'--add','Microsoft.VisualStudio.Component.VC.ATL',
|
||||
'--add','Microsoft.VisualStudio.Component.Windows10SDK.20348',
|
||||
'--add','Microsoft.VisualStudio.Component.VC.CMake.Project',
|
||||
'--includeRecommended'
|
||||
)
|
||||
$p = Start-Process -FilePath $vsBootstrapper -ArgumentList $vsArgs -Wait -PassThru
|
||||
if ($p.ExitCode -notin 0,3010) { throw "VS Build Tools installer exit $($p.ExitCode)" }
|
||||
}
|
||||
|
||||
# --- 3. Rust (stable + nightly with i686 target) ---
|
||||
# Install machine-wide so any user (including the dedicated runner account)
|
||||
# shares one toolchain registry. Without this, rustup state lives in the
|
||||
# installing user's profile and the service user has no default toolchain.
|
||||
$rustupHome = 'C:\rustup'
|
||||
$cargoHome = 'C:\cargo'
|
||||
[Environment]::SetEnvironmentVariable('RUSTUP_HOME', $rustupHome, 'Machine')
|
||||
[Environment]::SetEnvironmentVariable('CARGO_HOME', $cargoHome, 'Machine')
|
||||
$env:RUSTUP_HOME = $rustupHome
|
||||
$env:CARGO_HOME = $cargoHome
|
||||
Add-MachinePath "$cargoHome\bin"
|
||||
|
||||
if (-not (Test-Path "$cargoHome\bin\rustup.exe")) {
|
||||
Write-Host '==> Installing rustup (machine-wide at C:\rustup, C:\cargo)'
|
||||
Invoke-WebRequest -Uri 'https://win.rustup.rs/x86_64' -OutFile "$env:TEMP\rustup-init.exe"
|
||||
& "$env:TEMP\rustup-init.exe" -y --default-toolchain none --profile minimal
|
||||
}
|
||||
rustup toolchain install $RUST_VERSION --profile minimal --component rustfmt
|
||||
rustup target add --toolchain $RUST_VERSION x86_64-pc-windows-msvc
|
||||
rustup toolchain install $RUST_NIGHTLY --profile minimal --component rustfmt
|
||||
rustup target add --toolchain $RUST_NIGHTLY i686-pc-windows-msvc
|
||||
rustup default $RUST_VERSION
|
||||
|
||||
# --- 4. LLVM/Clang (matches KyleMayes/install-llvm-action layout) ---
|
||||
$llvmDir = "$ToolsRoot\llvm-$LLVM_VERSION"
|
||||
if (-not (Test-Path "$llvmDir\bin\clang.exe")) {
|
||||
Write-Host "==> Installing LLVM $LLVM_VERSION"
|
||||
$llvmExe = "$env:TEMP\LLVM-$LLVM_VERSION-win64.exe"
|
||||
Invoke-WebRequest -Uri "https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/LLVM-$LLVM_VERSION-win64.exe" -OutFile $llvmExe
|
||||
& $llvmExe /S "/D=$llvmDir" | Out-Null
|
||||
}
|
||||
[Environment]::SetEnvironmentVariable('LIBCLANG_PATH', "$llvmDir\bin", 'Machine')
|
||||
Add-MachinePath "$llvmDir\bin"
|
||||
|
||||
# --- 5. Flutter (stable channel, with windows precache) ---
|
||||
$flutterDir = "$ToolsRoot\flutter"
|
||||
if (-not (Test-Path "$flutterDir\bin\flutter.bat")) {
|
||||
Write-Host "==> Installing Flutter $FLUTTER_VERSION"
|
||||
$flutterZip = "$env:TEMP\flutter.zip"
|
||||
Invoke-WebRequest -Uri "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_$FLUTTER_VERSION-stable.zip" -OutFile $flutterZip
|
||||
Expand-Archive -Force -Path $flutterZip -DestinationPath $ToolsRoot
|
||||
}
|
||||
Add-MachinePath "$flutterDir\bin"
|
||||
& "$flutterDir\bin\flutter.bat" config --no-analytics | Out-Null
|
||||
& "$flutterDir\bin\flutter.bat" precache --windows | Out-Null
|
||||
|
||||
# --- 6. vcpkg pinned to commit ---
|
||||
$vcpkgDir = 'C:\vcpkg'
|
||||
if (-not (Test-Path "$vcpkgDir\.git")) {
|
||||
Write-Host '==> Cloning vcpkg'
|
||||
git clone https://github.com/microsoft/vcpkg.git $vcpkgDir
|
||||
}
|
||||
Push-Location $vcpkgDir
|
||||
git fetch --tags origin
|
||||
git -c advice.detachedHead=false checkout $VCPKG_COMMIT
|
||||
if (-not (Test-Path "$vcpkgDir\vcpkg.exe")) { & "$vcpkgDir\bootstrap-vcpkg.bat" -disableMetrics }
|
||||
Pop-Location
|
||||
[Environment]::SetEnvironmentVariable('VCPKG_ROOT', $vcpkgDir, 'Machine')
|
||||
Add-MachinePath $vcpkgDir
|
||||
|
||||
# --- 7. CI prerequisites that aren't tools, but environmental switches ---
|
||||
|
||||
# git's "dubious ownership" check (>= 2.35.2) refuses to operate on a repo whose
|
||||
# .git directory is owned by a different user than the one running git. The
|
||||
# Flutter SDK at C:\tools\flutter is provisioned by this script as Administrator
|
||||
# but the runner service runs as a non-admin user. Trust everything system-wide.
|
||||
git config --system --add safe.directory '*' 2>$null
|
||||
|
||||
# Flutter on Windows needs SeCreateSymbolicLinkPrivilege to build plugins.
|
||||
# Enable Developer Mode (registry) AND grant the privilege via Local Security
|
||||
# Policy to the built-in "Users" group (SID S-1-5-32-545). Either alone has been
|
||||
# observed to not take effect until logon-token refresh; doing both is
|
||||
# belt-and-suspenders. The privilege only reaches a long-running service after
|
||||
# a reboot or a fresh service-token issuance.
|
||||
$devKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock'
|
||||
if (-not (Test-Path $devKey)) { New-Item -Path $devKey -Force | Out-Null }
|
||||
New-ItemProperty -Path $devKey -Name 'AllowDevelopmentWithoutDevLicense' `
|
||||
-PropertyType DWORD -Value 1 -Force | Out-Null
|
||||
|
||||
$secCfg = "$env:TEMP\sec-symlink.cfg"
|
||||
secedit /export /cfg $secCfg | Out-Null
|
||||
$secContent = Get-Content $secCfg -Raw
|
||||
if ($secContent -match 'SeCreateSymbolicLinkPrivilege\s*=\s*([^\r\n]*)') {
|
||||
$cur = $matches[1]
|
||||
if ($cur -notmatch '\*S-1-5-32-545') {
|
||||
$secContent = $secContent -replace `
|
||||
'(SeCreateSymbolicLinkPrivilege\s*=\s*)([^\r\n]*)', `
|
||||
'$1$2,*S-1-5-32-545'
|
||||
}
|
||||
} else {
|
||||
$secContent = $secContent -replace `
|
||||
'(\[Privilege Rights\][\r\n]+)', `
|
||||
"`$1SeCreateSymbolicLinkPrivilege = *S-1-5-32-545`r`n"
|
||||
}
|
||||
$secContent | Set-Content $secCfg
|
||||
secedit /configure /db "$env:TEMP\sec-symlink.sdb" /cfg $secCfg /areas USER_RIGHTS /quiet
|
||||
Remove-Item $secCfg, "$env:TEMP\sec-symlink.sdb" -ErrorAction SilentlyContinue
|
||||
|
||||
# --- 8. Dedicated runner user ---
|
||||
# Running as LocalSystem causes a cascade of issues:
|
||||
# - $USERPROFILE = C:\Windows\System32\config\systemprofile, which Flutter,
|
||||
# dart pub, and other POSIX-leaning tools mis-handle.
|
||||
# - cargo install lands binaries in that systemprofile path -> not on PATH.
|
||||
# - flutter/windows occasionally vanishes during long cargo builds.
|
||||
# A normal local user fixes all of these.
|
||||
if ($ServiceAccount -ne 'LocalSystem') {
|
||||
if (-not (Get-LocalUser -Name $ServiceAccount -ErrorAction SilentlyContinue)) {
|
||||
if (-not $ServiceAccountPassword) {
|
||||
# Generate a 32-byte random password using the OS RNG. Encoded as
|
||||
# base64 (alphanumeric + +/) and trimmed of padding -- meets local
|
||||
# password complexity without needing System.Web (which is missing
|
||||
# on Server Core).
|
||||
$bytes = New-Object byte[] 24
|
||||
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
|
||||
$plain = ([Convert]::ToBase64String($bytes)).TrimEnd('=') + 'A1!'
|
||||
$ServiceAccountPassword = ConvertTo-SecureString $plain -AsPlainText -Force
|
||||
Remove-Variable plain, bytes
|
||||
}
|
||||
Write-Host "==> Creating local user '$ServiceAccount'"
|
||||
New-LocalUser -Name $ServiceAccount -Password $ServiceAccountPassword `
|
||||
-PasswordNeverExpires -AccountNeverExpires `
|
||||
-Description 'Gitea Actions runner service account' | Out-Null
|
||||
Add-LocalGroupMember -Group 'Users' -Member $ServiceAccount
|
||||
}
|
||||
|
||||
# Grant "Log on as a service" via secedit (no PS native cmdlet for this).
|
||||
$sid = (Get-LocalUser $ServiceAccount).SID.Value
|
||||
$svcCfg = "$env:TEMP\sec-svc.cfg"
|
||||
secedit /export /cfg $svcCfg | Out-Null
|
||||
$svcContent = Get-Content $svcCfg -Raw
|
||||
if ($svcContent -match "SeServiceLogonRight\s*=\s*([^\r\n]*)") {
|
||||
if ($matches[1] -notmatch [regex]::Escape($sid)) {
|
||||
$svcContent = $svcContent -replace `
|
||||
'(SeServiceLogonRight\s*=\s*)([^\r\n]*)', `
|
||||
"`$1`$2,*$sid"
|
||||
}
|
||||
} else {
|
||||
$svcContent = $svcContent -replace `
|
||||
'(\[Privilege Rights\][\r\n]+)', `
|
||||
"`$1SeServiceLogonRight = *$sid`r`n"
|
||||
}
|
||||
$svcContent | Set-Content $svcCfg
|
||||
secedit /configure /db "$env:TEMP\sec-svc.sdb" /cfg $svcCfg /areas USER_RIGHTS /quiet
|
||||
Remove-Item $svcCfg, "$env:TEMP\sec-svc.sdb" -ErrorAction SilentlyContinue
|
||||
|
||||
# Ensure the user can read/write everything it needs for builds.
|
||||
foreach ($p in @('C:\actions-runner','C:\cargo','C:\cargo-tools','C:\vcpkg','C:\vcpkg-cache')) {
|
||||
New-Item -ItemType Directory -Force -Path $p | Out-Null
|
||||
icacls $p /grant "${ServiceAccount}:(OI)(CI)F" /T 2>$null | Out-Null
|
||||
}
|
||||
foreach ($p in @('C:\rustup','C:\tools')) {
|
||||
if (Test-Path $p) { icacls $p /grant "${ServiceAccount}:(OI)(CI)RX" /T 2>$null | Out-Null }
|
||||
}
|
||||
}
|
||||
|
||||
# --- 9. Gitea act_runner ---
|
||||
$runnerDir = 'C:\actions-runner'
|
||||
New-Item -ItemType Directory -Force -Path $runnerDir | Out-Null
|
||||
$runnerExe = "$runnerDir\act_runner.exe"
|
||||
if (-not (Test-Path $runnerExe)) {
|
||||
Write-Host "==> Downloading act_runner $RunnerVersion"
|
||||
Invoke-WebRequest -Uri "https://gitea.com/gitea/act_runner/releases/download/v$RunnerVersion/act_runner-$RunnerVersion-windows-amd64.exe" -OutFile $runnerExe
|
||||
}
|
||||
|
||||
Push-Location $runnerDir
|
||||
if (-not (Test-Path "$runnerDir\.runner")) {
|
||||
Write-Host '==> Registering runner'
|
||||
& $runnerExe register --no-interactive `
|
||||
--instance $GiteaUrl `
|
||||
--token $RunnerToken `
|
||||
--name $RunnerName `
|
||||
--labels $RunnerLabels
|
||||
}
|
||||
|
||||
# Reconfigure the service every run so re-running with a different
|
||||
# -ServiceAccount actually takes effect.
|
||||
$svc = Get-Service -Name 'gitea-act-runner' -ErrorAction SilentlyContinue
|
||||
if ($svc) {
|
||||
if ($svc.Status -eq 'Running') { Stop-Service gitea-act-runner }
|
||||
} else {
|
||||
Write-Host '==> Installing runner as Windows service'
|
||||
choco install -y --no-progress nssm
|
||||
nssm install gitea-act-runner $runnerExe daemon | Out-Null
|
||||
}
|
||||
nssm set gitea-act-runner AppDirectory $runnerDir | Out-Null
|
||||
nssm set gitea-act-runner Start SERVICE_AUTO_START | Out-Null
|
||||
nssm set gitea-act-runner AppStdout "$runnerDir\runner.log" | Out-Null
|
||||
nssm set gitea-act-runner AppStderr "$runnerDir\runner.log" | Out-Null
|
||||
|
||||
if ($ServiceAccount -eq 'LocalSystem') {
|
||||
nssm set gitea-act-runner ObjectName 'LocalSystem' | Out-Null
|
||||
} else {
|
||||
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ServiceAccountPassword)
|
||||
try {
|
||||
$plain = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($bstr)
|
||||
nssm set gitea-act-runner ObjectName ".\$ServiceAccount" $plain | Out-Null
|
||||
} finally {
|
||||
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
||||
Remove-Variable plain -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# Start may fail before reboot if the new SeServiceLogonRight hasn't reached
|
||||
# SCM yet -- that's expected; the service will start cleanly after reboot.
|
||||
try {
|
||||
Start-Service gitea-act-runner
|
||||
} catch {
|
||||
Write-Warning "Could not start gitea-act-runner now ($($_.Exception.Message)). It will start on reboot."
|
||||
}
|
||||
Pop-Location
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '==> Done.'
|
||||
Write-Host ' A reboot is REQUIRED before the first build run, so:'
|
||||
Write-Host ' - the runner service inherits the new SeCreateSymbolicLinkPrivilege token'
|
||||
Write-Host ' - all PATH/env changes propagate to the SCM-launched service'
|
||||
Write-Host ' After reboot, verify the runner shows up in Gitea > Site Admin > Actions > Runners.'
|
||||
if ($ServiceAccount -eq 'LocalSystem') {
|
||||
Write-Warning 'Service is running as LocalSystem. RustDesk builds have been observed to fail in this configuration (Flutter pub get, symlinks, dubious ownership). Re-run with -ServiceAccount gitea-runner to switch.'
|
||||
}
|
||||
Reference in New Issue
Block a user