mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
Merge pull request #555 from jcastro/feature/oci-teslamate
feat(oci): add TeslaMate stack
This commit is contained in:
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "Zeitüberschreitung beim ordnungsgemäßen Herunterfahren.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Grafana-Administrator",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana ist ein vollständiger Observability Stack, mit dem Sie Metriken, Protokolle und Traces überwachen und analysieren können. Es ermöglicht Ihnen, Ihre Daten abzufragen, zu visualisieren, zu warnen und zu verstehen, unabhängig davon, wo sie gespeichert sind.",
|
||||
"Grafana web interface": "Grafana Web-Schnittstelle",
|
||||
"Graph database for native graph queries and GraphQL.": "Graphdatenbank für native Graphabfragen und GraphQL.",
|
||||
@@ -6777,6 +6778,7 @@
|
||||
"Temporary working directory (if present):": "Temporäres Arbeitsverzeichnis (falls vorhanden):",
|
||||
"Terminal Multiplexers": "Terminal-Multiplexer",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Terminal-Multiplexer (Strg+B, dann D zum Trennen oder Exit eingeben)",
|
||||
"Tesla data logger with Grafana dashboards": "Tesla-Datenlogger mit Grafana-Dashboards",
|
||||
"Test Connectivity": "Konnektivität testen",
|
||||
"Test NFS Connectivity": "Testen Sie die NFS-Konnektivität",
|
||||
"Test Samba Connectivity": "Testen Sie die Samba-Konnektivität",
|
||||
|
||||
@@ -2896,6 +2896,7 @@
|
||||
"Graceful shutdown timed out.": "Se agotó el tiempo de cierre elegante.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Administrador de Grafana",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana es un conjunto completo de observabilidad que permite monitorizar y analizar métricas, logs y trazas. Le permite consultar, visualizar, alertar y comprender sus datos sin importar dónde se almacena.",
|
||||
"Grafana web interface": "Interfaz web de Grafana",
|
||||
"Graph database for native graph queries and GraphQL.": "Base de datos de grafos para consultas nativas de grafos y GraphQL.",
|
||||
@@ -6762,6 +6763,7 @@
|
||||
"Temporary working directory (if present):": "Directorio de trabajo temporal (si está presente):",
|
||||
"Terminal Multiplexers": "Multiplexores de terminales",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Multiplexor de terminal (Ctrl+b y luego d para desconectar, o escribe exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Registro de datos de Tesla con paneles de Grafana",
|
||||
"Test Connectivity": "Prueba de conectividad",
|
||||
"Test NFS Connectivity": "Probar la conectividad NFS",
|
||||
"Test Samba Connectivity": "Probar la conectividad Samba",
|
||||
|
||||
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "L'arrêt progressif a expiré.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Administrateur Grafana",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana est une pile d'observation complète qui vous permet de surveiller et d'analyser les métriques, les journaux et les traces. Il vous permet d'interroger, de visualiser, d'alerter et de comprendre vos données, où qu'elles soient stockées.",
|
||||
"Grafana web interface": "Interface web Grafana",
|
||||
"Graph database for native graph queries and GraphQL.": "Base de données graphe pour les requêtes natives sur les graphes et GraphQL.",
|
||||
@@ -6777,6 +6778,7 @@
|
||||
"Temporary working directory (if present):": "Répertoire de travail temporaire (si présent) :",
|
||||
"Terminal Multiplexers": "Multiplexeurs de terminaux",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Multiplexeur de terminal (Ctrl+b puis d pour détacher, ou taper exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Enregistrement des données Tesla avec des tableaux de bord Grafana",
|
||||
"Test Connectivity": "Tester la connectivité",
|
||||
"Test NFS Connectivity": "Tester la connectivité NFS",
|
||||
"Test Samba Connectivity": "Tester la connectivité Samba",
|
||||
|
||||
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "Tempo di attesa per lo spegnimento ordinato scaduto.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Amministratore Grafana",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana è uno stack completo di osservabilità che consente di monitorare e analizzare metriche, log e tracce. Permette di interrogare e visualizzare i dati, impostare avvisi e comprenderli indipendentemente da dove siano archiviati.",
|
||||
"Grafana web interface": "Interfaccia web Grafana",
|
||||
"Graph database for native graph queries and GraphQL.": "Database a grafo per query native sui grafi e GraphQL.",
|
||||
@@ -6777,6 +6778,7 @@
|
||||
"Temporary working directory (if present):": "Directory di lavoro temporanea (se presente):",
|
||||
"Terminal Multiplexers": "Multiplexer terminali",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Multiplexer terminale (Ctrl+b poi d per scollegare o digitare exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Registrazione dei dati Tesla con dashboard Grafana",
|
||||
"Test Connectivity": "Test connettività",
|
||||
"Test NFS Connectivity": "Test connettività NFS",
|
||||
"Test Samba Connectivity": "Test connettività Samba",
|
||||
|
||||
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "O desligamento normal expirou.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Administrador do Grafana",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana é uma pilha de observação completa que permite monitorar e analisar métricas, registros e traços. Ele permite que você consulte, visualize, alerte e entenda seus dados, não importa onde esteja armazenado.",
|
||||
"Grafana web interface": "Interface Web Grafana",
|
||||
"Graph database for native graph queries and GraphQL.": "Banco de dados de grafos para consultas nativas de grafos e GraphQL.",
|
||||
@@ -6777,6 +6778,7 @@
|
||||
"Temporary working directory (if present):": "Diretório de trabalho temporário (se presente):",
|
||||
"Terminal Multiplexers": "Multiplexadores de terminais",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Multiplexador de terminal (Ctrl+b e depois d para desconectar ou digite exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Registo de dados do Tesla com painéis do Grafana",
|
||||
"Test Connectivity": "Testar conectividade",
|
||||
"Test NFS Connectivity": "Teste a conectividade NFS",
|
||||
"Test Samba Connectivity": "Teste a conectividade do Samba",
|
||||
|
||||
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "Korektné vypnutie nestihlo dobehnúť.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Správca Grafany",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana je komplexná platforma na monitorovanie a analýzu metrík, denníkov a trasovaní. Umožňuje údaje vyhľadávať, vizualizovať, nastavovať upozornenia a porozumieť im bez ohľadu na miesto uloženia.",
|
||||
"Grafana web interface": "Webové rozhranie Grafana",
|
||||
"Graph database for native graph queries and GraphQL.": "Grafová databáza na natívne grafové dopyty a GraphQL.",
|
||||
@@ -6779,6 +6780,7 @@
|
||||
"Temporary working directory (if present):": "Dočasný pracovný priečinok (ak existuje):",
|
||||
"Terminal Multiplexers": "Terminálové multiplexery",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Terminálový multiplexer (Ctrl+b potom d na odpojenie, alebo napíšte exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Záznam údajov vozidiel Tesla s prehľadmi v Grafane",
|
||||
"Test Connectivity": "Otestovať pripojenie",
|
||||
"Test NFS Connectivity": "Otestovať NFS pripojenie",
|
||||
"Test Samba Connectivity": "Otestovať Samba pripojenie",
|
||||
|
||||
@@ -2899,6 +2899,7 @@
|
||||
"Graceful shutdown timed out.": "Graciös avstängning tog timeout.",
|
||||
"Grafana Loki": "Grafana Loki",
|
||||
"Grafana Pyroscope": "Grafana Pyroscope",
|
||||
"Grafana administrator": "Grafana-administratör",
|
||||
"Grafana is a complete observability stack that allows you to monitor and analyze metrics, logs and traces. It allows you to query, visualize, alert on and understand your data no matter where it is stored.": "Grafana är en komplett observerbarhetsstack som låter dig övervaka och analysera mätvärden, loggar och spår. Det låter dig fråga, visualisera, varna och förstå dina data oavsett var den lagras.",
|
||||
"Grafana web interface": "Grafana webbgränssnitt",
|
||||
"Graph database for native graph queries and GraphQL.": "Grafdatabas för inbyggda graf-frågor och GraphQL.",
|
||||
@@ -6777,6 +6778,7 @@
|
||||
"Temporary working directory (if present):": "Tillfällig arbetskatalog (om sådan finns):",
|
||||
"Terminal Multiplexers": "Terminal multiplexorer",
|
||||
"Terminal multiplexer (Ctrl+b then d to detach, or type exit)": "Terminalmultiplexer (Ctrl+b sedan d för att koppla bort, eller skriv exit)",
|
||||
"Tesla data logger with Grafana dashboards": "Loggning av Tesla-data med Grafana-paneler",
|
||||
"Test Connectivity": "Testa anslutning",
|
||||
"Test NFS Connectivity": "Testa NFS-anslutning",
|
||||
"Test Samba Connectivity": "Testa Samba Connectivity",
|
||||
|
||||
+2
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
<!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. -->
|
||||
|
||||
43 of 529 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 2 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
|
||||
44 of 530 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
|
||||
|
||||
Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it.
|
||||
|
||||
@@ -26,6 +26,7 @@ Questions and ideas about OCI testing go in [discussion #440](https://github.com
|
||||
|---|---|---|---|---|---|
|
||||
| Glances | [@Vaso73](https://github.com/Vaso73) | 2026-09-27 | — | — | [Report](https://github.com/MacRimi/ProxMenux/discussions/392#discussioncomment-18623868) |
|
||||
| PocketBase | [@Vaso73](https://github.com/Vaso73) | 2026-09-27 | — | — | [Report](https://github.com/MacRimi/ProxMenux/discussions/392#discussioncomment-18623868) |
|
||||
| TeslaMate | [@jcastro](https://github.com/jcastro) | 2026-10-29 | `c85b3d447502` | MS03/amd64: default and advanced install, browser login, volumes/restart, Modify, 4.3 to 4.4 update, controlled rollback, native backup/restore to new VMIDs and removal. No vehicle tokens used. | — |
|
||||
|
||||
## Tested in the ProxMenux lab
|
||||
|
||||
|
||||
@@ -0,0 +1,690 @@
|
||||
{
|
||||
"schema_version": "0.5.0",
|
||||
"kind": "proxmenux.oci-template",
|
||||
"id": "image-teslamate",
|
||||
"status": "generated-unvalidated",
|
||||
"catalog_ui": {
|
||||
"title": {
|
||||
"en_US": "TeslaMate"
|
||||
},
|
||||
"tagline": {
|
||||
"en_US": "Tesla data logger with Grafana dashboards"
|
||||
},
|
||||
"description": {
|
||||
"en_US": "TeslaMate with PostgreSQL, Grafana and a private Mosquitto broker. Runs as four native OCI LXCs without Docker Engine."
|
||||
},
|
||||
"category": "monitoring",
|
||||
"category_label": "Monitoring & Analytics",
|
||||
"author": "TeslaMate",
|
||||
"developer": "TeslaMate",
|
||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/teslamate.webp",
|
||||
"thumbnail": null,
|
||||
"screenshots": [],
|
||||
"architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
],
|
||||
"launch": {
|
||||
"scheme": "http",
|
||||
"port": 4000,
|
||||
"path": "/"
|
||||
},
|
||||
"website": "https://teslamate.org",
|
||||
"documentation": "https://docs.teslamate.org/docs/installation/docker/",
|
||||
"repository": "https://github.com/teslamate-org/teslamate",
|
||||
"tips": [],
|
||||
"mini_changelog": [],
|
||||
"display_version": null,
|
||||
"updated_at": "2026-10-29"
|
||||
},
|
||||
"source": {
|
||||
"provider": "teslamate-org",
|
||||
"repository": "https://github.com/teslamate-org/teslamate",
|
||||
"revision": "012892b45e2ab159d3ae1eb1dc9bf85ced0eadfa20ca478821ab3cae0cef8f94",
|
||||
"image_repository_url": "https://hub.docker.com/r/teslamate/teslamate",
|
||||
"readme_pushed_at": "",
|
||||
"compose_sha256": "012892b45e2ab159d3ae1eb1dc9bf85ced0eadfa20ca478821ab3cae0cef8f94",
|
||||
"generated_at": "2026-10-28T23:42:45+00:00"
|
||||
},
|
||||
"container_contract": {
|
||||
"service_name": "teslamate",
|
||||
"container_name": "teslamate",
|
||||
"image": {
|
||||
"reference": "teslamate/teslamate:latest",
|
||||
"registry": "docker.io",
|
||||
"repository": "teslamate/teslamate",
|
||||
"tag": "latest",
|
||||
"digest": null,
|
||||
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
|
||||
},
|
||||
"environment": [
|
||||
{
|
||||
"name": "DATABASE_HOST",
|
||||
"example": "database",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_NAME",
|
||||
"example": "teslamate",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_PASS",
|
||||
"example": "${GENERATED_GENERATED_DB_PASSWORD}",
|
||||
"required": true,
|
||||
"sensitive": true,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_USER",
|
||||
"example": "teslamate",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "ENCRYPTION_KEY",
|
||||
"example": "${GENERATED_GENERATED_ENCRYPTION_KEY}",
|
||||
"required": true,
|
||||
"sensitive": true,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "MQTT_HOST",
|
||||
"example": "mosquitto",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"id": "volume-0",
|
||||
"container_path": "/opt/app/import",
|
||||
"compose_source_example": "import",
|
||||
"read_only": false,
|
||||
"required": true,
|
||||
"installation_choice": [
|
||||
"managed-volume",
|
||||
"host-bind"
|
||||
],
|
||||
"default": "managed-volume",
|
||||
"managed_volume": {
|
||||
"backup": true,
|
||||
"default_size_gb": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"ports": [
|
||||
{
|
||||
"container_port": 4000,
|
||||
"published_example": 4000,
|
||||
"protocol": "tcp",
|
||||
"required": true,
|
||||
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
|
||||
}
|
||||
],
|
||||
"related_services": [
|
||||
{
|
||||
"name": "database",
|
||||
"image": "postgres:18-trixie"
|
||||
},
|
||||
{
|
||||
"name": "grafana",
|
||||
"image": "teslamate/grafana:latest"
|
||||
},
|
||||
{
|
||||
"name": "mosquitto",
|
||||
"image": "eclipse-mosquitto:2"
|
||||
}
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"stop_grace_period": null,
|
||||
"original_compose": "name: teslamate\nservices:\n database:\n environment:\n POSTGRES_DB: teslamate\n POSTGRES_PASSWORD: ${GENERATED_DB_PASSWORD}\n POSTGRES_USER: teslamate\n healthcheck:\n interval: 5s\n retries: 30\n test:\n - CMD\n - pg_isready\n - -U\n - teslamate\n - -d\n - teslamate\n timeout: 5s\n image: postgres:18-trixie\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql\n grafana:\n depends_on:\n database:\n condition: service_healthy\n entrypoint:\n - /bin/sh\n - -c\n - mkdir -p \"$GF_PATHS_DATA\" && exec /run.sh \"$@\"\n - --\n environment:\n DATABASE_HOST: database\n DATABASE_NAME: teslamate\n DATABASE_PASS: ${GENERATED_DB_PASSWORD}\n DATABASE_USER: teslamate\n GF_PATHS_DATA: /var/lib/grafana/data\n GF_PATHS_PLUGINS: /var/lib/grafana/data/plugins\n image: teslamate/grafana:latest\n ports:\n - 3000:3000\n restart: unless-stopped\n volumes:\n - read_only: false\n source: grafana\n target: /var/lib/grafana\n mosquitto:\n command:\n - mosquitto\n - -c\n - /mosquitto-no-auth.conf\n image: eclipse-mosquitto:2\n restart: unless-stopped\n volumes:\n - read_only: false\n source: mosquitto-config\n target: /mosquitto/config\n - read_only: false\n source: mosquitto-data\n target: /mosquitto/data\n - read_only: false\n source: mosquitto-log\n target: /mosquitto/log\n teslamate:\n depends_on:\n database:\n condition: service_healthy\n mosquitto:\n condition: service_started\n environment:\n DATABASE_HOST: database\n DATABASE_NAME: teslamate\n DATABASE_PASS: ${GENERATED_DB_PASSWORD}\n DATABASE_USER: teslamate\n ENCRYPTION_KEY: ${GENERATED_ENCRYPTION_KEY}\n MQTT_HOST: mosquitto\n image: teslamate/teslamate:latest\n ports:\n - 4000:4000\n restart: unless-stopped\n volumes:\n - read_only: false\n source: import\n target: /opt/app/import\nvolumes:\n database: {}\n grafana: {}\n import: {}\n mosquitto-config: {}\n mosquitto-data: {}\n mosquitto-log: {}\n"
|
||||
},
|
||||
"compose_stack": {
|
||||
"project_name": "teslamate",
|
||||
"deployment_model": "one-native-oci-lxc-per-compose-service",
|
||||
"user_experience": "single-application-install",
|
||||
"main_service": "teslamate",
|
||||
"service_count": 4,
|
||||
"services": [
|
||||
{
|
||||
"name": "database",
|
||||
"image": "postgres:18-trixie",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 1,
|
||||
"depends_on": [],
|
||||
"frontend_network": false,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"environment": {
|
||||
"POSTGRES_USER": "teslamate",
|
||||
"POSTGRES_PASSWORD": "${GENERATED_DB_PASSWORD}",
|
||||
"POSTGRES_DB": "teslamate"
|
||||
},
|
||||
"healthcheck": {
|
||||
"interval": "5s",
|
||||
"retries": 30,
|
||||
"test": [
|
||||
"CMD",
|
||||
"pg_isready",
|
||||
"-U",
|
||||
"teslamate",
|
||||
"-d",
|
||||
"teslamate"
|
||||
],
|
||||
"timeout": "5s"
|
||||
},
|
||||
"image": "postgres:18-trixie",
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "database",
|
||||
"target": "/var/lib/postgresql",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "grafana",
|
||||
"image": "teslamate/grafana:latest",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 2,
|
||||
"depends_on": [
|
||||
"database"
|
||||
],
|
||||
"frontend_network": true,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"depends_on": {
|
||||
"database": {
|
||||
"condition": "service_healthy"
|
||||
}
|
||||
},
|
||||
"entrypoint": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"mkdir -p \"$GF_PATHS_DATA\" && exec /run.sh \"$@\"",
|
||||
"--"
|
||||
],
|
||||
"environment": {
|
||||
"DATABASE_USER": "teslamate",
|
||||
"DATABASE_PASS": "${GENERATED_DB_PASSWORD}",
|
||||
"DATABASE_NAME": "teslamate",
|
||||
"DATABASE_HOST": "database",
|
||||
"GF_PATHS_DATA": "/var/lib/grafana/data",
|
||||
"GF_PATHS_PLUGINS": "/var/lib/grafana/data/plugins"
|
||||
},
|
||||
"image": "teslamate/grafana:latest",
|
||||
"ports": [
|
||||
"3000:3000"
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "grafana",
|
||||
"target": "/var/lib/grafana",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mosquitto",
|
||||
"image": "eclipse-mosquitto:2",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 3,
|
||||
"depends_on": [],
|
||||
"frontend_network": false,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"command": [
|
||||
"mosquitto",
|
||||
"-c",
|
||||
"/mosquitto-no-auth.conf"
|
||||
],
|
||||
"image": "eclipse-mosquitto:2",
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "mosquitto-config",
|
||||
"target": "/mosquitto/config",
|
||||
"read_only": false
|
||||
},
|
||||
{
|
||||
"source": "mosquitto-data",
|
||||
"target": "/mosquitto/data",
|
||||
"read_only": false
|
||||
},
|
||||
{
|
||||
"source": "mosquitto-log",
|
||||
"target": "/mosquitto/log",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teslamate",
|
||||
"image": "teslamate/teslamate:latest",
|
||||
"is_main": true,
|
||||
"role": "frontend",
|
||||
"vmid_offset": 0,
|
||||
"depends_on": [
|
||||
"database",
|
||||
"mosquitto"
|
||||
],
|
||||
"frontend_network": true,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"depends_on": {
|
||||
"database": {
|
||||
"condition": "service_healthy"
|
||||
},
|
||||
"mosquitto": {
|
||||
"condition": "service_started"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"ENCRYPTION_KEY": "${GENERATED_ENCRYPTION_KEY}",
|
||||
"DATABASE_USER": "teslamate",
|
||||
"DATABASE_PASS": "${GENERATED_DB_PASSWORD}",
|
||||
"DATABASE_NAME": "teslamate",
|
||||
"DATABASE_HOST": "database",
|
||||
"MQTT_HOST": "mosquitto"
|
||||
},
|
||||
"image": "teslamate/teslamate:latest",
|
||||
"ports": [
|
||||
"4000:4000"
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "import",
|
||||
"target": "/opt/app/import",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"top_level": {
|
||||
"name": "teslamate",
|
||||
"volumes": {
|
||||
"database": {},
|
||||
"grafana": {},
|
||||
"import": {},
|
||||
"mosquitto-config": {},
|
||||
"mosquitto-data": {},
|
||||
"mosquitto-log": {}
|
||||
}
|
||||
},
|
||||
"networking": {
|
||||
"frontend": "selected-proxmox-bridge",
|
||||
"private_required": true,
|
||||
"private_creation": "automatic-create-if-missing",
|
||||
"private_address_allocation": "automatic-static-address-per-service",
|
||||
"service_discovery": "private-addresses-with-compose-service-host-aliases",
|
||||
"dependency_external_access": "disabled-unless-service-publishes-ports",
|
||||
"prompt_user_for_private_network": false
|
||||
},
|
||||
"storage": [
|
||||
{
|
||||
"id": "database-volume-0",
|
||||
"service": "database",
|
||||
"container_path": "/var/lib/postgresql",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "grafana-volume-0",
|
||||
"service": "grafana",
|
||||
"container_path": "/var/lib/grafana",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-0",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/config",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-1",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/data",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-2",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/log",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "teslamate-volume-0",
|
||||
"service": "teslamate",
|
||||
"container_path": "/opt/app/import",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
}
|
||||
],
|
||||
"orchestration": {
|
||||
"reserve_vmids_atomically": 4,
|
||||
"start_order": [
|
||||
"database",
|
||||
"grafana",
|
||||
"mosquitto",
|
||||
"teslamate"
|
||||
],
|
||||
"stop_order": [
|
||||
"teslamate",
|
||||
"mosquitto",
|
||||
"grafana",
|
||||
"database"
|
||||
],
|
||||
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
|
||||
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
|
||||
},
|
||||
"installer_inputs": {
|
||||
"prompted": [
|
||||
"stack_name",
|
||||
"base_vmid",
|
||||
"rootfs_storage",
|
||||
"persistent_data_destinations",
|
||||
"frontend_bridge",
|
||||
"frontend_ipv4_mode"
|
||||
],
|
||||
"automatic": [
|
||||
"dependent_vmids",
|
||||
"private_bridge",
|
||||
"private_subnet",
|
||||
"private_service_addresses",
|
||||
"compose_service_aliases",
|
||||
"generated_secrets",
|
||||
"dependency_start_and_stop_order"
|
||||
],
|
||||
"generated_secrets": [
|
||||
{
|
||||
"id": "generated-db-password",
|
||||
"strategy": "generate-cryptographically-random-at-install",
|
||||
"bindings": [
|
||||
{
|
||||
"service": "database",
|
||||
"environment_variable": "POSTGRES_PASSWORD"
|
||||
},
|
||||
{
|
||||
"service": "grafana",
|
||||
"environment_variable": "DATABASE_PASS"
|
||||
},
|
||||
{
|
||||
"service": "teslamate",
|
||||
"environment_variable": "DATABASE_PASS"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "generated-encryption-key",
|
||||
"strategy": "generate-cryptographically-random-at-install",
|
||||
"bindings": [
|
||||
{
|
||||
"service": "teslamate",
|
||||
"environment_variable": "ENCRYPTION_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": [
|
||||
{
|
||||
"label": "Web UI",
|
||||
"scheme": "http",
|
||||
"port": 4000,
|
||||
"path": "/",
|
||||
"source": "compose-metadata"
|
||||
}
|
||||
],
|
||||
"credentials": [
|
||||
{
|
||||
"label": "Grafana administrator",
|
||||
"type": "static-default",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"change_required": true,
|
||||
"source": "official-documentation",
|
||||
"retrieval": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"proxmox": {
|
||||
"runtime": "native-oci-lxc",
|
||||
"technology_status": "proxmox-technology-preview",
|
||||
"defaults": {
|
||||
"unprivileged": true,
|
||||
"ostype": "auto-from-image",
|
||||
"cores": 2,
|
||||
"memory_mb": 1024,
|
||||
"swap_mb": 512,
|
||||
"rootfs_size_gb": 8,
|
||||
"rootfs_storage": "local-lvm",
|
||||
"volume_storage": "local-lvm",
|
||||
"template_storage": "local",
|
||||
"bridge": "vmbr0",
|
||||
"ipv4": "dhcp",
|
||||
"firewall": true,
|
||||
"host_managed_network": true,
|
||||
"onboot": false,
|
||||
"features": [
|
||||
"nesting=1"
|
||||
],
|
||||
"shutdown_timeout_seconds": 30
|
||||
},
|
||||
"image_metadata_policy": {
|
||||
"entrypoint": "import-from-oci-image",
|
||||
"cmd": "import-from-oci-image",
|
||||
"environment": "import-image-env-then-apply-compose-overrides",
|
||||
"user": "import-from-oci-image",
|
||||
"working_dir": "import-from-oci-image",
|
||||
"stop_signal": "import-from-oci-image"
|
||||
},
|
||||
"adaptations": [
|
||||
{
|
||||
"id": "official-four-service-stack",
|
||||
"upstream_behavior": "The official Compose runs TeslaMate, PostgreSQL, Grafana and Mosquitto.",
|
||||
"native_lxc_behavior": "The generic OCI stack driver creates one native LXC per service with ordered startup and private service aliases.",
|
||||
"reason": "Preserve the complete upstream application without Docker Engine.",
|
||||
"behavioral_impact": "TeslaMate and Grafana receive frontend addresses; the database and MQTT broker remain private.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "postgres-major-version",
|
||||
"upstream_behavior": "The official deployment selects postgres:18-trixie and persists /var/lib/postgresql.",
|
||||
"native_lxc_behavior": "Keep that tag and volume root, including the versioned PostgreSQL data directory.",
|
||||
"reason": "Avoid an unintended PostgreSQL major-version change.",
|
||||
"behavioral_impact": "Minor image updates remain available within the selected major version.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-declared-log-volume",
|
||||
"upstream_behavior": "The Mosquitto image declares /mosquitto/log as a data volume in addition to /mosquitto/data.",
|
||||
"native_lxc_behavior": "Add a backup-enabled managed volume for /mosquitto/log alongside configuration and data.",
|
||||
"reason": "Every OCI-declared data path must survive image replacement.",
|
||||
"behavioral_impact": "The MQTT port remains private; no image is rebuilt.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "grafana-nested-persistent-data",
|
||||
"upstream_behavior": "Grafana stores SQLite at the persistent mount root.",
|
||||
"native_lxc_behavior": "Use a data subdirectory, created by a shell entrypoint before executing the original /run.sh.",
|
||||
"reason": "Proxmox restores mount roots with root ownership; nested data retains the Grafana user ownership.",
|
||||
"behavioral_impact": "Official image and original startup script are retained. Data and plugins remain backed up in the same volume.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
}
|
||||
],
|
||||
"custom_compose_stack": true,
|
||||
"custom_compose_interpolated": false,
|
||||
"stack_image_entrypoints": {},
|
||||
"installer_profile": {
|
||||
"stack_driver": "generic-multi-lxc-stack",
|
||||
"volume_preparations": [
|
||||
{
|
||||
"container_path": "/opt/app/import",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/var/lib/postgresql",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/var/lib/grafana",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/config",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/data",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/log",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"compatibility": {
|
||||
"automatic_install_candidate": true,
|
||||
"validated": false,
|
||||
"supported_compose_keys": [
|
||||
"cap_add",
|
||||
"command",
|
||||
"container_name",
|
||||
"cpu_shares",
|
||||
"deploy",
|
||||
"devices",
|
||||
"entrypoint",
|
||||
"environment",
|
||||
"extra_hosts",
|
||||
"group_add",
|
||||
"healthcheck",
|
||||
"hostname",
|
||||
"image",
|
||||
"init",
|
||||
"ipc",
|
||||
"labels",
|
||||
"logging",
|
||||
"mac_address",
|
||||
"mem_limit",
|
||||
"network_mode",
|
||||
"networks",
|
||||
"ports",
|
||||
"privileged",
|
||||
"restart",
|
||||
"runtime",
|
||||
"security_opt",
|
||||
"shm_size",
|
||||
"stdin_open",
|
||||
"stop_grace_period",
|
||||
"stop_signal",
|
||||
"sysctls",
|
||||
"tty",
|
||||
"ulimits",
|
||||
"user",
|
||||
"volumes",
|
||||
"working_dir"
|
||||
],
|
||||
"untranslated_blockers": [],
|
||||
"policy": "Reviewed four-service application using the generic native OCI stack driver; runtime evidence is recorded in validation."
|
||||
},
|
||||
"validation": {
|
||||
"schema": "passed-at-generation",
|
||||
"clean_install": "passed-ms03-default-resource-profile-and-advanced-settings",
|
||||
"service_health": "passed-teslamate-http-grafana-login-and-postgresql-datasource",
|
||||
"restart_persistence": "passed",
|
||||
"backup_restore": "passed-native-backups-restored-under-new-vmids",
|
||||
"update_preserves_data": "passed-4.3.0-to-4.4.0",
|
||||
"rollback": "passed-controlled-healthcheck-failure-with-accompanying-engine-fix",
|
||||
"modification": "passed-extra-managed-volume",
|
||||
"removal": "passed-containers-volumes-registry-and-network-cleanup-host-data-retained",
|
||||
"browser_ui": "passed-teslamate-sign-in-and-grafana-login-dashboard-rendered",
|
||||
"vehicle_authorization": "not-tested-no-Tesla-account-tokens-used",
|
||||
"runtime_architecture": "amd64-only-on-ms03",
|
||||
"oci_engine_tests": "1106-passed-3-root-only-cases-covered-separately",
|
||||
"offline_i18n_tests": "608-passed"
|
||||
},
|
||||
"lifecycle": {
|
||||
"update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update",
|
||||
"registry_state": {
|
||||
"resolved_architecture": null,
|
||||
"resolved_digest": null,
|
||||
"image_version_label": null,
|
||||
"image_created": null
|
||||
},
|
||||
"change_detection": "compare-compose-sha256-and-resolved-latest-image-digest",
|
||||
"automatic_unattended_updates": false,
|
||||
"dependency_lifecycle": {
|
||||
"implementation": "proxmox-hookscript",
|
||||
"trigger": "main-lxc-pre-start",
|
||||
"waits_for_dependency_healthchecks": true,
|
||||
"stops_dependencies_with_main": false
|
||||
}
|
||||
},
|
||||
"community_tested": {
|
||||
"by": "jcastro",
|
||||
"date": "2026-10-29",
|
||||
"digest": "c85b3d447502683ed7bf12fb478e0dae9a985fe975ac22cccb64faeb4c4a5d31",
|
||||
"scenario": "MS03/amd64: default and advanced install, browser login, volumes/restart, Modify, 4.3 to 4.4 update, controlled rollback, native backup/restore to new VMIDs and removal. No vehicle tokens used."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
{
|
||||
"schema_version": "0.5.0",
|
||||
"kind": "proxmenux.oci-template",
|
||||
"id": "image-teslamate",
|
||||
"status": "generated-unvalidated",
|
||||
"catalog_ui": {
|
||||
"title": {
|
||||
"en_US": "TeslaMate"
|
||||
},
|
||||
"tagline": {
|
||||
"en_US": "Tesla data logger with Grafana dashboards"
|
||||
},
|
||||
"description": {
|
||||
"en_US": "TeslaMate with PostgreSQL, Grafana and a private Mosquitto broker. Runs as four native OCI LXCs without Docker Engine."
|
||||
},
|
||||
"category": "monitoring",
|
||||
"category_label": "Monitoring & Analytics",
|
||||
"author": "TeslaMate",
|
||||
"developer": "TeslaMate",
|
||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/teslamate.webp",
|
||||
"thumbnail": null,
|
||||
"screenshots": [],
|
||||
"architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
],
|
||||
"launch": {
|
||||
"scheme": "http",
|
||||
"port": 4000,
|
||||
"path": "/"
|
||||
},
|
||||
"website": "https://teslamate.org",
|
||||
"documentation": "https://docs.teslamate.org/docs/installation/docker/",
|
||||
"repository": "https://github.com/teslamate-org/teslamate",
|
||||
"tips": [],
|
||||
"mini_changelog": [],
|
||||
"display_version": null,
|
||||
"updated_at": "2026-10-29"
|
||||
},
|
||||
"source": {
|
||||
"provider": "teslamate-org",
|
||||
"repository": "https://github.com/teslamate-org/teslamate",
|
||||
"revision": "012892b45e2ab159d3ae1eb1dc9bf85ced0eadfa20ca478821ab3cae0cef8f94",
|
||||
"image_repository_url": "https://hub.docker.com/r/teslamate/teslamate",
|
||||
"readme_pushed_at": "",
|
||||
"compose_sha256": "012892b45e2ab159d3ae1eb1dc9bf85ced0eadfa20ca478821ab3cae0cef8f94",
|
||||
"generated_at": "2026-10-28T23:42:45+00:00"
|
||||
},
|
||||
"container_contract": {
|
||||
"service_name": "teslamate",
|
||||
"container_name": "teslamate",
|
||||
"image": {
|
||||
"reference": "teslamate/teslamate:latest",
|
||||
"registry": "docker.io",
|
||||
"repository": "teslamate/teslamate",
|
||||
"tag": "latest",
|
||||
"digest": null,
|
||||
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
|
||||
},
|
||||
"environment": [
|
||||
{
|
||||
"name": "DATABASE_HOST",
|
||||
"example": "database",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_NAME",
|
||||
"example": "teslamate",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_PASS",
|
||||
"example": "${GENERATED_GENERATED_DB_PASSWORD}",
|
||||
"required": true,
|
||||
"sensitive": true,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_USER",
|
||||
"example": "teslamate",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "ENCRYPTION_KEY",
|
||||
"example": "${GENERATED_GENERATED_ENCRYPTION_KEY}",
|
||||
"required": true,
|
||||
"sensitive": true,
|
||||
"source": "docker-compose"
|
||||
},
|
||||
{
|
||||
"name": "MQTT_HOST",
|
||||
"example": "mosquitto",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"source": "docker-compose"
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"id": "volume-0",
|
||||
"container_path": "/opt/app/import",
|
||||
"compose_source_example": "import",
|
||||
"read_only": false,
|
||||
"required": true,
|
||||
"installation_choice": [
|
||||
"managed-volume",
|
||||
"host-bind"
|
||||
],
|
||||
"default": "managed-volume",
|
||||
"managed_volume": {
|
||||
"backup": true,
|
||||
"default_size_gb": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"ports": [
|
||||
{
|
||||
"container_port": 4000,
|
||||
"published_example": 4000,
|
||||
"protocol": "tcp",
|
||||
"required": true,
|
||||
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
|
||||
}
|
||||
],
|
||||
"related_services": [
|
||||
{
|
||||
"name": "database",
|
||||
"image": "postgres:18-trixie"
|
||||
},
|
||||
{
|
||||
"name": "grafana",
|
||||
"image": "teslamate/grafana:latest"
|
||||
},
|
||||
{
|
||||
"name": "mosquitto",
|
||||
"image": "eclipse-mosquitto:2"
|
||||
}
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"stop_grace_period": null,
|
||||
"original_compose": "name: teslamate\nservices:\n database:\n environment:\n POSTGRES_DB: teslamate\n POSTGRES_PASSWORD: ${GENERATED_DB_PASSWORD}\n POSTGRES_USER: teslamate\n healthcheck:\n interval: 5s\n retries: 30\n test:\n - CMD\n - pg_isready\n - -U\n - teslamate\n - -d\n - teslamate\n timeout: 5s\n image: postgres:18-trixie\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql\n grafana:\n depends_on:\n database:\n condition: service_healthy\n entrypoint:\n - /bin/sh\n - -c\n - mkdir -p \"$GF_PATHS_DATA\" && exec /run.sh \"$@\"\n - --\n environment:\n DATABASE_HOST: database\n DATABASE_NAME: teslamate\n DATABASE_PASS: ${GENERATED_DB_PASSWORD}\n DATABASE_USER: teslamate\n GF_PATHS_DATA: /var/lib/grafana/data\n GF_PATHS_PLUGINS: /var/lib/grafana/data/plugins\n image: teslamate/grafana:latest\n ports:\n - 3000:3000\n restart: unless-stopped\n volumes:\n - read_only: false\n source: grafana\n target: /var/lib/grafana\n mosquitto:\n command:\n - mosquitto\n - -c\n - /mosquitto-no-auth.conf\n image: eclipse-mosquitto:2\n restart: unless-stopped\n volumes:\n - read_only: false\n source: mosquitto-config\n target: /mosquitto/config\n - read_only: false\n source: mosquitto-data\n target: /mosquitto/data\n - read_only: false\n source: mosquitto-log\n target: /mosquitto/log\n teslamate:\n depends_on:\n database:\n condition: service_healthy\n mosquitto:\n condition: service_started\n environment:\n DATABASE_HOST: database\n DATABASE_NAME: teslamate\n DATABASE_PASS: ${GENERATED_DB_PASSWORD}\n DATABASE_USER: teslamate\n ENCRYPTION_KEY: ${GENERATED_ENCRYPTION_KEY}\n MQTT_HOST: mosquitto\n image: teslamate/teslamate:latest\n ports:\n - 4000:4000\n restart: unless-stopped\n volumes:\n - read_only: false\n source: import\n target: /opt/app/import\nvolumes:\n database: {}\n grafana: {}\n import: {}\n mosquitto-config: {}\n mosquitto-data: {}\n mosquitto-log: {}\n"
|
||||
},
|
||||
"compose_stack": {
|
||||
"project_name": "teslamate",
|
||||
"deployment_model": "one-native-oci-lxc-per-compose-service",
|
||||
"user_experience": "single-application-install",
|
||||
"main_service": "teslamate",
|
||||
"service_count": 4,
|
||||
"services": [
|
||||
{
|
||||
"name": "database",
|
||||
"image": "postgres:18-trixie",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 1,
|
||||
"depends_on": [],
|
||||
"frontend_network": false,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"environment": {
|
||||
"POSTGRES_USER": "teslamate",
|
||||
"POSTGRES_PASSWORD": "${GENERATED_DB_PASSWORD}",
|
||||
"POSTGRES_DB": "teslamate"
|
||||
},
|
||||
"healthcheck": {
|
||||
"interval": "5s",
|
||||
"retries": 30,
|
||||
"test": [
|
||||
"CMD",
|
||||
"pg_isready",
|
||||
"-U",
|
||||
"teslamate",
|
||||
"-d",
|
||||
"teslamate"
|
||||
],
|
||||
"timeout": "5s"
|
||||
},
|
||||
"image": "postgres:18-trixie",
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "database",
|
||||
"target": "/var/lib/postgresql",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "grafana",
|
||||
"image": "teslamate/grafana:latest",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 2,
|
||||
"depends_on": [
|
||||
"database"
|
||||
],
|
||||
"frontend_network": true,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"depends_on": {
|
||||
"database": {
|
||||
"condition": "service_healthy"
|
||||
}
|
||||
},
|
||||
"entrypoint": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"mkdir -p \"$GF_PATHS_DATA\" && exec /run.sh \"$@\"",
|
||||
"--"
|
||||
],
|
||||
"environment": {
|
||||
"DATABASE_USER": "teslamate",
|
||||
"DATABASE_PASS": "${GENERATED_DB_PASSWORD}",
|
||||
"DATABASE_NAME": "teslamate",
|
||||
"DATABASE_HOST": "database",
|
||||
"GF_PATHS_DATA": "/var/lib/grafana/data",
|
||||
"GF_PATHS_PLUGINS": "/var/lib/grafana/data/plugins"
|
||||
},
|
||||
"image": "teslamate/grafana:latest",
|
||||
"ports": [
|
||||
"3000:3000"
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "grafana",
|
||||
"target": "/var/lib/grafana",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mosquitto",
|
||||
"image": "eclipse-mosquitto:2",
|
||||
"is_main": false,
|
||||
"role": "dependency",
|
||||
"vmid_offset": 3,
|
||||
"depends_on": [],
|
||||
"frontend_network": false,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"command": [
|
||||
"mosquitto",
|
||||
"-c",
|
||||
"/mosquitto-no-auth.conf"
|
||||
],
|
||||
"image": "eclipse-mosquitto:2",
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "mosquitto-config",
|
||||
"target": "/mosquitto/config",
|
||||
"read_only": false
|
||||
},
|
||||
{
|
||||
"source": "mosquitto-data",
|
||||
"target": "/mosquitto/data",
|
||||
"read_only": false
|
||||
},
|
||||
{
|
||||
"source": "mosquitto-log",
|
||||
"target": "/mosquitto/log",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teslamate",
|
||||
"image": "teslamate/teslamate:latest",
|
||||
"is_main": true,
|
||||
"role": "frontend",
|
||||
"vmid_offset": 0,
|
||||
"depends_on": [
|
||||
"database",
|
||||
"mosquitto"
|
||||
],
|
||||
"frontend_network": true,
|
||||
"private_network": true,
|
||||
"compose": {
|
||||
"depends_on": {
|
||||
"database": {
|
||||
"condition": "service_healthy"
|
||||
},
|
||||
"mosquitto": {
|
||||
"condition": "service_started"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"ENCRYPTION_KEY": "${GENERATED_ENCRYPTION_KEY}",
|
||||
"DATABASE_USER": "teslamate",
|
||||
"DATABASE_PASS": "${GENERATED_DB_PASSWORD}",
|
||||
"DATABASE_NAME": "teslamate",
|
||||
"DATABASE_HOST": "database",
|
||||
"MQTT_HOST": "mosquitto"
|
||||
},
|
||||
"image": "teslamate/teslamate:latest",
|
||||
"ports": [
|
||||
"4000:4000"
|
||||
],
|
||||
"restart": "unless-stopped",
|
||||
"volumes": [
|
||||
{
|
||||
"source": "import",
|
||||
"target": "/opt/app/import",
|
||||
"read_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"top_level": {
|
||||
"name": "teslamate",
|
||||
"volumes": {
|
||||
"database": {},
|
||||
"grafana": {},
|
||||
"import": {},
|
||||
"mosquitto-config": {},
|
||||
"mosquitto-data": {},
|
||||
"mosquitto-log": {}
|
||||
}
|
||||
},
|
||||
"networking": {
|
||||
"frontend": "selected-proxmox-bridge",
|
||||
"private_required": true,
|
||||
"private_creation": "automatic-create-if-missing",
|
||||
"private_address_allocation": "automatic-static-address-per-service",
|
||||
"service_discovery": "private-addresses-with-compose-service-host-aliases",
|
||||
"dependency_external_access": "disabled-unless-service-publishes-ports",
|
||||
"prompt_user_for_private_network": false
|
||||
},
|
||||
"storage": [
|
||||
{
|
||||
"id": "database-volume-0",
|
||||
"service": "database",
|
||||
"container_path": "/var/lib/postgresql",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "grafana-volume-0",
|
||||
"service": "grafana",
|
||||
"container_path": "/var/lib/grafana",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-0",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/config",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-1",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/data",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-volume-2",
|
||||
"service": "mosquitto",
|
||||
"container_path": "/mosquitto/log",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
},
|
||||
{
|
||||
"id": "teslamate-volume-0",
|
||||
"service": "teslamate",
|
||||
"container_path": "/opt/app/import",
|
||||
"mode": "managed-volume",
|
||||
"user_selectable": false,
|
||||
"backup": true,
|
||||
"shared_with_other_lxc": false,
|
||||
"source_path": null,
|
||||
"source_path_prompt": null
|
||||
}
|
||||
],
|
||||
"orchestration": {
|
||||
"reserve_vmids_atomically": 4,
|
||||
"start_order": [
|
||||
"database",
|
||||
"grafana",
|
||||
"mosquitto",
|
||||
"teslamate"
|
||||
],
|
||||
"stop_order": [
|
||||
"teslamate",
|
||||
"mosquitto",
|
||||
"grafana",
|
||||
"database"
|
||||
],
|
||||
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
|
||||
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
|
||||
},
|
||||
"installer_inputs": {
|
||||
"prompted": [
|
||||
"stack_name",
|
||||
"base_vmid",
|
||||
"rootfs_storage",
|
||||
"persistent_data_destinations",
|
||||
"frontend_bridge",
|
||||
"frontend_ipv4_mode"
|
||||
],
|
||||
"automatic": [
|
||||
"dependent_vmids",
|
||||
"private_bridge",
|
||||
"private_subnet",
|
||||
"private_service_addresses",
|
||||
"compose_service_aliases",
|
||||
"generated_secrets",
|
||||
"dependency_start_and_stop_order"
|
||||
],
|
||||
"generated_secrets": [
|
||||
{
|
||||
"id": "generated-db-password",
|
||||
"strategy": "generate-cryptographically-random-at-install",
|
||||
"bindings": [
|
||||
{
|
||||
"service": "database",
|
||||
"environment_variable": "POSTGRES_PASSWORD"
|
||||
},
|
||||
{
|
||||
"service": "grafana",
|
||||
"environment_variable": "DATABASE_PASS"
|
||||
},
|
||||
{
|
||||
"service": "teslamate",
|
||||
"environment_variable": "DATABASE_PASS"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "generated-encryption-key",
|
||||
"strategy": "generate-cryptographically-random-at-install",
|
||||
"bindings": [
|
||||
{
|
||||
"service": "teslamate",
|
||||
"environment_variable": "ENCRYPTION_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": [
|
||||
{
|
||||
"label": "Web UI",
|
||||
"scheme": "http",
|
||||
"port": 4000,
|
||||
"path": "/",
|
||||
"source": "compose-metadata"
|
||||
}
|
||||
],
|
||||
"credentials": [
|
||||
{
|
||||
"label": "Grafana administrator",
|
||||
"type": "static-default",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"change_required": true,
|
||||
"source": "official-documentation",
|
||||
"retrieval": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"proxmox": {
|
||||
"runtime": "native-oci-lxc",
|
||||
"technology_status": "proxmox-technology-preview",
|
||||
"defaults": {
|
||||
"unprivileged": true,
|
||||
"ostype": "auto-from-image",
|
||||
"cores": 2,
|
||||
"memory_mb": 1024,
|
||||
"swap_mb": 512,
|
||||
"rootfs_size_gb": 8,
|
||||
"rootfs_storage": "local-lvm",
|
||||
"volume_storage": "local-lvm",
|
||||
"template_storage": "local",
|
||||
"bridge": "vmbr0",
|
||||
"ipv4": "dhcp",
|
||||
"firewall": true,
|
||||
"host_managed_network": true,
|
||||
"onboot": false,
|
||||
"features": [
|
||||
"nesting=1"
|
||||
],
|
||||
"shutdown_timeout_seconds": 30
|
||||
},
|
||||
"image_metadata_policy": {
|
||||
"entrypoint": "import-from-oci-image",
|
||||
"cmd": "import-from-oci-image",
|
||||
"environment": "import-image-env-then-apply-compose-overrides",
|
||||
"user": "import-from-oci-image",
|
||||
"working_dir": "import-from-oci-image",
|
||||
"stop_signal": "import-from-oci-image"
|
||||
},
|
||||
"adaptations": [
|
||||
{
|
||||
"id": "official-four-service-stack",
|
||||
"upstream_behavior": "The official Compose runs TeslaMate, PostgreSQL, Grafana and Mosquitto.",
|
||||
"native_lxc_behavior": "The generic OCI stack driver creates one native LXC per service with ordered startup and private service aliases.",
|
||||
"reason": "Preserve the complete upstream application without Docker Engine.",
|
||||
"behavioral_impact": "TeslaMate and Grafana receive frontend addresses; the database and MQTT broker remain private.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "postgres-major-version",
|
||||
"upstream_behavior": "The official deployment selects postgres:18-trixie and persists /var/lib/postgresql.",
|
||||
"native_lxc_behavior": "Keep that tag and volume root, including the versioned PostgreSQL data directory.",
|
||||
"reason": "Avoid an unintended PostgreSQL major-version change.",
|
||||
"behavioral_impact": "Minor image updates remain available within the selected major version.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "mosquitto-declared-log-volume",
|
||||
"upstream_behavior": "The Mosquitto image declares /mosquitto/log as a data volume in addition to /mosquitto/data.",
|
||||
"native_lxc_behavior": "Add a backup-enabled managed volume for /mosquitto/log alongside configuration and data.",
|
||||
"reason": "Every OCI-declared data path must survive image replacement.",
|
||||
"behavioral_impact": "The MQTT port remains private; no image is rebuilt.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
},
|
||||
{
|
||||
"id": "grafana-nested-persistent-data",
|
||||
"upstream_behavior": "Grafana stores SQLite at the persistent mount root.",
|
||||
"native_lxc_behavior": "Use a data subdirectory, created by a shell entrypoint before executing the original /run.sh.",
|
||||
"reason": "Proxmox restores mount roots with root ownership; nested data retains the Grafana user ownership.",
|
||||
"behavioral_impact": "Official image and original startup script are retained. Data and plugins remain backed up in the same volume.",
|
||||
"validation": "passed-ms03-2026-10-29"
|
||||
}
|
||||
],
|
||||
"custom_compose_stack": true,
|
||||
"custom_compose_interpolated": false,
|
||||
"stack_image_entrypoints": {},
|
||||
"installer_profile": {
|
||||
"stack_driver": "generic-multi-lxc-stack",
|
||||
"volume_preparations": [
|
||||
{
|
||||
"container_path": "/opt/app/import",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/var/lib/postgresql",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/var/lib/grafana",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/config",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/data",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
},
|
||||
{
|
||||
"container_path": "/mosquitto/log",
|
||||
"remove_lost_found": true,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"only_when_mount_type": "managed-volume"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"compatibility": {
|
||||
"automatic_install_candidate": true,
|
||||
"validated": false,
|
||||
"supported_compose_keys": [
|
||||
"cap_add",
|
||||
"command",
|
||||
"container_name",
|
||||
"cpu_shares",
|
||||
"deploy",
|
||||
"devices",
|
||||
"entrypoint",
|
||||
"environment",
|
||||
"extra_hosts",
|
||||
"group_add",
|
||||
"healthcheck",
|
||||
"hostname",
|
||||
"image",
|
||||
"init",
|
||||
"ipc",
|
||||
"labels",
|
||||
"logging",
|
||||
"mac_address",
|
||||
"mem_limit",
|
||||
"network_mode",
|
||||
"networks",
|
||||
"ports",
|
||||
"privileged",
|
||||
"restart",
|
||||
"runtime",
|
||||
"security_opt",
|
||||
"shm_size",
|
||||
"stdin_open",
|
||||
"stop_grace_period",
|
||||
"stop_signal",
|
||||
"sysctls",
|
||||
"tty",
|
||||
"ulimits",
|
||||
"user",
|
||||
"volumes",
|
||||
"working_dir"
|
||||
],
|
||||
"untranslated_blockers": [],
|
||||
"policy": "Reviewed four-service application using the generic native OCI stack driver; runtime evidence is recorded in validation."
|
||||
},
|
||||
"validation": {
|
||||
"schema": "passed-at-generation",
|
||||
"clean_install": "passed-ms03-default-resource-profile-and-advanced-settings",
|
||||
"service_health": "passed-teslamate-http-grafana-login-and-postgresql-datasource",
|
||||
"restart_persistence": "passed",
|
||||
"backup_restore": "passed-native-backups-restored-under-new-vmids",
|
||||
"update_preserves_data": "passed-4.3.0-to-4.4.0",
|
||||
"rollback": "passed-controlled-healthcheck-failure-with-accompanying-engine-fix",
|
||||
"modification": "passed-extra-managed-volume",
|
||||
"removal": "passed-containers-volumes-registry-and-network-cleanup-host-data-retained",
|
||||
"browser_ui": "passed-teslamate-sign-in-and-grafana-login-dashboard-rendered",
|
||||
"vehicle_authorization": "not-tested-no-Tesla-account-tokens-used",
|
||||
"runtime_architecture": "amd64-only-on-ms03",
|
||||
"oci_engine_tests": "1106-passed-3-root-only-cases-covered-separately",
|
||||
"offline_i18n_tests": "608-passed"
|
||||
},
|
||||
"lifecycle": {
|
||||
"update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update",
|
||||
"registry_state": {
|
||||
"resolved_architecture": null,
|
||||
"resolved_digest": null,
|
||||
"image_version_label": null,
|
||||
"image_created": null
|
||||
},
|
||||
"change_detection": "compare-compose-sha256-and-resolved-latest-image-digest",
|
||||
"automatic_unattended_updates": false,
|
||||
"dependency_lifecycle": {
|
||||
"implementation": "proxmox-hookscript",
|
||||
"trigger": "main-lxc-pre-start",
|
||||
"waits_for_dependency_healthchecks": true,
|
||||
"stops_dependencies_with_main": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16329,6 +16329,43 @@
|
||||
"hidden": false,
|
||||
"template_id": "image-termix"
|
||||
},
|
||||
{
|
||||
"id": "teslamate",
|
||||
"provider": "teslamate-org",
|
||||
"template_family": "curated-profile",
|
||||
"title": "TeslaMate",
|
||||
"repository": "https://github.com/teslamate-org/teslamate",
|
||||
"description": "TeslaMate with PostgreSQL, Grafana and a private Mosquitto broker. Runs as four native OCI LXCs without Docker Engine.",
|
||||
"website": "https://teslamate.org",
|
||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/teslamate.webp",
|
||||
"architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
],
|
||||
"updated_at": "2026-10-29",
|
||||
"main_image": "teslamate/teslamate:latest",
|
||||
"category": "monitoring",
|
||||
"category_label": "Monitoring & Analytics",
|
||||
"replaces_discovered_ids": [],
|
||||
"curated_path": "catalog/curated/teslamate.json",
|
||||
"template_id": "image-teslamate",
|
||||
"template": "apps/teslamate.json",
|
||||
"template_status": "generated-unvalidated",
|
||||
"hidden": false,
|
||||
"automatic_install_candidate": true,
|
||||
"untranslated_blockers": [],
|
||||
"requires_privileged_lxc": false,
|
||||
"optional_privileged_lxc": false,
|
||||
"requires_host_pid_namespace": false,
|
||||
"requires_relaxed_confinement": false,
|
||||
"optional_relaxed_confinement": false,
|
||||
"requires_security_confirmation": false,
|
||||
"multi_container": true,
|
||||
"community_tested": {
|
||||
"by": "jcastro",
|
||||
"date": "2026-10-29"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "thelounge",
|
||||
"provider": "linuxserver.io",
|
||||
|
||||
@@ -126,6 +126,14 @@
|
||||
"tandoor": {
|
||||
"status": "laboratory-validated"
|
||||
},
|
||||
"teslamate": {
|
||||
"community_tested": {
|
||||
"by": "jcastro",
|
||||
"date": "2026-10-29",
|
||||
"digest": "c85b3d447502683ed7bf12fb478e0dae9a985fe975ac22cccb64faeb4c4a5d31",
|
||||
"scenario": "MS03/amd64: default and advanced install, browser login, volumes/restart, Modify, 4.3 to 4.4 update, controlled rollback, native backup/restore to new VMIDs and removal. No vehicle tokens used."
|
||||
}
|
||||
},
|
||||
"thelounge": {
|
||||
"status": "laboratory-validated"
|
||||
},
|
||||
|
||||
@@ -1352,6 +1352,22 @@ def complete_recovery(root, journal, state):
|
||||
msg_info2(translate('Shared host files are kept as they are; the backup does not restore their content.'))
|
||||
|
||||
|
||||
def check_recovery_runtime(config, state):
|
||||
"""Validate owned route assets before excluding their include from GPU/sysctl checks."""
|
||||
error = None
|
||||
for key in ('record', 'candidate_contract'):
|
||||
saved = state.get(key) or {}
|
||||
try:
|
||||
public_routes.check(config, saved.get('deployment', {}), state['vmid'],
|
||||
state['record']['installation_id'])
|
||||
break
|
||||
except public_routes.RouteError as failure:
|
||||
error = failure
|
||||
else:
|
||||
raise error
|
||||
gpu_devices.check_recovery_entries(without_public_routes(config, state['vmid']), state)
|
||||
|
||||
|
||||
def recover(root, journal):
|
||||
state = json.loads(journal.read_text())
|
||||
if state.get('kind') == 'native-modify':
|
||||
@@ -1418,7 +1434,7 @@ def recover(root, journal):
|
||||
if current_path.exists():
|
||||
stop(vmid)
|
||||
current = owned(vmid, record['installation_id'])
|
||||
gpu_devices.check_recovery_entries(current, state)
|
||||
check_recovery_runtime(current, state)
|
||||
if unused_volumes(current).keys() - state.get('retained_unused', {}).keys():
|
||||
raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced'))
|
||||
move_retained_unused(state, to_stage=True)
|
||||
|
||||
@@ -523,6 +523,23 @@ class DefaultsUI:
|
||||
pass
|
||||
|
||||
|
||||
def stack_lxc_hostname(stack_name, service_name, main=False):
|
||||
"""Give the main LXC the stack name and avoid repeating it in members."""
|
||||
from .installer import _hostname_default
|
||||
|
||||
stack = _hostname_default(stack_name)
|
||||
if main:
|
||||
return stack
|
||||
|
||||
suffix = _hostname_default(service_name)
|
||||
prefix = stack + '-'
|
||||
if suffix.startswith(prefix):
|
||||
suffix = suffix[len(prefix):]
|
||||
if suffix == 'database':
|
||||
suffix = 'db'
|
||||
return _hostname_default(f'{stack}-{suffix}')
|
||||
|
||||
|
||||
def _service_plan(template, service):
|
||||
"""The single-container template and plan of one stack member."""
|
||||
from .installer import build_deployment, DEFAULT_MODE
|
||||
@@ -677,7 +694,7 @@ def build_stack(template, ui, mode='advanced'):
|
||||
k = kind(s['image'])
|
||||
# Values are already resolved; do not reinterpret user credentials as Compose variables.
|
||||
plan['environment'] = [{'name':key,'value':value,'sensitive':True} for key,value in env.items() if value != '' or literal]
|
||||
plan.update(hostname=_hostname_default(name+'-'+s['name']), template_storage=cache,
|
||||
plan.update(hostname=stack_lxc_hostname(name, s['name'], main), template_storage=cache,
|
||||
start_after_create=False)
|
||||
plan['rootfs']['storage'] = root
|
||||
if 'memory_default_mb' not in single['proxmox'].get('installer_profile', {}).get('resources', {}):
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Stack LXC names use the stack name once and readable dependency suffixes."""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from proxmenux_oci.catalog import Catalog
|
||||
from proxmenux_oci.installer import DEFAULT_MODE
|
||||
from proxmenux_oci.stack import DefaultsUI, build_stack, stack_lxc_hostname
|
||||
|
||||
|
||||
class StackLxcHostnameTests(unittest.TestCase):
|
||||
def test_main_service_uses_stack_name_without_repeating_service_name(self):
|
||||
self.assertEqual(stack_lxc_hostname("TeslaMate", "teslamate", True), "teslamate")
|
||||
|
||||
def test_database_uses_db_suffix_and_other_services_keep_their_names(self):
|
||||
self.assertEqual(stack_lxc_hostname("teslamate", "database"), "teslamate-db")
|
||||
self.assertEqual(stack_lxc_hostname("teslamate", "grafana"), "teslamate-grafana")
|
||||
self.assertEqual(stack_lxc_hostname("teslamate", "mosquitto"), "teslamate-mosquitto")
|
||||
|
||||
def test_service_already_prefixed_with_stack_name_does_not_repeat_it(self):
|
||||
self.assertEqual(stack_lxc_hostname("blinko", "blinko-postgres"), "blinko-postgres")
|
||||
|
||||
def test_teslamate_build_plan_contains_the_expected_proxmox_names(self):
|
||||
template = Catalog(ROOT).compose("teslamate")
|
||||
with patch("proxmenux_oci.host.default_storage", return_value="local-zfs"), \
|
||||
patch("proxmenux_oci.host.default_bridge", return_value="vmbr0"), \
|
||||
patch("proxmenux_oci.network.ask_addresses",
|
||||
side_effect=lambda ui, bridge, labels: ({label: "dhcp" for label in labels}, None)), \
|
||||
patch("proxmenux_oci.host.timezone", return_value="UTC"):
|
||||
result = build_stack(template, DefaultsUI(), DEFAULT_MODE)
|
||||
|
||||
actual = {service["name"]: service["deployment"]["hostname"]
|
||||
for service in result["services"]}
|
||||
self.assertEqual(actual, {
|
||||
"database": "teslamate-db",
|
||||
"grafana": "teslamate-grafana",
|
||||
"mosquitto": "teslamate-mosquitto",
|
||||
"teslamate": "teslamate",
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -59,6 +59,41 @@ class ManagedRouteLifecycleTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError,'Invalid stack contract'):
|
||||
plans.validate_public_route_members(4900,bad)
|
||||
|
||||
def test_recovery_accepts_validated_owned_routes_and_keeps_unknown_include_guard(self):
|
||||
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||
plan=deployment();routes.install(plan,4901,WORKER)
|
||||
config=f'lxc.include: {routes.include_path(4901)}\n'.encode()
|
||||
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':plan},
|
||||
'candidate_contract':{'installation_id':WORKER,'deployment':copy.deepcopy(plan)}}
|
||||
tx.check_recovery_runtime(config,state)
|
||||
with self.assertRaisesRegex(ValueError,'not part of the journal'):
|
||||
tx.check_recovery_runtime(config+b'lxc.include: /unknown/profile.conf\n',state)
|
||||
|
||||
def test_recovery_refuses_tampered_owned_route_assets(self):
|
||||
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||
plan=deployment();routes.install(plan,4901,WORKER)
|
||||
config=f'lxc.include: {routes.include_path(4901)}\n'.encode()
|
||||
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':plan},
|
||||
'candidate_contract':{'installation_id':WORKER,'deployment':copy.deepcopy(plan)}}
|
||||
routes.include_path(4901).write_text('lxc.hook.start-host: /untrusted\n')
|
||||
with self.assertRaisesRegex(routes.RouteError,'was changed'):
|
||||
tx.check_recovery_runtime(config,state)
|
||||
|
||||
def test_recovery_refuses_an_owned_include_not_declared_by_either_contract(self):
|
||||
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||
routes.install(deployment(),4901,WORKER)
|
||||
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':{}},
|
||||
'candidate_contract':{'installation_id':WORKER,'deployment':{}}}
|
||||
with self.assertRaisesRegex(routes.RouteError,'differs from the contract'):
|
||||
tx.check_recovery_runtime(f'lxc.include: {routes.include_path(4901)}\n'.encode(),state)
|
||||
|
||||
def test_recovery_can_validate_the_candidate_route_contract(self):
|
||||
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||
plan=deployment();routes.install(plan,4901,WORKER)
|
||||
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':{}},
|
||||
'candidate_contract':{'installation_id':WORKER,'deployment':plan}}
|
||||
tx.check_recovery_runtime(f'lxc.include: {routes.include_path(4901)}\n'.encode(),state)
|
||||
|
||||
def test_plan_rejects_changed_target_and_stack_identities(self):
|
||||
for field in ('target_installation_id','stack_id'):
|
||||
bad=records();spec=bad[4901]['deployment']['stack_public_routes']
|
||||
|
||||
Reference in New Issue
Block a user