using System.Text; using System.Text.Json; using Fleet.Core; using Monitor.Protocol; namespace Monitor.Measurement; /// /// 节点进程和宿主机更新器之间的文件交换(容器里 /etc/monitor,也就是数据目录): /// 节点把面板发来的清单和签名写进 update/,把「更新」请求写成 update/apply.request;更新器验签并更新,状态写在 root 拥有的 update-status/status.json。 /// 节点进程不执行任何命令,也不信任清单的内容(验签在宿主机上)。没有安装更新器时目录写不了或没人读,调用只是返回 false。 /// public sealed class UpdateBridge { private const int MaxManifestBytes = 4096; private const int MaxSignatureBytes = 512; private const int MaxStatusBytes = 4096; private readonly string _updateDirectory; private readonly string _statusFile; private readonly Action _write; private readonly INodeLog _log; /// 写数据目录里的文件(相对路径,原子替换):由节点传入 ConfigStore.Write——常驻进程里只有它可以写文件。 public UpdateBridge(string dataDirectory, Action write, INodeLog log) { _updateDirectory = Path.Combine(dataDirectory, "update"); _statusFile = Path.Combine(dataDirectory, "update-status", "status.json"); _write = write; _log = log; } /// 放下面板发来的清单和签名(和已有的一样就不动,免得每次重连都惊动更新器)。先写清单、后写签名:更新器监视的是签名文件。 public bool WriteOffer(UpdateOfferParams offer) { byte[] manifest = Encoding.UTF8.GetBytes(offer.Manifest); byte[] signature = Encoding.UTF8.GetBytes(offer.Signature); if (manifest.Length is 0 or > MaxManifestBytes || signature.Length is 0 or > MaxSignatureBytes) { _log.Warn("ignoring an update offer of an unexpected size"); return false; } try { string manifestPath = Path.Combine(_updateDirectory, "offer.manifest"); string signaturePath = Path.Combine(_updateDirectory, "offer.sig"); if (Same(manifestPath, manifest) && Same(signaturePath, signature)) { return true; } _write("update/offer.manifest", manifest); _write("update/offer.sig", signature); _log.Info("update offer received"); return true; } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { _log.Warn("could not store the update offer (is the updater installed?)", ("err", ex.Message)); return false; } } /// 请求更新:写一个空的 apply.request(更新器验签通过、清单更新才会真的动手)。 public bool RequestApply() { try { _write("update/apply.request", []); _log.Info("update requested"); return true; } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { _log.Warn("could not request the update (is the updater installed?)", ("err", ex.Message)); return false; } } /// 更新器的状态;没有安装更新器、文件不存在或内容不对时返回 null。 public UpdateStatus? ReadStatus() { try { if (!File.Exists(_statusFile) || new FileInfo(_statusFile).Length > MaxStatusBytes) { return null; } return JsonSerializer.Deserialize(File.ReadAllBytes(_statusFile), MonitorJsonContext.Default.UpdateStatus); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) { return null; } } private static bool Same(string path, byte[] content) => File.Exists(path) && File.ReadAllBytes(path).AsSpan().SequenceEqual(content); }