# 🚀 **Application Container Scripts (ct/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` --- ## 📋 Table of Contents - [Overview](#overview) - [Architecture & Flow](#architecture--flow) - [File Structure](#file-structure) - [Complete Script Template](#complete-script-template) - [Function Reference](#function-reference) - [Advanced Features](#advanced-features) - [Real Examples](#real-examples) - [Troubleshooting](#troubleshooting) - [Contribution Checklist](#contribution-checklist) --- ## Overview ### Purpose Container scripts (`ct/AppName.sh`) are **entry points for creating LXC containers** with specific applications pre-installed. They: 1. Define container defaults (CPU, RAM, disk, OS) 2. Call the main build orchestrator (`build.func`) 3. Implement application-specific update mechanisms 4. Provide user-facing success messages ### Execution Context ``` 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 ``` ### Key Integration Points - **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 --- ## Architecture & Flow ### Container Creation Flow ``` 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 ↓ END ``` ### Default Values Precedence ``` Priority 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) ``` --- ## File Structure ### Minimal ct/AppName.sh Template ``` #!/usr/bin/env bash # [1] Shebang # [2] Copyright/License source <(curl -s .../misc/build.func) # [3] Import functions # [4] APP metadata APP="AppName" # [5] Default values var_tags="tag1;tag2" var_cpu="2" var_ram="2048" ... header_info "$APP" # [6] Display header variables # [7] Process arguments color # [8] Setup colors catch_errors # [9] Setup error handling function update_script() { ... } # [10] Update function (optional) start # [11] Launch container creation build_container description msg_ok "Completed Successfully!\n" ``` --- ## Complete Script Template ### 1. File Header & Imports ```bash #!/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 orchestrator source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) ``` > **⚠️ IMPORTANT**: Before opening a PR, change URL to `community-scripts` repo! ### 2. Application Metadata ```bash # Application Configuration APP="ApplicationName" var_tags="tag1;tag2;tag3" # Max 3-4 tags, no spaces, semicolon-separated # Container Resources var_cpu="2" # CPU cores var_ram="2048" # RAM in MB var_disk="10" # Disk in GB # Container Type & OS var_os="debian" # Options: alpine, debian, ubuntu var_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`) ### 3. Display & Initialization ```bash # Display header ASCII art header_info "$APP" # Process command-line arguments and load configuration variables # Setup ANSI color codes and formatting color # Initialize error handling (trap ERR, EXIT, INT, TERM) catch_errors ``` ### 4. Update Function (Highly Recommended) ```bash function update_script() { header_info # Always start with these checks check_container_storage check_container_resources # Verify app is installed if [[ ! -d /opt/appname ]]; then msg_error "No ${APP} Installation Found!" exit fi # 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 version if [[ ! -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 update cd /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 version echo "${RELEASE}" > /opt/${APP}_version.txt msg_ok "Updated ${APP} to v${RELEASE}" else msg_ok "No update required. ${APP} is already at v${RELEASE}." fi exit } ``` ### 5. Script Launch ```bash # Start the container creation workflow start # Build the container with selected configuration build_container # Set container description/notes in Proxmox UI description # Display success message msg_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}" ``` --- ## Function Reference ### Core Functions (From build.func) #### `variables()` **Purpose**: Initialize container variables, load user arguments, setup orchestration **Triggered by**: Called automatically at script start **Behavior**: 1. Parse command-line arguments (if any) 2. Generate random UUID for session tracking 3. Load container storage from Proxmox 4. Initialize application-specific defaults 5. Setup SSH/environment configuration **Example Output**: ``` Setting up variables... Container ID: 100 Storage: local-lvm Install method: Default ``` #### `start()` **Purpose**: Launch the container creation menu with 5 installation modes **Triggered by**: Called just before `build_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) Default 2) Advanced 3) User Defaults 4) App Defaults 5) Settings Menu Enter choice: 2 ``` #### `build_container()` **Purpose**: Main orchestrator for LXC container creation **Operations**: 1. Validates all variables 2. Creates LXC container via `pct create` 3. Executes `install/AppName-install.sh` inside container 4. Monitors installation progress 5. Handles errors and rollback on failure **Exit Codes**: - `0` - Success - `1-255` - Various error conditions (see error_handler.func) #### `description()` **Purpose**: Set container description/notes visible in Proxmox UI **Format**: ``` AppName IP: [IP] Version: [Version] Tags: [Tags] ``` #### `header_info()` **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 --- ## Advanced Features ### 1. Integration with Defaults System #### Save App Defaults After Installation ```bash # 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 ``` #### Load Saved Defaults During Container Creation ```bash # In ct/AppName.sh, user selects "App Defaults" mode # Automatically loads /defaults/appname.vars # Container uses previously saved configuration ``` ### 2. Custom Configuration Menus If your app has additional setup beyond standard vars: ```bash # 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 60 3>&1 1>&2 2>&3) DB_PORT=$(whiptail --inputbox "Database Port:" 8 60 "3306" 3>&1 1>&2 2>&3) fi } custom_app_settings ``` ### 3. Version Tracking Save installed version for update checks: ```bash # 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.txt 2>/dev/null) LATEST=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | jq -r '.tag_name') if [[ "$LATEST" != "$CURRENT" ]]; then echo "Update available: $CURRENT → $LATEST" fi ``` ### 4. Health Check Functions Add custom validation: ```bash function health_check() { header_info if [[ ! -d /opt/appname ]]; then msg_error "Application not found!" exit 1 fi if ! systemctl is-active --quiet appname; then msg_error "Application service not running" exit 1 fi msg_ok "Health check passed" } # Called via: bash ct/appname.sh health_check ``` --- ## Real Examples ### Example 1: Simple Web App (Debian-based) ```bash #!/usr/bin/env bash source <(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" variables color catch_errors function update_script() { header_info check_container_storage check_container_resources if [[ ! -d /opt/homarr ]]; then msg_error "No ${APP} Installation Found!" exit fi 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 homarr cd /opt/homarr wget -q "https://github.com/ajnart/homarr/releases/download/v${RELEASE}/docker-compose.yml" docker-compose up -d echo "${RELEASE}" > /opt/${APP}_version.txt msg_ok "Updated ${APP} to v${RELEASE}" else msg_ok "No update required. ${APP} is already at v${RELEASE}." fi exit } start build_container description msg_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}" ``` ### Example 2: Database App (Alpine-based) ```bash #!/usr/bin/env bash source <(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" variables color catch_errors function update_script() { header_info check_container_storage if ! command -v psql &>/dev/null; then msg_error "PostgreSQL not installed!" exit fi msg_info "Updating Alpine packages" apk update apk upgrade msg_ok "Updated Alpine packages" exit } start build_container description msg_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}" ``` --- ## Troubleshooting ### Container Creation Fails **Symptom**: `pct create` exits with error code 209 **Causes**: 1. CTID already exists: `pct list` shows duplicate 2. Storage full: Check storage space 3. Network template unavailable **Solution**: ```bash # Check existing containers pct list | grep CTID # Remove conflicting container pct destroy CTID # Retry ct/AppName.sh ``` ### Update Function Doesn't Detect New Version **Symptom**: Update available but script says "already at latest" **Causes**: 1. Version file missing: `/opt/AppName_version.txt` 2. GitHub API rate limit exceeded 3. Release tag format mismatch **Debug**: ```bash # Check version file cat /opt/AppName_version.txt # Test GitHub API curl -fsSL https://api.github.com/repos/user/repo/releases/latest | grep tag_name # Inside container bash ct/appname.sh update_script ``` ### Header ASCII Art Not Displaying **Symptom**: Container script runs but no header shown **Causes**: 1. Header file not in repository 2. Caching issue **Solution**: ```bash # Create header file manually mkdir -p /usr/local/community-scripts/headers/ct echo "Your ASCII art here" > /usr/local/community-scripts/headers/ct/appname # Or remove cache to force re-download rm -f /usr/local/community-scripts/headers/ct/appname ``` --- ## Contribution Checklist Before submitting a PR: ### Script Structure - [ ] Shebang is `#!/usr/bin/env bash` - [ ] Imports `build.func` from community-scripts repo (not personal fork) - [ ] Copyright header with author and source URL - [ ] APP variable matches filename - [ ] `var_tags` are semicolon-separated (no spaces) ### Default Values - [ ] `var_cpu` set appropriately (2-4 for most apps) - [ ] `var_ram` set appropriately (1024-4096 MB minimum) - [ ] `var_disk` sufficient for app + data (5-20 GB) - [ ] `var_os` is realistic (Alpine if lightweight, Debian/Ubuntu otherwise) - [ ] `var_unprivileged="1"` unless app absolutely needs privileges ### Functions - [ ] `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_error` on failure ### Output - [ ] Success message displayed with access URL - [ ] URL format: `http://IP:PORT/path` (if web-based) - [ ] Uses `msg_ok`, `msg_info`, `msg_error` for feedback ### Testing - [ ] 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) --- ## Best Practices ### ✅ DO: 1. **Use meaningful defaults** ```bash var_cpu="2" # ✅ Good: Typical workload var_cpu="128" # ❌ Bad: Unrealistic ``` 2. **Implement version tracking** ```bash echo "${RELEASE}" > /opt/${APP}_version.txt # ✅ Good # ❌ Bad: No version tracking ``` 3. **Handle edge cases** ```bash if [[ ! -f /opt/${APP}_version.txt ]]; then msg_info "First installation detected" fi ``` 4. **Use proper messaging** ```bash msg_info "Updating..." # ✅ Good: Clear status echo "Updating..." # ❌ Bad: No formatting ``` ### ❌ DON'T: 1. **Hardcode versions** ```bash RELEASE="1.2.3" # ❌ Bad: Won't auto-update ``` 2. **Use custom color codes** ```bash echo -e "\033[32mSuccess" # ❌ Bad: Use $GN instead ``` 3. **Forget error handling** ```bash wget file.zip # ❌ Bad: No error check if ! wget -q file.zip; then # ✅ Good msg_error "Download failed" fi ``` 4. **Leave temporary files** ```bash rm -rf /opt/file.zip # ✅ Always cleanup ``` --- ## Related Documentation - [install/AppName-install.sh Guide](UPDATED_APP-install.md) - [build.func Wiki](../misc/build.func.md) - [tools.func Wiki](../misc/tools.func.md) - [Defaults System Guide](../DEFAULTS_SYSTEM_GUIDE.md) --- **Last Updated**: December 2025 **Compatibility**: ProxmoxVED with build.func v3+ **Questions?** Open an issue in the repository