Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.2k
[Script]: AppName.sh
Modern Guide to Creating LXC Container Installation Scripts
Updated: December 2025
Context: Fully integrated with build.func, advanced_settings wizard, and defaults system
Example Used:/ct/pihole.sh,/ct/docker.sh
- Overview
- Architecture & Flow
- File Structure
- Complete Script Template
- Function Reference
- Advanced Features
- Real Examples
- Troubleshooting
- Contribution Checklist
Container scripts (ct/AppName.sh) areentry points for creating LXC containers with specific applications pre-installed. They:
- Define container defaults (CPU, RAM, disk, OS)
- Call the main build orchestrator (
build.func) - Implement application-specific update mechanisms
- Provide user-facing success messages
Proxmox Host ↓ct/AppName.sh sourced (runs as root on host) ↓build.func: Creates LXC container + runs install script inside ↓install/AppName-install.sh (runs inside container) ↓Container ready with app installed- build.func - Main orchestrator (container creation, storage, variable management)
- install.func - Container-specific setup (OS update, package management)
- tools.func - Tool installation helpers (repositories, GitHub releases)
- core.func - UI/messaging functions (colors, spinners, validation)
- error_handler.func - Error handling and signal management
START: bash ct/pihole.sh ↓[1] Set APP, var_*, defaults ↓[2] header_info() → Display ASCII art ↓[3] variables() → Parse arguments & load build.func ↓[4] color() → Setup ANSI codes ↓[5] catch_errors() → Setup trap handlers ↓[6] install_script() → Show mode menu (5 options) ↓ ├─ INSTALL_MODE="0" (Default) ├─ INSTALL_MODE="1" (Advanced - 19-step wizard) ├─ INSTALL_MODE="2" (User Defaults) ├─ INSTALL_MODE="3" (App Defaults) └─ INSTALL_MODE="4" (Settings Menu) ↓[7] advanced_settings() → Collect user configuration (if mode=1) ↓[8] start() → Confirm or re-edit settings ↓[9] build_container() → Create LXC + execute install script ↓[10] description() → Set container description ↓[11] SUCCESS → Display access URL ↓ENDPriority 1 (Highest): Environment Variables (var_cpu, var_ram, etc.)Priority 2: App-Specific Defaults (/defaults/AppName.vars)Priority 3: User Global Defaults (/default.vars)Priority 4 (Lowest): Built-in Defaults (in build.func)#!/usr/bin/env bash # [1] Shebang # [2] Copyright/Licensesource <(curl -s .../misc/build.func) # [3] Import functions # [4] APP metadataAPP="AppName" # [5] Default valuesvar_tags="tag1;tag2"var_cpu="2"var_ram="2048"...header_info "$APP" # [6] Display headervariables # [7] Process argumentscolor # [8] Setup colorscatch_errors # [9] Setup error handlingfunction update_script() { ... } # [10] Update function (optional)start # [11] Launch container creationbuild_containerdescriptionmsg_ok "Completed Successfully!\n"#!/usr/bin/env bash# Copyright (c) 2021-2025 community-scripts ORG# Author: YourUsername# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE# Source: https://github.com/example/project# Import main orchestratorsource<(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
⚠️ IMPORTANT: Before opening a PR, change URL tocommunity-scriptsrepo!
# Application ConfigurationAPP="ApplicationName"var_tags="tag1;tag2;tag3"# Max 3-4 tags, no spaces, semicolon-separated# Container Resourcesvar_cpu="2"# CPU coresvar_ram="2048"# RAM in MBvar_disk="10"# Disk in GB# Container Type & OSvar_os="debian"# Options: alpine, debian, ubuntuvar_version="12"# Alpine: 3.20+, Debian: 11-13, Ubuntu: 20.04+var_unprivileged="1"# 1=unprivileged (secure), 0=privileged (rarely needed)
Variable Naming Convention:
- Variables exposed to user:
var_*(e.g.,var_cpu,var_hostname,var_ssh) - Internal variables: lowercase (e.g.,
container_id,app_version)
# Display header ASCII artheader_info"$APP"# Process command-line arguments and load configurationvariables# Setup ANSI color codes and formattingcolor# Initialize error handling (trap ERR, EXIT, INT, TERM)catch_errors
functionupdate_script() { header_info# Always start with these checks check_container_storage check_container_resources# Verify app is installedif [[!-d /opt/appname ]];then msg_error"No${APP} Installation Found!"exitfi# Get latest version from GitHub RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest| \ grep"tag_name"| awk'{print substr($2, 2, length($2)-3)}')# Compare with saved versionif [[!-f /opt/${APP}_version.txt ]]|| [["${RELEASE}"!="$(cat /opt/${APP}_version.txt)" ]];then msg_info"Updating${APP} to v${RELEASE}"# Backup user data cp -r /opt/appname /opt/appname-backup# Perform updatecd /opt wget -q"https://github.com/user/repo/releases/download/v${RELEASE}/app-${RELEASE}.tar.gz" tar -xzf app-${RELEASE}.tar.gz# Restore user data cp /opt/appname-backup/config/* /opt/appname/config/# Cleanup rm -rf app-${RELEASE}.tar.gz /opt/appname-backup# Save new versionecho"${RELEASE}"> /opt/${APP}_version.txt msg_ok"Updated${APP} to v${RELEASE}"else msg_ok"No update required.${APP} is already at v${RELEASE}."fiexit}
# Start the container creation workflowstart# Build the container with selected configurationbuild_container# Set container description/notes in Proxmox UIdescription# Display success messagemsg_ok"Completed Successfully!\n"echo -e"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"echo -e"${INFO}${YW} Access it using the following URL:${CL}"echo -e"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"
Purpose: Initialize container variables, load user arguments, setup orchestration
Triggered by: Called automatically at script start
Behavior:
- Parse command-line arguments (if any)
- Generate random UUID for session tracking
- Load container storage from Proxmox
- Initialize application-specific defaults
- Setup SSH/environment configuration
Example Output:
Setting up variables...Container ID: 100Storage: local-lvmInstall method: DefaultPurpose: Launch the container creation menu with 5 installation modes
Triggered by: Called just beforebuild_container()
Menu Options:
1. Default Installation (Quick setup, predefined settings)2. Advanced Installation (19-step wizard with full control)3. User Defaults (Load ~/.community-scripts/default.vars)4. App Defaults (Load /defaults/AppName.vars)5. Settings Menu (Interactive mode selection)User Flow:
Select installation mode:1) Default2) Advanced3) User Defaults4) App Defaults5) Settings MenuEnter choice: 2Purpose: Main orchestrator for LXC container creation
Operations:
- Validates all variables
- Creates LXC container via
pct create - Executes
install/AppName-install.shinside container - Monitors installation progress
- Handles errors and rollback on failure
Exit Codes:
0- Success1-255- Various error conditions (see error_handler.func)
Purpose: Set container description/notes visible in Proxmox UI
Format:
AppNameIP: [IP]Version: [Version]Tags: [Tags]Purpose: Display ASCII art header for application
Sources:
- Tries
/usr/local/community-scripts/headers/ct/appname(cached) - Falls back to remote fetch from GitHub
- Returns silently if not found
# At end of install script, after successful setup:maybe_offer_save_app_defaults# Output:# "Save these settings as App Defaults for AppName? (Y/n)"# Yes → Saves to /defaults/appname.vars# No → Skips saving
# In ct/AppName.sh, user selects "App Defaults" mode# Automatically loads /defaults/appname.vars# Container uses previously saved configuration
If your app has additional setup beyond standard vars:
# In ct/AppName.sh, after variables()custom_app_settings() { CONFIGURE_DB=$(whiptail --title"Database Setup" \ --yesno"Would you like to configure a custom database?" 8 60)if [[$?-eq 0 ]];then DB_HOST=$(whiptail --inputbox"Database Host:" 8 603>&11>&22>&3) DB_PORT=$(whiptail --inputbox"Database Port:" 8 60"3306"3>&11>&22>&3)fi}custom_app_settings
Save installed version for update checks:
# In install script, after successful app download:RELEASE="1.2.3"echo"${RELEASE}"> /opt/${APP}_version.txt# In update function, compare:CURRENT=$(cat /opt/${APP}_version.txt2>/dev/null)LATEST=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest| jq -r'.tag_name')if [["$LATEST"!="$CURRENT" ]];thenecho"Update available:$CURRENT →$LATEST"fi
Add custom validation:
functionhealth_check() { header_infoif [[!-d /opt/appname ]];then msg_error"Application not found!"exit 1fiif! systemctl is-active --quiet appname;then msg_error"Application service not running"exit 1fi msg_ok"Health check passed"}# Called via: bash ct/appname.sh health_check
#!/usr/bin/env bashsource<(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)APP="Homarr"var_tags="dashboard;homepage"var_cpu="2"var_ram="1024"var_disk="5"var_os="debian"var_version="12"var_unprivileged="1"header_info"$APP"variablescolorcatch_errorsfunctionupdate_script() { header_info check_container_storage check_container_resourcesif [[!-d /opt/homarr ]];then msg_error"No${APP} Installation Found!"exitfi RELEASE=$(curl -fsSL https://api.github.com/repos/ajnart/homarr/releases/latest| grep"tag_name"| awk'{print substr($2, 3, length($2)-4)}')if [[!-f /opt/${APP}_version.txt ]]|| [["${RELEASE}"!="$(cat /opt/${APP}_version.txt)" ]];then msg_info"Updating${APP} to v${RELEASE}" systemctl stop homarrcd /opt/homarr wget -q"https://github.com/ajnart/homarr/releases/download/v${RELEASE}/docker-compose.yml" docker-compose up -decho"${RELEASE}"> /opt/${APP}_version.txt msg_ok"Updated${APP} to v${RELEASE}"else msg_ok"No update required.${APP} is already at v${RELEASE}."fiexit}startbuild_containerdescriptionmsg_ok"Completed Successfully!\n"echo -e"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"echo -e"${INFO}${YW} Access it using the following URL:${CL}"echo -e"${TAB}${GATEWAY}${BGN}http://${IP}:5100${CL}"
#!/usr/bin/env bashsource<(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)APP="PostgreSQL"var_tags="database;sql"var_cpu="4"var_ram="4096"var_disk="20"var_os="alpine"var_version="3.20"var_unprivileged="1"header_info"$APP"variablescolorcatch_errorsfunctionupdate_script() { header_info check_container_storageif!command -v psql&>/dev/null;then msg_error"PostgreSQL not installed!"exitfi msg_info"Updating Alpine packages" apk update apk upgrade msg_ok"Updated Alpine packages"exit}startbuild_containerdescriptionmsg_ok"Completed Successfully!\n"echo -e"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"echo -e"${INFO}${YW} Connect using:${CL}"echo -e"${TAB}${GATEWAY}${BGN}psql -h${IP} -U postgres${CL}"
Symptom:pct create exits with error code 209
Causes:
- CTID already exists:
pct listshows duplicate - Storage full: Check storage space
- Network template unavailable
Solution:
# Check existing containerspct list| grep CTID# Remove conflicting containerpct destroy CTID# Retry ct/AppName.sh
Symptom: Update available but script says "already at latest"
Causes:
- Version file missing:
/opt/AppName_version.txt - GitHub API rate limit exceeded
- Release tag format mismatch
Debug:
# Check version filecat /opt/AppName_version.txt# Test GitHub APIcurl -fsSL https://api.github.com/repos/user/repo/releases/latest| grep tag_name# Inside containerbash ct/appname.sh update_script
Symptom: Container script runs but no header shown
Causes:
- Header file not in repository
- Caching issue
Solution:
# Create header file manuallymkdir -p /usr/local/community-scripts/headers/ctecho"Your ASCII art here"> /usr/local/community-scripts/headers/ct/appname# Or remove cache to force re-downloadrm -f /usr/local/community-scripts/headers/ct/appname
Before submitting a PR:
- Shebang is
#!/usr/bin/env bash - Imports
build.funcfrom community-scripts repo (not personal fork) - Copyright header with author and source URL
- APP variable matches filename
var_tagsare semicolon-separated (no spaces)
var_cpuset appropriately (2-4 for most apps)var_ramset appropriately (1024-4096 MB minimum)var_disksufficient for app + data (5-20 GB)var_osis realistic (Alpine if lightweight, Debian/Ubuntu otherwise)var_unprivileged="1"unless app absolutely needs privileges
update_script()implemented (or marked as unavailable)- Update function checks if app installed
- Update function checks for new version
- Update function performs cleanup after update
- Proper error handling with
msg_erroron failure
- Success message displayed with access URL
- URL format:
http://IP:PORT/path(if web-based) - Uses
msg_ok,msg_info,msg_errorfor feedback
- Script tested with default installation
- Script tested with advanced (19-step) installation
- Update function tested on existing installation
- Error handling tested (invalid settings, network issues)
Use meaningful defaults
var_cpu="2"# ✅ Good: Typical workloadvar_cpu="128"# ❌ Bad: Unrealistic
Implement version tracking
echo"${RELEASE}"> /opt/${APP}_version.txt# ✅ Good# ❌ Bad: No version tracking
Handle edge cases
if [[!-f /opt/${APP}_version.txt ]];then msg_info"First installation detected"fi
Use proper messaging
msg_info"Updating..."# ✅ Good: Clear statusecho"Updating..."# ❌ Bad: No formatting
Hardcode versions
RELEASE="1.2.3"# ❌ Bad: Won't auto-update
Use custom color codes
echo -e"\033[32mSuccess"# ❌ Bad: Use $GN instead
Forget error handling
wget file.zip# ❌ Bad: No error checkif! wget -q file.zip;then# ✅ Good msg_error"Download failed"fi
Leave temporary files
rm -rf /opt/file.zip# ✅ Always cleanup
Last Updated: December 2025
Compatibility: ProxmoxVED with build.func v3+
Questions? Open an issue in the repository