Kaynağa Gözat

fix: proper permission management across setup and CLI scripts

- Add run_as_user() helper to run git/pip/venv commands as real user
  instead of root, so files are owned correctly from the start
- Add fix_repo_ownership() safety net for legacy/edge cases
- Remove scattered chown band-aids in favor of correct ownership at source
- Fix sudoers entries using $USER (root under sudo) → $REAL_USER
- Apply same pattern to dw update and dw checkout commands
- Use .venv/bin/pip explicitly to avoid relying on activated venv as root

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 ay önce
ebeveyn
işleme
949f0efc3f
2 değiştirilmiş dosya ile 74 ekleme ve 50 silme
  1. 32 13
      dw
  2. 42 37
      setup-pi.sh

+ 32 - 13
dw

@@ -44,6 +44,23 @@ find_install_dir() {
 
 INSTALL_DIR=$(find_install_dir)
 
+# Run a command as the real (non-root) user when invoked via sudo.
+# For dw commands that are not typically run with sudo, this is a no-op.
+run_as_user() {
+    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
+        sudo -u "$SUDO_USER" -- "$@"
+    else
+        "$@"
+    fi
+}
+
+# Ensure repo files are owned by the real user (not root).
+fix_repo_ownership() {
+    if [[ -n "$INSTALL_DIR" && $EUID -eq 0 && -n "$SUDO_USER" ]]; then
+        chown -R "$SUDO_USER:$SUDO_USER" "$INSTALL_DIR"
+    fi
+}
+
 # Check if installed
 check_installed() {
     if [[ -z "$INSTALL_DIR" ]]; then
@@ -106,11 +123,12 @@ cmd_update() {
     # Check if we should skip the pull phase (called after re-exec)
     if [[ "$1" != "--continue" ]]; then
         echo "Pulling latest code..."
-        git config --global --add safe.directory "$INSTALL_DIR" 2>/dev/null || true
+        run_as_user git config --global --add safe.directory "$INSTALL_DIR" 2>/dev/null || true
         local branch
         branch=$(git rev-parse --abbrev-ref HEAD)
-        git fetch origin "$branch"
-        git reset --hard "origin/$branch"
+        run_as_user git fetch origin "$branch"
+        run_as_user git reset --hard "origin/$branch"
+        fix_repo_ownership
 
         # Update dw CLI
         echo "Updating dw command..."
@@ -139,9 +157,9 @@ cmd_update() {
             libgpiod-dev gpiod \
             nginx
 
-        # Create Python venv
+        # Create Python venv as real user
         echo "Creating Python virtual environment..."
-        python3 -m venv .venv
+        run_as_user python3 -m venv .venv
 
         # Stop and remove Docker containers/service if running
         if command -v docker &> /dev/null; then
@@ -153,12 +171,13 @@ cmd_update() {
     # Fix venv ownership if created by sudo (legacy installs)
     if [[ -d ".venv" ]] && [[ "$(stat -c '%U' .venv 2>/dev/null)" == "root" ]]; then
         echo "Fixing .venv ownership..."
-        sudo chown -R "$USER:$USER" .venv
+        local real_user="${SUDO_USER:-$USER}"
+        sudo chown -R "$real_user:$real_user" .venv
     fi
 
     echo "Updating Python dependencies..."
     source .venv/bin/activate
-    pip install -r requirements.txt
+    run_as_user .venv/bin/pip install -r requirements.txt
 
     # Update nginx config
     echo "Updating nginx config..."
@@ -171,7 +190,6 @@ cmd_update() {
     # Update systemd service
     echo "Updating systemd service..."
     sudo cp "$INSTALL_DIR/dune-weaver.service" /etc/systemd/system/dune-weaver.service
-    sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/dune-weaver.service
     sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/dune-weaver.service
     sudo systemctl daemon-reload
     sudo systemctl enable dune-weaver
@@ -331,25 +349,26 @@ cmd_checkout() {
 
     # Repo may be cloned with --single-branch; fetch the target branch explicitly
     echo -e "${BLUE}Fetching branch ${branch}...${NC}"
-    if ! git fetch origin "$branch" 2>/dev/null; then
+    if ! run_as_user git fetch origin "$branch" 2>/dev/null; then
         echo -e "${RED}Branch '${branch}' not found on remote${NC}"
         exit 1
     fi
 
     # Checkout: create local tracking branch if it doesn't exist
     if git show-ref --verify --quiet "refs/heads/$branch"; then
-        git checkout "$branch"
-        git reset --hard "origin/$branch"
+        run_as_user git checkout "$branch"
+        run_as_user git reset --hard "origin/$branch"
     else
-        git checkout -b "$branch" "origin/$branch"
+        run_as_user git checkout -b "$branch" "origin/$branch"
     fi
+    fix_repo_ownership
 
     echo -e "Switched to branch: ${GREEN}${branch}${NC}"
 
     # Update Python dependencies
     echo "Updating Python dependencies..."
     source .venv/bin/activate
-    pip install -r requirements.txt
+    run_as_user .venv/bin/pip install -r requirements.txt
 
     # Update dw CLI from the new branch
     sudo cp "$INSTALL_DIR/dw" /usr/local/bin/dw

+ 42 - 37
setup-pi.sh

@@ -71,6 +71,29 @@ while [[ $# -gt 0 ]]; do
     esac
 done
 
+# ── Permission helpers ──────────────────────────────────────────────
+# Run a command as the real (non-root) user.
+# When the script is invoked with sudo, commands like git clone, pip install,
+# and venv creation should run as the real user so files are owned correctly
+# from the start — no chown needed afterward.
+run_as_user() {
+    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
+        sudo -u "$SUDO_USER" -- "$@"
+    else
+        "$@"
+    fi
+}
+
+# Ensure the entire repo tree is owned by the real user.
+# Call this as a safety net after any operation that may have created
+# files as root (e.g. an older version of this script, or a plugin).
+fix_repo_ownership() {
+    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
+        chown -R "$SUDO_USER:$SUDO_USER" "$INSTALL_DIR"
+    fi
+}
+# ────────────────────────────────────────────────────────────────────
+
 # Helper functions
 print_step() {
     echo -e "\n${BLUE}==>${NC} ${GREEN}$1${NC}"
@@ -223,6 +246,7 @@ ensure_repo() {
     if [[ -f "main.py" ]] && [[ -f "requirements.txt" ]]; then
         INSTALL_DIR="$(pwd)"
         print_success "Using existing repo at $INSTALL_DIR"
+        fix_repo_ownership
         return
     fi
 
@@ -231,22 +255,14 @@ ensure_repo() {
         print_success "Found existing repo at $INSTALL_DIR"
         cd "$INSTALL_DIR"
         echo "Pulling latest changes..."
-        git pull
-        # Fix ownership after pull (new files may be created as root)
-        if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
-            chown -R "$SUDO_USER:$SUDO_USER" "$INSTALL_DIR"
-        fi
+        run_as_user git pull
+        fix_repo_ownership
         return
     fi
 
-    # Clone the repository
+    # Clone the repository as the real user so files are owned correctly
     print_step "Cloning dune-weaver repository..."
-    git clone "$REPO_URL" --single-branch "$INSTALL_DIR"
-    # When running as sudo, git clone creates files as root.
-    # Fix ownership so the real user can write to the repo (e.g. create .venv).
-    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
-        chown -R "$SUDO_USER:$SUDO_USER" "$INSTALL_DIR"
-    fi
+    run_as_user git clone "$REPO_URL" --single-branch "$INSTALL_DIR"
     cd "$INSTALL_DIR"
     print_success "Cloned to $INSTALL_DIR"
 }
@@ -257,29 +273,18 @@ deploy_native() {
 
     cd "$INSTALL_DIR"
 
-    # Ensure repo is owned by real user before creating venv
-    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
-        chown -R "$SUDO_USER:$SUDO_USER" "$INSTALL_DIR"
-    fi
+    # Safety net: fix ownership in case repo was cloned/pulled as root
+    # by an older version of this script or manual sudo git operations
+    fix_repo_ownership
 
-    # Create venv — if running as root (sudo), create as the real user
-    local real_user="${SUDO_USER:-$USER}"
-    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
-        sudo -u "$SUDO_USER" python3 -m venv .venv
-    else
-        python3 -m venv .venv
-    fi
+    # Create venv as real user
+    run_as_user python3 -m venv .venv
     source .venv/bin/activate
 
-    # Install dependencies
+    # Install dependencies as real user (pip writes to user-owned .venv)
     print_step "Installing Python packages..."
-    pip install --upgrade pip
-    pip install -r requirements.txt
-
-    # Ensure venv is owned by the real user (not root)
-    if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then
-        chown -R "$SUDO_USER:$SUDO_USER" .venv
-    fi
+    run_as_user .venv/bin/pip install --upgrade pip
+    run_as_user .venv/bin/pip install -r requirements.txt
 
     # Ensure nginx (www-data) can traverse to static files
     # chmod o+x grants traversal only, not directory listing
@@ -302,7 +307,6 @@ deploy_native() {
     # Create systemd service
     print_step "Creating systemd service..."
     sudo cp "$INSTALL_DIR/dune-weaver.service" /etc/systemd/system/dune-weaver.service
-    sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/dune-weaver.service
     sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/dune-weaver.service
 
     # Enable and start service
@@ -311,13 +315,14 @@ deploy_native() {
     sudo systemctl start dune-weaver
 
     # Create sudoers entry for passwordless systemctl commands
+    # Use REAL_USER (not $USER which is root under sudo)
     print_step "Configuring sudo permissions..."
     sudo tee /etc/sudoers.d/dune-weaver > /dev/null << EOF
-$USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart dune-weaver
-$USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop dune-weaver
-$USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl start dune-weaver
-$USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl poweroff
-$USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
+$REAL_USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart dune-weaver
+$REAL_USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop dune-weaver
+$REAL_USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl start dune-weaver
+$REAL_USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl poweroff
+$REAL_USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
 EOF
     sudo chmod 0440 /etc/sudoers.d/dune-weaver