feat(welcome): account + BitLocker + bootstrap services
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public sealed class AccountService(IProcessRunner runner) : IAccountService
|
||||
{
|
||||
public async Task CreateAccountsAsync(string user, string password, string adminPassword, CancellationToken ct = default)
|
||||
{
|
||||
// Daily account = Standard User (Users group only — NOT Administrators).
|
||||
await Ps($"$p=ConvertTo-SecureString '{Esc(password)}' -AsPlainText -Force; " +
|
||||
$"New-LocalUser -Name '{Esc(user)}' -Password $p -FullName '{Esc(user)}' -AccountNeverExpires; " +
|
||||
$"Add-LocalGroupMember -Group 'Users' -Member '{Esc(user)}'", ct);
|
||||
// Separate elevation account.
|
||||
await Ps($"$a=ConvertTo-SecureString '{Esc(adminPassword)}' -AsPlainText -Force; " +
|
||||
$"New-LocalUser -Name 'SilverOS Admin' -Password $a -AccountNeverExpires; " +
|
||||
$"Add-LocalGroupMember -Group 'Administrators' -Member 'SilverOS Admin'", ct);
|
||||
}
|
||||
private Task Ps(string script, CancellationToken ct) =>
|
||||
runner.RunAsync("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command \"{script}\"", ct);
|
||||
private static string Esc(string s) => s.Replace("'", "''");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public sealed class BitLockerService(IProcessRunner runner) : IBitLockerService
|
||||
{
|
||||
public Task EnableAsync(string pin, CancellationToken ct = default) =>
|
||||
runner.RunAsync("powershell.exe",
|
||||
$"-NoProfile -ExecutionPolicy Bypass -Command \"$p=ConvertTo-SecureString '{pin.Replace("'", "''")}' -AsPlainText -Force; " +
|
||||
"Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p -SkipHardwareTest\"", ct);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService
|
||||
{
|
||||
public async Task TearDownAsync(string bootstrapUser, CancellationToken ct = default)
|
||||
{
|
||||
const string key = "'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon'";
|
||||
await Ps($"Set-ItemProperty {key} -Name AutoAdminLogon -Value 0; " +
|
||||
$"Remove-ItemProperty {key} -Name DefaultPassword -EA SilentlyContinue", ct);
|
||||
await Ps($"Remove-LocalUser -Name '{bootstrapUser}' -EA SilentlyContinue", ct);
|
||||
}
|
||||
private Task Ps(string s, CancellationToken ct) =>
|
||||
runner.RunAsync("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command \"{s}\"", ct);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public interface IAccountService { Task CreateAccountsAsync(string user, string password, string adminPassword, CancellationToken ct = default); }
|
||||
@@ -0,0 +1,2 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public interface IBitLockerService { Task EnableAsync(string pin, CancellationToken ct = default); }
|
||||
@@ -0,0 +1,2 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public interface IBootstrapService { Task TearDownAsync(string bootstrapUser, CancellationToken ct = default); }
|
||||
@@ -0,0 +1,45 @@
|
||||
using Moq;
|
||||
using SilverOS.Welcome.Core.Apply;
|
||||
|
||||
public class ApplyServicesTests
|
||||
{
|
||||
private static Mock<IProcessRunner> Ok()
|
||||
{
|
||||
var m = new Mock<IProcessRunner>();
|
||||
m.Setup(r => r.RunAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ProcessResult(0, "", ""));
|
||||
return m;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AccountService_creates_standard_daily_and_admin()
|
||||
{
|
||||
var run = Ok();
|
||||
await new AccountService(run.Object).CreateAccountsAsync("alice", "pw1", "adminpw");
|
||||
// daily user is a Standard user (added to Users, NOT Administrators)
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("New-LocalUser") && s.Contains("alice")), It.IsAny<CancellationToken>()));
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("'SilverOS Admin'") && s.Contains("Administrators")), It.IsAny<CancellationToken>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BitLockerService_enables_tpm_and_pin()
|
||||
{
|
||||
var run = Ok();
|
||||
await new BitLockerService(run.Object).EnableAsync("123456");
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("Enable-BitLocker") && s.Contains("TpmAndPinProtector")), It.IsAny<CancellationToken>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BootstrapService_removes_autologon_and_account()
|
||||
{
|
||||
var run = Ok();
|
||||
await new BootstrapService(run.Object).TearDownAsync("sm-bootstrap");
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("AutoAdminLogon") && s.Contains("0")), It.IsAny<CancellationToken>()));
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("Remove-LocalUser") && s.Contains("sm-bootstrap")), It.IsAny<CancellationToken>()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user