feat(forecast): integrate full forecast charts and complete SDL visualization
Tests / test (push) Failing after 52s
Tests / test (push) Failing after 52s
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Deploy static assets only. Abort on drift; keep a private rollback snapshot."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import time
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--apply', action='store_true')
|
||||
args = parser.parse_args()
|
||||
package = Path(__file__).resolve().parent
|
||||
root = Path('/home/agent/services/license')
|
||||
manifest = json.loads((package / 'manifest.json').read_text())
|
||||
sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() if path.exists() else None
|
||||
for entry in manifest:
|
||||
relative = Path(entry['path'])
|
||||
if relative.is_absolute() or '..' in relative.parts or relative.parts[0] != 'public':
|
||||
raise SystemExit('Unsafe manifest path')
|
||||
target = root / relative
|
||||
if target.is_symlink() or sha(target) != entry['before']:
|
||||
raise SystemExit('Runtime drift: ' + str(relative))
|
||||
if sha(package / relative) != entry['after']:
|
||||
raise SystemExit('Release checksum mismatch: ' + str(relative))
|
||||
if not args.apply:
|
||||
print(json.dumps({'preflight': 'ok', 'files': len(manifest)}))
|
||||
raise SystemExit(0)
|
||||
backup = Path('/srv/agent/forecast-completion-20261006') / ('portal-backup-' + str(time.time_ns()))
|
||||
backup.mkdir(mode=0o700)
|
||||
for entry in manifest:
|
||||
source = root / entry['path']
|
||||
if source.exists():
|
||||
target = backup / entry['path']
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target)
|
||||
shutil.copyfile(package / 'manifest.json', backup / 'manifest.json')
|
||||
written = []
|
||||
try:
|
||||
# New imported dependencies first; the HTML entrypoint is switched last.
|
||||
order = sorted(manifest, key=lambda e: (e['path'].endswith('index.html'), e['path'].endswith('app.js')))
|
||||
for entry in order:
|
||||
target = root / entry['path']
|
||||
if sha(target) != entry['before']:
|
||||
raise RuntimeError('Concurrent change: ' + entry['path'])
|
||||
temp = target.with_name(target.name + '.forecast-new')
|
||||
with temp.open('xb') as stream:
|
||||
stream.write((package / entry['path']).read_bytes())
|
||||
os.chmod(temp, target.stat().st_mode & 0o777 if target.exists() else 0o644)
|
||||
os.replace(temp, target)
|
||||
written.append(entry)
|
||||
if any(sha(root / entry['path']) != entry['after'] for entry in manifest):
|
||||
raise RuntimeError('Post-deployment checksum mismatch')
|
||||
except Exception:
|
||||
for entry in reversed(written):
|
||||
target = root / entry['path']
|
||||
if sha(target) != entry['after']:
|
||||
continue
|
||||
if entry['before'] is not None:
|
||||
shutil.copy2(backup / entry['path'], target)
|
||||
raise
|
||||
print(json.dumps({'deployed': True, 'files': len(written), 'backup': str(backup)}))
|
||||
Reference in New Issue
Block a user