feat(toolbox): preconfig store (load fail-open, clear-pin, configured marker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace SilverOS.Welcome.Core.Preconfig;
|
||||
|
||||
public interface IPreconfigStore
|
||||
{
|
||||
Preconfig? Load(); // null if missing/corrupt (fail-open)
|
||||
void ClearPin(); // rewrite preconfig without the BitLocker pin
|
||||
bool IsConfigured(); // configured marker present?
|
||||
void MarkConfigured(); // write the configured marker
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SilverOS.Welcome.Core.Preconfig;
|
||||
|
||||
public sealed record BitlockerConfig { public bool Enable { get; init; } public string? Pin { get; init; } }
|
||||
public sealed record AppsConfig { public bool UseFlavourDefaults { get; init; } = true; public IReadOnlyList<string>? Selected { get; init; } }
|
||||
|
||||
public sealed record Preconfig
|
||||
{
|
||||
public int SchemaVersion { get; init; } = 1;
|
||||
public string Flavour { get; init; } = "";
|
||||
public BitlockerConfig Bitlocker { get; init; } = new();
|
||||
public AppsConfig Apps { get; init; } = new();
|
||||
|
||||
public static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
AllowTrailingCommas = true,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SilverOS.Welcome.Core.Preconfig;
|
||||
|
||||
public sealed class PreconfigStore(string dir) : IPreconfigStore
|
||||
{
|
||||
private string File_ => Path.Combine(dir, "preconfig.json");
|
||||
private string Marker => Path.Combine(dir, "configured");
|
||||
|
||||
public Preconfig? Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(File_)) return null;
|
||||
return JsonSerializer.Deserialize<Preconfig>(File.ReadAllText(File_), Preconfig.JsonOptions);
|
||||
}
|
||||
catch (JsonException) { return null; } // fail-open
|
||||
}
|
||||
|
||||
public void ClearPin()
|
||||
{
|
||||
var p = Load();
|
||||
if (p is null) return;
|
||||
var stripped = p with { Bitlocker = p.Bitlocker with { Pin = null } };
|
||||
File.WriteAllText(File_, JsonSerializer.Serialize(stripped, Preconfig.JsonOptions));
|
||||
}
|
||||
|
||||
public bool IsConfigured() => File.Exists(Marker);
|
||||
public void MarkConfigured() { Directory.CreateDirectory(dir); File.WriteAllText(Marker, "1"); }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using SilverOS.Welcome.Core.Preconfig;
|
||||
using Xunit;
|
||||
|
||||
public class PreconfigTests
|
||||
{
|
||||
static string TempDir()
|
||||
{
|
||||
var d = Path.Combine(Path.GetTempPath(), "smpre-" + System.Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
const string Sample = """
|
||||
{ "schemaVersion":1, "flavour":"developer",
|
||||
"bitlocker":{"enable":true,"pin":"246810"},
|
||||
"apps":{"useFlavourDefaults":true} }
|
||||
""";
|
||||
|
||||
[Fact]
|
||||
public void Loads_flavour_and_pin()
|
||||
{
|
||||
var dir = TempDir();
|
||||
File.WriteAllText(Path.Combine(dir, "preconfig.json"), Sample);
|
||||
var p = new PreconfigStore(dir).Load();
|
||||
Assert.NotNull(p);
|
||||
Assert.Equal("developer", p!.Flavour);
|
||||
Assert.True(p.Bitlocker.Enable);
|
||||
Assert.Equal("246810", p.Bitlocker.Pin);
|
||||
Assert.True(p.Apps.UseFlavourDefaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Missing_or_bad_file_returns_null_not_throw()
|
||||
{
|
||||
Assert.Null(new PreconfigStore(TempDir()).Load()); // missing
|
||||
var dir = TempDir();
|
||||
File.WriteAllText(Path.Combine(dir, "preconfig.json"), "{ not json");
|
||||
Assert.Null(new PreconfigStore(dir).Load()); // corrupt
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearPin_rewrites_without_the_pin()
|
||||
{
|
||||
var dir = TempDir();
|
||||
File.WriteAllText(Path.Combine(dir, "preconfig.json"), Sample);
|
||||
var store = new PreconfigStore(dir);
|
||||
store.ClearPin();
|
||||
var reread = store.Load();
|
||||
Assert.True(string.IsNullOrEmpty(reread!.Bitlocker.Pin));
|
||||
Assert.Equal("developer", reread.Flavour); // rest preserved
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configured_marker_roundtrips()
|
||||
{
|
||||
var dir = TempDir();
|
||||
var store = new PreconfigStore(dir);
|
||||
Assert.False(store.IsConfigured());
|
||||
store.MarkConfigured();
|
||||
Assert.True(store.IsConfigured());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user