fix(growing): inline CIFS creds + capture caps + storage probe timeout
Three fixes to restore growing-files (XDCAM HD422 MXF) recording: 1. capture-manager mountGrowingShare: pass username=/password= inline instead of a credentials= file. TrueNAS SMB3 rejects the creds-file form with EACCES (-13, 'cannot mount read-only') while the identical inline creds mount fine. This was causing every growing record to silently fall back to the HEVC/S3 path (producing .mov, not .mxf). 2. docker-compose capture: add cap_add SYS_ADMIN + DAC_READ_SEARCH and apparmor:unconfined so mount.cifs can run inside the container. 3. storage /overview: wrap S3 HeadBucket/ListObjects probe in a 5s timeout so the admin 'Mount health' card stops hanging on 'Probing…' forever when S3 is slow.
This commit is contained in:
parent
2812705d1c
commit
cb25711ec6
3 changed files with 25 additions and 8 deletions
|
|
@ -120,6 +120,15 @@ services:
|
||||||
profiles: [capture]
|
profiles: [capture]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
runtime: nvidia
|
runtime: nvidia
|
||||||
|
# Growing-files mode mounts an SMB/CIFS share inside the container
|
||||||
|
# (mount.cifs). That syscall needs CAP_SYS_ADMIN + DAC_READ_SEARCH and an
|
||||||
|
# unconfined AppArmor profile; without these the mount fails with
|
||||||
|
# "Unable to apply new capability set" and growing falls back to HEVC/S3.
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
- DAC_READ_SEARCH
|
||||||
|
security_opt:
|
||||||
|
- apparmor:unconfined
|
||||||
environment:
|
environment:
|
||||||
REDIS_URL: ${REDIS_URL}
|
REDIS_URL: ${REDIS_URL}
|
||||||
DATABASE_URL: ${DATABASE_URL}
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
|
|
||||||
|
|
@ -64,13 +64,14 @@ function mountGrowingShare() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try { mkdirSync(GROWING_PATH, { recursive: true }); } catch (_) {}
|
try { mkdirSync(GROWING_PATH, { recursive: true }); } catch (_) {}
|
||||||
writeFileSync(
|
// Pass credentials inline rather than via a credentials= file. Some SMB
|
||||||
SMB_CREDS_FILE,
|
// servers (notably TrueNAS SMB3) reject the credentials-file form with
|
||||||
`username=${GROWING_SMB_USERNAME}\npassword=${GROWING_SMB_PASSWORD}\n`,
|
// EACCES (-13) — "cannot mount ... read-only" — even though the very same
|
||||||
{ mode: 0o600 }
|
// username/password mount inline and smbclient lists the share fine. Inline
|
||||||
);
|
// user=/password= is the reliable form here.
|
||||||
const opts = [
|
const opts = [
|
||||||
`credentials=${SMB_CREDS_FILE}`,
|
`username=${GROWING_SMB_USERNAME}`,
|
||||||
|
`password=${GROWING_SMB_PASSWORD}`,
|
||||||
'uid=0', 'gid=0', 'file_mode=0664', 'dir_mode=0775',
|
'uid=0', 'gid=0', 'file_mode=0664', 'dir_mode=0775',
|
||||||
`vers=${GROWING_SMB_VERS}`,
|
`vers=${GROWING_SMB_VERS}`,
|
||||||
].join(',');
|
].join(',');
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,21 @@ async function probeS3Bucket() {
|
||||||
if (!bucket) { out.error = 'no bucket configured'; return out; }
|
if (!bucket) { out.error = 'no bucket configured'; return out; }
|
||||||
|
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
|
// Hard cap the whole probe so the admin "Mount health" card never hangs on
|
||||||
|
// "Probing…" when S3 is slow/unreachable. Without this, the SDK's default
|
||||||
|
// retry/backoff can block the request for tens of seconds.
|
||||||
|
const withTimeout = (p, ms) => Promise.race([
|
||||||
|
p,
|
||||||
|
new Promise((_, rej) => setTimeout(() => rej(new Error('probe timed out after ' + ms + 'ms')), ms)),
|
||||||
|
]);
|
||||||
try {
|
try {
|
||||||
await s3Client.send(new HeadBucketCommand({ Bucket: bucket }));
|
await withTimeout(s3Client.send(new HeadBucketCommand({ Bucket: bucket })), 5000);
|
||||||
out.reachable = true;
|
out.reachable = true;
|
||||||
out.method = 'HeadBucket';
|
out.method = 'HeadBucket';
|
||||||
} catch (headErr) {
|
} catch (headErr) {
|
||||||
// Fall back to a 0-key list for stores that don't expose HeadBucket.
|
// Fall back to a 0-key list for stores that don't expose HeadBucket.
|
||||||
try {
|
try {
|
||||||
await s3Client.send(new ListObjectsV2Command({ Bucket: bucket, MaxKeys: 0 }));
|
await withTimeout(s3Client.send(new ListObjectsV2Command({ Bucket: bucket, MaxKeys: 0 })), 5000);
|
||||||
out.reachable = true;
|
out.reachable = true;
|
||||||
out.method = 'ListObjectsV2';
|
out.method = 'ListObjectsV2';
|
||||||
} catch (listErr) {
|
} catch (listErr) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue