Keep release pointer unchanged on publication failure and canonicalize SS links
This commit is contained in:
parent
348d34f8da
commit
99007b4518
@ -38,6 +38,8 @@ final class Codec
|
||||
}
|
||||
self::endpoint((string) ($config['add'] ?? ''), (int) ($config['port'] ?? 0));
|
||||
self::uuid((string) ($config['id'] ?? ''));
|
||||
$config['port'] = (int) $config['port'];
|
||||
$config['aid'] = (int) ($config['aid'] ?? 0);
|
||||
$name = (string) ($config['ps'] ?? 'VMess');
|
||||
$config['ps'] = '';
|
||||
return self::node($type, $config, $name);
|
||||
@ -114,6 +116,14 @@ final class Codec
|
||||
$config['ps'] = $name;
|
||||
return 'vmess://'.base64_encode(json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
|
||||
}
|
||||
if ($node['protocol'] === 'ss') {
|
||||
$d = $node['data'];
|
||||
$auth = str_starts_with($d['user'], '2022-')
|
||||
? rawurlencode($d['user']).':'.rawurlencode($d['password'])
|
||||
: rtrim(strtr(base64_encode($d['user'].':'.$d['password']), '+/', '-_'), '=');
|
||||
$query = $d['query'] ? '?'.http_build_query($d['query'], '', '&', PHP_QUERY_RFC3986) : '';
|
||||
return 'ss://'.$auth.'@'.(str_contains($d['host'], ':') ? '['.$d['host'].']' : $d['host']).':'.$d['port'].$query.'#'.rawurlencode($name);
|
||||
}
|
||||
return $node['protocol'].'://'.$node['data']['connection'].'#'.rawurlencode($name);
|
||||
}
|
||||
|
||||
|
||||
@ -75,8 +75,8 @@ final class ReleaseStore
|
||||
$this->atomic($target, $content, 0644);
|
||||
}
|
||||
// Readers of v2 use this manifest and immutable release files under the shared lock.
|
||||
$this->atomic($this->root.'/current.json', json_encode($manifest, JSON_THROW_ON_ERROR), 0600);
|
||||
if ($previous && $previous['id'] !== $id) $this->atomic($this->root.'/previous.json', json_encode($previous, JSON_THROW_ON_ERROR), 0600);
|
||||
$this->atomic($this->root.'/current.json', json_encode($manifest, JSON_THROW_ON_ERROR), 0600);
|
||||
} catch (\Throwable $e) {
|
||||
foreach ($backups as $target => $content) {
|
||||
if ($content === null) @unlink($target); else $this->atomic($target, $content, 0644);
|
||||
@ -165,7 +165,7 @@ final class ReleaseStore
|
||||
try {
|
||||
if (file_put_contents($temp, $content) !== strlen($content)) throw new RuntimeException('artifact_write_failed');
|
||||
chmod($temp, $mode);
|
||||
if (!rename($temp, $path)) throw new RuntimeException('artifact_rename_failed');
|
||||
if (!@rename($temp, $path)) throw new RuntimeException('artifact_rename_failed');
|
||||
} finally { if (is_file($temp)) unlink($temp); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,6 +156,10 @@ merge 下仍有旧命名目录,不能据目录名推断当前站点归属。fn
|
||||
|
||||
运行目录需由实际 PHP/调度身份可写。发布前记录旧 Git 提交、并行修改、当前引擎配置及兼容文件哈希。切回旧链路须先停止/避开本轮生成发布,在配置中恢复 `FREENODE_ENGINE=legacy`,清除相应配置缓存,再恢复保留的旧产物;仅回退 Git 不会自动撤回已分发到 Nodehub/各站的文件,必要时按原接口再次同步旧产物。不能声称本地恢复等于全部下游已回滚。
|
||||
|
||||
本地验证命令:`TEST_SINGBOX=/path/to/sing-box TEST_MIHOMO=/path/to/mihomo php -d error_reporting=22527 vendor/bin/phpunit tests/Unit/Freenode --do-not-cache-result`。当前 13 项、29 断言通过,含真实原生内核检查、坏节点隔离、参数完整性、数量骤减保护、清单篡改拒绝及旧产物恢复。未安装原生内核时该集成测试会跳过,不能把跳过当作原生验收通过。
|
||||
本地验证命令:`TEST_SINGBOX=/path/to/sing-box TEST_MIHOMO=/path/to/mihomo php -d error_reporting=22527 vendor/bin/phpunit tests/Unit/Freenode --do-not-cache-result`。当前 14 项、32 断言通过,含真实原生内核检查、坏节点隔离、参数完整性、数量骤减保护、清单篡改拒绝及旧产物恢复。未安装原生内核时该集成测试会跳过,不能把跳过当作原生验收通过。
|
||||
|
||||
尚未完成:服务器内真实输入影子生成、生产切换、线上产物回滚演练、Nodehub 接收及用户客户端更新验收。白色机实际连通测试服务不包含在本次生成链切换中,尚未部署。
|
||||
|
||||
本机合成规模验证:2000 个虚构 URI 节点及 2000 个虚构 Clash 节点,各格式输出 2000 条,整批生成及原生检查约 1.081 秒,PHP 峰值 24 MiB,原生内核检查 6 次,实际节点联网测试 0 次。此结果只代表本机合成输入,不代表生产实测或联网测速性能。
|
||||
|
||||
当前发布状态:本地代码已提交;Git 推送尚未执行,自动审批要求用户确认现有 Gitea 仓库 `https://gitea.tomshell.com.de/tom/dd.git` 的具体推送及后续部署授权。生产仍为旧版本。另一次将完整生产节点配置导出到本机临时目录的操作被自动审批拒绝,未执行;后续真实数据验证改为服务器内影子生成,仅回传脱敏统计,不绕过 Git 上传代码。待上述推送授权后继续服务器内验证、回滚演练和切换,不把本地测试写成已上线。
|
||||
|
||||
@ -42,4 +42,13 @@ final class ReleaseStoreTest extends TestCase
|
||||
$legacy = $store->previous(); self::assertFalse($legacy['validated']);
|
||||
$store->activate($legacy['id']); self::assertSame('legacy', file_get_contents($public.'/'.date('Y-m-d').'.txt'));
|
||||
}
|
||||
public function testPublicationFailureRestoresPublicBytesAndKeepsCurrent(): void {
|
||||
$store = new ReleaseStore($this->dir.'/private', $this->dir.'/public');
|
||||
$a = $store->stage($this->fixture('first'), date('Y-m-d')); $store->publishPrepared();
|
||||
$store->stage($this->fixture('second'), date('Y-m-d'));
|
||||
mkdir($this->dir.'/private/previous.json');
|
||||
try { $store->publishPrepared(); self::fail(); } catch (RuntimeException $e) { self::assertSame('artifact_rename_failed', $e->getMessage()); }
|
||||
self::assertSame($a['id'], $store->current()['id']);
|
||||
self::assertSame('first', file_get_contents($this->dir.'/public/site/v2ray/'.date('Y-m-d').'.txt'));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user