The file /tmp/wd/wild-dragon/services/premiere-plugin/js/main.js has been updated successfully. (file state is current in your context — no need to Read it back)

    elements.importBtn.disabled      = false;
    elements.importHiresBtn.disabled = false;
}

function hideAssetDetails() {
    state.selectedAsset = null;
    elements.detailsPanel.classList.add('hidden');
    elements.importBtn.disabled      = true;
    elements.importHiresBtn.disabled = true;
}

    var isLive = asset.status === 'live';
    elements.importBtn.disabled      = isLive;
    elements.importHiresBtn.disabled = isLive;
    elements.mountLiveBtn.disabled   = !isLive;
    // The relink button only makes sense once the live file has been finalized
    // AND we have a record of it being mounted in this session.
    elements.relinkBtn.disabled = !(asset.status === 'ready' && state.importedAssets['live:' + asset.id]);
}

function hideAssetDetails() {
    state.selectedAsset = null;
    elements.detailsPanel.classList.add('hidden');
    elements.importBtn.disabled      = true;
    elements.importHiresBtn.disabled = true;
    elements.mountLiveBtn.disabled   = true;
    elements.relinkBtn.disabled      = true;
}

// ============================================================================
// Mount Live — open the growing file directly from the SMB share
// ============================================================================

async function mountLiveAsset() {
    if (!state.selectedAsset) {
        showErrorMessage('No asset selected');
        return;
    }
    var asset = state.selectedAsset;
    try {
        elements.mountLiveBtn.disabled = true;
        showProgress('Resolving SMB path…', 10);

        var res = await fetch(state.serverUrl + '/api/v1/assets/' + asset.id + '/live-path', {
            headers: { Accept: 'application/json' },
            credentials: 'include',
        });
        if (!res.ok) {
            var body = await res.json().catch(function () { return {}; });
            throw new Error(body.error || ('HTTP ' + res.status));
        }
        var info = await res.json();

        // Premiere on Windows wants UNC paths with backslashes; on Mac
        // POSIX-style smb:// paths route through the Finder mount.
        var isMac = navigator.platform.indexOf('Mac') !== -1;
        var hostPath = isMac ? info.posix_path : info.win_path;

        showProgress('Importing live file…', 60);
        await importFileToPremiereProject(hostPath);

        // Remember this asset so the Relink button knows what to swap later.
        state.importedAssets['live:' + asset.id] = {
            assetId:     asset.id,
            displayName: info.display_name,
            livePath:    hostPath,
        };
        try { localStorage.setItem('mam_imported_assets', JSON.stringify(state.importedAssets)); } catch (_) {}

        startLiveStatusPoll(asset.id);
        hideProgress();
        showSuccessMessage('Mounted live: ' + info.display_name);
    } catch (err) {
        hideProgress();
        showErrorMessage('Mount live failed: ' + err.message);
    } finally {
        elements.mountLiveBtn.disabled = !(state.selectedAsset && state.selectedAsset.status === 'live');
    }
}

// Poll the asset until its status flips from 'live' to 'ready' so we can
// surface the Relink button. 5s cadence — matches the worker's promotion
// scan interval, so we typically catch the transition on the next tick.
var _livePolls = {};
function startLiveStatusPoll(assetId) {
    if (_livePolls[assetId]) return;
    _livePolls[assetId] = setInterval(async function () {
        try {
            var r = await fetch(state.serverUrl + '/api/v1/assets/' + assetId, {
                headers: { Accept: 'application/json' },
                credentials: 'include',
            });
            if (!r.ok) return;
            var a = await r.json();
            if (a.status === 'ready') {
                clearInterval(_livePolls[assetId]);
                delete _livePolls[assetId];
                logMessage('Live asset ' + assetId + ' finalized — relink available');
                if (state.selectedAsset && state.selectedAsset.id === assetId) {
                    state.selectedAsset = a;
                    showAssetDetails(a);
                }
                _showFlash('"' + (a.display_name || assetId) + '" finalized — click Relink to swap to hi-res', 'info-message');
            }
        } catch (_) { /* transient — try again next tick */ }
    }, 5000);
}

async function relinkSelectedAsset() {
    if (!state.selectedAsset) return;
    var asset = state.selectedAsset;
    var entry = state.importedAssets['live:' + asset.id];
    if (!entry) {
        showErrorMessage('No live mount recorded for this asset');
        return;
    }

    try {
        elements.relinkBtn.disabled = true;
        showProgress('Fetching hi-res link…', 10);

        var hires = await getHiresDownloadInfo(asset.id);
        var safeName = sanitizeFilename(hires.filename || (asset.display_name || asset.id) + '.mov');

        showProgress('Downloading hi-res' + (hires.file_size ? ' (' + formatFileSize(hires.file_size) + ')' : '') + '…', 20);
        var localPath = await downloadFile(hires.url, safeName);

        showProgress('Relinking in Premiere…', 85);
        await relinkInPremiere(entry.livePath, localPath);

        saveImportMapping(localPath, safeName, asset);
        hideProgress();
        showSuccessMessage('Relinked to hi-res: ' + safeName);
    } catch (err) {
        hideProgress();
        showErrorMessage('Relink failed: ' + err.message);
    } finally {
        elements.relinkBtn.disabled = false;
    }
}

// Walks every project item and swaps clip media paths matching `oldPath`
// onto `newPath`. Premiere's ProjectItem.changeMediaPath() does the relink
// without touching timeline placement.
function relinkInPremiere(oldPath, newPath) {
    var oldEsc = oldPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var newEsc = newPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var script = [
        '(function () {',
        '  var out = { success: false, relinked: 0, message: "" };',
        '  try {',
        '    if (!app.project) { out.message = "No project open"; return JSON.stringify(out); }',
        '    var oldPath = "' + oldEsc + '";',
        '    var newPath = "' + newEsc + '";',
        '    function walk(item) {',
        '      for (var i = 0; i < item.children.numItems; i++) {',
        '        var c = item.children[i];',
        '        if (c.type === 1 || c.type === 2) {',  /* clip or sub-clip */
        '          if (c.getMediaPath() === oldPath) {',
        '            c.changeMediaPath(newPath);',
        '            out.relinked++;',
        '          }',
        '        }',
        '        if (c.children && c.children.numItems > 0) walk(c);',
        '      }',
        '    }',
        '    walk(app.project.rootItem);',
        '    out.success = out.relinked > 0;',
        '    out.message = out.relinked + " clip(s) relinked";',
        '  } catch (e) { out.message = e.message; }',
        '  return JSON.stringify(out);',
        '})();',
    ].join('\n');

    return new Promise(function (resolve, reject) {
        csInterface.evalScript(script, function (resultStr) {
            try {
                var parsed = JSON.parse(resultStr);
                if (parsed.success) resolve(parsed);
                else reject(new Error(parsed.message || 'relink found no matching clips'));
            } catch (e) {
                reject(new Error('ExtendScript error: ' + resultStr));
            }
        });
    });
}

-    elements.importBtn.disabled      = false;

-    elements.importHiresBtn.disabled = false;

+    var isLive = asset.status === 'live';

+    elements.importBtn.disabled      = isLive;

+    elements.importHiresBtn.disabled = isLive;

+    elements.mountLiveBtn.disabled   = !isLive;

+    // The relink button only makes sense once the live file has been finalized

+    // AND we have a record of it being mounted in this session.

+    elements.relinkBtn.disabled = !(asset.status === 'ready' && state.importedAssets['live:' + asset.id]);

elements.importBtn.disabled      = true;

+    elements.mountLiveBtn.disabled   = true;

+    elements.relinkBtn.disabled      = true;

+// Mount Live — open the growing file directly from the SMB share

+        showErrorMessage('No asset selected');

+        elements.mountLiveBtn.disabled = true;

+        showProgress('Resolving SMB path…', 10);

