fix(admin): align UTM notes and block obsolete chb publication
deploy-preland / deploy zoomstage.online (push) Successful in 54s
deploy-preland / deploy zoomstage.online (push) Successful in 54s
This commit is contained in:
1 parent
f2097d4800
commit
84f70b1c71
14 files changed
+93
-29
No files matched your search
@@ -9,6 +9,10 @@ final class Config
|
||||
return $value;
|
||||
}
|
||||
public static function encode(\stdClass $value): string { return json_encode($value, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR) . "\n"; }
|
||||
/** Старый raw читается без миграции, но повторная публикация chb запрещена. */
|
||||
public static function assertWritable(\stdClass $config): void {
|
||||
if(($config->utm??null) instanceof \stdClass&&property_exists($config->utm,'chb'))throw new Problem('Устаревший ключ utm.chb нельзя записывать. Явно удалите его перед сохранением; восстановление такого архива запрещено.');
|
||||
}
|
||||
public static function patch(\stdClass $config, array $changes): \stdClass {
|
||||
if (!$changes || count($changes)>500) throw new Problem('Выберите изменённые поля (не более 500).');
|
||||
$copy = self::decode(self::encode($config));
|
||||
@@ -31,7 +35,7 @@ final class Config
|
||||
}
|
||||
unset($ref);
|
||||
}
|
||||
return $copy;
|
||||
self::assertWritable($copy);return $copy;
|
||||
}
|
||||
private static function adaptChange(\stdClass $config,array $change): array {
|
||||
$path=$change['path']??[];if(($path[0]??null)!=='utm'||!isset($path[1]))return $change;
|
||||
@@ -51,6 +55,7 @@ final class Config
|
||||
$change['path']=$path;return $change;
|
||||
}
|
||||
public static function validate(\stdClass $config, callable $setExists,?array $catalog=null): array {
|
||||
self::assertWritable($config);
|
||||
if (isset($config->host)) {
|
||||
$host = $config->host;
|
||||
if (!is_string($host) || !preg_match('~^(?:https://)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::[0-9]{1,5})?$~iD',$host) || str_contains($host,'..')) throw new Problem('Адрес сайта должен быть HTTPS-хостом без пути.');
|
||||
|
||||
@@ -32,6 +32,12 @@ final class Remote
|
||||
ksort($hashes);return hash('sha256',json_encode($hashes,JSON_THROW_ON_ERROR));
|
||||
}
|
||||
public static function execute(array $p): array {
|
||||
// Этот обработчик исполняется отдельно по SSH, без автозагрузки Config.
|
||||
// Отказ до любых приватных каталогов, recovery, резервов и temp-файлов.
|
||||
if(($p['op']??null)==='write'&&is_string($p['content']??null)){
|
||||
$candidate=json_decode($p['content']);
|
||||
if($candidate instanceof \stdClass&&($candidate->utm??null) instanceof \stdClass&&property_exists($candidate->utm,'chb'))return ['ok'=>false,'error'=>'Устаревший ключ utm.chb нельзя записывать. Явно удалите его перед сохранением; восстановление такого архива запрещено.'];
|
||||
}
|
||||
$root=$p['root'];
|
||||
if (!is_string($root)||!str_starts_with($root,'/')||str_contains($root,'..')||str_contains($root,'\\')||realpath($root)!==$root||is_link($root)) return ['ok'=>false,'error'=>'Небезопасный корневой путь.'];
|
||||
$private=$p['local_private']??dirname($root).'/.preland-admin-'.substr(hash('sha256',$root),0,16);
|
||||
|
||||
@@ -78,6 +78,7 @@ final class Transport
|
||||
public function test(): array {return $this->call(['op'=>'test']);}
|
||||
public function readConfig(): array {$r=$this->call(['op'=>'read']);return ['config'=>Config::decode($r['content']),'version'=>$r['version'],'content'=>$r['content']];}
|
||||
public function writeConfig(string $version,string $content): array {
|
||||
Config::assertWritable(Config::decode($content));
|
||||
$before=$this->call(['op'=>'read']);if(!hash_equals($version,$before['version']))throw new Conflict();
|
||||
$this->store->backup($this->target['id'],$before['content']);
|
||||
return $this->call(['op'=>'write','version'=>$version,'content'=>$content]);
|
||||
|
||||
Reference in new issue
Block a user