d48e2b7d9d
Major features: - Full backup & restore system (JSON snapshots of all 20 tables + settings) - Web UI, REST API, OCC CLI commands, scheduled background job - Self-update from Gitea releases with Ed25519 signature verification - Configurable column visibility on all data tables (persisted via localStorage) Fixes: - NC admin group fallback for PermissionService (IGroupManager) - Bundle import inline error correction (editable error rows) New files: BackupService, BackupSettingsService, BackupController, BackupJob, SelfUpdateService, 4 OCC commands, ColumnPicker component, Backup.vue, Ed25519 signing scripts, signature verification tests (18 tests) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
1.8 KiB
PHP
68 lines
1.8 KiB
PHP
#!/usr/bin/env php
|
|
<?php
|
|
/**
|
|
* Sign a release tarball with Ed25519.
|
|
*
|
|
* Usage: php scripts/sign-release.php <tarball-path>
|
|
*
|
|
* Reads the private key from ~/.mv-release-key (base64-encoded).
|
|
* Produces <tarball-path>.sig (raw 64-byte Ed25519 signature).
|
|
*/
|
|
|
|
if ($argc < 2) {
|
|
fwrite(STDERR, "Usage: php scripts/sign-release.php <tarball-path>\n");
|
|
exit(1);
|
|
}
|
|
|
|
$tarballPath = $argv[1];
|
|
|
|
if (!file_exists($tarballPath)) {
|
|
fwrite(STDERR, "ERROR: File not found: $tarballPath\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (!function_exists('sodium_crypto_sign_detached')) {
|
|
fwrite(STDERR, "ERROR: sodium extension is required.\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Read private key
|
|
$keyFile = $_SERVER['HOME'] . '/.mv-release-key';
|
|
if (!file_exists($keyFile)) {
|
|
fwrite(STDERR, "ERROR: Private key not found at $keyFile\n");
|
|
fwrite(STDERR, "Run: php scripts/generate-keypair.php\n");
|
|
exit(1);
|
|
}
|
|
|
|
$secretKeyBase64 = trim(file_get_contents($keyFile));
|
|
$secretKey = base64_decode($secretKeyBase64, true);
|
|
if ($secretKey === false || strlen($secretKey) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
|
|
fwrite(STDERR, "ERROR: Invalid private key in $keyFile\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Read tarball
|
|
$content = file_get_contents($tarballPath);
|
|
if ($content === false) {
|
|
fwrite(STDERR, "ERROR: Could not read $tarballPath\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Sign
|
|
$signature = sodium_crypto_sign_detached($content, $secretKey);
|
|
|
|
// Write signature
|
|
$sigPath = $tarballPath . '.sig';
|
|
file_put_contents($sigPath, $signature);
|
|
|
|
echo "Signed: $sigPath (" . strlen($signature) . " bytes)\n";
|
|
|
|
// Verify as sanity check
|
|
$publicKey = sodium_crypto_sign_publickey_from_secretkey($secretKey);
|
|
if (sodium_crypto_sign_verify_detached($signature, $content, $publicKey)) {
|
|
echo "Verification: OK\n";
|
|
} else {
|
|
fwrite(STDERR, "ERROR: Self-verification failed!\n");
|
|
exit(1);
|
|
}
|