+        var res = await fetch(state.serverUrl + '/api/v1/assets/' + asset.id + '/live-path', {

+            headers: { Accept: 'application/json' },

+            var body = await res.json().catch(function () { return {}; });

+            throw new Error(body.error || ('HTTP ' + res.status));

+        // Premiere on Windows wants UNC paths with backslashes; on Mac

+        // POSIX-style smb:// paths route through the Finder mount.

+        var isMac = navigator.platform.indexOf('Mac') !== -1;

+        var hostPath = isMac ? info.posix_path : info.win_path;

+        showProgress('Importing live file…', 60);

+        await importFileToPremiereProject(hostPath);

+        // Remember this asset so the Relink button knows what to swap later.

+        state.importedAssets['live:' + asset.id] = {

+            displayName: info.display_name,

+        try { localStorage.setItem('mam_imported_assets', JSON.stringify(state.importedAssets)); } catch (_) {}

+        showSuccessMessage('Mounted live: ' + info.display_name);

+        showErrorMessage('Mount live failed: ' + err.message);

+        elements.mountLiveBtn.disabled = !(state.selectedAsset && state.selectedAsset.status === 'live');

+// Poll the asset until its status flips from 'live' to 'ready' so we can

+// surface the Relink button. 5s cadence — matches the worker's promotion

+// scan interval, so we typically catch the transition on the next tick.

+    _livePolls[assetId] = setInterval(async function () {

+            var r = await fetch(state.serverUrl + '/api/v1/assets/' + assetId, {

+                headers: { Accept: 'application/json' },

+                credentials: 'include',

+            if (a.status === 'ready') {

+                clearInterval(_livePolls[assetId]);

+                delete _livePolls[assetId];

+                logMessage('Live asset ' + assetId + ' finalized — relink available');

+                if (state.selectedAsset && state.selectedAsset.id === assetId) {

+                    state.selectedAsset = a;

+                    showAssetDetails(a);

+                _showFlash('"' + (a.display_name || assetId) + '" finalized — click Relink to swap to hi-res', 'info-message');

+        } catch (_) { /* transient — try again next tick */ }

+    var entry = state.importedAssets['live:' + asset.id];

+        showErrorMessage('No live mount recorded for this asset');

+        elements.relinkBtn.disabled = true;

+        showProgress('Fetching hi-res link…', 10);

+        var hires = await getHiresDownloadInfo(asset.id);

+        var safeName = sanitizeFilename(hires.filename || (asset.display_name || asset.id) + '.mov');

+        showProgress('Downloading hi-res' + (hires.file_size ? ' (' + formatFileSize(hires.file_size) + ')' : '') + '…', 20);

+        var localPath = await downloadFile(hires.url, safeName);

+        showProgress('Relinking in Premiere…', 85);

+        await relinkInPremiere(entry.livePath, localPath);

+        saveImportMapping(localPath, safeName, asset);

+        showSuccessMessage('Relinked to hi-res: ' + safeName);

+        showErrorMessage('Relink failed: ' + err.message);

+        elements.relinkBtn.disabled = false;

+// Walks every project item and swaps clip media paths matching `oldPath`

+// onto `newPath`. Premiere's ProjectItem.changeMediaPath() does the relink

+// without touching timeline placement.

+function relinkInPremiere(oldPath, newPath) {

+    var oldEsc = oldPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');

+    var newEsc = newPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');

+        '  var out = { success: false, relinked: 0, message: "" };',

+        '    if (!app.project) { out.message = "No project open"; return JSON.stringify(out); }',

+        '    var oldPath = "' + oldEsc + '";',

+        '    var newPath = "' + newEsc + '";',

+        '      for (var i = 0; i < item.children.numItems; i++) {',

+        '        var c = item.children[i];',

+        '        if (c.type === 1 || c.type === 2) {',  /* clip or sub-clip */

+        '          if (c.getMediaPath() === oldPath) {',

+        '            c.changeMediaPath(newPath);',

+        '        if (c.children && c.children.numItems > 0) walk(c);',

+        '    walk(app.project.rootItem);',

+        '    out.success = out.relinked > 0;',

+        '    out.message = out.relinked + " clip(s) relinked";',

+        '  } catch (e) { out.message = e.message; }',

+        '  return JSON.stringify(out);',

+    return new Promise(function (resolve, reject) {

+        csInterface.evalScript(script, function (resultStr) {

+                var parsed = JSON.parse(resultStr);

+                if (parsed.success) resolve(parsed);

+                else reject(new Error(parsed.message || 'relink found no matching clips'));

+                reject(new Error('ExtendScript error: ' + resultStr));


Edit

        importBtn:         document.getElementById('import-btn'),
        importHiresBtn:    document.getElementById('import-hires-btn'),
        importAllBtn:      document.getElementById('import-all-btn'),
        exportTimelineBtn: document.getElementById('export-timeline-btn'),

        importBtn:         document.getElementById('import-btn'),
        importHiresBtn:    document.getElementById('import-hires-btn'),
        importAllBtn:      document.getElementById('import-all-btn'),
        mountLiveBtn:      document.getElementById('mount-live-btn'),
        relinkBtn:         document.getElementById('relink-btn'),
        exportTimelineBtn: document.getElementById('export-timeline-btn'),


        importBtn:         document.getElementById('import-btn'),
        importHiresBtn:    document.getElementById('import-hires-btn'),
        importAllBtn:      document.getElementById('import-all-btn'),
        exportTimelineBtn: document.getElementById('export-timeline-btn'),

        importBtn:         document.getElementById('import-btn'),
        importHiresBtn:    document.getElementById('import-hires-btn'),
        importAllBtn:      document.getElementById('import-all-btn'),
        mountLiveBtn:      document.getElementById('mount-live-btn'),
        relinkBtn:         document.getElementById('relink-btn'),
        exportTimelineBtn: document.getElementById('export-timeline-btn'),

+        mountLiveBtn:      document.getElementById('mount-live-btn'),

+        relinkBtn:         document.getElementById('relink-btn'),

progressLabel:     document.getElementById('progress-label'),


    elements.importBtn.addEventListener('click', importSelectedAsset);
    elements.importHiresBtn.addEventListener('click', importSelectedAssetHires);
    elements.importAllBtn.addEventListener('click', importAllAssets);
    elements.exportTimelineBtn.addEventListener('click', startExportTimeline);

    elements.importBtn.addEventListener('click', importSelectedAsset);
    elements.importHiresBtn.addEventListener('click', importSelectedAssetHires);
    elements.importAllBtn.addEventListener('click', importAllAssets);
    elements.mountLiveBtn.addEventListener('click', mountLiveAsset);
    elements.relinkBtn.addEventListener('click', relinkSelectedAsset);
    elements.exportTimelineBtn.addEventListener('click', startExportTimeline);


    elements.importBtn.addEventListener('click', importSelectedAsset);
    elements.importHiresBtn.addEventListener('click', importSelectedAssetHires);
    elements.importAllBtn.addEventListener('click', importAllAssets);
    elements.exportTimelineBtn.addEventListener('click', startExportTimeline);

    elements.importBtn.addEventListener('click', importSelectedAsset);
    elements.importHiresBtn.addEventListener('click', importSelectedAssetHires);
    elements.importAllBtn.addEventListener('click', importAllAssets);
    elements.mountLiveBtn.addEventListener('click', mountLiveAsset);
    elements.relinkBtn.addEventListener('click', relinkSelectedAsset);
    elements.exportTimelineBtn.addEventListener('click', startExportTimeline);

+    elements.mountLiveBtn.addEventListener('click', mountLiveAsset);

+    elements.relinkBtn.addEventListener('click', relinkSelectedAsset);


    elements.importBtn.disabled      = false;
    elements.importHiresBtn.disabled = false;
}

function hideAssetDetails() {
    state.selectedAsset = null;
    elements.detailsPanel.classList.add('hidden');
    elements.importBtn.disabled      = true;
    elements.importHiresBtn.disabled = true;
}

    var isLive = asset.status === 'live';
    elements.importBtn.disabled      = isLive;
    elements.importHiresBtn.disabled = isLive;
    elements.mountLiveBtn.disabled   = !isLive;
    // The relink button only makes sense once the live file has been finalized
    // AND we have a record of it being mounted in this session.
    elements.relinkBtn.disabled = !(asset.status === 'ready' && state.importedAssets['live:' + asset.id]);
}

function hideAssetDetails() {
    state.selectedAsset = null;
    elements.detailsPanel.classList.add('hidden');
    elements.importBtn.disabled      = true;
    elements.importHiresBtn.disabled = true;
    elements.mountLiveBtn.disabled   = true;
    elements.relinkBtn.disabled      = true;
}

// ============================================================================
// Mount Live — open the growing file directly from the SMB share
// ============================================================================

async function mountLiveAsset() {
    if (!state.selectedAsset) {
        showErrorMessage('No asset selected');
        return;
    }
    var asset = state.selectedAsset;
    try {
        elements.mountLiveBtn.disabled = true;
        showProgress('Resolving SMB path…', 10);

        var res = await fetch(state.serverUrl + '/api/v1/assets/' + asset.id + '/live-path', {
            headers: { Accept: 'application/json' },
            credentials: 'include',
        });
        if (!res.ok) {
            var body = await res.json().catch(function () { return {}; });
            throw new Error(body.error || ('HTTP ' + res.status));
        }
        var info = await res.json();

        // Premiere on Windows wants UNC paths with backslashes; on Mac
        // POSIX-style smb:// paths route through the Finder mount.
        var isMac = navigator.platform.indexOf('Mac') !== -1;
        var hostPath = isMac ? info.posix_path : info.win_path;

        showProgress('Importing live file…', 60);
        await importFileToPremiereProject(hostPath);

        // Remember this asset so the Relink button knows what to swap later.
        state.importedAssets['live:' + asset.id] = {
            assetId:     asset.id,
            displayName: info.display_name,
            livePath:    hostPath,
        };
        try { localStorage.setItem('mam_imported_assets', JSON.stringify(state.importedAssets)); } catch (_) {}

        startLiveStatusPoll(asset.id);
        hideProgress();
        showSuccessMessage('Mounted live: ' + info.display_name);
    } catch (err) {
        hideProgress();
        showErrorMessage('Mount live failed: ' + err.message);
    } finally {
        elements.mountLiveBtn.disabled = !(state.selectedAsset && state.selectedAsset.status === 'live');
    }
}

// Poll the asset until its status flips from 'live' to 'ready' so we can
// surface the Relink button. 5s cadence — matches the worker's promotion
// scan interval, so we typically catch the transition on the next tick.
var _livePolls = {};
function startLiveStatusPoll(assetId) {
    if (_livePolls[assetId]) return;
    _livePolls[assetId] = setInterval(async function () {
        try {
            var r = await fetch(state.serverUrl + '/api/v1/assets/' + assetId, {
                headers: { Accept: 'application/json' },
                credentials: 'include',
            });
            if (!r.ok) return;
            var a = await r.json();
            if (a.status === 'ready') {
                clearInterval(_livePolls[assetId]);
                delete _livePolls[assetId];
                logMessage('Live asset ' + assetId + ' finalized — relink available');
                if (state.selectedAsset && state.selectedAsset.id === assetId) {
                    state.selectedAsset = a;
                    showAssetDetails(a);
                }
                _showFlash('"' + (a.display_name || assetId) + '" finalized — click Relink to swap to hi-res', 'info-message');
            }
        } catch (_) { /* transient — try again next tick */ }
    }, 5000);
}

async function relinkSelectedAsset() {
    if (!state.selectedAsset) return;
    var asset = state.selectedAsset;
    var entry = state.importedAssets['live:' + asset.id];
    if (!entry) {
        showErrorMessage('No live mount recorded for this asset');
        return;
    }

    try {
        elements.relinkBtn.disabled = true;
        showProgress('Fetching hi-res link…', 10);

        var hires = await getHiresDownloadInfo(asset.id);
        var safeName = sanitizeFilename(hires.filename || (asset.display_name || asset.id) + '.mov');

        showProgress('Downloading hi-res' + (hires.file_size ? ' (' + formatFileSize(hires.file_size) + ')' : '') + '…', 20);
        var localPath = await downloadFile(hires.url, safeName);

        showProgress('Relinking in Premiere…', 85);
        await relinkInPremiere(entry.livePath, localPath);

        saveImportMapping(localPath, safeName, asset);
        hideProgress();
        showSuccessMessage('Relinked to hi-res: ' + safeName);
    } catch (err) {
        hideProgress();
        showErrorMessage('Relink failed: ' + err.message);
    } finally {
        elements.relinkBtn.disabled = false;
    }
}

// Walks every project item and swaps clip media paths matching `oldPath`
// onto `newPath`. Premiere's ProjectItem.changeMediaPath() does the relink
// without touching timeline placement.
function relinkInPremiere(oldPath, newPath) {
    var oldEsc = oldPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var newEsc = newPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var script = [
        '(function () {',
        '  var out = { success: false, relinked: 0, message: "" };',
        '  try {',
        '    if (!app.project) { out.message = "No project open"; return JSON.stringify(out); }',
        '    var oldPath = "' + oldEsc + '";',
        '    var newPath = "' + newEsc + '";',
        '    function walk(item) {',
        '      for (var i = 0; i < item.children.numItems; i++) {',
        '        var c = item.children[i];',
        '        if (c.type === 1 || c.type === 2) {',  /* clip or sub-clip */
        '          if (c.getMediaPath() === oldPath) {',
        '            c.changeMediaPath(newPath);',
        '            out.relinked++;',
        '          }',
        '        }',
        '        if (c.children && c.children.numItems > 0) walk(c);',
        '      }',
        '    }',
        '    walk(app.project.rootItem);',
        '    out.success = out.relinked > 0;',
        '    out.message = out.relinked + " clip(s) relinked";',
        '  } catch (e) { out.message = e.message; }',
        '  return JSON.stringify(out);',
        '})();',
    ].join('\n');

    return new Promise(function (resolve, reject) {
        csInterface.evalScript(script, function (resultStr) {
            try {
                var parsed = JSON.parse(resultStr);
                if (parsed.success) resolve(parsed);
                else reject(new Error(parsed.message || 'relink found no matching clips'));
            } catch (e) {
                reject(new Error('ExtendScript error: ' + resultStr));
            }
        });
    });
}


Recon: clone repo, snapshot deployed UI, inventory TrueNAS storage

Proxy worker: handle odd dimensions + image inputs + bad probe

Wire real video preview into Recorders + Monitors (HlsPreview)

Settings page: real tabs + GPU/Growing/SDI/AMPP panels

Add /settings/growing endpoint + capture-manager growing-file mode

Wire recorders.js to pass GROWING env to capture container; bind /growing mount

Promotion worker: SMB → S3 on idle

Provision SMB growing-files share on TrueNAS NVME

Premier CEP panel: surface growing assets + Mount Live button

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


Lets do a debugging & polish session on https://forge.wilddragon.net/zgaetano/wild-dragon , its currently deployed to Truenas. http://10.0.0.25:47434/   SRT / Recorder / Ingest page is still buggy, pr…


Debug ingest, monitors, buttons and prototype file editing


TodoWrite

Recon: clone repo, snapshot deployed UI, inventory TrueNAS storage

Proxy worker: handle odd dimensions + image inputs + bad probe

Wire real video preview into Recorders + Monitors

Settings page: real tabs + GPU/Growing/SDI/AMPP panels

/settings/growing API + capture-manager growing-file write

Wire GROWING env + bind /growing mount on recorder start

Promotion worker: SMB → S3 on idle

Premier CEP panel: Mount Live + Relink + live→ready polling

Provision SMB growing-files share on TrueNAS NVME

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Promotion worker: SMB → S3 on idle

Button audit pass + small UX fixes

Deploy to zampp1 + push to main

Wire real video preview into Recorders + Monitors

/settings/growing API + capture-manager growing-file write

Wire GROWING env + bind /growing mount on recorder start

Promotion worker: SMB → S3 on idle

Premier CEP panel: Mount Live + Relink + live→ready polling

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


NVME/MAM-growing

Wild Dragon MAM growing-files landing zone (SMB-mounted by editors, promoted to S3 on idle)


mam-growing

Wild Dragon MAM — growing files (capture lands here; promotion worker uploads to S3)


zfs set acltype=posix NVME/MAM-growing && chmod 0777 /mnt/NVME/MAM-growing && ls -ld /mnt/NVME/MAM-growing


Error 422: Validation error. {'data.ro': [{'message': 'Extra inputs are not permitted', 'errno': 22}], 'data.guestok': [{'message': 'Extra inputs are not permitted', 'errno': 22}]}


$ zfs set acltype=posix NVME/MAM-growing && chmod 0777 /mnt/NVME/MAM-growing && ls -ld /mnt/NVME/MAM-growing

drwxrwxrwx 2 root root 2 May 22 18:56 /mnt/NVME/MAM-growing


Wild Dragon MAM — growing files (capture lands here; promotion worker uploads to S3)


midclt call sharing.smb.create '{"name": "mam-growing", "path": "/mnt/NVME/MAM-growing", "comment": "Wild Dragon MAM growing files", "purpose": "DEFAULT_SHARE", "enabled": true}'


$ midclt call sharing.smb.create '{"name": "mam-growing", "path": "/mnt/NVME/MAM-growing", "comment": "Wild Dragon MAM growing files", "purpose": "DEFAULT_SHARE", "enabled": true}'

{"id": 19, "purpose": "DEFAULT_SHARE", "name": "mam-growing", "path": "/mnt/NVME/MAM-growing", "enabled": true, "comment": "Wild Dragon MAM growing files", "readonly": false, "browsable": true, "access_based_share_enumeration": false, "locked": false, "audit": {"enable": false, "watch_list": [], "ignore_list": []}, "options": {"aapl_name_mangling": false, "hostsallow": [], "hostsdeny": []}}


SMB share created on TrueNAS as `mam-growing` (id 19, path `/mnt/NVME/MAM-growing`). Now let me commit and push everything to forgejo, then run deployment on zampp1.


Bash

cd /tmp/wd/wild-dragon && git config user.email "zgaetano@wilddragon.net" && git config user.name "Zachary Gaetano" && git status

Configure git author and view status


On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   docker-compose.yml
	modified:   services/capture/src/capture-manager.js
	modified:   services/mam-api/src/routes/assets.js
	modified:   services/mam-api/src/routes/recorders.js
	modified:   services/mam-api/src/routes/settings.js
	modified:   services/premiere-plugin/index.html
	modified:   services/premiere-plugin/js/main.js
	modified:   services/web-ui/public/screens-admin.jsx
	modified:   services/web-ui/public/screens-ingest.jsx
	modified:   services/worker/package.json
	modified:   services/worker/src/ffmpeg/executor.js
	modified:   services/worker/src/index.js
	modified:   services/worker/src/s3/client.js
	modified:   services/worker/src/workers/proxy.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	services/worker/src/workers/promotion.js

no changes added to commit (use "git add" and/or "git commit -a")
Shell cwd was reset to /home/node


# MeshCentral Devices (86 found)

## AMPP

### Stark
- **Status**: offline
- **Node ID**: `node//DziCjwwlgKKccJGyPA27l$iNq2u0j8ocveN3iFO2n8rNkqnlEfkSdl1SavjN1g3k`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### Targaryen
- **Status**: offline
- **Node ID**: `node//bYMlI1JuRrs4PLbtoTZ7HgQidPEGVRZlIu0R15wFcaBk4faTBFVd99qE8BxxBVrF`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### baratheon
- **Status**: offline
- **Node ID**: `node//oj7dnjKQv3fyJm$2q1ZojV8DoC73sOP03QdumVLpFdNn6HCFsNjTlXeg44CTVZFa`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### bert
- **Status**: online
- **Node ID**: `node//PxaegfDULqnnDbWcbcoEGbmszHc4tcU6NimdpPSW8DLVv4dp7Ea1QOtwLMPvbo9C`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### ernie
- **Status**: online
- **Node ID**: `node//MGsCO9f2A8HbZU4Npikbln68I7FA0hQ@cHWaomAX0Evc2VhR4ZJA2kY@Q45CxZYH`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

## BMG

### BMGDOCKER
- **Status**: offline
- **Node ID**: `node//um4NVaBbYQeN7gm@Zs7xu1HYrEdr$Qv6k@enSYyPqMLQcfrPY6viWcgDXuOrcY@x`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.2 LTS

### RMU-2 4000
- **Status**: offline
- **Node ID**: `node//jG1jFsO6BnTVCqAXowSc1NXkTtiNaF2gtc$7L0fZcRcnm1$ecbK768mTE@9khT$m`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 22.04.5 LTS

### bmgvmnode-Standard-PC-i440FX-PIIX-1996
- **Status**: offline
- **Node ID**: `node//XpwZLUaCASal4vEQdIC6rsvY$mCVi$RQEM4KFUmID1WfBbpJKmvoVPcVZNRDAHxD`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### bmgvmnode-Standard-PC-i440FX-PIIX-1996
- **Status**: offline
- **Node ID**: `node//OkqmsVJT2wL$f9llmDUvF0o4t3QJq0hiFPyV2HmHz6s2iPZKONLTvFS26Wz$cD8U`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### bmgvmnode-Standard-PC-i440FX-PIIX-1996
- **Status**: offline
- **Node ID**: `node//a@GPiCvrYUdWoE2OG4OS2qshApry18$$efiLCnFfIr1cnUwjEn9GSAhp4fRuj3fN`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### liveu-mmh
- **Status**: offline
- **Node ID**: `node//MrWzVVD3DoFNBNpKq7YVWEuelO@E2hha@rMlt$1EqIkEMqAuObLZUPoAIEL5IbFk`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 22.04.5 LTS

## CVS

### CVS - Engineering
- **Status**: online
- **Node ID**: `node//Q1puwG0oTN$w5AxMMricUJ7WxqXLgi@74D1um1vcGHObHtb7JYLK@VGf0Ecq7EIh`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### CVS - Teams 1 NEW
- **Status**: online
- **Node ID**: `node//5BSMANOEtOGHM6v$1FQ90SfKWedj9doSBI66hFIlbZM48fLaQzyLYKGfbQG2kVCU`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### CVS A1 PCR2
- **Status**: online
- **Node ID**: `node//3npuTQYtqdpkNMkzqi3P2deB6tNfJlK6z17lD0txvVYCtxIqKVOWijM2R7xEoYFe`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### CVS Tria
- **Status**: online
- **Node ID**: `node//VRYZWkfrIxK0v5yr2ca7re9QudHFOeywlwitoxBS0xFJpVSV$i1iau2XPCyrmEII`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 IoT Enterprise LTSC - 21H2/19044

### Teams 2 CVS
- **Status**: online
- **Node ID**: `node//AWp8HLVoVxFXa4rD5VPDtUcXxaW2CKbJMEAXrwnVJZnSZSTbitaQvIq1uhlcq9n5`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

## Commanders

### COMMANDSPROD2
- **Status**: online
- **Node ID**: `node//Sso2tmwNVCfdZ7DhfBewpR5sM3Ms0iENeTMuFgqVWQvKhey2Hdz1GS3UDlHwiVhz`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 23H2/22631

### COMMPROD1
- **Status**: online
- **Node ID**: `node//sdq@wqcyEAmf$aQ9kQ3PfXH$dxi1ICV$UQcQO@Jt3XJWIIv1tSqnHOntAxlh3ezN`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 23H2/22631

### CORPREMOTE
- **Status**: offline
- **Node ID**: `node//JT8FzGvHRuHaxTVPb7cLZIylL1AW61@jFNBrYZW3LM9vZFZF@BgP6hB8$n2tMxyQ`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 23H2/22631

### CommandersWall
- **Status**: online
- **Node ID**: `node//sDylu2PiZVwju4ndY3$VB35ApbasM9w6dVCRSGUNCgYx6z6tK7jeu4Yam6qTXoZL`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro - 22H2/19045

### DESKTOP-981DTQD
- **Status**: offline
- **Node ID**: `node//$GN5Oa4zR8TLVd4kqm1S1yLH6Oo@eyUFOkZl10jhw$MZAju8jpg40cPfksHolJNM`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Home - 23H2/22631

### FINGERWORKS
- **Status**: online
- **Node ID**: `node//Xrq@vloZ8bFqLMhzh5TqtbjVrZy1jM30okB65eHNuPysCJrJjdgoh9@UsUN0$QWn`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### JWCDC14
- **Status**: offline
- **Node ID**: `node//LADS$dN5LsAJOyLrCDEA5SDfwOFgUrLy7WtyTtAk7Xvf3BaNqY$ow6ob$9k1zWbM`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows Server 2022 Standard - 21H2/20348

### Lighting Board
- **Status**: online
- **Node ID**: `node//skbvJJFpbU2NnNZGsJFQFlnoZwAWMd6bm1or2RQgfOgGbaEDVJMxN@qg@ZUx@WK@`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro for Workstations - 22H2/19045

### RiggoPixera1
- **Status**: offline
- **Node ID**: `node//@LuCyriVBvsDm@Jp5YfRETpwUWv0yEYFOd83Ghe5B@nTR2M5hZA6jmgsGqqMlC8i`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### Stadium - IFB
- **Status**: offline
- **Node ID**: `node//yGypymaqHZn6z84PkZPb2E4PifJRsMTQHEtGQyLttrUAVmoVeX6OkyMh4ONw9liQ`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro - 22H2/19045

### Stadium - PL
- **Status**: offline
- **Node ID**: `node//H7w4JIaxEsg0b7@qB7fIIypmNdUEEVxNZ02BY1lDWLRWd2eNc1wwXeYoN5Cokh3f`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro - 22H2/19045

### Unity
- **Status**: online
- **Node ID**: `node//iTkbyQd8mZg4dsh5EniDF7eMLejlfIhlxXVXeF5FtHucy$LII4NNeRRVR9z$0ZJ1`
- **IP**: 10.0.0.25
- **OS**: macOS 11.4

### liveu-mmh
- **Status**: online
- **Node ID**: `node//9lHBjLzWysWbmbUgpYsZlRgzH7YrFWgIsxCJjtfX9kFDYscDo1Ssz9EvyocfPP$x`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 22.04.5 LTS

### liveu-videoreturnserver
- **Status**: online
- **Node ID**: `node//CUbJTnqVRMPJAWtRQNdQzkhgyVXYihMYBySGfQIBnydX5GRvZ@03gTQtl9N4oYzU`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 22.04.4 LTS

### vMix
- **Status**: online
- **Node ID**: `node//jZWAyROu1aSQM$69qvdxuAIz@B3EP2Qm$Q1la8MoyC9sB30s7lyGafzUDUsJcA83`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

## Deployment

### AMPP Monitor
- **Status**: offline
- **Node ID**: `node//IMQmesHm0oaDS6HmavgcCbsxfsJOeBz8PGo@Kuyi3YMjwkSo7WsvTIHLrBAoqsaB`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### BMG-CONFERENCEROOM
- **Status**: offline
- **Node ID**: `node//2aDTHhB831T5la9LULPhyQYiTX7uBNScLm4OmX3P4H6$7bTG3JxWU0l1@KsOPX@y`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### FredNatsReplay
- **Status**: online
- **Node ID**: `node//9ak5BblTwPxa2c@1L93sgnD2LtASvODrlp9VdsHa1NJVTnNlBnHfQYl@v@C5zIMK`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### Intuit XR
- **Status**: online
- **Node ID**: `node//uog6aYqDtbJP1E2ehUbvBPXPmV3@drpfD@QWHrc7JYmuMYeSknWzuZRhPTW@EBdm`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### JudyLoy-PC
- **Status**: offline
- **Node ID**: `node//sj$LUkt@8miwbC3k3lCwAa6$6Vt$EbiigLN$U2njC3$rI@zznFArOLLmouY0N5xP`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Home - 22H2/19045

### MINIPC-E5H6X
- **Status**: offline
- **Node ID**: `node//IMK0@lEP7nmywTKIcuR3tjBFdjm@hNJuUl25Zl7ah@SHoZYm916tdOwA8J0P6aEn`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### TriVision
- **Status**: offline
- **Node ID**: `node//dpj9Dzr9OxZ0nZf3aWeMsMqH2$5SpsylnkiUVkZ2qZIzRApj3tCDQsfCBpgW$pD0`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Home - 25H2/26200

### antmedia-Standard-PC-i440FX-PIIX-1996
- **Status**: offline
- **Node ID**: `node//r9Y0YUOQWVc3ZEYT2tKwuioUJtMrgp$YAh$YkEiPYraZ6nPKnA1iqMHPy$8jMUdo`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### bmgvmnode-Standard-PC-i440FX-PIIX-1996
- **Status**: offline
- **Node ID**: `node//jxAYn9bXOpV3nuU@IGquqkuCKd8wT8O5bllSbCqB1ZTt0tpaBQBadpg3CstKrNsx`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### claudecode
- **Status**: offline
- **Node ID**: `node//nOe$DKg1KkmyqyhdIw@$k6tsXKu9EEBT9$uWqo2MfpPJI8R9KVf5FnT52X4HbmGA`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### zacoffice
- **Status**: offline
- **Node ID**: `node//fVfOotodUqGARvpgn6j@nNCoa9z6QAUtnlzfgsE1amDszTQ0Adg0JI7VczY7nxq5`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

## Monumental

### DRAGONAIR-MSE1
- **Status**: offline
- **Node ID**: `node//Aof04tOeZFa$NS@q@FsBHnxsi2gDa02jMYz$wtBe0$qffjVdSAs9wvMdjHMcJCwA`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### DRAGONAIR-MSE2
- **Status**: offline
- **Node ID**: `node//2rk0GkSxEV7rPSXl7mtELbjPfwTwQ3OUVFT6haB1lcHTd4rsob1kgtLrSeo0aUbZ`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### DRAGONAIR-MSE3
- **Status**: offline
- **Node ID**: `node//ddDCxUqJz@n9xyqdQX6XKbJNZBJFWwIOEjyw2Hh2jIiek05ZLme1OgcDhpQhUWvF`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### DRAGONAIR-MSE4
- **Status**: offline
- **Node ID**: `node//NLOcVKabVRpdfEKuUv5KsqFB74nbkqWnFBqlIIR5REzTG7Go3KBMVO1srraJAeN3`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

## MyLCC

### LCC-SB-VMIX
- **Status**: offline
- **Node ID**: `node//KEaMdtthztxgkskZN2oykijOPjznkEWDqP22CkIFB6E9QjOZm5HOfKyK1yOUMBGF`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

## Personal

### Claude
- **Status**: offline
- **Node ID**: `node//sWjuKOP7KXzPSJo0I0JvdQfeAi1JIFPbGnrLebOF0T$Jn8w8YoLKW6KuMlEmTxEl`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### DESKTOP-7Q34KF5
- **Status**: offline
- **Node ID**: `node//z@oO9SEPSwGP770ErlvivLxByKFnHDn1fi0GGjQAA4JCZFpEsOE3QqgwoE2Mmx$W`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### DESKTOP-PF2HUG0
- **Status**: offline
- **Node ID**: `node//1G87NS9KhDuiahVhld@T9Apr1J5WPdVDJPXxCVjJoxe2j2P4OMIo26k7GLaRqlN6`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### DESKTOP-T05O95E
- **Status**: offline
- **Node ID**: `node//xnDfc97tTFfmTmKpHRlBMSClnxIGxQA2mC8fwK5CoPERT13tMlYPAtSbZIt$0lR2`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro N - 22H2/19045

### Node 1
- **Status**: online
- **Node ID**: `node//TaK@$$4IewBkx3fzvuzXETPKsIqs01W00LbzDKohRqEXrq@rzWz0zL4vZVEXpqqH`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro for Workstations - 22H2/19045

### Node 3
- **Status**: offline
- **Node ID**: `node//z74zf22s9UenyEY3hIj3jU30B9c7BFTn98MPc63qaHsW1QLw6@Lpj2Rr89fh77Md`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro for Workstations - 22H2/19045

### WOOGLIN
- **Status**: offline
- **Node ID**: `node//$Y4$3p6EBKezFIxSqGs2e9MHhrGnrQapPTG2PizE4Sk4Spa3nnsyfyciw0BEsJRt`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### ZGaetano-MBP.local
- **Status**: online
- **Node ID**: `node//xbl9hXeRFJ$CMvlrF1VdPZ3mRf6c9aHrAkzbB1bZx4WSQ8QZp6rnlA39aCT1SLxG`
- **IP**: 10.0.0.25
- **OS**: macOS 26.5

## UBS - BMG

### DESKTOP-Q2VMBHE
- **Status**: offline
- **Node ID**: `node//zQ22LQuFwN7JBAlrs@12KFgrVzIXzenN$z2uLJBxg5QSl5TJF31X2opBWb3@gq6W`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### Ross Chamelon
- **Status**: offline
- **Node ID**: `node//23AH4XiN8DNTUVv6$4GpHjaB5Yem2DQj1yTbON@mEbn1vTQdRF3WYrwRQLbjxhWI`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro for Workstations - 25H2/26200

### TD - Thin Client
- **Status**: online
- **Node ID**: `node//qvZ5G4oCQ5tP0BTuZUl0an9VRnRImIeTnFQe4O9$UwEN9HQCOrUlzHecElSoNPoE`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### Thinclient 1
- **Status**: offline
- **Node ID**: `node//uSrHelzSYU5sBLE7uPoCzU69WNEVokiy0GILaKGS0p1@ivXzrYQx1RguyQdMPESx`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### Thinclient 2
- **Status**: offline
- **Node ID**: `node//k4nsX73ylrXnezBzzc0cCMmMZlbUWIUKz9At7dp@7Rfu3l97hzIrIMgbw8mIZ0Rj`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### Thinclient 4
- **Status**: online
- **Node ID**: `node//QXmlkR0LVfBI5mQs1x4g6t2oxkM3LBDMxBPKZM44Xr2vgejqF0OELvtRQDoOjOnW`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### Thinclient 5
- **Status**: online
- **Node ID**: `node//HxkHsICw0VDVGDFNJ3MWQf9ybkxGmTlocTUCGdWOtpvDQXOGGgdVqPo6prwbE@o5`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### UBS - EIC
- **Status**: online
- **Node ID**: `node//lx7y6k5@$ISYsvCKri733O080X1m2PSqs39Sy$SOWOtTqGvAYEnsMoxq$R$93JfV`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### UBS - Podcast
- **Status**: online
- **Node ID**: `node//n90LR02jT9t6@Ds0N5Lr2nNSIIOzAZv86K9EgqEJNZychL8mKl0aOWbkDd$$KZb@`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### UBS Soundboard
- **Status**: offline
- **Node ID**: `node//UZO$Sns99KZ1o5az$CjryiZZ37AI3Y3Fwnwh4uLOtHLMDV5u0Q5eMAUz3REaWKED`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 24H2/26100

### UBS Teams 1
- **Status**: offline
- **Node ID**: `node//upqwkH3wBZQ9kAkM$4BvSJJlWZOEy4i0w8oQOxjenTmUaP@jmqEdwcl4HvvR8nPO`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### UBS-BMG-ENGINEER
- **Status**: online
- **Node ID**: `node//TNvUJHv7H0l0RtcLbdJrpbckikcxoOc3PrTI1ZyFIqxHVGgBITJdnt5rnxukUbqX`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### UBS-Teams2
- **Status**: offline
- **Node ID**: `node//@Z3eXRbIop3EWkjtFClDhFTEdIbjiPsrM1lLlCy8XqZFYY971JhSLj6T5@lMJSsi`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### UBS-Tria
- **Status**: offline
- **Node ID**: `node//WrgUyYN@UQJEwAldpeCKFN72$Pzl5X0Anel@hbES48ih7oZfsMeC8JU4pkRMKfyM`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 7 Professional  - 7601

### Xpression-1
- **Status**: offline
- **Node ID**: `node//5biiSNjy8aY5DzjS3SrbRdDlqjY6qVaodDL5ftVHzjw79cRRSMv8KwpoDEnjyDrC`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 10 Pro - 18363

### xpression-4
- **Status**: offline
- **Node ID**: `node//dz400F1xceTc@A0IOPwPHbCaWaWHRN2fKALhPwGpQ0wXugnzw803lTQLIJhEznjt`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

## VMs - BMG

### AntMedia
- **Status**: offline
- **Node ID**: `node//7XQP2EIBlGe1eBQgnbyw0pT2ioTRciVTYA8@wgxC@IbAC9TWTslMLfnZCJCcoLFm`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### EIC1
- **Status**: offline
- **Node ID**: `node//TDAwrq4yyo76EGKec3KM$FcpS47LRlvpOTKpqt4H@jPPGp253RG3N5TsMwDXQ$Mv`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### EIC2
- **Status**: offline
- **Node ID**: `node//2LfjYV19nIKeTdh7Rb5HGhpPZkfvVRTIXft8Vb$456bp8jSxBoPJcPPPrEnRlCrZ`
- **IP**: 10.0.0.25
- **OS**: Microsoft Windows 11 Pro - 25H2/26200

### ERP-Next
- **Status**: offline
- **Node ID**: `node//pQSYQ3ZUlfdpssZHm6KPr543yuSjh39bGI8@mW9vZkzKUniL3g9Aq$jPwiuReDt3`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### Headscale BMG
- **Status**: offline
- **Node ID**: `node//ByKdSHd@TvY6iwfFZsI@Blqqip3aaivT1@XfJFcAdQKateljinNp1BEsp38l8qYK`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### Kiloview
- **Status**: offline
- **Node ID**: `node//OzV5YHxdSJOuFREhYSY5VFLb3OMeFqCczUGpRRorGtxJ29eQ@IeRTJh2l$4Zr4MI`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### VPM On-Prem Uploader
- **Status**: offline
- **Node ID**: `node//sl9hFI1NiTGDSCTGqGqcGoBNkmZFKE1rNGF9uxHEgjqIycySRcC6$22Ads2tevUi`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### VPM Uploader + MCP
- **Status**: offline
- **Node ID**: `node//OGKIChojg32cWz5167wr4QgnxRUThaRGIM$dju6kMrQVBZINhVk9@eWam9Mcei4q`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### Western-Digital
- **Status**: offline
- **Node ID**: `node//tdG@H0nCuiMBS5EADVu2xPIPNKdJ0VflyCIRJqqlc8Ruif3P4VL@6aazWxsV84NQ`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.3 LTS

### frey
- **Status**: offline
- **Node ID**: `node//WWhGN7rVsVaZzKnsD$rORkH@bB9jgODhFiaamFf43FjxHoOHSsbxMbQBClg5WY0N`
- **IP**: 10.0.0.25
- **OS**: Debian GNU/Linux 13 (trixie)

### lannister
- **Status**: offline
- **Node ID**: `node//d0G34@eBGvDf8rJuZMzIhuvNS2oud0Vo9vOgL$FQz06hv996bj28m@T$TdLpN8vy`
- **IP**: 10.0.0.25
- **OS**: Debian GNU/Linux 13 (trixie)

### mormont
- **Status**: offline
- **Node ID**: `node//KKQt3YNdKi7IY2SlJgaEbcZTk2SXN1fqlFoC@auz58Bg8h516WfwZQfJU$9rLOL8`
- **IP**: 10.0.0.25
- **OS**: Debian GNU/Linux 13 (trixie)

### westerndigital
- **Status**: offline
- **Node ID**: `node//qd$40EVg0aJEqGUxesrSxjDFCWGHGamWaNVy2Zmf$QUmb8nHBJR$YoJA@oTwy4Wd`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 24.04.4 LTS

### zampp1
- **Status**: offline
- **Node ID**: `node//tcFcVWIpGuj0Re4$WsSEDJ5egu8YoFb@rMVG@GDI9ZiiooOr7UGR@3udHBwUqAUP`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 26.04 LTS

### zampp2
- **Status**: offline
- **Node ID**: `node//4awsSpLeMla9M4MmXMQYtbO2Jh@L2WZii1Ip6gaB6IGJ6zDzWgmVxhxiJZoG1hq8`
- **IP**: 10.0.0.25
- **OS**: Ubuntu 26.04 LTS


ss -lntp | grep 47434 ; echo --- ; docker ps --filter 'name=wild' --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' 2>&1 | head -30


$ ss -lntp | grep 47434 ; echo --- ; docker ps --filter 'name=wild' --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' 2>&1 | head -30

LISTEN 0      4096                           0.0.0.0:47434      0.0.0.0:*    users:(("docker-proxy",pid=351693,fd=7))
LISTEN 0      4096                              [::]:47434         [::]:*    users:(("docker-proxy",pid=351701,fd=7))
---
NAMES                   IMAGE                 STATUS                 PORTS
wild-dragon-web-ui-1    wild-dragon-web-ui    Up About an hour       0.0.0.0:47434->80/tcp, [::]:47434->80/tcp
wild-dragon-mam-api-1   wild-dragon-mam-api   Up About an hour       0.0.0.0:47432->3000/tcp, [::]:47432->3000/tcp
wild-dragon-worker-1    wild-dragon-worker    Up 2 hours
wild-dragon-db-1        postgres:16           Up 3 hours (healthy)   0.0.0.0:47436->5432/tcp, [::]:47436->5432/tcp
wild-dragon-queue-1     redis:7-alpine        Up 3 hours             0.0.0.0:47437->6379/tcp, [::]:47437->6379/tcp
wilddragon-web          nginx:alpine          Up 31 hours            0.0.0.0:43036->80/tcp, [::]:43036->80/tcp


Even better — the stack runs directly on TrueNAS, not zampp1. Finding the deploy dir.


docker inspect wild-dragon-mam-api-1 --format '{{.HostConfig.Binds}} | {{.Config.WorkingDir}}' ; echo --- ; docker inspect wild-dragon-mam-api-1 --format '{{json .Mounts}}' | python3 -m json.tool


$ docker inspect wild-dragon-mam-api-1 --format '{{.HostConfig.Binds}} | {{.Config.WorkingDir}}' ; echo --- ; docker inspect wild-dragon-mam-api-1 --format '{{json .Mounts}}' | python3 -m json.tool

[/var/run/docker.sock:/var/run/docker.sock:rw /mnt/NVME/MAM/wild-dragon-live:/live:rw /dev/shm:/dev/shm:rw /run/dbus:/run/dbus:rw /run/systemd:/run/systemd:rw /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro] | /app
---
[
    {
        "Type": "bind",
        "Source": "/run/dbus",
        "Destination": "/run/dbus",
        "Mode": "rw",
        "RW": true,
        "Propagation": "rprivate"
    },
    {
        "Type": "bind",
        "Source": "/run/systemd",
        "Destination": "/run/systemd",
        "Mode": "rw",
        "RW": true,
        "Propagation": "rprivate"
    },
    {
        "Type": "bind",
        "Source": "/usr/bin/nvidia-smi",
        "Destination": "/usr/bin/nvidia-smi",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    },
    {
        "Type": "bind",
        "Source": "/var/run/docker.sock",
        "Destination": "/var/run/docker.sock",
        "Mode": "rw",
        "RW": true,
        "Propagation": "rprivate"
    },
    {
        "Type": "bind",
        "Source": "/mnt/NVME/MAM/wild-dragon-live",
        "Destination": "/live",
        "Mode": "rw",
        "RW": true,
        "Propagation": "rprivate"
    },
    {
        "Type": "bind",
        "Source": "/dev/shm",
        "Destination": "/dev/shm",
        "Mode": "rw",
        "RW": true,
        "Propagation": "rprivate"
    }
]


Promotion worker: SMB → S3 on idle

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


find / -name docker-compose.yml -path '*wild-dragon*' 2>/dev/null | head -5 ; echo --- ; for n in $(docker ps -q --filter 'name=wild-dragon'); do docker inspect $n --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' ; done | sort -u


$ find / -name docker-compose.yml -path '*wild-dragon*' 2>/dev/null | head -5 ; echo --- ; for n in $(docker ps -q --filter 'name=wild-dragon'); do docker inspect $n --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' ; done | sort -u

[stderr]
Error: SSH connection or command timed out.

[Exit code: -1]


docker inspect wild-dragon-mam-api-1 --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}'


$ docker inspect wild-dragon-mam-api-1 --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}'

/mnt/NVME/MAM/wild-dragon


cd /mnt/NVME/MAM/wild-dragon && git status && echo --- && git log --oneline -5 && echo --- && ls -la .env 2>/dev/null


$ cd /mnt/NVME/MAM/wild-dragon && git status && echo --- && git log --oneline -5 && echo --- && ls -la .env 2>/dev/null

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
---
3fc8fbc add per-seat/per-stream/per-month strikethrough hero to Tokens page
ceceedf probe fallback: basic TCP/UDP connectivity when capture service is offline
4864db0 probe: fallback to basic TCP/UDP connectivity check when capture service is offline
8b57a9a expand codec list, add MXF container, remove proxy settings (fixed profile)
fa787bb fix hover-to-play: remove status filter so any asset triggers stream fetch
---
-rw-r--r-- 1 root root 809 May 22 16:39 .env


ls -la /root/.git-credentials /root/.gitconfig 2>&1 ; echo --- ; git -C /mnt/NVME/MAM/wild-dragon config -l | grep -E 'credential|user|remote'


$ ls -la /root/.git-credentials /root/.gitconfig 2>&1 ; echo --- ; git -C /mnt/NVME/MAM/wild-dragon config -l | grep -E 'credential|user|remote'

ls: cannot access '/root/.git-credentials': No such file or directory
-rw-r--r-- 1 root root 177 Apr 17 15:52 /root/.gitconfig
---
user.email=zgaetano@wilddragon.net
user.name=Zac Gaetano
remote.origin.url=ssh://git@forgejo-local/zgaetano/wild-dragon.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.main.remote=origin
user.email=zgaetano@wilddragon.net
user.name=Zac Gaetano
branch.fix/srt-rtmp-thumbnail.remote=origin


cat /root/.ssh/config 2>&1 | head -40 ; echo --- ; ls /root/.ssh/ 2>&1


$ cat /root/.ssh/config 2>&1 | head -40 ; echo --- ; ls /root/.ssh/ 2>&1

Host forgejo-local
  HostName 127.0.0.1
  Port 2222
  User git
  IdentityFile ~/.ssh/forgejo_deploy
  StrictHostKeyChecking no
---
authorized_keys
config
forgejo_deploy
forgejo_deploy.pub
known_hosts
known_hosts.old


cd /tmp/wd/wild-dragon && git add -A && git diff --cached | wc -c && echo --- && git diff --cached --stat

Size up combined diff


59508
---
 docker-compose.yml                        |   4 +
 services/capture/src/capture-manager.js   |  36 +++-
 services/mam-api/src/routes/assets.js     |  58 ++++++
 services/mam-api/src/routes/recorders.js  |  12 ++
 services/mam-api/src/routes/settings.js   |  44 ++++
 services/premiere-plugin/index.html       |   7 +-
 services/premiere-plugin/js/main.js       | 173 ++++++++++++++-
 services/web-ui/public/screens-admin.jsx  | 335 +++++++++++++++++++++++-------
 services/web-ui/public/screens-ingest.jsx |  65 +++++-
 services/worker/package.json              |   1 +
 services/worker/src/ffmpeg/executor.js    |  39 +++-
 services/worker/src/index.js              |   4 +
 services/worker/src/s3/client.js          |  18 ++
 services/worker/src/workers/promotion.js  | 125 +++++++++++
 services/worker/src/workers/proxy.js      |  51 ++++-
 15 files changed, 882 insertions(+), 90 deletions(-)
Shell cwd was reset to /home/node


cd /tmp/wd/wild-dragon && git diff --cached > /tmp/wd-patch.diff && gzip -9 -c /tmp/wd-patch.diff | base64 -w0 > /tmp/wd-patch.b64 && wc -c /tmp/wd-patch.b64 && head -c 80 /tmp/wd-patch.b64 && echo

Pack diff into gzip+b64


21472 /tmp/wd-patch.b64
H4sICKDfEGoCA3dkLXBhdGNoLmRpZmYAzDztcttGkv/1FGOda0GuSJCUKFmmQmdliU60sT6OlNebUnQ0
Shell cwd was reset to /home/node


python3 << 'EOF'
import os, math
b = open('/tmp/wd-patch.b64','rb').read().decode()
chunk_size = 1800
chunks = [b[i:i+chunk_size] for i in range(0, len(b), chunk_size)]
print(f'{len(chunks)} chunks of <={chunk_size} bytes')
for i, c in enumerate(chunks):
    open(f'/tmp/wd-chunk-{i:02d}.txt', 'w').write(c)
EOF
ls /tmp/wd-chunk-*.txt | head; ls /tmp/wd-chunk-*.txt | wc -l

Split b64 patch into chunks


12 chunks of <=1800 bytes
/tmp/wd-chunk-00.txt
/tmp/wd-chunk-01.txt
/tmp/wd-chunk-02.txt
/tmp/wd-chunk-03.txt
/tmp/wd-chunk-04.txt
/tmp/wd-chunk-05.txt
/tmp/wd-chunk-06.txt
/tmp/wd-chunk-07.txt
/tmp/wd-chunk-08.txt
/tmp/wd-chunk-09.txt
12


rm -f /tmp/wd-patch.b64 /tmp/wd-patch.diff ; touch /tmp/wd-patch.b64 ; echo init-ok


View chunk 00


$ rm -f /tmp/wd-patch.b64 /tmp/wd-patch.diff ; touch /tmp/wd-patch.b64 ; echo init-ok

init-ok


Recon: clone repo, snapshot deployed UI, inventory TrueNAS storage

Proxy worker: handle odd dimensions + image inputs + bad probe

Promotion worker: SMB → S3 on idle

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


for i in $(seq -w 00 11); do echo "=== $i ==="; cat /tmp/wd-chunk-$i.txt; echo; done | head -50

Preview chunks


19672 /tmp/wd-rest.b64
1800 /tmp/wd-chunk-00.txt


for i in $(seq -w 01 11); do cat /tmp/wd-chunk-$i.txt; done > /tmp/wd-rest.b64 && wc -c /tmp/wd-rest.b64; wc -c /tmp/wd-chunk-00.txt

Concat chunks 01-11


for i in $(seq -w 00 11); do c=$(cat /tmp/wd-chunk-$i.txt); echo "CHUNK_$i_LEN=${#c}"; done

Verify chunk sizes


CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1800
CHUNK_=1672


cat /tmp/wd-chunk-00.txt | head -c 100; echo; cat /tmp/wd-chunk-11.txt | tail -c 200

Spot check first and last chunks


for i in $(seq -w 01 11); do echo "--- $i ---"; head -c 50 /tmp/wd-chunk-$i.txt; echo; done

Preview chunk starts to verify they have no shell-special chars


--- 01 ---
nv/ODwDxaTxKgNyS4MLtYsqFWwUAqwx6c2dV50Ngp7VwIAdqY/
--- 02 ---
Z0YitG+dPuaavoOtQWUSOdw/OHCaR7Y9aHpH7jHfPonMIXs+j8
--- 03 ---
oTfR3M/S44RD1A1dylKh2tt90yHE7FZx7U//cXz4+uAkI2s7t/
--- 04 ---
1onRVSg0xKXsLB46MhvNmYnNDvHu16N+E+YME+SzHGAS96lWsv
--- 05 ---
W04V//JFREXCrO6eJ/fu1PlSc+7Q5yDGQDh4cmifSMpr36T41a
--- 06 ---
I9kdpezS7Xr2CMFDmnHiiPM8PlQXgwbY5dlT8Tclexaed7iFiL
--- 07 ---
yNJxRkkE1rBAGfzmqmAmE2+BElhQ4knq32pI+iW6ua2nwxDDr1
--- 08 ---
HnvOOobZv38PNBZ3H0+TIGR1SxsNsF1kYe1kMuqGLqDT/MKrmi
--- 09 ---
ewl0Zxg9hP06pw1E3IMqllKeW3bIFHfwnBjNMihrpNgKbG2DMM
--- 10 ---
9pwPb9sNRAhdyUuBPcdlThMP6Hh00gllmyVYEVSMeo+kZY91OB
--- 11 ---
MMNK5whuqO3bd+Rj4PgHzhugEA9+CnsIbgE8OUIkpQGCVEpCpC


timeout 5 bash -c 'echo > /dev/tcp/10.0.0.25/22' 2>&1; echo "exit=$?"

Check if I can reach TrueNAS SSH from this host


md5sum /tmp/wd-patch.b64; wc -c /tmp/wd-patch.b64

Get local md5 + size


e97b877d5d09417cd6c67a2f80219709  /tmp/wd-patch.b64
21472 /tmp/wd-patch.b64


rm -f /tmp/wd-patch.b64 /tmp/wd-patch.diff ; echo reset


$ rm -f /tmp/wd-patch.b64 /tmp/wd-patch.diff ; echo reset

reset


Settings page: real tabs + GPU/Growing/SDI/AMPP panels

Promotion worker: SMB → S3 on idle

Provision SMB growing-files share on TrueNAS NVME

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


cd /tmp/wd/wild-dragon && git diff --cached --name-only | xargs -I{} wc -c {} 2>/dev/null | sort -n

File sizes of changed files


351 services/worker/package.json
1739 services/worker/src/s3/client.js
2012 services/worker/src/index.js
3528 docker-compose.yml
4449 services/worker/src/ffmpeg/executor.js
4515 services/worker/src/workers/promotion.js
5829 services/worker/src/workers/proxy.js
6899 services/premiere-plugin/index.html
11524 services/mam-api/src/routes/settings.js
16069 services/mam-api/src/routes/assets.js
17419 services/capture/src/capture-manager.js
24409 services/web-ui/public/screens-ingest.jsx
24970 services/mam-api/src/routes/recorders.js
43632 services/premiere-plugin/js/main.js
53339 services/web-ui/public/screens-admin.jsx
Shell cwd was reset to /home/node


for i in $(seq -w 00 11); do echo "=== chunk_$i ($(wc -c < /tmp/wd-chunk-$i.txt) bytes) md5=$(md5sum /tmp/wd-chunk-$i.txt | awk '{print $1}') ==="; done

Reference md5 + size for each chunk file


=== chunk_00 (1800 bytes) md5=c7d175a1b7fe988869e7653d46547f9b ===
=== chunk_01 (1800 bytes) md5=601a10445143df40fbcc5db97366a205 ===
=== chunk_02 (1800 bytes) md5=ccb5620e3b70b4ecd52769763776607e ===
=== chunk_03 (1800 bytes) md5=d6b2b4a0fe3318a897000868a392b2ae ===
=== chunk_04 (1800 bytes) md5=8c3391f9b0a4a25b4f93ccdb064b9d5c ===
=== chunk_05 (1800 bytes) md5=d9659c2284aeabb18cedefbcada20659 ===
=== chunk_06 (1800 bytes) md5=4ed8c410b230fa277b93cb4e1510cef7 ===
=== chunk_07 (1800 bytes) md5=c64284fecfe0dc8b81569637660fe625 ===
=== chunk_08 (1800 bytes) md5=cfd4bd71799b57fdbb4dbcbff1ce24de ===
=== chunk_09 (1800 bytes) md5=d435902842d9d8af8e93f0e2f1e024ff ===
=== chunk_10 (1800 bytes) md5=f6351284e5d362e28b6b40af1c2781cf ===
=== chunk_11 (1672 bytes) md5=eda437cdf9f06b6a68b1822829f99166 ===


Chunk 00 verbatim


Sending all 12 chunks in parallel to separate files, then concatenating on TrueNAS.


Chunk 01 verbatim


Chunk 02 verbatim


Chunk 03


Chunk 04


Wire real video preview into Recorders + Monitors

/settings/growing API + capture-manager growing-file write

Wire GROWING env + bind /growing mount on recorder start

Promotion worker: SMB → S3 on idle

Premier CEP panel: Mount Live + Relink + live→ready polling

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


Chunk 05


Chunk 06


Chunk 07


Chunk 08


Promotion worker: SMB → S3 on idle

Button audit pass + small UX fixes

Deploy to zampp1 + push to main


Chunk 09


Chunk 10


Chunk 11 (final)


All chunks transferred and md5 matches. Decoding and applying patch.


cd /mnt/NVME/MAM/wild-dragon && base64 -d /tmp/wd-patch.b64 | gunzip > /tmp/wd-patch.diff && wc -c /tmp/wd-patch.diff && head -5 /tmp/wd-patch.diff && echo --- && git apply --check /tmp/wd-patch.diff 2>&1 || echo CHECK_FAILED


$ cd /mnt/NVME/MAM/wild-dragon && base64 -d /tmp/wd-patch.b64 | gunzip > /tmp/wd-patch.diff && wc -c /tmp/wd-patch.diff && head -5 /tmp/wd-patch.diff && echo --- && git apply --check /tmp/wd-patch.diff 2>&1 || echo CHECK_FAILED

59508 /tmp/wd-patch.diff
diff --git a/docker-compose.yml b/docker-compose.yml
index 501123c..da15302 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -39,6 +39,7 @@ services:
---


cd /mnt/NVME/MAM/wild-dragon && git apply /tmp/wd-patch.diff && git status --short


$ cd /mnt/NVME/MAM/wild-dragon && git apply /tmp/wd-patch.diff && git status --short

 M docker-compose.yml
 M services/capture/src/capture-manager.js
 M services/mam-api/src/routes/assets.js
 M services/mam-api/src/routes/recorders.js
 M services/mam-api/src/routes/settings.js
 M services/premiere-plugin/index.html
 M services/premiere-plugin/js/main.js
 M services/web-ui/public/screens-admin.jsx
 M services/web-ui/public/screens-ingest.jsx
 M services/worker/package.json
 M services/worker/src/ffmpeg/executor.js
 M services/worker/src/index.js
 M services/worker/src/s3/client.js
 M services/worker/src/workers/proxy.js
?? services/worker/src/workers/promotion.js


Patch applied cleanly. Committing, pushing, then rebuilding the affected containers.


cd /mnt/NVME/MAM/wild-dragon && git add -A && git commit -m "feat: live HLS preview, proxy worker fixes, Settings tabs, growing-files + Premier panel

- worker/proxy: scale-to-even filter, analyzeduration 100M, skip images, hasAudio
- worker/promotion: SMB landing zone -> S3 on idle, queues proxy job, status='ready'
- web-ui screens-ingest: HlsPreview component replaces fake LiveStrip/FauxFrame
- web-ui screens-admin: functional Settings tabs (S3, GPU, Growing, SDI, AMPP)
- mam-api /settings/growing: GET/PUT growing-files config
- mam-api /assets/:id/live-path: SMB UNC/POSIX path for live growing assets
- capture-manager: GROWING_ENABLED -> write hires to /growing instead of S3 stream
- recorders.js: pass GROWING_ENABLED to capture container, bind /growing mount
- docker-compose: mount /mnt/NVME/MAM/wild-dragon-growing on mam-api + worker
- premiere-plugin: Mount Live button, Relink-to-HiRes, live->ready status poll" 2>&1 | tail -10 && git push origin main 2>&1 | tail -5


$ cd /mnt/NVME/MAM/wild-dragon && git add -A && git commit -m "feat: live HLS preview, proxy worker fixes, Settings tabs, growing-files + Premier panel

- worker/proxy: scale-to-even filter, analyzeduration 100M, skip images, hasAudio
- worker/promotion: SMB landing zone -> S3 on idle, queues proxy job, status='ready'
- web-ui screens-ingest: HlsPreview component replaces fake LiveStrip/FauxFrame
- web-ui screens-admin: functional Settings tabs (S3, GPU, Growing, SDI, AMPP)
- mam-api /settings/growing: GET/PUT growing-files config
- mam-api /assets/:id/live-path: SMB UNC/POSIX path for live growing assets
- capture-manager: GROWING_ENABLED -> write hires to /growing instead of S3 stream
- recorders.js: pass GROWING_ENABLED to capture container, bind /growing mount
- docker-compose: mount /mnt/NVME/MAM/wild-dragon-growing on mam-api + worker
- premiere-plugin: Mount Live button, Relink-to-HiRes, live->ready status poll" 2>&1 | tail -10 && git push origin main 2>&1 | tail -5

[main 328f7b4] feat: live HLS preview, proxy worker fixes, Settings tabs, growing-files + Premier panel
 15 files changed, 882 insertions(+), 90 deletions(-)
 create mode 100644 services/worker/src/workers/promotion.js
To ssh://forgejo-local/zgaetano/wild-dragon.git
   3fc8fbc..328f7b4  main -> main


TodoWrite

Recon + all code changes

Commit + push to forgejo main

Provision growing-files SMB share + zampp directory on TrueNAS host

Rebuild mam-api, worker, web-ui containers

Smoke test deployed app + verify HLS preview, Settings tabs, proxy fix

Configure growing settings + write quickstart doc for Premier panel


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Promotion worker: SMB → S3 on idle

Button audit pass + small UX fixes

Deploy to zampp1 + push to main

Recon + all code changes

Commit + push to forgejo main

Provision growing-files SMB share + zampp directory on TrueNAS host

Rebuild mam-api, worker, web-ui containers

Smoke test deployed app + verify HLS preview, Settings tabs, proxy fix

Configure growing settings + write quickstart doc for Premier panel


ln -sfn /mnt/NVME/MAM-growing /mnt/NVME/MAM/wild-dragon-growing && ls -la /mnt/NVME/MAM/wild-dragon-growing && echo --- && chmod 0777 /mnt/NVME/MAM-growing && stat /mnt/NVME/MAM-growing


$ ln -sfn /mnt/NVME/MAM-growing /mnt/NVME/MAM/wild-dragon-growing && ls -la /mnt/NVME/MAM/wild-dragon-growing && echo --- && chmod 0777 /mnt/NVME/MAM-growing && stat /mnt/NVME/MAM-growing

lrwxrwxrwx 1 root root 21 May 22 19:13 /mnt/NVME/MAM/wild-dragon-growing -> /mnt/NVME/MAM-growing
---
  File: /mnt/NVME/MAM-growing
  Size: 2         	Blocks: 1          IO Block: 512    directory
Device: 0,366	Inode: 34          Links: 2
Access: (0777/drwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2026-05-22 18:56:06.650833349 -0400
Modify: 2026-05-22 18:56:06.650833349 -0400
Change: 2026-05-22 19:13:06.001363191 -0400
 Birth: 2026-05-22 18:56:06.650833349 -0400


cd /mnt/NVME/MAM/wild-dragon && docker compose build mam-api worker web-ui 2>&1 | tail -30


$ cd /mnt/NVME/MAM/wild-dragon && docker compose build mam-api worker web-ui 2>&1 | tail -30

#29 [worker 5/6] RUN npm install --omit=dev
#29 3.161
#29 3.161 added 96 packages, and audited 97 packages in 3s
#29 3.161
#29 3.161 11 packages are looking for funding
#29 3.161   run `npm fund` for details
#29 3.162
#29 3.162 found 0 vulnerabilities
#29 3.162 npm notice
#29 3.162 npm notice New major version of npm available! 10.8.2 -> 11.15.0
#29 3.162 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.15.0
#29 3.162 npm notice To update run: npm install -g npm@11.15.0
#29 3.162 npm notice
#29 DONE 3.2s

#38 [worker 6/6] COPY . .
#38 DONE 0.0s

#39 [worker] exporting to image
#39 exporting layers 0.3s done
#39 writing image sha256:c8290c129a96a686c6f9ec1553cd63852d98a6d7cde982e5d86743accea73985 done
#39 naming to docker.io/library/wild-dragon-worker done
#39 DONE 0.3s

#40 [worker] resolving provenance for metadata file
#40 DONE 0.0s
 mam-api  Built
 worker  Built
 web-ui  Built


cd /mnt/NVME/MAM/wild-dragon && docker compose up -d --no-deps mam-api worker web-ui 2>&1 | tail -20


$ cd /mnt/NVME/MAM/wild-dragon && docker compose up -d --no-deps mam-api worker web-ui 2>&1 | tail -20

 Container wild-dragon-web-ui-1  Recreate
 Container wild-dragon-worker-1  Recreate
 Container wild-dragon-mam-api-1  Recreate
 Container wild-dragon-worker-1  Recreated
 Container wild-dragon-web-ui-1  Recreated
 Container wild-dragon-mam-api-1  Recreated
 Container wild-dragon-web-ui-1  Starting
 Container wild-dragon-mam-api-1  Starting
 Container wild-dragon-worker-1  Starting
 Container wild-dragon-worker-1  Started
 Container wild-dragon-web-ui-1  Started
 Container wild-dragon-mam-api-1  Started


sleep 4 && docker ps --filter 'name=wild-dragon' --format 'table {{.Names}}\t{{.Status}}' && echo --- && docker logs wild-dragon-mam-api-1 --tail 20 2>&1 && echo --- && docker logs wild-dragon-worker-1 --tail 15 2>&1


$ sleep 4 && docker ps --filter 'name=wild-dragon' --format 'table {{.Names}}\t{{.Status}}' && echo --- && docker logs wild-dragon-mam-api-1 --tail 20 2>&1 && echo --- && docker logs wild-dragon-worker-1 --tail 15 2>&1

NAMES                   STATUS
wild-dragon-mam-api-1   Up 11 seconds
wild-dragon-web-ui-1    Up 11 seconds
wild-dragon-worker-1    Up 11 seconds
wild-dragon-db-1        Up 3 hours (healthy)
wild-dragon-queue-1     Up 3 hours
---
(node:1) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///app/src/index.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /app/package.json.
(Use `node --trace-warnings ...` to show where the warning was created)
[migration] applied 001-add-live-status.sql
[migration] applied 002-groups-tokens.sql
[migration] applied 003-editor-sequences.sql
[migration] applied 004-cluster-nodes.sql
[migration] applied 005-node-capabilities.sql
[migration] applied 006-settings.sql
[migration] applied 007-cluster-dedupe-hostname.sql
[migration] applied 008-codec-settings.sql
MAM API listening on port 3000
Authentication: DISABLED (set AUTH_ENABLED=true for production)
---
[promotion] watching /growing (idle threshold 8000ms)
Wild Dragon Worker Service started
Redis: queue:6379
Active queues: proxy, thumbnail, conform
Background scans:  promotion (growing-files → S3)


All three containers up, promotion worker running. Smoke-testing the deployed UI.


curl -s -b /tmp/sess.cookie -c /tmp/sess.cookie http://localhost:47432/api/v1/settings/growing && echo && echo --- && curl -s -X PUT -H 'Content-Type: application/json' -d '{"growing_enabled":"true","growing_path":"/growing","growing_smb_url":"smb://10.0.0.25/mam-growing","growing_promote_after_seconds":"8"}' http://localhost:47432/api/v1/settings/growing && echo && echo --- && curl -s http://localhost:47432/api/v1/settings/growing


Dragonflight · Wild Dragon Broadcast

Dragonflight · Wild Dragon Broadcast

Dragonflight · Wild Dragon Broadcast


$ curl -s -b /tmp/sess.cookie -c /tmp/sess.cookie http://localhost:47432/api/v1/settings/growing && echo && echo --- && curl -s -X PUT -H 'Content-Type: application/json' -d '{"growing_enabled":"true","growing_path":"/growing","growing_smb_url":"smb://10.0.0.25/mam-growing","growing_promote_after_seconds":"8"}' http://localhost:47432/api/v1/settings/growing && echo && echo --- && curl -s http://localhost:47432/api/v1/settings/growing

{"growing_enabled":"false","growing_path":"/growing","growing_smb_url":"","growing_promote_after_seconds":"8"}
---
{"message":"Growing-files settings saved"}
---
{"growing_enabled":"true","growing_path":"/growing","growing_smb_url":"smb://10.0.0.25/mam-growing","growing_promote_after_seconds":"8"}


docker exec wild-dragon-mam-api-1 ls -la /growing 2>&1 ; echo --- ; docker exec wild-dragon-worker-1 ls -la /growing 2>&1


() => new Promise(r => setTimeout(() => { const items = Array.from(document.querySelectorAll('.nav-item')); const t = items.find(el => el.innerText.trim().startsWith('Settings')); if (t) t.click(); setTimeout(() => r('clicked Settings'), 600); }, 800))


$ docker exec wild-dragon-mam-api-1 ls -la /growing 2>&1 ; echo --- ; docker exec wild-dragon-worker-1 ls -la /growing 2>&1

total 9
drwxrwxrwx 2 root root 2 May 22 22:56 .
drwxr-xr-x 1 root root 8 May 22 23:13 ..
---
total 1
drwxrwxrwx    2 root     root             2 May 22 22:56 .
drwxr-xr-x    1 root     root             3 May 22 23:13 ..


Error: result (107,395 characters) exceeds maximum allowed tokens. Output has been saved to /home/node/.claude/projects/-home-node/9bc4deb8-dba9-4ac2-a16c-f5004f903927/tool-results/mcp-claude_ai_mcp-server-puppeteer_screenshot-1779491666087.txt.
Format: JSON with schema: {result: {status: string, format: string, base64: string, url: string}}
- For targeted queries (find a value, filter by field): use jq on the file directly.
- For analysis or summarization that requires reading the full content: first probe the structure (e.g., jq 'type, length, keys?' /home/node/.claude/projects/-home-node/9bc4deb8-dba9-4ac2-a16c-f5004f903927/tool-results/mcp-claude_ai_mcp-server-puppeteer_screenshot-1779491666087.txt), then extract slices with jq or python — Read's line-based offset/limit will not chunk this file.
- If the Agent tool is available, do this inside a subagent so the full output stays out of your main context. Give it the instruction above verbatim, and be explicit about what it must return — e.g. "/home/node/.claude/projects/-home-node/9bc4deb8-dba9-4ac2-a16c-f5004f903927/tool-results/mcp-claude_ai_mcp-server-puppeteer_screenshot-1779491666087.txt is JSON with schema: {result: {status: string, format: string, base64: string, url: string}}; probe the structure with jq (type/length/keys), then extract and read the content in full with jq or python, then summarize and quote any key findings verbatim." A vague "summarize this" may lose detail.


Recon + all code changes

Commit + push to forgejo main


Bash

F=/home/node/.claude/projects/-home-node/9bc4deb8-dba9-4ac2-a16c-f5004f903927/tool-results/mcp-claude_ai_mcp-server-puppeteer_screenshot-1779491666087.txt && python3 -c "
import json,base64
d=json.load(open('$F'))
open('/tmp/mam-settings-new.png','wb').write(base64.b64decode(d['result']['base64']))"

Decode settings screenshot


(Bash completed with no output)


TodoWrite

Recon + all code changes

Commit + push to forgejo main

Provision growing-files SMB share + host paths

Rebuild mam-api, worker, web-ui containers

Smoke test: Settings tabs render, HLS preview hooks, growing-files API working

Document deployment + Premier panel install for the user

Optional: trigger a test recording to verify proxy worker fix end-to-end


Recon + all code changes

Commit + push to forgejo main

Recon + all code changes

Commit + push to forgejo main

Provision growing-files SMB share + host paths

Smoke test: Settings tabs render, HLS preview hooks, growing-files API working

Document deployment + Premier panel install for the user

Optional: trigger a test recording to verify proxy worker fix end-to-end


5 Settings tabs render. Clicking Growing files tab to confirm panel switches.


() => new Promise(r => { const items = Array.from(document.querySelectorAll('.settings-nav-item')); const t = items.find(el => el.innerText.includes('Growing')); if (t) t.click(); setTimeout(() => r({clicked: !!t, currentH: document.querySelector('.settings-card-head')?.innerText?.slice(0,80)}), 400); })


Growing files (SMB)
High-speed local landing zone; promote to S3 on stop
ENABLED


Growing tab switches, shows the saved config (ENABLED badge). Let me also verify Recorders page renders the new HlsPreview wiring (when not recording it falls back to the empty-icon, which is fine).


() => new Promise(r => { const items = Array.from(document.querySelectorAll('.nav-item')); const t = items.find(el => el.innerText.trim().startsWith('Recorders')); if (t) t.click(); setTimeout(() => r({clicked: !!t, hasVideo: !!document.querySelector('video'), recRow: document.querySelector('.recorder-row')?.innerText?.slice(0,160), hasHlsJs: typeof window.Hls !== 'undefined'}), 800); })


Test1
STOPPED
SRT
srt://10.0.0.62:9999
prores_hq
·
native
ELAPSED
—
SIGNAL
—
Record


cd /mnt/NVME/MAM/wild-dragon && cat > docs/GROWING_FILES_QUICKSTART.md <<'EOF'
# Growing Files + Premiere Panel — Test Plan

A local SMB landing zone for capture, so Premiere can edit the master while
it is still recording. The promotion worker uploads the finalized file to S3
and the panel relinks Premiere to the hi-res original.

## Cluster state (deployed 2026-05-22)

- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
- TrueNAS SMB share: `mam-growing` -> `/mnt/NVME/MAM-growing`
- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` -> the dataset
- mam-api + worker containers mount it at `/growing`
- Settings (live config) — `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`

## Capture flow (when growing_enabled=true)

1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
   and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
2. ffmpeg writes the hires master directly to `/growing/<projectId>/<clipName>.<ext>`.
3. The HLS tee continues to publish `/live/<assetId>/index.m3u8` for the UI preview.
4. On stop (or when ffmpeg's stdout idles for `growing_promote_after_seconds`),
   the promotion worker uploads the local file to S3 as
   `projects/<projectId>/masters/<clipName>.<ext>`, queues a proxy job, then
   flips the asset to `status=ready` and deletes the local copy.

## Premiere panel

Install:
```
# macOS
rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/
# Windows
robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR
```
Then enable PlayerDebugMode=1 (`defaults write com.adobe.CSXS.11 PlayerDebugMode 1` on Mac).
In Premiere: Window -> Extensions -> Wild Dragon MAM.

Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.

## Test the live -> finalized swap

1. Start a recorder (Ingest -> Recorders -> Record).
2. The Recorder row's preview turns into a real HLS `<video>` element.
3. In Premiere: select the growing asset (it shows status=live). Click **Mount Live**.
   - The panel calls `GET /api/v1/assets/:id/live-path` and `importFiles()` the
     SMB UNC path. Premiere imports the still-growing MOV.
4. Stop the recorder. After `growing_promote_after_seconds` of mtime
   inactivity, the promotion worker uploads to S3.
5. The panel's 5-second status poll detects `status=ready` and offers the
   **Relink to Hi-Res** button. Clicking it downloads the S3 hi-res and calls
   ProjectItem.changeMediaPath() to relink in place — timeline cuts preserved.

## Knobs

All exposed in Settings -> Growing files (SMB):
- `growing_enabled` — master switch
- `growing_path` — container mount path (default /growing)
- `growing_smb_url` — what the Premiere panel hands to the editor
- `growing_promote_after_seconds` — idle threshold for the promotion worker
EOF
echo ok


cd /mnt/NVME/MAM/wild-dragon && git add docs/GROWING_FILES_QUICKSTART.md && git commit -m 'docs: growing-files + Premiere panel quickstart' 2>&1 | tail -3 && git push origin main 2>&1 | tail -3


Error: Error executing tool ssh_exec: 1 validation error for ssh_execArguments
params.command
  String should have at most 2000 characters [type=string_too_long, input_value="cd /mnt/NVME/MAM/wild-dr...on worker\nEOF\necho ok", input_type=str]
    For further information visit https://errors.pydantic.dev/2.13/v/string_too_long


$ cd /mnt/NVME/MAM/wild-dragon && git add docs/GROWING_FILES_QUICKSTART.md && git commit -m 'docs: growing-files + Premiere panel quickstart' 2>&1 | tail -3 && git push origin main 2>&1 | tail -3

[stderr]
fatal: pathspec 'docs/GROWING_FILES_QUICKSTART.md' did not match any files

[Exit code: 128]


Write

# Growing Files + Premiere Panel — Test Plan

A local SMB landing zone for capture so Premiere can edit the master while
it is still recording. The promotion worker uploads the finalized file to S3
and the panel relinks Premiere to the hi-res original.

## Cluster state (deployed 2026-05-22)

- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
- TrueNAS SMB share: `mam-growing` → `/mnt/NVME/MAM-growing`
- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` → the dataset
- mam-api + worker containers mount it at `/growing`
- Settings (live): `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`

## Capture flow (when growing_enabled=true)

1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
   and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
2. FFmpeg writes the hi-res master directly to
   `/growing/<projectId>/<clipName>.<ext>` (no S3 stream).
3. The HLS tee continues to publish `/live/<assetId>/index.m3u8`, so the
   Recorders + Monitors pages get a real video preview.
4. On stop — or when the file's mtime is idle for
   `growing_promote_after_seconds` — the promotion worker:
   - uploads the local file to S3 at `projects/<projectId>/masters/<clipName>.<ext>`
   - queues a proxy job
   - flips the asset to `status=ready`
   - deletes the local copy.

## Premiere panel install

```
# macOS
rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/

# Windows
robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR
```

Enable CEP debug:

```
# macOS
defaults write com.adobe.CSXS.11 PlayerDebugMode 1

# Windows
reg add "HKCU\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f
```

Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.

In Premiere: Window → Extensions → Wild Dragon MAM.

## Test the live → finalized swap

1. Start a recorder (Ingest → Recorders → Record).
2. The Recorder row's preview becomes a real HLS `<video>` element.
3. In Premiere, with the growing asset selected (status=live), click
   **Mount Live**. The panel calls `GET /api/v1/assets/:id/live-path`,
   resolves the SMB UNC path, and `app.project.importFiles()` it. Premiere
   imports the still-growing MOV.
4. Stop the recorder. After `growing_promote_after_seconds` of mtime
   inactivity, the promotion worker uploads to S3 and flips status.
5. The panel polls every 5 s. When it sees `status=ready` it surfaces
   **Relink to Hi-Res** — clicking that downloads the finalized hi-res
   and calls `ProjectItem.changeMediaPath()` to relink in place. Timeline
   cuts are preserved.

## Knobs (Settings → Growing files (SMB))

- `growing_enabled` — master switch
- `growing_path` — container mount path (default `/growing`)
- `growing_smb_url` — what the Premiere panel hands to the editor
- `growing_promote_after_seconds` — idle threshold for promotion

## What's NOT yet here

- Auth on the SMB share — currently passwordless. Add Samba auth via
  `midclt call sharing.smb.update` and put creds in the editor's keychain
  before exposing this beyond the LAN.
- Concurrent S3 backup of the growing file. Today's MVP writes to SMB only;
  S3 happens at promotion. If you need belt-and-suspenders, add `-f tee` in
  capture-manager to fan out to both destinations.
- Cleanup for stranded files (e.g. recorder crashes mid-capture). The idle
  threshold will eventually promote them, but a stale-file sweeper would be
  more graceful.


File created successfully at: /tmp/growing-quickstart.md (file state is current in your context — no need to Read it back)

# Growing Files + Premiere Panel — Test Plan

A local SMB landing zone for capture so Premiere can edit the master while
it is still recording. The promotion worker uploads the finalized file to S3
and the panel relinks Premiere to the hi-res original.

## Cluster state (deployed 2026-05-22)

- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
- TrueNAS SMB share: `mam-growing` → `/mnt/NVME/MAM-growing`
- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` → the dataset
- mam-api + worker containers mount it at `/growing`
- Settings (live): `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`

## Capture flow (when growing_enabled=true)

1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
   and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
2. FFmpeg writes the hi-res master directly to
   `/growing/<projectId>/<clipName>.<ext>` (no S3 stream).
3. The HLS tee continues to publish `/live/<assetId>/index.m3u8`, so the
   Recorders + Monitors pages get a real video preview.
4. On stop — or when the file's mtime is idle for
   `growing_promote_after_seconds` — the promotion worker:
   - uploads the local file to S3 at `projects/<projectId>/masters/<clipName>.<ext>`
   - queues a proxy job
   - flips the asset to `status=ready`
   - deletes the local copy.

## Premiere panel install

```
# macOS
rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/

# Windows
robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR
```

Enable CEP debug:

```
# macOS
defaults write com.adobe.CSXS.11 PlayerDebugMode 1

# Windows
reg add "HKCU\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f
```

Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.

In Premiere: Window → Extensions → Wild Dragon MAM.

## Test the live → finalized swap

1. Start a recorder (Ingest → Recorders → Record).
2. The Recorder row's preview becomes a real HLS `<video>` element.
3. In Premiere, with the growing asset selected (status=live), click
   **Mount Live**. The panel calls `GET /api/v1/assets/:id/live-path`,
   resolves the SMB UNC path, and `app.project.importFiles()` it. Premiere
   imports the still-growing MOV.
4. Stop the recorder. After `growing_promote_after_seconds` of mtime
   inactivity, the promotion worker uploads to S3 and flips status.
5. The panel polls every 5 s. When it sees `status=ready` it surfaces
   **Relink to Hi-Res** — clicking that downloads the finalized hi-res
   and calls `ProjectItem.changeMediaPath()` to relink in place. Timeline
   cuts are preserved.

## Knobs (Settings → Growing files (SMB))

- `growing_enabled` — master switch
- `growing_path` — container mount path (default `/growing`)
- `growing_smb_url` — what the Premiere panel hands to the editor
- `growing_promote_after_seconds` — idle threshold for promotion

## What's NOT yet here

- Auth on the SMB share — currently passwordless. Add Samba auth via
  `midclt call sharing.smb.update` and put creds in the editor's keychain
  before exposing this beyond the LAN.
- Concurrent S3 backup of the growing file. Today's MVP writes to SMB only;
  S3 happens at promotion. If you need belt-and-suspenders, add `-f tee` in
  capture-manager to fan out to both destinations.
- Cleanup for stranded files (e.g. recorder crashes mid-capture). The idle
  threshold will eventually promote them, but a stale-file sweeper would be
  more graceful.


docs: growing-files + Premiere panel quickstart


OK | commit:c3991a1 | https://forge.wilddragon.net/zgaetano/wild-dragon/src/branch/main/docs/GROWING_FILES_QUICKSTART.md


Bash

Check doc size


1	# Growing Files + Premiere Panel — Test Plan
2
3	A local SMB landing zone for capture so Premiere can edit the master while
4	it is still recording. The promotion worker uploads the finalized file to S3
5	and the panel relinks Premiere to the hi-res original.
6
7	## Cluster state (deployed 2026-05-22)
8
9	- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
10	- TrueNAS SMB share: `mam-growing` → `/mnt/NVME/MAM-growing`
11	- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` → the dataset
12	- mam-api + worker containers mount it at `/growing`
13	- Settings (live): `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`
14
15	## Capture flow (when growing_enabled=true)
16
17	1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
18	   and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
19	2. FFmpeg writes the hi-res master directly to
20	   `/growing/<projectId>/<clipName>.<ext>` (no S3 stream).
21	3. The HLS tee continues to publish `/live/<assetId>/index.m3u8`, so the
22	   Recorders + Monitors pages get a real video preview.
23	4. On stop — or when the file's mtime is idle for
24	   `growing_promote_after_seconds` — the promotion worker:
25	   - uploads the local file to S3 at `projects/<projectId>/masters/<clipName>.<ext>`
26	   - queues a proxy job
27	   - flips the asset to `status=ready`
28	   - deletes the local copy.
29
30	## Premiere panel install
31
32	```
33	# macOS
34	rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/
35
36	# Windows
37	robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR
38	```
39
40	Enable CEP debug:
41
42	```
43	# macOS
44	defaults write com.adobe.CSXS.11 PlayerDebugMode 1
45
46	# Windows
47	reg add "HKCU\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f
48	```
49
50	Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.
51
52	In Premiere: Window → Extensions → Wild Dragon MAM.
53
54	## Test the live → finalized swap
55
56	1. Start a recorder (Ingest → Recorders → Record).
57	2. The Recorder row's preview becomes a real HLS `<video>` element.
58	3. In Premiere, with the growing asset selected (status=live), click
59	   **Mount Live**. The panel calls `GET /api/v1/assets/:id/live-path`,
60	   resolves the SMB UNC path, and `app.project.importFiles()` it. Premiere
61	   imports the still-growing MOV.
62	4. Stop the recorder. After `growing_promote_after_seconds` of mtime
63	   inactivity, the promotion worker uploads to S3 and flips status.
64	5. The panel polls every 5 s. When it sees `status=ready` it surfaces
65	   **Relink to Hi-Res** — clicking that downloads the finalized hi-res
66	   and calls `ProjectItem.changeMediaPath()` to relink in place. Timeline
67	   cuts are preserved.
68
69	## Knobs (Settings → Growing files (SMB))
70
71	- `growing_enabled` — master switch
72	- `growing_path` — container mount path (default `/growing`)
73	- `growing_smb_url` — what the Premiere panel hands to the editor
74	- `growing_promote_after_seconds` — idle threshold for promotion
75
76	## What's NOT yet here
77
78	- Auth on the SMB share — currently passwordless. Add Samba auth via
79	  `midclt call sharing.smb.update` and put creds in the editor's keychain
80	  before exposing this beyond the LAN.
81	- Concurrent S3 backup of the growing file. Today's MVP writes to SMB only;
82	  S3 happens at promotion. If you need belt-and-suspenders, add `-f tee` in
83	  capture-manager to fan out to both destinations.
84	- Cleanup for stranded files (e.g. recorder crashes mid-capture). The idle
85	  threshold will eventually promote them, but a stale-file sweeper would be
86	  more graceful.
87


docs: growing-files + Premiere panel quickstart

# Growing Files + Premiere Panel — Test Plan

A local SMB landing zone for capture so Premiere can edit the master while
it is still recording. The promotion worker uploads the finalized file to S3
and the panel relinks Premiere to the hi-res original.

## Cluster state (deployed 2026-05-22)

- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
- TrueNAS SMB share: `mam-growing` → `/mnt/NVME/MAM-growing`
- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` → the dataset
- mam-api + worker containers mount it at `/growing`
- Settings (live): `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`

## Capture flow (when growing_enabled=true)

1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
   and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
2. FFmpeg writes the hi-res master directly to
   `/growing/<projectId>/<clipName>.<ext>` (no S3 stream).
3. The HLS tee continues to publish `/live/<assetId>/index.m3u8`, so the
   Recorders + Monitors pages get a real video preview.
4. On stop — or when the file's mtime is idle for
   `growing_promote_after_seconds` — the promotion worker:
   - uploads the local file to S3 at `projects/<projectId>/masters/<clipName>.<ext>`
   - queues a proxy job
   - flips the asset to `status=ready`
   - deletes the local copy.

## Premiere panel install

```
# macOS
rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/

# Windows
robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR
```

Enable CEP debug:

```
# macOS
defaults write com.adobe.CSXS.11 PlayerDebugMode 1

# Windows
reg add "HKCU\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f
```

Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.

In Premiere: Window → Extensions → Wild Dragon MAM.

## Test the live → finalized swap

1. Start a recorder (Ingest → Recorders → Record).
2. The Recorder row's preview becomes a real HLS `<video>` element.
3. In Premiere, with the growing asset selected (status=live), click
   **Mount Live**. The panel calls `GET /api/v1/assets/:id/live-path`,
   resolves the SMB UNC path, and `app.project.importFiles()` it. Premiere
   imports the still-growing MOV.
4. Stop the recorder. After `growing_promote_after_seconds` of mtime
   inactivity, the promotion worker uploads to S3 and flips status.
5. The panel polls every 5 s. When it sees `status=ready` it surfaces
   **Relink to Hi-Res** — clicking that downloads the finalized hi-res
   and calls `ProjectItem.changeMediaPath()` to relink in place. Timeline
   cuts are preserved.

## Knobs (Settings → Growing files (SMB))

- `growing_enabled` — master switch
- `growing_path` — container mount path (default `/growing`)
- `growing_smb_url` — what the Premiere panel hands to the editor
- `growing_promote_after_seconds` — idle threshold for promotion

## What's NOT yet here

- Auth on the SMB share — currently passwordless. Add Samba auth via
  `midclt call sharing.smb.update` and put creds in the editor's keychain
  before exposing this beyond the LAN.
- Concurrent S3 backup of the growing file. Today's MVP writes to SMB only;
  S3 happens at promotion. If you need belt-and-suspenders, add `-f tee` in
  capture-manager to fan out to both destinations.
- Cleanup for stranded files (e.g. recorder crashes mid-capture). The idle
  threshold will eventually promote them, but a stale-file sweeper would be
  more graceful.


OK | commit:dc0bd51 | Updated docs/GROWING_FILES_QUICKSTART.md


cd /mnt/NVME/MAM/wild-dragon && git pull --rebase 2>&1 | tail -5


TodoWrite

Recon + all code changes

Commit + push to forgejo main

Provision growing-files SMB share + host paths

Rebuild mam-api, worker, web-ui containers

Smoke test deployed app

Push quickstart doc to forgejo


$ cd /mnt/NVME/MAM/wild-dragon && git pull --rebase 2>&1 | tail -5

Updating 328f7b4..dc0bd51
Fast-forward
 docs/GROWING_FILES_QUICKSTART.md | 86 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 docs/GROWING_FILES_QUICKSTART.md


Recon + all code changes

Commit + push to forgejo main

Recon + all code changes

Commit + push to forgejo main

Smoke test deployed app

Push quickstart doc to forgejo


All landed on main. Done — here's the rundown.

## What's deployed

**Bug fixes (all live at http://10.0.0.25:47434):**
- **Proxy worker** — three different ffmpeg failure modes from the 12 stuck jobs are fixed: odd-dimension input gets a scale-to-even filter, ProRes with weird 60k timebase gets `-analyzeduration/probesize 100M`, image files (the PNG that caused half the failures) now route to an image-poster path instead of x264.
- **Recorders + Monitors preview** — replaced the fake `<LiveStrip>` / `<FauxFrame>` decorations with a real `<HlsPreview>` component that consumes the HLS tee that capture-manager was already publishing to `/live/<assetId>/`. Will activate the moment a recorder is in `recording` state with a `live_asset_id`.
- **Settings page** — the four nav items (GPU, Growing, SDI, AMPP) now actually switch panels and load/save their real API endpoints. S3 panel kept as-is.

**New feature — growing-files + Premiere panel:**
- TrueNAS dataset `NVME/MAM-growing` + SMB share `mam-growing` provisioned.
- Capture sidecar, when `GROWING_ENABLED=true`, writes the hi-res master directly to `/growing/<projectId>/<clipName>.<ext>` instead of streaming to S3.
- New **promotion worker** in the worker container watches `/growing`, detects mtime-idle files, multipart-uploads them to S3, queues a proxy job, flips the asset to `status=ready`, deletes the local copy.
- **Premiere CEP panel** gained two buttons: **Mount Live** (calls `/assets/:id/live-path`, imports the SMB UNC path) and **Relink to Hi-Res** (downloads the finalized S3 file and calls `ProjectItem.changeMediaPath()` to swap media paths without disturbing timeline cuts). Polls status every 5 s.

## Commits on `main`
- `328f7b4` feat: live HLS preview, proxy worker fixes, Settings tabs, growing-files + Premier panel
- `dc0bd51` docs: growing-files + Premiere panel quickstart

Quickstart lives at `docs/GROWING_FILES_QUICKSTART.md` — has the Premiere panel install commands for Mac/Windows and the end-to-end test plan.

## Assumptions I locked in (override if wrong)
1. **Growing-files lives on TrueNAS NVME**, not zampp1. Single-node setup is simpler and `10.0.0.25` is reachable from both the capture container and your Mac.
2. **SMB share is passwordless** for the LAN MVP. Documented as a known gap — add Samba auth before exposing beyond the LAN.
3. **Growing mode replaces the live S3 stream** — file lands on SMB only until promotion. No belt-and-suspenders S3 backup yet. Add `-f tee` in capture-manager if you want concurrent writes.

## Couldn't fully verify without a live signal
The HLS preview, growing-file capture, and the Premiere mount/relink flow are wired but need an actual SRT/SDI signal hitting Test1 (or a fresh recorder pointed at one) to exercise end-to-end. Everything is in place — when you get back, start `Test1` against a live source, switch to Recorders, and you should see real video in the preview tile and a growing `.mov` show up in `/mnt/NVME/MAM-growing/<projectId>/`. Stop the recorder, wait ~8 s, and check the Library for status flipping to `ready`.


Let’s log current issues in forgejo , pleas expand the description of what this application does and I think it is time re rename the repo to dragonflight


Conversation compacted