Skip to content

mpy_setup.sh - App Server Setup Script Guide

Introduction

mpy_setup.sh is an idempotent bash script that installs your project's dependencies on an App Server. It handles system packages, language runtimes, build tools, and any other setup your project needs to run.

The script is always located at .muppy/mpy_setup.sh in your repository directory on the server.

Key Characteristics

  • Idempotent: Safe to run multiple times -- it checks before installing
  • Environment-aware: Detects LXC vs Docker via MPY_ENVIRONMENT
  • Muppy-integrated: Has access to environment variables from /etc/muppy.env
  • Template-based: Can be saved and shared as reusable mpy.template records

Philosophy: Source of Truth

The mpy_setup.sh workflow follows a two-track model depending on whether you control the repository:

Private Repositories (you can commit)

Commit .muppy/mpy_setup.sh directly in your repository. The builder task runs it automatically during server provisioning. The repository is the source of truth.

Use the Muppy Setup Script editor for iterative development, then commit the working version to your repo.

Open Source / External Repositories (you cannot commit)

Save the script as a mpy.template in Muppy. The script is stored in the database and uploaded to .muppy/mpy_setup.sh on the server when needed. The template is the source of truth.

Share templates across your company's servers for consistent deployments.


Workflow: Creating a Setup Script

Step 1: Create an App Server

Create a new App Server from an Application Definition, pointing it at your project's git repository.

Step 2: Analyze the Repository

Identify what your project needs by examining:

  • package.json / requirements.txt / Gemfile -- language dependencies
  • Dockerfile -- often lists system packages and build steps
  • Makefile / build scripts -- build toolchain requirements
  • README -- installation instructions

Step 3: Write the Script

You have three options:

  1. Start from a template: Select an existing template in the Setup Script tab
  2. Ask your AI agent: Use MCP tools to let your AI assistant analyze the repo and generate the script
  3. Write manually: Use the Ace editor in the Setup Script tab

Step 4: Upload and Run

  1. Click "Upload to Server" to push the script to .muppy/mpy_setup.sh
  2. Click "Run mpy_setup.sh script" to execute it asynchronously
  3. Monitor the task in the IMQ queue for progress and logs

Step 5: Iterate Until Success

If the script fails:

  1. Check the IMQ task logs for error details
  2. Edit the script in the Setup Script tab
  3. Re-upload and re-run

The script is idempotent -- previously successful steps will be skipped on re-run.

Step 6: Save as Template

Once the script runs successfully:

  • Click "Save as New Template" to create a reusable mpy.template record
  • For private repos, also commit .muppy/mpy_setup.sh to your repository

Script Writing Guidelines

Idempotency Rules

Always check before acting:

# Check before installing packages
if ! command -v node &> /dev/null; then
    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
    sudo apt-get install -y nodejs
fi

# Use -p for directories
mkdir -p /var/lib/myapp/data

# Use apt-get install -y (safe to re-run)
sudo apt-get install -y redis-server

# Check before generating config files
if [ ! -f .env ]; then
    # Generate .env from Muppy environment
    cat > .env << EOF
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE}
EOF
fi

Available Environment Variables

The script has access to:

Variable Source Description
MPY_ENVIRONMENT Runtime detection docker, lxc_container, lxc_vm, or bare_metal
MPY_REPO_ABS_PATH /etc/muppy.env Absolute path to the repository root on the server. Use it to cd "$MPY_REPO_ABS_PATH" or reference artifact paths.
PGHOST /etc/muppy.env PostgreSQL host
PGPORT /etc/muppy.env PostgreSQL port
PGUSER /etc/muppy.env PostgreSQL user
PGPASSWORD /etc/muppy.env PostgreSQL password
PGDATABASE /etc/muppy.env PostgreSQL database name
APP_PRIMARY_URL /etc/muppy.env Application primary URL

Additional custom environment variables configured on the App Server's Environment Variables tab are also written to /etc/muppy.env and become available after source /etc/muppy.env. Read the tab on your own server to see the full list for that specific instance.

Script Structure

A typical mpy_setup.sh follows this pattern:

#!/bin/bash
set -e

# 1. Detect environment context
if [ -f /.dockerenv ]; then
    export MPY_ENVIRONMENT="docker"
elif systemd-detect-virt --quiet 2>/dev/null; then
    VIRT_TYPE=$(systemd-detect-virt)
    if [[ "$VIRT_TYPE" == *"lxc"* ]]; then
        export MPY_ENVIRONMENT="lxc_container"
    else
        export MPY_ENVIRONMENT="lxc_vm"
    fi
else
    export MPY_ENVIRONMENT="bare_metal"
