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.templaterecords
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 dependenciesDockerfile-- often lists system packages and build stepsMakefile/ build scripts -- build toolchain requirementsREADME-- installation instructions
Step 3: Write the Script¶
You have three options:
- Start from a template: Select an existing template in the Setup Script tab
- Ask your AI agent: Use MCP tools to let your AI assistant analyze the repo and generate the script
- Write manually: Use the Ace editor in the Setup Script tab
Step 4: Upload and Run¶
- Click "Upload to Server" to push the script to
.muppy/mpy_setup.sh - Click "Run mpy_setup.sh script" to execute it asynchronously
- Monitor the task in the IMQ queue for progress and logs
Step 5: Iterate Until Success¶
If the script fails:
- Check the IMQ task logs for error details
- Edit the script in the Setup Script tab
- 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.templaterecord - For private repos, also commit
.muppy/mpy_setup.shto 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):
- Inspect:
ps auxf, thenpgrep -af '<tool name>'for the usual suspects. - Kill with
sudo:sudo kill -TERM <pid>(escalate tosudo kill -KILLafter ~10s if ignored). Usesudobecause setup scripts routinely fork root-owned processes viasudo apt,sudo -E bash -, etc. — plainkillfails with "Operation not permitted" on those. - Fix the script: remove the offending call, wrap in
timeout, or add|| true. - Re-run:
mgx_upload_mpy_setup_sh(force=True)thenmgx_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:
- Node.js 22.x via NodeSource
- Yarn via corepack
- Redis as a systemd service
- System packages: build-essential, postgresql-client
- File storage directory at
/var/lib/outline/data - Environment configuration (
.envgenerated from Muppy env vars) - Project build:
yarn install+yarn build - 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.