diff --git a/AppImage/scripts/oci/description_templates.py b/AppImage/scripts/oci/description_templates.py
new file mode 100644
index 00000000..c340d808
--- /dev/null
+++ b/AppImage/scripts/oci/description_templates.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+"""
+ProxMenux - HTML Description Templates for OCI Containers
+==========================================================
+Generates beautiful HTML descriptions for the Proxmox Notes panel.
+Can be used from both Python (oci_manager.py) and bash scripts.
+
+Usage from bash:
+ python3 description_templates.py --app-id "secure-gateway" --hostname "my-gateway"
+
+Usage from Python:
+ from description_templates import generate_description
+ html = generate_description(app_def, container_def, hostname)
+"""
+
+import sys
+import json
+import argparse
+import urllib.parse
+from pathlib import Path
+from typing import Dict, Optional
+
+# Default paths
+CATALOG_PATH = Path(__file__).parent / "catalog.json"
+
+
+def get_shield_icon_svg(color: str = "#0EA5E9") -> str:
+ """Generate a shield icon SVG with checkmark."""
+ return f""""""
+
+
+def get_default_icon_svg(color: str = "#0EA5E9") -> str:
+ """Generate a default container icon SVG."""
+ return f""""""
+
+
+# Pre-defined icon types
+ICON_TYPES = {
+ "shield": get_shield_icon_svg,
+ "container": get_default_icon_svg,
+ "default": get_default_icon_svg,
+}
+
+
+def generate_description(
+ app_def: Dict,
+ container_def: Optional[Dict] = None,
+ hostname: str = "",
+ extra_info: str = ""
+) -> str:
+ """
+ Generate HTML description for Proxmox Notes panel.
+
+ Args:
+ app_def: Application definition from catalog
+ container_def: Container definition (optional)
+ hostname: Container hostname
+ extra_info: Additional info to display (e.g., disk info)
+
+ Returns:
+ HTML string for the description
+ """
+ # Extract app info
+ app_name = app_def.get("name", "ProxMenux App")
+ app_subtitle = app_def.get("subtitle", "")
+ app_color = app_def.get("color", "#0EA5E9")
+ app_icon_type = app_def.get("icon_type", "default")
+ doc_url = app_def.get("documentation_url", "https://macrimi.github.io/ProxMenux/")
+ code_url = app_def.get("code_url", "https://github.com/MacRimi/ProxMenux")
+ installer_url = app_def.get("installer_url", "")
+ kofi_url = "https://ko-fi.com/macrimi"
+
+ # Get the icon SVG
+ icon_func = ICON_TYPES.get(app_icon_type, ICON_TYPES["default"])
+ icon_svg = icon_func(app_color)
+ icon_data = "data:image/svg+xml," + urllib.parse.quote(icon_svg)
+
+ # Build badge buttons
+ badges = []
+ badges.append(f"")
+ badges.append(f"")
+
+ if installer_url:
+ badges.append(f"")
+
+ badges.append(f"")
+
+ badges_html = "\n".join(badges)
+
+ # Build footer info
+ footer_parts = []
+ if hostname:
+ footer_parts.append(f"Hostname: {hostname}")
+ if extra_info:
+ footer_parts.append(extra_info)
+ footer_html = " ".join(footer_parts) if footer_parts else ""
+
+ # Build the complete HTML
+ html = f"""
+
+
+
+
+
+
+
{app_name}
+
Created with ProxMenuX
+
+
+
+
+
+
+
+
+
+
+
+{app_name}
+{app_subtitle}
+
+
+
+
+
+
+{badges_html}
+
+"""
+
+ if footer_html:
+ html += f"""
+
+{footer_html}
+
+"""
+
+ html += "
"
+
+ return html
+
+
+def generate_vm_description(
+ vm_name: str,
+ vm_version: str = "",
+ doc_url: str = "",
+ code_url: str = "",
+ installer_url: str = "",
+ extra_info: str = "",
+ icon_url: str = ""
+) -> str:
+ """
+ Generate HTML description for VMs (like ZimaOS).
+
+ Args:
+ vm_name: Name of the VM
+ vm_version: Version string
+ doc_url: Documentation URL
+ code_url: Code repository URL
+ installer_url: Installer URL
+ extra_info: Additional info (e.g., disk info)
+ icon_url: Custom icon URL for the VM
+
+ Returns:
+ HTML string for the description
+ """
+ # Build badge buttons
+ badges = []
+ if doc_url:
+ badges.append(f"")
+ if code_url:
+ badges.append(f"")
+ if installer_url:
+ badges.append(f"")
+ badges.append("")
+
+ badges_html = "\n".join(badges)
+
+ # Version line
+ version_html = f"
{vm_version}
" if vm_version else ""
+
+ # Extra info
+ extra_html = f"
{extra_info}
" if extra_info else ""
+
+ html = f"""
+
+
+
+
+
+
+
{vm_name}
+
Created with ProxMenuX
+{version_html}
+
+
+
+
+
+{badges_html}
+
+
+{extra_html}
+
"""
+
+ return html
+
+
+def load_catalog() -> Dict:
+ """Load the OCI catalog."""
+ if CATALOG_PATH.exists():
+ with open(CATALOG_PATH) as f:
+ return json.load(f)
+ return {"apps": {}}
+
+
+def main():
+ """CLI interface for generating descriptions."""
+ parser = argparse.ArgumentParser(description="Generate HTML descriptions for Proxmox")
+ parser.add_argument("--app-id", help="Application ID from catalog")
+ parser.add_argument("--hostname", default="", help="Container hostname")
+ parser.add_argument("--extra-info", default="", help="Additional info to display")
+ parser.add_argument("--output", choices=["html", "encoded"], default="html",
+ help="Output format: html or url-encoded")
+
+ # For VM descriptions (not from catalog)
+ parser.add_argument("--vm-name", help="VM name (for non-catalog VMs)")
+ parser.add_argument("--vm-version", default="", help="VM version")
+ parser.add_argument("--doc-url", default="", help="Documentation URL")
+ parser.add_argument("--code-url", default="", help="Code repository URL")
+ parser.add_argument("--installer-url", default="", help="Installer URL")
+
+ args = parser.parse_args()
+
+ if args.app_id:
+ # Generate from catalog
+ catalog = load_catalog()
+ apps = catalog.get("apps", {})
+
+ if args.app_id not in apps:
+ print(f"Error: App '{args.app_id}' not found in catalog", file=sys.stderr)
+ sys.exit(1)
+
+ app_def = apps[args.app_id]
+ html = generate_description(app_def, hostname=args.hostname, extra_info=args.extra_info)
+
+ elif args.vm_name:
+ # Generate for VM
+ html = generate_vm_description(
+ vm_name=args.vm_name,
+ vm_version=args.vm_version,
+ doc_url=args.doc_url,
+ code_url=args.code_url,
+ installer_url=args.installer_url,
+ extra_info=args.extra_info
+ )
+ else:
+ parser.print_help()
+ sys.exit(1)
+
+ if args.output == "encoded":
+ print(urllib.parse.quote(html))
+ else:
+ print(html)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/AppImage/scripts/oci_manager.py b/AppImage/scripts/oci_manager.py
index a6767e36..b8fc3ff7 100644
--- a/AppImage/scripts/oci_manager.py
+++ b/AppImage/scripts/oci_manager.py
@@ -773,6 +773,15 @@ def deploy_app(app_id: str, config: Dict[str, Any], installed_by: str = "web") -
except Exception as e:
logger.warning(f"Could not apply extra LXC config: {e}")
+ # Step 3.1: Set HTML description for Proxmox Notes panel
+ html_desc = _generate_html_description(app_def, container_def, hostname)
+ if html_desc:
+ rc, _, err = _run_pve_cmd(["pct", "set", str(vmid), "-description", html_desc])
+ if rc == 0:
+ logger.info(f"Set HTML description for container {vmid}")
+ else:
+ logger.warning(f"Could not set description: {err}")
+
# Step 4: Configure environment variables (only for OCI containers)
if use_oci:
env_vars = []
@@ -859,6 +868,94 @@ def deploy_app(app_id: str, config: Dict[str, Any], installed_by: str = "web") -
return result
+def _generate_html_description(app_def: Dict, container_def: Dict, hostname: str) -> str:
+ """
+ Generate HTML description for Proxmox Notes panel.
+
+ Tries to use the shared description_templates module if available,
+ otherwise falls back to inline implementation.
+ """
+ try:
+ # Try to import the shared templates module
+ import sys
+ templates_path = Path("/usr/local/share/proxmenux/scripts/oci")
+ if templates_path.exists():
+ sys.path.insert(0, str(templates_path))
+
+ from description_templates import generate_description
+ return generate_description(app_def, container_def, hostname)
+ except ImportError:
+ pass
+
+ # Fallback: inline implementation
+ import urllib.parse
+
+ app_name = app_def.get("name", "ProxMenux App")
+ app_subtitle = app_def.get("subtitle", app_def.get("short_name", ""))
+ app_color = app_def.get("color", "#0EA5E9")
+ app_icon_type = app_def.get("icon_type", "default")
+ doc_url = app_def.get("documentation_url", "https://macrimi.github.io/ProxMenux/")
+ code_url = app_def.get("code_url", "https://github.com/MacRimi/ProxMenux")
+ installer_url = app_def.get("installer_url", "")
+
+ # Generate icon based on type
+ if app_icon_type == "shield":
+ icon_svg = f""""""
+ else:
+ icon_svg = f""""""
+
+ icon_data = "data:image/svg+xml," + urllib.parse.quote(icon_svg)
+
+ # Build badges
+ badges = [
+ f"",
+ f"",
+ ]
+ if installer_url:
+ badges.append(f"")
+ badges.append("")
+
+ badges_html = "\n".join(badges)
+
+ html_desc = f"""
+
+
+
+
+
+
+
{app_name}
+
Created with ProxMenuX
+
+
+
+
+
+
+
+
+
+
+
+{app_name}
+{app_subtitle}
+
+
+
+
+
+
+{badges_html}
+
+
+
+Hostname: {hostname}
+
+
"""
+
+ return html_desc
+
+
def _enable_host_ip_forwarding() -> bool:
"""Enable IP forwarding on the Proxmox host."""
logger.info("Enabling IP forwarding on host")