fi

# 2. Source Muppy environment
if [ -f /etc/muppy.env ]; then
    set -a
    source /etc/muppy.env
    set +a
fi

# 3. Install system packages
sudo apt-get update
sudo apt-get install -y <your-packages>

# 4. Install language runtimes (if needed)
# ...

# 5. Install project dependencies
# ...

# 6. Generate configuration from environment
# ...

# 7. Build the project
# ...

# 8. Run database migrations
# ...

echo "=== mpy_setup.sh completed successfully ==="

UI Reference: Setup Script Tab

The Setup Script tab in the App Server form provides:

Buttons

Button Action
Reload from Template Overwrites current script with the selected template content
Save to Template Saves current script back to the selected template (non-system only)
Upload to Server Pushes script to .muppy/mpy_setup.sh on the server
Download from Server Retrieves script from server (useful after manual edits via Code Server)
Run mpy_setup.sh script Executes the script asynchronously on the server
Save as New Template Creates a new template from the current script content

Template Selector

Select from available mpysetupscript type templates:

  • System templates (read-only): Shared reference scripts (e.g., Default, Outline)
  • User templates: Your own saved scripts, editable

Warnings

The UI shows warnings when:

  • The selected template differs from the Application Definition's default
  • The script content has been modified from the template (with options to save or reload)
  • A system template is selected but cannot be modified (suggests duplicating)

Known Pitfalls

Four rules, one recovery loop. Hitting any of the rules either hangs the IMQ task or breaks re-runs.

1. Never leave a long-running process behind

The script must return. Any command that forks a background process and doesn't wait for it (build-tool daemons, REPLs, long-polling clients, tail -f, watch) leaves the shell hanging and the IMQ task stuck in wip. Common offenders: ./gradlew (without --no-daemon), ./sbt (REPL), npm run dev/watch, docker run (without --rm), background jobs ending in &.

One-shot invocations of the same tools are safe: npm ci, npm run build, mvn package, ./gradlew build --no-daemon all run to completion and exit cleanly. The dangerous pattern is the lingering fork, not the tool itself.

Never call a build tool in a "summary / version print" tail (e.g. ./gradlew --version at the end of the script) — it's the most common cause of stuck IMQ tasks.

2. Every step must be re-runnable

The script gets replayed on every iteration. Check before acting — don't trust that "it already ran once".

# Safe: apt -y, command -v guard, mkdir -p, existence check
sudo apt-get install -y redis-server
command -v node &>/dev/null || install_node
mkdir -p /var/lib/myapp/data
[ -f .env ] || cat > .env <<EOF ... EOF

# Unsafe: unconditional downloads, appending to configs, destructive writes

3. No stdin — unattended runs only

Anything that waits on input hangs forever. Use DEBIAN_FRONTEND=noninteractive for apt, avoid bare REPLs (python, mysql, …), and set -o StrictHostKeyChecking=accept-new on outbound SSH.

4. set -euo pipefail + scoped || true

set -euo pipefail is the Muppy default — it catches 80% of sneaky failures. Never wrap a whole block in set +e; instead, guard individual commands that are allowed to fail:

optional_check || true
curl -fsSL https://... > /tmp/x || { echo "[WARN] skipped"; true; }

Recovering from a stuck task

When the IMQ task sits in state='wip' with no log progress for more than ~2 minutes, rule 1 is almost always the cause. Recovery loop (all via mgx_exec on your own App Server — ACL-scoped, safe to use):

  1. Inspect: ps auxf, then pgrep -af '<tool name>' for the usual suspects.
  2. Kill with sudo: sudo kill -TERM <pid> (escalate to sudo kill -KILL after ~10s if ignored). Use sudo because setup scripts routinely fork root-owned processes via sudo apt, sudo -E bash -, etc. — plain kill fails with "Operation not permitted" on those.
  3. Fix the script: remove the offending call, wrap in timeout, or add || true.
  4. Re-run: mgx_upload_mpy_setup_sh(force=True) then mgx_run_mpy_setup_sh().

Example: Outline Wiki Setup

The Outline system template (outline-mpy-setup-sh) is a complete real-world example of a mpy_setup.sh script. It installs:

  1. Node.js 22.x via NodeSource
  2. Yarn via corepack
  3. Redis as a systemd service
  4. System packages: build-essential, postgresql-client
  5. File storage directory at /var/lib/outline/data
  6. Environment configuration (.env generated from Muppy env vars)
  7. Project build: yarn install + yarn build
  8. Database migrations: yarn db:migrate

Each step includes idempotency checks (skip if already done) and clear logging. This template demonstrates best practices for writing production-ready setup scripts.