fix(apps): place n8n sandbox in app directory (#1)
Test / test (push) Canceled after 0s

Preserve the modern Runtipi YAML compose format and validate both modern and legacy app definitions.
This commit was merged in pull request #1.
This commit is contained in:
2026-09-02 15:31:38 +00:00
10 changed files with 103 additions and 236 deletions
+34 -6
View File
@@ -3,6 +3,7 @@ import { appInfoSchema, dynamicComposeSchema } from '@runtipi/common/schemas'
import { fromError } from 'zod-validation-error';
import fs from 'node:fs'
import path from 'node:path'
import YAML from 'yaml'
const getApps = async () => {
const appsDir = await fs.promises.readdir(path.join(process.cwd(), 'apps'))
@@ -29,7 +30,7 @@ describe("each app should have the required files", async () => {
const apps = await getApps()
for (const app of apps) {
const files = ['config.json', 'docker-compose.json', 'metadata/logo.jpg', 'metadata/description.md']
const files = ['config.json', 'metadata/logo.jpg', 'metadata/description.md']
for (const file of files) {
test(`app ${app} should have ${file}`, async () => {
@@ -37,6 +38,12 @@ describe("each app should have the required files", async () => {
expect(fileContent).not.toBeNull()
})
}
test(`app ${app} should have a compose file`, async () => {
const legacyCompose = await getFile(app, 'docker-compose.json')
const modernCompose = await getFile(app, 'docker-compose.yml')
expect(legacyCompose || modernCompose).not.toBeNull()
})
}
})
@@ -58,17 +65,38 @@ describe("each app should have a valid config.json", async () => {
}
})
describe("each app should have a valid docker-compose.json", async () => {
describe("modern compose files preserve runtime semantics", () => {
test("n8n-sandbox keeps its one-shot certificate service", async () => {
const fileContent = await getFile('n8n-sandbox', 'docker-compose.yml')
expect(fileContent).not.toBeNull()
const parsed = YAML.parse(fileContent || '')
expect(parsed['x-runtipi']?.schema_version).toBe(2)
expect(parsed.services?.['sandbox-certs']?.restart).toBe('no')
expect(parsed.services?.['sandbox-api']?.['x-runtipi']?.is_main).toBe(true)
})
})
describe("each app should have a valid compose file", async () => {
const apps = await getApps()
for (const app of apps) {
test(`app ${app} should have a valid docker-compose.json`, async () => {
const fileContent = await getFile(app, 'docker-compose.json')
const parsed = dynamicComposeSchema.safeParse(JSON.parse(fileContent || '{}'))
test(`app ${app} should have a valid compose file`, async () => {
const legacyCompose = await getFile(app, 'docker-compose.json')
const modernCompose = await getFile(app, 'docker-compose.yml')
if (modernCompose) {
const parsed = YAML.parse(modernCompose)
expect(parsed['x-runtipi']?.schema_version).toBeTypeOf('number')
expect(parsed.services).toBeTypeOf('object')
return
}
const parsed = dynamicComposeSchema.safeParse(JSON.parse(legacyCompose || '{}'))
if (!parsed.success) {
const validationError = fromError(parsed.error);
console.error(`Error parsing docker-compose.json for app ${app}:`, validationError.toString());
console.error(`Error parsing compose file for app ${app}:`, validationError.toString());
}
expect(parsed.success).toBe(true)
+15 -57
View File
@@ -7,64 +7,22 @@
"hostname": "mediamtx",
"isMain": true,
"internalPort": "8889",
"environment": [
{
"key": "MTX_PROTOCOLS",
"value": "tcp"
"environment": {
"MTX_PROTOCOLS": "tcp",
"MTX_LOGLEVEL": "info",
"MTX_LOGDESTINATIONS": "stdout",
"MTX_RTSPADDRESS": ":8554",
"MTX_RTMPADDRESS": ":1935",
"MTX_HLSADDRESS": ":8888",
"MTX_WEBRTCADDRESS": ":8889",
"MTX_SRTADDRESS": ":8890",
"MTX_APIADDRESS": ":9997",
"MTX_METRICSADDRESS": ":9998",
"MTX_AUTHINTERNALUSERS": "${MTX_API_USERNAME:+${MTX_API_USERNAME}:${MTX_API_PASSWORD}}",
"MTX_PATHDEFAULTS_RECORD": "${MTX_RECORD_ENABLED:-false}",
"MTX_PATHDEFAULTS_RECORDPATH": "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f",
"MTX_PATHDEFAULTS_RECORDFORMAT": "fmp4"
},
{
"key": "MTX_LOGLEVEL",
"value": "info"
},
{
"key": "MTX_LOGDESTINATIONS",
"value": "stdout"
},
{
"key": "MTX_RTSPADDRESS",
"value": ":8554"
},
{
"key": "MTX_RTMPADDRESS",
"value": ":1935"
},
{
"key": "MTX_HLSADDRESS",
"value": ":8888"
},
{
"key": "MTX_WEBRTCADDRESS",
"value": ":8889"
},
{
"key": "MTX_SRTADDRESS",
"value": ":8890"
},
{
"key": "MTX_APIADDRESS",
"value": ":9997"
},
{
"key": "MTX_METRICSADDRESS",
"value": ":9998"
},
{
"key": "MTX_AUTHINTERNALUSERS",
"value": "${MTX_API_USERNAME:+${MTX_API_USERNAME}:${MTX_API_PASSWORD}}"
},
{
"key": "MTX_PATHDEFAULTS_RECORD",
"value": "${MTX_RECORD_ENABLED:-false}"
},
{
"key": "MTX_PATHDEFAULTS_RECORDPATH",
"value": "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f"
},
{
"key": "MTX_PATHDEFAULTS_RECORDFORMAT",
"value": "fmp4"
}
],
"addPorts": [
{
"containerPort": 8554,

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+3 -9
View File
@@ -29,16 +29,10 @@
"readOnly": true
}
],
"environment": [
{
"key": "NGINX_HOST",
"value": "${NGINX_SERVER_NAME:-localhost}"
"environment": {
"NGINX_HOST": "${NGINX_SERVER_NAME:-localhost}",
"TZ": "${TZ:-Europe/Paris}"
},
{
"key": "TZ",
"value": "${TZ:-Europe/Paris}"
}
],
"healthCheck": {
"test": "curl --fail http://localhost:80 || exit 1",
"interval": "30s",
+43 -157
View File
@@ -9,12 +9,9 @@
"sh",
"/scripts/init-certs.sh"
],
"environment": [
{
"key": "CERT_TOOL_VERSION",
"value": "4.14"
}
],
"environment": {
"CERT_TOOL_VERSION": "4.14"
},
"volumes": [
{
"hostPath": "${APP_DATA_DIR}/data/config",
@@ -74,48 +71,18 @@
"condition": "service_healthy"
}
},
"environment": [
{
"key": "OPENSEARCH_JAVA_OPTS",
"value": "-Xms1g -Xmx1g -Dlog4j2.formatMsgNoLookups=true"
"environment": {
"OPENSEARCH_JAVA_OPTS": "-Xms1g -Xmx1g -Dlog4j2.formatMsgNoLookups=true",
"DISABLE_INSTALL_DEMO_CONFIG": "true",
"bootstrap.memory_lock": "true",
"network.host": "wazuh.indexer",
"node.name": "wazuh.indexer",
"cluster.initial_cluster_manager_nodes": "wazuh.indexer",
"node.max_local_storage_nodes": "1",
"plugins.security.allow_default_init_securityindex": "true",
"NODES_DN": "CN=wazuh.indexer,OU=Wazuh,O=Wazuh,L=California,C=US",
"plugins.security.ssl.http.clientauth_mode": "OPTIONAL"
},
{
"key": "DISABLE_INSTALL_DEMO_CONFIG",
"value": "true"
},
{
"key": "bootstrap.memory_lock",
"value": "true"
},
{
"key": "network.host",
"value": "wazuh.indexer"
},
{
"key": "node.name",
"value": "wazuh.indexer"
},
{
"key": "cluster.initial_cluster_manager_nodes",
"value": "wazuh.indexer"
},
{
"key": "node.max_local_storage_nodes",
"value": "1"
},
{
"key": "plugins.security.allow_default_init_securityindex",
"value": "true"
},
{
"key": "NODES_DN",
"value": "CN=wazuh.indexer,OU=Wazuh,O=Wazuh,L=California,C=US"
},
{
"key": "plugins.security.ssl.http.clientauth_mode",
"value": "OPTIONAL"
}
],
"ulimits": {
"memlock": {
"soft": -1,
@@ -209,64 +176,22 @@
"condition": "service_healthy"
}
},
"environment": [
{
"key": "WAZUH_INDEXER_HOSTS",
"value": "wazuh.indexer:9200"
"environment": {
"WAZUH_INDEXER_HOSTS": "wazuh.indexer:9200",
"WAZUH_NODE_NAME": "manager",
"WAZUH_NODE_TYPE": "master",
"WAZUH_CLUSTER_NODES": "wazuh.manager",
"WAZUH_CLUSTER_BIND_ADDR": "wazuh.manager",
"INDEXER_URL": "https://wazuh.indexer:9200",
"INDEXER_USERNAME": "${INDEXER_USERNAME:-admin}",
"INDEXER_PASSWORD": "${INDEXER_PASSWORD:-admin}",
"FILEBEAT_SSL_VERIFICATION_MODE": "full",
"SSL_CERTIFICATE_AUTHORITIES": "/var/ossec/etc/certs/root-ca.pem",
"SSL_CERTIFICATE": "/var/ossec/etc/certs/server.pem",
"SSL_KEY": "/var/ossec/etc/certs/server-key.pem",
"API_USERNAME": "wazuh-wui",
"API_PASSWORD": "${API_PASSWORD:-MyS3cr37P450r.*-}"
},
{
"key": "WAZUH_NODE_NAME",
"value": "manager"
},
{
"key": "WAZUH_NODE_TYPE",
"value": "master"
},
{
"key": "WAZUH_CLUSTER_NODES",
"value": "wazuh.manager"
},
{
"key": "WAZUH_CLUSTER_BIND_ADDR",
"value": "wazuh.manager"
},
{
"key": "INDEXER_URL",
"value": "https://wazuh.indexer:9200"
},
{
"key": "INDEXER_USERNAME",
"value": "${INDEXER_USERNAME:-admin}"
},
{
"key": "INDEXER_PASSWORD",
"value": "${INDEXER_PASSWORD:-admin}"
},
{
"key": "FILEBEAT_SSL_VERIFICATION_MODE",
"value": "full"
},
{
"key": "SSL_CERTIFICATE_AUTHORITIES",
"value": "/var/ossec/etc/certs/root-ca.pem"
},
{
"key": "SSL_CERTIFICATE",
"value": "/var/ossec/etc/certs/server.pem"
},
{
"key": "SSL_KEY",
"value": "/var/ossec/etc/certs/server-key.pem"
},
{
"key": "API_USERNAME",
"value": "wazuh-wui"
},
{
"key": "API_PASSWORD",
"value": "${API_PASSWORD:-MyS3cr37P450r.*-}"
}
],
"ulimits": {
"memlock": {
"soft": -1,
@@ -368,60 +293,21 @@
"condition": "service_healthy"
}
},
"environment": [
{
"key": "SERVER_HOST",
"value": "0.0.0.0"
"environment": {
"SERVER_HOST": "0.0.0.0",
"OPENSEARCH_HOSTS": "https://wazuh.indexer:9200",
"SERVER_SSL_ENABLED": "true",
"INDEXER_USERNAME": "${INDEXER_USERNAME:-admin}",
"INDEXER_PASSWORD": "${INDEXER_PASSWORD:-admin}",
"WAZUH_API_URL": "https://wazuh.manager",
"DASHBOARD_USERNAME": "${DASHBOARD_USERNAME:-kibanaserver}",
"DASHBOARD_PASSWORD": "${DASHBOARD_PASSWORD:-kibanaserver}",
"API_USERNAME": "wazuh-wui",
"API_PASSWORD": "${API_PASSWORD:-MyS3cr37P450r.*-}",
"SERVER_SSL_CERTIFICATE": "/usr/share/wazuh-dashboard/config/certs/dashboard.pem",
"SERVER_SSL_KEY": "/usr/share/wazuh-dashboard/config/certs/dashboard-key.pem",
"OPENSEARCH_SSL_CERTIFICATE_AUTHORITIES": "/usr/share/wazuh-dashboard/config/certs/root-ca.pem"
},
{
"key": "OPENSEARCH_HOSTS",
"value": "https://wazuh.indexer:9200"
},
{
"key": "SERVER_SSL_ENABLED",
"value": "true"
},
{
"key": "INDEXER_USERNAME",
"value": "${INDEXER_USERNAME:-admin}"
},
{
"key": "INDEXER_PASSWORD",
"value": "${INDEXER_PASSWORD:-admin}"
},
{
"key": "WAZUH_API_URL",
"value": "https://wazuh.manager"
},
{
"key": "DASHBOARD_USERNAME",
"value": "${DASHBOARD_USERNAME:-kibanaserver}"
},
{
"key": "DASHBOARD_PASSWORD",
"value": "${DASHBOARD_PASSWORD:-kibanaserver}"
},
{
"key": "API_USERNAME",
"value": "wazuh-wui"
},
{
"key": "API_PASSWORD",
"value": "${API_PASSWORD:-MyS3cr37P450r.*-}"
},
{
"key": "SERVER_SSL_CERTIFICATE",
"value": "/usr/share/wazuh-dashboard/config/certs/dashboard.pem"
},
{
"key": "SERVER_SSL_KEY",
"value": "/usr/share/wazuh-dashboard/config/certs/dashboard-key.pem"
},
{
"key": "OPENSEARCH_SSL_CERTIFICATE_AUTHORITIES",
"value": "/usr/share/wazuh-dashboard/config/certs/root-ca.pem"
}
],
"volumes": [
{
"hostPath": "${APP_DATA_DIR}/data/config/wazuh_ssl_certs",
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -12,7 +12,8 @@
"license": "ISC",
"devDependencies": {
"@types/bun": "latest",
"@types/node": "^22.14.1"
"@types/node": "^22.14.1",
"yaml": "2"
},
"dependencies": {
"@runtipi/common": "^0.8.0",