20 KiB
Raw Permalink Blame History

🤖 AI Contribution Guidelines for ProxmoxVED

This documentation is intended for all AI assistants (GitHub Copilot, Claude, ChatGPT, etc.) contributing to this project.

🎯 Core Principles

1. Maximum Use of tools.func Functions

We have an extensive library of helper functions. NEVER implement your own solutions when a function already exists!

2. No Pointless Variables

Only create variables when they:

  • Are used multiple times
  • Improve readability
  • Are intended for configuration

3. Consistent Script Structure

All scripts follow an identical structure. Deviations are not acceptable.

4. Bare-Metal Installation

We do NOT use Docker for our installation scripts. All applications are installed directly on the system.


📁 Script Types and Their Structure

CT Script (ct/AppName.sh)

#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: AuthorName (GitHubUsername)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://application-url.com

APP="AppName"
var_tags="${var_tags:-tag1;tag2;tag3}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"

header_info "$APP"
variables
color
catch_errors

function update_script() {
  header_info
  check_container_storage
  check_container_resources

  if [[ ! -d /opt/appname ]]; then
    msg_error "No ${APP} Installation Found!"
    exit
  fi

  if check_for_gh_release "appname" "owner/repo"; then
    msg_info "Stopping Service"
    systemctl stop appname
    msg_ok "Stopped Service"

    msg_info "Backing up Data"
    cp -r /opt/appname/data /opt/appname_data_backup
    msg_ok "Backed up Data"

    CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo"

    # Build steps...

    msg_info "Restoring Data"
    cp -r /opt/appname_data_backup/. /opt/appname/data
    rm -rf /opt/appname_data_backup
    msg_ok "Restored Data"

    msg_info "Starting Service"
    systemctl start appname
    msg_ok "Started Service"
    msg_ok "Updated successfully!"
  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}:PORT${CL}"

Install Script (install/AppName-install.sh)

#!/usr/bin/env bash

# Copyright (c) 2021-2026 community-scripts ORG
# Author: AuthorName (GitHubUsername)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://application-url.com

source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os

msg_info "Installing Dependencies"
$STD apt-get install -y \
  dependency1 \
  dependency2
msg_ok "Installed Dependencies"

# Runtime Setup (ALWAYS use our functions!)
NODE_VERSION="22" setup_nodejs
# or
PG_VERSION="16" setup_postgresql
# or
setup_uv
# etc.

fetch_and_deploy_gh_release "appname" "owner/repo"

msg_info "Setting up Application"
cd /opt/appname
# Build/Setup Schritte...
msg_ok "Set up Application"

msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/appname.service
[Unit]
Description=AppName Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/appname
ExecStart=/path/to/executable
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now appname
msg_ok "Created Service"

motd_ssh
customize
cleanup_lxc

🔧 Available Helper Functions

Release Management

Function Description Example
fetch_and_deploy_gh_release Fetches and installs GitHub Release fetch_and_deploy_gh_release "app" "owner/repo"
check_for_gh_release Checks for new version if check_for_gh_release "app" "owner/repo"; then

Modes for fetch_and_deploy_gh_release:

# Tarball/Source (Standard)
fetch_and_deploy_gh_release "appname" "owner/repo"

# Binary (.deb)
fetch_and_deploy_gh_release "appname" "owner/repo" "binary"

# Prebuilt Archive
fetch_and_deploy_gh_release "appname" "owner/repo" "prebuild" "latest" "/opt/appname" "filename.tar.gz"

# Single Binary
fetch_and_deploy_gh_release "appname" "owner/repo" "singlefile" "latest" "/opt/appname" "binary-linux-amd64"

Clean Install Flag:

CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo"

Runtime/Language Setup

Function Variable(s) Example
setup_nodejs NODE_VERSION, NODE_MODULE NODE_VERSION="22" setup_nodejs
setup_uv UV_PYTHON UV_PYTHON="3.12" setup_uv
setup_go GO_VERSION GO_VERSION="1.22" setup_go
setup_rust RUST_VERSION, RUST_CRATES RUST_CRATES="monolith" setup_rust
setup_ruby RUBY_VERSION RUBY_VERSION="3.3" setup_ruby
setup_java JAVA_VERSION JAVA_VERSION="21" setup_java
setup_php PHP_VERSION, PHP_MODULES PHP_VERSION="8.3" PHP_MODULES="redis,gd" setup_php

Database Setup

