feat: self-host Hobbs Family Organizer (Shield TV dashboard) on hf-pdocker-02:8340
- tv app/Hobbs-Family-Organizer-Self-Hosted-v1.0.0: release bundle (Next.js app + Shield APK v1.4 + source) - compose/hf-pdocker-02/hobbs-organizer: override (local-disk secrets, watchtower off), .env (port 8340), Shield provisioning script - CLAUDE.md: service tree + internal ports Trusted-LAN mode. Secrets live only on .100 /var/lib/hobbs-organizer/secrets (not in git). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E9NnVEico3WvFxLyjy1iW3
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$DestinationDirectory
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$ProjectRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
|
||||
if (-not $DestinationDirectory) {
|
||||
$DestinationDirectory = Join-Path $ProjectRoot "backups"
|
||||
}
|
||||
$DestinationDirectory = [IO.Path]::GetFullPath($DestinationDirectory)
|
||||
|
||||
$Sources = @{
|
||||
Environment = Join-Path $ProjectRoot ".env"
|
||||
Calendars = Join-Path $ProjectRoot "secrets/calendar_ical_urls.json"
|
||||
Token = Join-Path $ProjectRoot "secrets/organizer_access_token.txt"
|
||||
}
|
||||
|
||||
foreach ($source in $Sources.GetEnumerator()) {
|
||||
if (-not (Test-Path -LiteralPath $source.Value -PathType Leaf)) {
|
||||
throw "Required configuration is missing. Run scripts/configure.ps1 before creating a backup."
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $DestinationDirectory -Force | Out-Null
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$suffix = [Guid]::NewGuid().ToString("N").Substring(0, 8)
|
||||
$archivePath = Join-Path $DestinationDirectory "hobbs-organizer-config-$timestamp-$suffix.zip"
|
||||
$stagingPath = Join-Path $DestinationDirectory (".backup-staging-" + [Guid]::NewGuid().ToString("N"))
|
||||
$stagingFullPath = [IO.Path]::GetFullPath($stagingPath)
|
||||
$destinationPrefix = $DestinationDirectory.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
||||
|
||||
if (-not $stagingFullPath.StartsWith($destinationPrefix, [StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Refusing to create a staging directory outside the selected backup directory."
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Path (Join-Path $stagingPath "config") -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $stagingPath "secrets") -Force | Out-Null
|
||||
Copy-Item -LiteralPath $Sources.Environment -Destination (Join-Path $stagingPath "config/environment.env")
|
||||
Copy-Item -LiteralPath $Sources.Calendars -Destination (Join-Path $stagingPath "secrets/calendar_ical_urls.json")
|
||||
Copy-Item -LiteralPath $Sources.Token -Destination (Join-Path $stagingPath "secrets/organizer_access_token.txt")
|
||||
|
||||
$manifest = @(
|
||||
"format=HOBBS_ORGANIZER_CONFIG_BACKUP_V1",
|
||||
"created_utc=$((Get-Date).ToUniversalTime().ToString('o'))",
|
||||
"contains_secrets=true"
|
||||
) -join [Environment]::NewLine
|
||||
[IO.File]::WriteAllText(
|
||||
(Join-Path $stagingPath "manifest.txt"),
|
||||
$manifest + [Environment]::NewLine,
|
||||
(New-Object System.Text.UTF8Encoding($false))
|
||||
)
|
||||
|
||||
Compress-Archive -Path (Join-Path $stagingPath "*") -DestinationPath $archivePath -CompressionLevel Optimal
|
||||
|
||||
if ($PSVersionTable.PSEdition -eq "Desktop" -or [Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::Windows)) {
|
||||
$userName = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
& icacls.exe $archivePath "/inheritance:r" "/grant:r" "${userName}:(F)" | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Could not restrict backup archive permissions." }
|
||||
}
|
||||
else {
|
||||
& chmod 600 -- $archivePath
|
||||
if ($LASTEXITCODE -ne 0) { throw "Could not restrict backup archive permissions." }
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $stagingFullPath) {
|
||||
Remove-Item -LiteralPath $stagingFullPath -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Created a protected configuration backup at: $archivePath"
|
||||
Write-Host "The archive contains secrets. Store it in an encrypted or access-controlled backup system."
|
||||
Reference in New Issue
Block a user