# 🛠️ **Application Installation Scripts (install/AppName-install.sh)** **Modern Guide to Writing In-Container Installation Scripts** > **Updated**: December 2025 > **Context**: Integrated with tools.func, error_handler.func, and install.func > **Examples Used**: `/install/pihole-install.sh`, `/install/mealie-install.sh` --- ## 📋 Table of Contents - [Overview](#overview) - [Execution Context](#execution-context) - [File Structure](#file-structure) - [Complete Script Template](#complete-script-template) - [Installation Phases](#installation-phases) - [Function Reference](#function-reference) - [Best Practices](#best-practices) - [Real Examples](#real-examples) - [Troubleshooting](#troubleshooting) - [Contribution Checklist](#contribution-checklist) --- ## Overview ### Purpose Installation scripts (`install/AppName-install.sh`) **run inside the LXC container** and are responsible for: 1. Setting up the container OS (updates, packages) 2. Installing application dependencies 3. Downloading and configuring the application 4. Setting up services and systemd units 5. Creating version tracking files for updates 6. Generating credentials/configurations 7. Final cleanup and validation ### Key Characteristics - Runs as **root inside container** (not on Proxmox host) - Executed automatically by `build_container()` from ct/AppName.sh - Uses `$FUNCTIONS_FILE_PATH` for function library access - Interactive elements via **whiptail** (GUI menus) - Version-aware for update tracking ### Execution Flow ``` ct/AppName.sh (Proxmox Host) ↓ build_container() ↓ pct exec CTID bash -c "$(cat install/AppName-install.sh)" ↓ install/AppName-install.sh (Inside Container) ↓ Container Ready with App Installed ``` --- ## Execution Context ### Environment Variables Available ```bash # From Proxmox/Container CTID # Container ID (100, 101, etc.) PCT_OSTYPE # OS type (alpine, debian, ubuntu) HOSTNAME # Container hostname # From build.func FUNCTIONS_FILE_PATH # Bash functions library (core.func + tools.func) VERBOSE # Verbose mode (yes/no) STD # Standard redirection variable (silent/empty) # From install.func APP # Application name NSAPP # Normalized app name (lowercase, no spaces) METHOD # Installation method (ct/install) RANDOM_UUID # Session UUID for telemetry ``` ### Access to Functions ```bash # All functions from core.func available: source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color # ANSI colors catch_errors # Error handling msg_info # Display messages msg_ok msg_error # All functions from tools.func available: setup_nodejs # Tool installation setup_php setup_python setup_docker # ... many more # All functions from install.func available: motd_ssh # Final setup customize cleanup_lxc ``` --- ## File Structure ### Minimal install/AppName-install.sh Template ```bash #!/usr/bin/env bash # [1] Shebang # [2] Copyright/Metadata # Copyright (c) 2021-2025 community-scripts ORG # Author: YourUsername # License: MIT # Source: https://example.com # [3] Load functions source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color verb_ip6 catch_errors setting_up_container network_check update_os # [4] Installation steps msg_info "Installing Dependencies" $STD apt-get install -y package1 package2 msg_ok "Installed Dependencies" # [5] Final setup motd_ssh customize cleanup_lxc ``` --- ## Complete Script Template ### Phase 1: Header & Initialization ```bash #!/usr/bin/env bash # Copyright (c) 2021-2025 community-scripts ORG # Author: YourUsername # Co-Author: AnotherAuthor (for updates) # License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE # Source: https://github.com/application/repo # Load all available functions (from core.func + tools.func) source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" # Initialize environment color # Setup ANSI colors and icons verb_ip6 # Configure IPv6 (if needed) catch_errors # Setup error traps setting_up_container # Verify OS is ready network_check # Verify internet connectivity update_os # Update packages (apk/apt) ``` ### Phase 2: Dependency Installation ```bash msg_info "Installing Dependencies" $STD apt-get install -y \ curl \ wget \ git \ nano \ build-essential \ libssl-dev \ python3-dev msg_ok "Installed Dependencies" ``` **Guidelines**: - Use `\` for line continuation (readability) - Group related packages together - Collapse repeated prefixes: `php8.4-{bcmath,curl,gd,intl,mbstring}` - Use `-y` flag for non-interactive installation - Silence output with `$STD` unless debugging ### Phase 3: Tool Setup (Using tools.func) ```bash # Setup specific tool versions NODE_VERSION="22" setup_nodejs # Or for databases MYSQL_VERSION="8.0" setup_mysql # Or for languages PHP_VERSION="8.4" PHP_MODULE="redis,imagick" setup_php # Or for version control setup_composer ``` **Available Tool Functions**: ```bash setup_nodejs # Node.js from official repo setup_php # PHP with optional modules setup_python # Python 3 setup_mariadb # MariaDB database setup_mysql # MySQL database setup_postgresql # PostgreSQL database setup_mongodb # MongoDB database setup_docker # Docker Engine setup_nodejs # Node.js runtime setup_composer # PHP Composer setup_ruby # Ruby runtime setup_rust # Rust toolchain setup_go # Go language setup_java # Java/Temurin # ... many more in tools.func.md ``` ### Phase 4: Application Download & Setup ```bash # Method A: Download from GitHub releases msg_info "Downloading ${APP}" RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \ grep "tag_name" | awk '{print substr($2, 2, length($2)-3)}') wget -q "https://github.com/user/repo/releases/download/v${RELEASE}/app-${RELEASE}.tar.gz" \ -O /opt/app-${RELEASE}.tar.gz cd /opt tar -xzf app-${RELEASE}.tar.gz rm -f app-${RELEASE}.tar.gz msg_ok "Downloaded and extracted ${APP}" # Method B: Clone from Git git clone https://github.com/user/repo /opt/appname # Method C: Download single file fetch_and_deploy_gh_release "AppName" "user/repo" "tarball" ``` ### Phase 5: Configuration Files ```bash # Method A: Using cat << EOF (multiline) cat <<'EOF' >/etc/nginx/sites-available/appname server { listen 80; server_name _; root /opt/appname/public; index index.php index.html; location ~ \.php$ { fastcgi_pass unix:/run/php-fpm.sock; include fastcgi_params; } } EOF # Method B: Using sed for replacements sed -i -e "s|^DB_HOST=.*|DB_HOST=localhost|" \ -e "s|^DB_USER=.*|DB_USER=appuser|" \ /opt/appname/.env # Method C: Using echo for simple configs echo "APP_KEY=base64:$(openssl rand -base64 32)" >> /opt/appname/.env ``` ### Phase 6: Database Setup (If Needed) ```bash msg_info "Setting up Database" DB_NAME="appname_db" DB_USER="appuser" DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) # For MySQL/MariaDB mysql -u root <> ~/appname.creds Database Credentials Database: ${DB_NAME} Username: ${DB_USER} Password: ${DB_PASS} EOF msg_ok "Database setup complete" ``` ### Phase 7: Permission & Ownership ```bash msg_info "Setting permissions" # Web applications typically run as www-data chown -R www-data:www-data /opt/appname chmod -R 755 /opt/appname chmod -R 644 /opt/appname/* chmod 755 /opt/appname/*/.* # For apps with specific requirements find /opt/appname/storage -type f -exec chmod 644 {} \; find /opt/appname/storage -type d -exec chmod 755 {} \; msg_ok "Permissions set" ``` ### Phase 8: Service Configuration ```bash # Enable systemd service systemctl enable -q --now appname # Or for OpenRC (Alpine) rc-service appname start rc-update add appname default # Verify service is running if systemctl is-active --quiet appname; then msg_ok "Service running successfully" else msg_error "Service failed to start" journalctl -u appname -n 20 exit 1 fi ``` ### Phase 9: Version Tracking ```bash # Essential for update detection echo "${RELEASE}" > /opt/${APP}_version.txt # Or with additional metadata cat > /opt/${APP}_version.txt < /opt/${APP}_version.txt ``` ### Phase 5: Configuration ```bash # Application-specific configuration cat > /opt/appname/.env < /opt/appname/config.yml < /opt/${APP}_version.txt # ❌ Bad: No version file # (Update function won't work) ``` #### 6. Handle Alpine vs Debian Differences ```bash # ✅ Good: Detect OS if grep -qi 'alpine' /etc/os-release; then apk add package else apt-get install -y package fi # ❌ Bad: Assumes Debian apt-get install -y package ``` #### 7. Use Proper Messaging ```bash # ✅ Good: Clear status progression msg_info "Installing Dependencies" $STD apt-get install -y package msg_ok "Installed Dependencies" msg_info "Configuring Application" # ... configuration ... msg_ok "Application configured" # ❌ Bad: No status messages apt-get install -y package # ... configuration ... ``` ### ❌ DON'T: #### 1. Hardcode Versions ```bash # ❌ Bad: Won't auto-update VERSION="1.2.3" wget https://example.com/app-1.2.3.tar.gz # ✅ Good: Fetch latest RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | jq -r '.tag_name') wget https://example.com/app-${RELEASE}.tar.gz ``` #### 2. Use Root Without Password ```bash # ❌ Bad: Allows unprompted root access mysql -u root < /etc/systemd/system/appname.service < /opt/${APP}_version.txt motd_ssh customize cleanup_lxc ``` ### Example 2: Database Application (PHP + MySQL) ```bash #!/usr/bin/env bash source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color catch_errors setting_up_container network_check update_os msg_info "Installing Dependencies" $STD apt-get install -y git curl nginx supervisor msg_ok "Installed Dependencies" msg_info "Setting up PHP" PHP_VERSION="8.4" PHP_MODULE="bcmath,curl,gd,intl,mbstring,pdo_mysql,redis" setup_php msg_ok "PHP installed" msg_info "Setting up Database" MARIADB_VERSION="11.4" setup_mariadb msg_ok "MariaDB installed" DB_NAME="appname_db" DB_USER="appuser" DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) mysql -u root < /opt/${APP}_version.txt motd_ssh customize cleanup_lxc ``` --- ## Troubleshooting ### Installation Hangs **Symptom**: Script appears to freeze at particular step **Causes**: 1. Network connectivity lost 2. Repository server timing out 3. Interactive prompt waiting for input **Debug**: ```bash # Check if process still running ps aux | grep -i appname # Check network ping -c 1 8.8.8.8 # Check apt lock lsof /var/lib/apt/lists/lock ``` ### Package Installation Fails **Symptom**: `E: Unable to locate package xyz` **Causes**: 1. Repository not updated 2. Package name incorrect for OS version 3. Conflicting repository configuration **Solution**: ```bash # Force update apt-get update --allow-releaseinfo-change apt-cache search package | grep exact_name ``` ### Permission Denied on Files **Symptom**: Application can't write to `/opt/appname` **Causes**: 1. Wrong owner 2. Wrong permissions (644 for files, 755 for directories) **Fix**: ```bash chown -R www-data:www-data /opt/appname chmod -R 755 /opt/appname find /opt/appname -type f -exec chmod 644 {} \; find /opt/appname -type d -exec chmod 755 {} \; ``` ### Service Won't Start **Symptom**: `systemctl status appname` shows failed **Debug**: ```bash # Check service status systemctl status appname # View logs journalctl -u appname -n 50 # Check configuration systemctl cat appname ``` --- ## Contribution Checklist Before submitting a PR: ### Script Structure - [ ] Shebang is `#!/usr/bin/env bash` - [ ] Copyright header with author and source URL - [ ] Functions loaded via `$FUNCTIONS_FILE_PATH` - [ ] Initial setup: `color`, `catch_errors`, `setting_up_container`, `network_check`, `update_os` ### Installation Flow - [ ] Dependencies installed with `$STD apt-get install -y \` - [ ] Package names collapsed (`php-{bcmath,curl}`) - [ ] Tool setup uses functions from tools.func (not manual installation) - [ ] Application version fetched dynamically (not hardcoded) - [ ] Version saved to `/opt/${APP}_version.txt` ### Configuration - [ ] Configuration files created properly (heredoc or sed) - [ ] Credentials generated randomly (`openssl rand`) - [ ] Credentials stored in creds file - [ ] Passwords use alphanumeric only (no special chars) - [ ] Proper file permissions set ### Messaging - [ ] `msg_info` followed by action then `msg_ok` - [ ] Error cases use `msg_error` and exit - [ ] No bare `echo` statements for status (use msg_* functions) ### Cleanup - [ ] Temporary files removed - [ ] Package manager cache cleaned (`autoremove`, `autoclean`) - [ ] `cleanup_lxc` called at end - [ ] `motd_ssh` called before `customize` - [ ] `customize` called before exit ### Testing - [ ] Script tested with default OS (Debian 12/Ubuntu 22.04) - [ ] Script tested with Alpine (if applicable) - [ ] Script tested with verbose mode (`VERBOSE=yes`) - [ ] Error handling tested (network interruption, missing packages) - [ ] Cleanup verified (disk space reduced, temp files removed) --- ## Related Documentation - [ct/AppName.sh Guide](UPDATED_APP-ct.md) - [tools.func Wiki](../misc/tools.func.md) - [install.func Wiki](../misc/install.func.md) - [error_handler.func Wiki](../misc/error_handler.func.md) --- **Last Updated**: December 2025 **Compatibility**: ProxmoxVED with tools.func v2+ **Questions?** Open an issue in the repository