# 🤖 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`)

```bash
#!/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" "tarball"

    # 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`)

```bash
#!/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 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" "tarball"

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" "tarball"` |
| `check_for_gh_release` | Checks for new version | `if check_for_gh_release "app" "owner/repo"; then` |
| `get_latest_github_release` | Returns latest release version string | `VERSION=$(get_latest_github_release "owner/repo")` |

**Modes for `fetch_and_deploy_gh_release`:**
```bash
# Tarball/Source (Standard) - always specify "tarball" explicitly
fetch_and_deploy_gh_release "appname" "owner/repo" "tarball"

# 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:**
```bash
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "appname" "owner/repo" "tarball"
```

**Version file:** After `fetch_and_deploy_gh_release`, the deployed version is stored in `~/.appname`. You can read it with `cat ~/.appname` — useful when you need the version later (e.g. for build-time environment variables).

### 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/Variable | Description | Example |
|-------------------|-------------|----------|
| `$LOCAL_IP` | Always available - contains the container's IP address | `echo "Access: http://${LOCAL_IP}:3000"` |
| `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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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
```bash
# ❌ 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`
```bash
# ❌ 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
```bash
# ❌ 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
```

### 15. Wrong Footer Pattern
```bash
# ❌ 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
```bash
# ❌ 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
```

### 18. Hardcoded Versions for External Tools
```bash
# ❌ WRONG - hardcoded versions that will become outdated
RESTIC_VERSION="0.18.1"
RCLONE_VERSION="1.73.0"
curl -L -o restic.bz2 "https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2"

# ✅ CORRECT - use fetch_and_deploy_gh_release (always fetches latest)
fetch_and_deploy_gh_release "restic" "restic/restic" "singlefile" "latest" "/usr/local/bin" "restic_*_linux_amd64.bz2"

# If you need the version number later, read from the version file:
RES_VERSION=$(cat ~/.restic)
# Or use get_latest_github_release:
VERSION=$(get_latest_github_release "restic/restic")
```

### 19. Backing Up to /tmp in Update Scripts
```bash
# ❌ WRONG - /tmp can be cleared by the system
msg_info "Backing up Configuration"
cp /opt/appname/.env /tmp/appname.env.bak
msg_ok "Backed up Configuration"
# ... update ...
cp /tmp/appname.env.bak /opt/appname/.env

# ✅ CORRECT - back up directly into /opt
msg_info "Backing up Configuration"
cp /opt/appname/.env /opt/appname.env.bak
msg_ok "Backed up Configuration"
# ... update ...
cp /opt/appname.env.bak /opt/appname/.env
rm -f /opt/appname.env.bak
```

### 20. Using "(Patience)" in msg_info by Default
```bash
# ❌ WRONG - "(Patience)" should not be a default label
msg_info "Building Application (Patience)"
$STD npm run build
msg_ok "Built Application"

# ✅ CORRECT - use a plain label; only add (Patience) if the build truly takes 10+ minutes
msg_info "Building Application"
$STD npm run build
msg_ok "Built Application"
```

### 21. Writing Files Without Heredocs
```bash
# ❌ WRONG - echo / printf / tee
echo "# Config" > /opt/app/config.yml
echo "port: 3000" >> /opt/app/config.yml

printf "# Config\nport: 3000\n" > /opt/app/config.yml
cat config.yml | tee /opt/app/config.yml
```

```bash
# ✅ CORRECT - always use a single heredoc
cat <<EOF >/opt/app/config.yml
# Config
port: 3000
EOF
```

### 22. Using `apt-get` Instead of `apt`
```bash
# ❌ WRONG - apt-get is not the project convention
$STD apt-get install -y nginx
$STD apt-get update

# ✅ CORRECT - always use apt (consistent with tools.func)
$STD apt install -y nginx
$STD apt update
```

### 23. Listing Core/Pre-installed Packages as Dependencies
```bash
# ❌ WRONG - curl is already installed by _bootstrap() in install.func
# sudo is already available in LXC, mc is not a dependency
msg_info "Installing Dependencies"
$STD apt install -y \
  curl \
  sudo \
  mc \
  fuse3
msg_ok "Installed Dependencies"

# ✅ CORRECT - only list packages that are actually needed by the application
msg_info "Installing Dependencies"
$STD apt install -y fuse3
msg_ok "Installed Dependencies"
```

**Packages that must NOT be listed as dependencies (already available):**
- `curl` — installed by `_bootstrap()` in `install.func`
- `sudo` — base LXC package (and scripts run as root anyway)
- `wget` — base Debian LXC package
- `gnupg` / `gpg` — base Debian package
- `ca-certificates` — base Debian package
- `apt-transport-https` — obsolete on Debian 12+
- `jq` — auto-installed by `ensure_dependencies` in `tools.func` when needed
- `mc` — not a dependency, personal preference tool

**When to omit the dependency block entirely:** If the app only needs packages provided by `setup_*` helpers (e.g., Node.js, PostgreSQL, Go) or is a prebuilt binary with no native deps, skip the "Installing Dependencies" block completely.

---

## 📝 Important Rules

### Variable Declarations (CT Script)
```bash
# 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
```bash
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" "tarball"

    # 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
```bash
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"
```

### Installation Script Footer
```bash
# 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 (with explicit mode like `"tarball"`)
- [ ] `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
- [ ] No hardcoded versions for external tools (use `fetch_and_deploy_gh_release` or `get_latest_github_release`)
- [ ] `$STD` before all apt/npm/build commands
- [ ] `apt` used (NOT `apt-get`) — consistent with `tools.func`
- [ ] No core packages listed as dependencies (`curl`, `sudo`, `wget`, `jq`, `mc` are pre-installed)
- [ ] `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 (backups go to `/opt`, NOT `/tmp`)
- [ ] `motd_ssh`, `customize`, `cleanup_lxc` at the end
- [ ] No custom download/version-check logic
- [ ] No default `(Patience)` text in msg_info labels
- [ ] JSON metadata file created in `frontend/public/json/<appname>.json`

---

## 📖 Reference: Good Example (Termix)

### CT Script: [ct/termix.sh](../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](../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`

---

## � JSON Metadata Files

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

### JSON Structure

```json
{
    "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

```json
"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

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

Or no credentials:
```json
"default_credentials": {
    "username": null,
    "password": null
}
```

---

## �💡 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

- [CONTRIBUTING.md](contribution/CONTRIBUTING.md) - General contribution guidelines
- [GUIDE.md](contribution/GUIDE.md) - Detailed developer documentation
- [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md) - Technical details
- [EXIT_CODES.md](EXIT_CODES.md) - Exit code reference
