Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift
T
Qiu Yuzhou 00fcd61a99 refactor(ng2): change bundle id to ShadowsocksX-NG2
Distinct id from Legacy (com.qiuyuzhou.ShadowsocksX-NG) lets NG2 own
~/Library/Application Support/ShadowsocksX-NG2/ directly; the v2/
subdir prefix is removed from all runtime paths.

- app/agent/tests targets + LaunchAgent plist renamed accordingly
- keychain service, queue labels, and Vendor signIdentifiers follow
  the new bundle id; embedded helpers re-sign at build time
- Legacy handoff/import constants keep pointing at the old id

Migration: dev data under ShadowsocksX-NG/v2/ is not carried over
(fresh catalog/activation); old LaunchAgent registration and old
keychain entries must be cleaned up manually.
2026-09-21 20:33:37 +08:00

65 lines
2.4 KiB
Swift

import Foundation
/// 活动目标持久化(spec #21 D3/D5):落盘 `~/Library/Application Support/
/// ShadowsocksX-NG2/activation.json`,权限与原子替换基线同 catalog.json。
/// 只含目标身份,永不落秘密明文。损坏或版本未知按安全侧恢复为「无活动目标」
/// (代理保持停止,由状态机重校验同步);文件系统级错误仍抛出。
struct ActivationStateFileStore {
enum PersistenceError: Error, Equatable {
/// 读取或写入时的文件系统错误。
case ioFailure(detail: String)
}
private static let currentVersion = 1
private static let jsonEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
return encoder
}()
let fileURL: URL
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/activation.json`。
static func defaultFileURL() -> URL {
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[
0]
return support.appendingPathComponent("ShadowsocksX-NG2/activation.json")
}
/// 文件缺失/损坏/版本未知 → `nil`(安全侧:无活动目标)。
func loadActiveTargetID() throws -> NodeID? {
guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil }
let data: Data
do {
data = try Data(contentsOf: fileURL)
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
let payload = try? JSONDecoder().decode(ActivationStatePayload.self, from: data)
guard let payload, payload.version == Self.currentVersion else { return nil }
return payload.activeTargetID
}
/// 整体重写并原子替换;失败时保留原文件。目录按 D5 基线强制 0700(含自愈)。
func save(activeTargetID: NodeID?) throws {
let data: Data
do {
data = try Self.jsonEncoder.encode(
ActivationStatePayload(version: Self.currentVersion, activeTargetID: activeTargetID))
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
do {
try AtomicFileWriter.write(data, to: fileURL)
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
}
}
/// 落盘文档形态:版本号 + 活动目标身份(可为 null)。
private struct ActivationStatePayload: Codable {
var version: Int
var activeTargetID: NodeID?
}