Function Variable(s) Example
setup_postgresql PG_VERSION, PG_MODULES PG_VERSION="16" setup_postgresql
setup_postgresql_db PG_DB_NAME, PG_DB_USER PG_DB_NAME="mydb" PG_DB_USER="myuser" setup_postgresql_db
setup_mariadb_db MARIADB_DB_NAME, MARIADB_DB_USER MARIADB_DB_NAME="mydb" setup_mariadb_db
setup_mysql MYSQL_VERSION setup_mysql
setup_mongodb MONGO_VERSION setup_mongodb
setup_clickhouse - setup_clickhouse

Tools & Utilities

Function Description
setup_adminer Installs Adminer for DB management
setup_composer Install PHP Composer
setup_ffmpeg Install FFmpeg
setup_imagemagick Install ImageMagick
setup_gs Install Ghostscript
setup_hwaccel Configure hardware acceleration

Helper Utilities

Function Description Example
import_local_ip Sets $LOCAL_IP variable import_local_ip
ensure_dependencies Checks/installs dependencies ensure_dependencies curl jq
install_packages_with_retry APT install with retry install_packages_with_retry nginx redis

Anti-Patterns (NEVER use!)

1. Pointless Variables

# ❌ WRONG - unnecessary variables
APP_NAME="myapp"
APP_DIR="/opt/${APP_NAME}"
APP_USER="root"
APP_PORT="3000"
cd $APP_DIR

# ✅ CORRECT - use directly
cd /opt/myapp

2. Custom Download Logic

# ❌ WRONG - custom wget/curl logic
RELEASE=$(curl -s https://api.github.com/repos/owner/repo/releases/latest | jq -r '.tag_name')
wget https://github.com/owner/repo/archive/${RELEASE}.tar.gz
tar -xzf ${RELEASE}.tar.gz
mv repo-${RELEASE} /opt/myapp

# ✅ CORRECT - use our function
fetch_and_deploy_gh_release "myapp" "owner/repo"

3. Custom Version-Check Logic

# ❌ WRONG - custom version check
CURRENT=$(cat /opt/myapp/version.txt)
LATEST=$(curl -s https://api.github.com/repos/owner/repo/releases/latest | jq -r '.tag_name')
if [[ "$CURRENT" != "$LATEST" ]]; then
  # update...
fi

# ✅ CORRECT - use our function
if check_for_gh_release "myapp" "owner/repo"; then
  # update...
fi

4. Docker-based Installation

# ❌ WRONG - using Docker
docker pull myapp/myapp:latest
docker run -d --name myapp myapp/myapp:latest

# ✅ CORRECT - Bare-Metal Installation
fetch_and_deploy_gh_release "myapp" "owner/repo"
npm install && npm run build

5. Custom Runtime Installation

# ❌ WRONG - custom Node.js installation
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs

# ✅ CORRECT - use our function
NODE_VERSION="22" setup_nodejs

6. Redundant echo Statements

# ❌ WRONG - custom logging messages
echo "Installing dependencies..."
apt install -y curl
echo "Done!"

# ✅ CORRECT - use msg_info/msg_ok
msg_info "Installing Dependencies"
$STD apt install -y curl
msg_ok "Installed Dependencies"

7. Missing $STD Usage

# ❌ WRONG - apt without $STD
apt install -y nginx

# ✅ CORRECT - with $STD for silent output
$STD apt install -y nginx

8. Wrapping tools.func Functions in msg Blocks

# ❌ WRONG - tools.func functions have their own msg_info/msg_ok!
msg_info "Installing Node.js"
NODE_VERSION="22" setup_nodejs
msg_ok "Installed Node.js"

msg_info "Updating Application"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo"
msg_ok "Updated Application"

# ✅ CORRECT - call directly without msg wrapper
NODE_VERSION="22" setup_nodejs

CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo"

Functions with built-in messages (NEVER wrap in msg blocks):

  • fetch_and_deploy_gh_release
  • check_for_gh_release
  • setup_nodejs
  • setup_postgresql / setup_postgresql_db
  • setup_mariadb / setup_mariadb_db
  • setup_mongodb
  • setup_mysql
  • setup_ruby
  • setup_go
  • setup_java
  • setup_php
  • setup_uv
  • setup_rust
  • setup_composer
  • setup_ffmpeg
  • setup_imagemagick
  • setup_gs
  • setup_adminer
  • setup_hwaccel

9. Creating Unnecessary System Users

# ❌ WRONG - LXC containers run as root, no separate user needed
useradd -m -s /usr/bin/bash appuser
chown -R appuser:appuser /opt/appname
sudo -u appuser npm install

# ✅ CORRECT - run directly as root
cd /opt/appname
$STD npm install

10. Using export in .env Files

# ❌ WRONG - export is unnecessary in .env files
cat <<EOF >/opt/appname/.env
export DATABASE_URL=postgres://...
export SECRET_KEY=abc123
export NODE_ENV=production
EOF

# ✅ CORRECT - simple KEY=VALUE format (files are sourced with set -a)
cat <<EOF >/opt/appname/.env
DATABASE_URL=postgres://...
SECRET_KEY=abc123
NODE_ENV=production
EOF

11. Using External Shell Scripts

# ❌ WRONG - external script that gets executed
cat <<'EOF' >/opt/appname/install_script.sh
#!/bin/bash
cd /opt/appname
npm install
npm run build
EOF
chmod +x /opt/appname/install_script.sh
$STD bash /opt/appname/install_script.sh
rm -f /opt/appname/install_script.sh

# ✅ CORRECT - run commands directly
cd /opt/appname
$STD npm install
$STD npm run build

12. Using sudo in LXC Containers

# ❌ WRONG - sudo is unnecessary in LXC (already root)
sudo -u postgres psql -c "CREATE DATABASE mydb;"
sudo -u appuser npm install

# ✅ CORRECT - use functions or run directly as root
PG_DB_NAME="mydb" PG_DB_USER="myuser" setup_postgresql_db

cd /opt/appname
$STD npm install

13. Unnecessary systemctl daemon-reload

# ❌ WRONG - daemon-reload is only needed when MODIFYING existing services
cat <<EOF >/etc/systemd/system/appname.service
# ... service config ...
EOF
systemctl daemon-reload  # Unnecessary for new services!
systemctl enable -q --now appname

# ✅ CORRECT - new services don't need daemon-reload
cat <<EOF >/etc/systemd/system/appname.service
# ... service config ...
EOF
systemctl enable -q --now appname

14. Creating Custom Credentials Files

# ❌ WRONG - custom credentials file is not part of the standard template
msg_info "Saving Credentials"
cat <<EOF >~/appname.creds
Database User: ${DB_USER}
Database Pass: ${DB_PASS}
EOF
msg_ok "Saved Credentials"

# ✅ CORRECT - credentials are stored in .env or shown in final message only
# The .env file contains credentials, no need for separate file
# ❌ WRONG - old cleanup pattern with msg blocks
motd_ssh
customize

msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned"

# ✅ CORRECT - use cleanup_lxc function
motd_ssh
customize
cleanup_lxc

16. Manual Database Creation Instead of Functions

# ❌ WRONG - manual database creation
DB_USER="myuser"
DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)
$STD sudo -u postgres psql -c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';"
$STD sudo -u postgres psql -c "CREATE DATABASE mydb WITH OWNER $DB_USER;"
$STD sudo -u postgres psql -d mydb -c "CREATE EXTENSION IF NOT EXISTS postgis;"

# ✅ CORRECT - use setup_postgresql_db function
# This sets PG_DB_USER, PG_DB_PASS, PG_DB_NAME automatically
PG_DB_NAME="mydb" PG_DB_USER="myuser" PG_DB_EXTENSIONS="postgis" setup_postgresql_db

📝 Important Rules

Variable Declarations (CT Script)

# Standard declarations (ALWAYS present)
APP="AppName"
var_tags="${var_tags:-tag1;tag2}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"

Update-Script Pattern

function update_script() {
  header_info
  check_container_storage
  check_container_resources

  # 1. Check if installation exists
  if [[ ! -d /opt/appname ]]; then
    msg_error "No ${APP} Installation Found!"
    exit
  fi

  # 2. Check for update
  if check_for_gh_release "appname" "owner/repo"; then
    # 3. Stop service
    msg_info "Stopping Service"
    systemctl stop appname
    msg_ok "Stopped Service"

    # 4. Backup data (if present)
    msg_info "Backing up Data"
    cp -r /opt/appname/data /opt/appname_data_backup
    msg_ok "Backed up Data"

    # 5. Perform clean install
    CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo"

    # 6. Rebuild (if needed)
    cd /opt/appname
    $STD npm install
    $STD npm run build

    # 7. Restore data
    msg_info "Restoring Data"
    cp -r /opt/appname_data_backup/. /opt/appname/data
    rm -rf /opt/appname_data_backup
    msg_ok "Restored Data"

    # 8. Start service
    msg_info "Starting Service"
    systemctl start appname
    msg_ok "Started Service"
    msg_ok "Updated successfully!"
  fi
  exit  # IMPORTANT: Always end with exit!
}

Systemd Service Pattern

msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/appname.service
[Unit]
Description=AppName Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/appname
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /opt/appname/server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now appname
msg_ok "Created Service"
# ALWAYS at the end of the install script:
motd_ssh
customize
cleanup_lxc

🔍 Checklist Before PR Creation

  • No Docker installation used
  • fetch_and_deploy_gh_release used for GitHub releases
  • check_for_gh_release used for update checks
  • setup_* functions used for runtimes (nodejs, postgresql, etc.)
  • tools.func functions NOT wrapped in msg_info/msg_ok blocks
  • No redundant variables
  • $STD before all apt/npm/build commands
  • msg_info/msg_ok/msg_error for logging (only for custom code)
  • Correct script structure followed
  • Update function present and functional
  • Data backup implemented in update function
  • motd_ssh, customize, cleanup_lxc at the end
  • No custom download/version-check logic
  • JSON metadata file created in frontend/public/json/<appname>.json

📖 Reference: Good Example (Termix)

CT Script: ct/termix.sh

  • Uses check_for_gh_release for version checking
  • Uses CLEAN_INSTALL=1 fetch_and_deploy_gh_release for clean updates
  • Backup/restore of /opt/termix/data
  • Correct structure with all required variables

Install Script: install/termix-install.sh

  • NODE_VERSION="22" setup_nodejs instead of manual installation
  • fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix" instead of wget/curl
  • Clean service configuration
  • Correct footer with motd_ssh, customize, cleanup_lxc

<EFBFBD> JSON Metadata Files

Every application requires a JSON metadata file in frontend/public/json/<appname>.json.

JSON Structure

{
    "name": "AppName",
    "slug": "appname",
    "categories": [1],
    "date_created": "2026-01-16",
    "type": "ct",
    "updateable": true,
    "privileged": false,
    "interface_port": 3000,
    "documentation": "https://docs.appname.com/",
    "website": "https://appname.com/",
    "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/appname.webp",
    "config_path": "/opt/appname/.env",
    "description": "Short description of the application and its purpose.",
    "install_methods": [
        {
            "type": "default",
            "script": "ct/appname.sh",
            "resources": {
                "cpu": 2,
                "ram": 2048,
                "hdd": 8,
                "os": "Debian",
                "version": "13"
            }
        }
    ],
    "default_credentials": {
        "username": null,
        "password": null
    },
    "notes": []
}

Required Fields

Field Type Description
name string Display name of the application
slug string Lowercase, no spaces, used for filenames
categories array Category ID(s) - see category list below
date_created string Creation date (YYYY-MM-DD)
type string ct for container, vm for virtual machine
updateable boolean Whether update_script is implemented
privileged boolean Whether container needs privileged mode
interface_port number Primary web interface port (or null)
documentation string Link to official docs
website string Link to official website
logo string URL to application logo (preferably selfhst icons)
config_path string Path to main config file (or empty string)
description string Brief description of the application
install_methods array Installation configurations
default_credentials object Default username/password (or null)
notes array Additional notes/warnings

Categories

ID Category
0 Miscellaneous
1 Proxmox & Virtualization
2 Operating Systems
3 Containers & Docker
4 Network & Firewall
5 Adblock & DNS
6 Authentication & Security
7 Backup & Recovery
8 Databases
9 Monitoring & Analytics
10 Dashboards & Frontends
11 Files & Downloads
12 Documents & Notes
13 Media & Streaming
14 *Arr Suite
15 NVR & Cameras
16 IoT & Smart Home
17 ZigBee, Z-Wave & Matter
18 MQTT & Messaging
19 Automation & Scheduling
20 AI / Coding & Dev-Tools
21 Webservers & Proxies
22 Bots & ChatOps
23 Finance & Budgeting
24 Gaming & Leisure
25 Business & ERP

Notes Format

"notes": [
    {
        "text": "Change the default password after first login!",
        "type": "warning"
    },
    {
        "text": "Requires at least 4GB RAM for optimal performance.",
        "type": "info"
    }
]

Note types: info, warning, error

Examples with Credentials

"default_credentials": {
    "username": "admin",
    "password": "admin"
}

Or no credentials:

"default_credentials": {
    "username": null,
    "password": null
}

<EFBFBD>💡 Tips for AI Assistants

  1. Search tools.func first before implementing custom solutions
  2. Use existing scripts as reference (e.g., linkwarden-install.sh, homarr-install.sh)
  3. Ask when uncertain instead of introducing wrong patterns
  4. Consistency > Creativity - follow established patterns
  5. Test local variables - use ${VAR:-default} pattern for optional values

📚 Further Documentation