`;if(running)panel.querySelector('#backup-cancel').onclick=async()=>{try{await request('cancel',{});view.querySelector('#backup-message').textContent='Abbruch angefordert; laufender Container-Schritt wird sicher beendet.';}catch(e){view.querySelector('#backup-message').textContent=e.message;}};if(job.state==='complete'&&jobId!==job.id){jobId=job.id;applyDeckTheme(job.theme);}}}catch(e){view.querySelector('#backup-message').textContent=e.message;}setTimeout(()=>poll(view),1500);}
diff --git a/deploy/docker_backup.py b/deploy/docker_backup.py
index fd74809..658bbfe 100644
--- a/deploy/docker_backup.py
+++ b/deploy/docker_backup.py
@@ -89,7 +89,9 @@ def validate(c):
targets.add(target)
if m.get('index')!=i or type(m.get('read_only')) is not bool or type(m.get('was_file')) is not bool:raise ValueError('Ungültige Volume-Einstellung.')
rel=m.get('deck_path')
- if rel is not None and (rel not in ('models','video/comfy-work/models','video/comfy-client-token') or not m['read_only']):raise ValueError('Deck-Dateien dürfen nur als bekannte, lesende Modell-/Token-Volumes eingebunden werden.')
+ media={'video/original-work/remote-inputs/deskweb':('/data/inputs',False),'video/original-work/outputs':('/data/outputs',True)}
+ native_media=rel in media and c['name']=='ltx-deskweb' and (target,m['read_only'])==media[rel] and not m['was_file']
+ if rel is not None and not native_media and (rel not in ('models','video/comfy-work/models','video/comfy-client-token') or not m['read_only']):raise ValueError('Deck-Volume nicht freigegeben; nur bekannte Modell-/Tokenpfade und LTX-Medienordner erlaubt.')
for name,data in m.get('files',{}).items():
if name not in ('file','Settings.fds','Backends.fds'):raise ValueError('Unbekannte Konfigurationsdatei.')
if not isinstance(data,str) or len(base64.b64decode(data,validate=True))>MAX_FILE:raise ValueError('Ungültige Konfigurationsdatei.')
@@ -150,7 +152,10 @@ def restore(manager,c):
if m['deck_path']=='video/comfy-client-token' and not source.exists():
import secrets
source.parent.mkdir(parents=True,exist_ok=True);source.write_text(secrets.token_urlsafe(48)+'\n');source.chmod(0o600);os.chown(source,65534,65534)
- elif m['deck_path']!='video/comfy-client-token':source.mkdir(parents=True,exist_ok=True)
+ elif m['deck_path']!='video/comfy-client-token':
+ source.mkdir(parents=True,exist_ok=True)
+ if m['deck_path'].startswith('video/original-work/'):
+ is_input=m['deck_path'].endswith('/deskweb');os.chown(source,1000 if is_input else 65534,65534);source.chmod(0o2770 if is_input else 0o2750)
elif m['was_file']:
if 'file' not in m['files']:raise ValueError('Benötigte Volume-Datei fehlt im Backup.')
source.write_bytes(base64.b64decode(m['files']['file']));source.chmod(0o600)
diff --git a/test_backup.py b/test_backup.py
index b993176..6003352 100644
--- a/test_backup.py
+++ b/test_backup.py
@@ -82,6 +82,11 @@ class ContainerTests(unittest.TestCase):
for change in (lambda x:x.update(unsupported=True),lambda x:x.update(network='host'),lambda x:x['ports']['8000/tcp'][0].update(HostIp='0.0.0.0'),lambda x:x.update(mounts=[dict(index=0,target='/var/run/docker.sock',deck_path=None,read_only=True,was_file=True,files={})])):
bad=copy.deepcopy(c);change(bad)
with self.assertRaises(ValueError):validate_container(bad)
+ def test_only_deskweb_can_mount_its_specific_writable_media_folder(self):
+ c=self.config();c.update(name='ltx-deskweb',mounts=[dict(index=0,target='/data/inputs',deck_path='video/original-work/remote-inputs/deskweb',read_only=False,was_file=False,files={})]);validate_container(c)
+ for change in (lambda x:x.update(name='other-ui'),lambda x:x['mounts'][0].update(deck_path='models'),lambda x:x['mounts'][0].update(deck_path='video/original-work'),lambda x:x['mounts'][0].update(target='/data/outputs')):
+ bad=copy.deepcopy(c);change(bad)
+ with self.assertRaises(ValueError):validate_container(bad)
def test_never_replaces_existing_foreign_container(self):
with tempfile.TemporaryDirectory() as d:
manager=Mock(state=Path(d));(Path(d)/'backup-policy.json').write_text(json.dumps({'deck_state':d}))