Skip to content

Instantly share code, notes, and snippets.

@shekohex
Created January 25, 2026 00:41
Show Gist options
  • Select an option

  • Save shekohex/efd7655fd6c8fd80075279ecb692dcbb to your computer and use it in GitHub Desktop.

Select an option

Save shekohex/efd7655fd6c8fd80075279ecb692dcbb to your computer and use it in GitHub Desktop.
Full, robust setup for Proxmox LXC (unprivileged) to make: root's `systemctl --user` work reliably, root user manager starts at boot, env available in pct enter shells (bash non-login) + login shells, unit name mismatch fixed WITHOUT symlinks (gateway unit is a real file copy)
#!/usr/bin/env bash
# clawdbot-lxc-root-user-systemd-setup.sh
#
# Full, robust setup for Proxmox LXC (unprivileged) to make:
# - root's `systemctl --user` work reliably
# - root user manager starts at boot
# - env available in pct enter shells (bash non-login) + login shells
# - unit name mismatch fixed WITHOUT symlinks (gateway unit is a real file copy)
#
# Usage:
# sudo bash clawdbot-lxc-root-user-systemd-setup.sh
#
# Optional overrides:
# EXPECTED_UNIT=clawdbot-gateway.service ACTUAL_UNIT=clawdbot-node.service bash clawdbot-lxc-root-user-systemd-setup.sh
#
set -Eeuo pipefail
EXPECTED_UNIT="${EXPECTED_UNIT:-clawdbot-gateway.service}"
ACTUAL_UNIT="${ACTUAL_UNIT:-clawdbot-node.service}"
LOG_DIR="/var/log/clawdbot-setup"
TS="$(date +'%Y%m%d-%H%M%S')"
LOG_FILE="${LOG_DIR}/setup-${TS}.log"
ROOT_UNIT_DIR="/root/.config/systemd/user"
EXPECTED_PATH="${ROOT_UNIT_DIR}/${EXPECTED_UNIT}"
ACTUAL_PATH="${ROOT_UNIT_DIR}/${ACTUAL_UNIT}"
RUN_USER_DIR="/run/user/0"
BUS_PATH="${RUN_USER_DIR}/bus"
TMPFILES_CONF="/etc/tmpfiles.d/root-runtime.conf"
BOOT_SERVICE="/etc/systemd/system/root-user-manager.service"
PROFILED_FILE="/etc/profile.d/root-user-bus.sh"
BASHBASHRC_FILE="/etc/bash.bashrc"
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
chmod 600 "$LOG_FILE" || true
log() {
local lvl="$1"; shift
printf '%s [%s] %s\n' "$(date +'%F %T')" "$lvl" "$*" | tee -a "$LOG_FILE" >&2
}
run() {
log "CMD" "$*"
"$@" 2>&1 | tee -a "$LOG_FILE"
}
die() {
log "ERROR" "$*"
log "ERROR" "Log: $LOG_FILE"
exit 1
}
need_root() {
[[ "$(id -u)" -eq 0 ]] || die "Run this script as root."
}
ensure_block_in_file() {
# ensure_block_in_file <file> <marker> <block>
local file="$1"
local marker="$2"
local block="$3"
if [[ ! -f "$file" ]]; then
run install -Dm0644 /dev/null "$file"
fi
if grep -qF "$marker" "$file"; then
log "INFO" "Already configured: $file"
return 0
fi
log "INFO" "Patching: $file"
{
echo ""
echo "$block"
} >> "$file"
}
banner() {
cat <<EOF | tee -a "$LOG_FILE"
============================================================
Clawdbot Root LXC Setup
- Fix root systemd --user (user@0 + user dbus)
- Make env available in pct enter shells (bashrc + profile.d)
- Fix unit name mismatch (expected -> actual) WITHOUT symlinks
Expected unit: ${EXPECTED_UNIT}
Actual unit: ${ACTUAL_UNIT}
Log file: ${LOG_FILE}
============================================================
EOF
}
show_next_steps() {
cat <<EOF | tee -a "$LOG_FILE"
------------------------------------------------------------
Done ✅
Important:
- Your CURRENT shell won't gain env vars retroactively.
- Open a new shell or run:
exec bash
Verify:
echo "\$XDG_RUNTIME_DIR"
systemctl --user status
Service:
systemctl --user status ${EXPECTED_UNIT} -n 80
journalctl --user -u ${EXPECTED_UNIT} -e --no-pager
------------------------------------------------------------
EOF
}
need_root
banner
log "INFO" "Step 1/8: Ensure /run/user/0 exists at boot (tmpfiles) + right now"
run bash -c "cat >'${TMPFILES_CONF}' <<'EOF'
d /run/user/0 0700 root root -
EOF"
run systemd-tmpfiles --create
run mkdir -p "$RUN_USER_DIR"
run chmod 700 "$RUN_USER_DIR"
log "INFO" "Step 2/8: Ensure root user manager ([email protected]) starts at boot"
run bash -c "cat >'${BOOT_SERVICE}' <<'EOF'
[Unit]
Description=Start root user systemd manager (user@0)
After=systemd-user-sessions.service
Wants=systemd-user-sessions.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/systemctl start [email protected]
[Install]
WantedBy=multi-user.target
EOF"
log "INFO" "Step 3/8: Enable boot helper + start [email protected] now"
run systemctl daemon-reload
run systemctl enable --now root-user-manager.service
run systemctl start [email protected] || true
log "INFO" "Step 4/8: Install env exports for BOTH login shells and pct-enter bash shells"
ENV_BLOCK=$'# >>> clawdbot root user bus >>>\n# Make systemctl --user work in LXC shells (pct enter / lxc-attach)\nif [ \"$(id -u)\" -eq 0 ]; then\n export XDG_RUNTIME_DIR=/run/user/0\n export DBUS_SESSION_BUS_ADDRESS=\"unix:path=/run/user/0/bus\"\nfi\n# <<< clawdbot root user bus <<<'
# login shells
if [[ ! -f "$PROFILED_FILE" ]] || ! grep -qF '# >>> clawdbot root user bus >>>' "$PROFILED_FILE"; then
run bash -c "cat >'${PROFILED_FILE}' <<'EOF'
${ENV_BLOCK}
EOF"
run chmod 0644 "$PROFILED_FILE"
else
log "INFO" "Already configured: $PROFILED_FILE"
fi
# interactive non-login bash shells (common for pct enter)
ensure_block_in_file "$BASHBASHRC_FILE" "# >>> clawdbot root user bus >>>" "$ENV_BLOCK"
log "INFO" "Step 5/8: Ensure the user bus exists at ${BUS_PATH}"
if [[ ! -S "$BUS_PATH" ]]; then
log "WARN" "Bus not found yet; restarting [email protected]"
run systemctl restart [email protected] || true
fi
if [[ ! -S "$BUS_PATH" ]]; then
log "ERROR" "Still no user bus at ${BUS_PATH}. Diagnostics:"
run systemctl status [email protected] -n 150 || true
run ls -la "$RUN_USER_DIR" || true
die "Root user bus not available; cannot use systemctl --user."
fi
log "INFO" "Step 6/8: Verify systemctl --user works (explicit env, should pass)"
run env XDG_RUNTIME_DIR=/run/user/0 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/0/bus" systemctl --user status
log "INFO" "Step 7/8: Fix unit name mismatch WITHOUT symlink (make expected a real unit file)"
run mkdir -p "$ROOT_UNIT_DIR"
run chmod 700 "/root/.config" || true
run chmod 700 "$ROOT_UNIT_DIR" || true
if [[ ! -f "$ACTUAL_PATH" ]]; then
log "ERROR" "Actual unit not found at: ${ACTUAL_PATH}"
log "INFO" "Files in ${ROOT_UNIT_DIR}:"
run ls -la "$ROOT_UNIT_DIR" || true
die "Install the app first so it creates ${ACTUAL_UNIT}, then re-run this script."
fi
# Remove symlink/old file if it's a symlink (enable refuses symlinks/aliases)
if [[ -L "$EXPECTED_PATH" ]]; then
log "INFO" "Removing symlinked expected unit (systemctl enable refuses linked units): ${EXPECTED_PATH}"
run rm -f "$EXPECTED_PATH"
fi
# If missing, create a REAL unit file by copying the actual unit
if [[ ! -f "$EXPECTED_PATH" ]]; then
log "INFO" "Creating real unit file: ${EXPECTED_PATH} (copy of ${ACTUAL_UNIT})"
run install -Dm0644 "$ACTUAL_PATH" "$EXPECTED_PATH"
else
log "INFO" "Expected unit already exists as a real file: ${EXPECTED_PATH}"
fi
log "INFO" "Step 8/8: Reload user daemon and enable/start expected unit"
run env XDG_RUNTIME_DIR=/run/user/0 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/0/bus" systemctl --user daemon-reload
run env XDG_RUNTIME_DIR=/run/user/0 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/0/bus" systemctl --user enable --now "$EXPECTED_UNIT"
run env XDG_RUNTIME_DIR=/run/user/0 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/0/bus" systemctl --user status "$EXPECTED_UNIT" -n 80
log "INFO" "Installed clawdbot units:"
run env XDG_RUNTIME_DIR=/run/user/0 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/0/bus" systemctl --user list-unit-files | grep -i clawdbot || true
show_next_steps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment