diff --git a/ka-note/scripts/download.ps1 b/ka-note/scripts/download.ps1 index a092367..973bd3d 100644 --- a/ka-note/scripts/download.ps1 +++ b/ka-note/scripts/download.ps1 @@ -80,9 +80,12 @@ if (Test-Path $WorkDir) { if ($Prod) { $BaseUrl = "https://ka-note.azurewebsites.net" - $BearerToken = if ($Token) { $Token } else { $env:KA_NOTE_TOKEN } + $BearerToken = if ($Token) { $Token } elseif ($env:KA_NOTE_TOKEN) { $env:KA_NOTE_TOKEN } else { + Write-Host " Acquiring token via MSAL..." -ForegroundColor DarkGray + & "$PSScriptRoot\get-token.ps1" + } if (-not $BearerToken) { - Write-Err "Prod mode requires a Bearer token. Pass -Token or set `$env:KA_NOTE_TOKEN." + Write-Err "Failed to acquire a Bearer token." exit 1 } $Headers = @{ Authorization = "Bearer $BearerToken" } diff --git a/ka-note/scripts/get-token.ps1 b/ka-note/scripts/get-token.ps1 new file mode 100644 index 0000000..da6ce66 --- /dev/null +++ b/ka-note/scripts/get-token.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + Gets a Bearer token for the Ka-Note production API via MSAL.PS. + Installs MSAL.PS automatically if missing. + Uses cached tokens / refresh tokens — browser login only needed on first run + or after token cache is cleared. + +.OUTPUTS + Writes the access token string to stdout. + +.EXAMPLE + $token = & "$PSScriptRoot\get-token.ps1" +#> +param() +$ErrorActionPreference = 'Stop' + +$ClientId = '1aba7af7-eec1-4e49-b87e-9f941c0e8630' +$TenantId = '94cf90d7-e9ff-49a1-bc3b-a5b94d3cc8ca' +$Scopes = "api://$ClientId/access" + +# --- Ensure MSAL.PS is available --------------------------------------------- +if (-not (Get-Module -ListAvailable -Name 'MSAL.PS')) { + Write-Host " [INFO] Installing MSAL.PS module..." -ForegroundColor DarkGray + Install-Module -Name 'MSAL.PS' -Scope CurrentUser -Force -AllowClobber +} +Import-Module MSAL.PS -ErrorAction Stop + +# --- Acquire token ----------------------------------------------------------- +$params = @{ + ClientId = $ClientId + TenantId = $TenantId + Scopes = $Scopes +} + +$result = $null + +# 1. Try silent first (uses cached access token or refresh token) +try { + $result = Get-MsalToken @params -Silent 2>$null +} catch { + # No cached token or refresh failed — fall back to interactive +} + +# 2. Interactive browser login +if (-not $result) { + Write-Host " [AUTH] Opening browser for login..." -ForegroundColor Yellow + $result = Get-MsalToken @params -Interactive +} + +if (-not $result -or -not $result.AccessToken) { + Write-Error "Failed to acquire token." + exit 1 +} + +# Output only the token (callers capture via $token = & .\get-token.ps1) +Write-Output $result.AccessToken diff --git a/ka-note/scripts/import-helpers.ps1 b/ka-note/scripts/import-helpers.ps1 new file mode 100644 index 0000000..5237459 --- /dev/null +++ b/ka-note/scripts/import-helpers.ps1 @@ -0,0 +1,160 @@ +<# +.SYNOPSIS + Shared helper functions for UpNote → Ka-Note bundle imports. + Dot-source this file in import scripts: . "$PSScriptRoot\import-helpers.ps1" + +.NOTES + Bundle file formats (as of current schema): + + contexts.json — array of AgendaContext: + id, name, type (meeting|project|person|company), sortOrder, + updatedAt, deletedAt, version, archivedAt, isFavorite, meta + + topics.json — array of Topic: + id, contextId, title, body, sortOrder, isPinned, + deletedAt, updatedAt, version + + history/.meta.json — HistoryEntry (without text): + id, topicId, date (YYYY-MM-DD), sortOrder, linkedContextId, + doneAt, wiedervorlageDate, wiedervorlageResolvedAt, + updatedAt, deletedAt, version + history/.md — plain text / markdown content + + IMPORTANT: + - History is TOPIC-scoped (topicId), NOT context-scoped. + - History date is "YYYY-MM-DD" (no time part). + - History has no "title" field. + - Soft-deleted contexts cause 500 on upload if new topics reference them + → always call Upsert-Context which auto-resurrects deleted contexts. +#> + +# Globals set by Initialize-BundleSession +$script:WorkDir = $null +$script:Now = $null + +function Initialize-BundleSession { + param([string]$WorkDir) + $script:WorkDir = $WorkDir + $script:Now = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + if (-not (Test-Path "$WorkDir\history")) { New-Item -ItemType Directory "$WorkDir\history" | Out-Null } +} + +# Upsert a context: creates new OR resurrects if soft-deleted OR skips if active. +# Always bumps version and sets name/type/sortOrder if changed. +function Upsert-Context { + param( + [string]$CtxPath, + [string]$Id, + [string]$Name, + [string]$Type = 'meeting', + [int] $SortOrder = 10, + [bool] $IsFavorite = $true + ) + $ctxs = Get-Content $CtxPath -Raw -Encoding UTF8 | ConvertFrom-Json + $existing = $ctxs | Where-Object { $_.id -eq $Id } + + if ($existing) { + $wasDeleted = $null -ne $existing.deletedAt + $existing | Add-Member -NotePropertyName deletedAt -NotePropertyValue $null -Force + $existing | Add-Member -NotePropertyName name -NotePropertyValue $Name -Force + $existing | Add-Member -NotePropertyName updatedAt -NotePropertyValue $script:Now -Force + $existing.version++ + if ($wasDeleted) { + Write-Host " [CTX] Resurrected: $Id" -ForegroundColor Green + } else { + Write-Host " [CTX] Already active, updated: $Id" -ForegroundColor DarkGray + } + } else { + $new = [PSCustomObject]@{ + id = $Id + name = $Name + type = $Type + sortOrder = $SortOrder + updatedAt = $script:Now + deletedAt = $null + version = 1 + archivedAt = $null + isFavorite = $IsFavorite + meta = $null + } + $ctxs = @($ctxs) + $new + Write-Host " [CTX] Created: $Id ($Name)" -ForegroundColor Green + } + $ctxs | ConvertTo-Json -Depth 10 | Set-Content $CtxPath -Encoding UTF8 +} + +# Soft-delete all topics for a context (cleanup orphans or old data) +function Remove-ContextTopics { + param([string]$TopicsPath, [string]$ContextId) + $tops = Get-Content $TopicsPath -Raw -Encoding UTF8 | ConvertFrom-Json + $count = 0 + foreach ($t in $tops) { + if ($t.contextId -eq $ContextId -and -not $t.deletedAt) { + $t | Add-Member -NotePropertyName deletedAt -NotePropertyValue $script:Now -Force + $t | Add-Member -NotePropertyName updatedAt -NotePropertyValue $script:Now -Force + $t.version++ + $count++ + } + } + $tops | ConvertTo-Json -Depth 10 | Set-Content $TopicsPath -Encoding UTF8 + if ($count -gt 0) { Write-Host " [TOP] Soft-deleted $count orphan topics for $ContextId" -ForegroundColor DarkGray } +} + +# Add a new topic. Returns the topic ID. +function Add-Topic { + param( + [string]$TopicsPath, + [string]$ContextId, + [string]$Title, + [string]$Body = '', + [int] $SortOrder = 0, + [bool] $IsPinned = $false + ) + $id = [System.Guid]::NewGuid().ToString() + $tops = Get-Content $TopicsPath -Raw -Encoding UTF8 | ConvertFrom-Json + $new = [PSCustomObject]@{ + id = $id + contextId = $ContextId + title = $Title + body = $Body + sortOrder = $SortOrder + isPinned = $IsPinned + deletedAt = $null + updatedAt = $script:Now + version = 1 + } + $tops = @($tops) + $new + $tops | ConvertTo-Json -Depth 10 | Set-Content $TopicsPath -Encoding UTF8 + Write-Host " [TOP] + $Title" -ForegroundColor DarkGray + return $id +} + +# Add a history entry (attached to a topic). +# $Date: 'YYYY-MM-DD' +# $Text: markdown string +function Add-HistoryEntry { + param( + [string]$TopicId, + [string]$Date, + [string]$Text, + [int] $SortOrder = 0 + ) + $id = [System.Guid]::NewGuid().ToString() + $meta = [PSCustomObject]@{ + id = $id + topicId = $TopicId + date = $Date + sortOrder = $SortOrder + linkedContextId = $null + doneAt = $null + wiedervorlageDate = $null + wiedervorlageResolvedAt = $null + updatedAt = $script:Now + deletedAt = $null + version = 1 + } + $meta | ConvertTo-Json -Depth 5 | Set-Content "$($script:WorkDir)\history\$id.meta.json" -Encoding UTF8 + $Text | Set-Content "$($script:WorkDir)\history\$id.md" -Encoding UTF8 + Write-Host " [HIS] + $Date" -ForegroundColor DarkGray + return $id +} diff --git a/ka-note/scripts/import-jf-sysadmins.ps1 b/ka-note/scripts/import-jf-sysadmins.ps1 new file mode 100644 index 0000000..b605291 --- /dev/null +++ b/ka-note/scripts/import-jf-sysadmins.ps1 @@ -0,0 +1,131 @@ +<# +.SYNOPSIS + Imports / re-imports the "JF: Team Sysadmins" context into Ka-Note. + Idempotent: resurrects soft-deleted context, soft-deletes orphan topics, + creates clean topic set and history entries. + +.PARAMETER Prod + Target production server. + +.PARAMETER Force + Skip upload conflict confirmation. + +.EXAMPLE + .\import-jf-sysadmins.ps1 + .\import-jf-sysadmins.ps1 -Prod +#> +param( + [switch]$Prod, + [switch]$Force +) +$ErrorActionPreference = 'Stop' + +. "$PSScriptRoot\import-helpers.ps1" + +$scripts = $PSScriptRoot +$RepoRoot = Resolve-Path "$PSScriptRoot\..\.." +$workDir = Join-Path $RepoRoot "work" + +# ── 1. Download ─────────────────────────────────────────────────────────────── +Write-Host "`n[1] Downloading bundle..." -ForegroundColor Cyan +$dlArgs = @('-File', "$scripts\download.ps1", '-Force') +if ($Prod) { $dlArgs += '-Prod' } +& powershell @dlArgs +if ($LASTEXITCODE -ne 0) { throw "Download failed (exit $LASTEXITCODE)" } + +Initialize-BundleSession -WorkDir $workDir + +$ctxPath = "$workDir\contexts.json" +$topsPath = "$workDir\topics.json" +$ctxId = 'jf-sysadmins' + +# ── 2. Upsert context (creates or resurrects soft-deleted) ──────────────────── +Write-Host "`n[2] Context..." -ForegroundColor Cyan +Upsert-Context -CtxPath $ctxPath -Id $ctxId -Name 'JF: Team Sysadmins' -SortOrder 20 + +# ── 3. Soft-delete orphan topics ───────────────────────────────────────────── +Write-Host "`n[3] Cleanup orphan topics..." -ForegroundColor Cyan +Remove-ContextTopics -TopicsPath $topsPath -ContextId $ctxId + +# ── 4. Create topics ────────────────────────────────────────────────────────── +Write-Host "`n[4] Creating topics..." -ForegroundColor Cyan + +$journalId = Add-Topic $topsPath $ctxId 'Sitzungsprotokoll' ` + 'Meeting-Notizen chronologisch.' 5 $true + +Add-Topic $topsPath $ctxId 'TISAX: Sperren Produktionsrechner' ` + "Benutzergruppe kein Internet + VLAN shared`n- EOL-VLAN, SERVICE-ACCOUNT VLAN`n- PC-0259`n- Stas funktioniert nicht" 10 | Out-Null + +Add-Topic $topsPath $ctxId 'TISAX: Allgemeine Anforderungen' ` + "- Doku Backup-Prozess`n- Situationen simulieren`n- Pen-Test vor TISAX (ueber RES)`n- DocuSnap fuer Notfallhandbuch`n- Anforderung 5.3.2 Netzwerkdienste" 20 | Out-Null + +Add-Topic $topsPath $ctxId 'Backup und SAN' ` + "- Backup-Probleme DW und BUW (warten auf Ports, Hees, Transaktionslogs, Wartungsjobs)`n- VMware Upgrade HKR: Bootreihenfolge festlegen`n- Rucksicherungstest, Prozess mit Hees beschreiben" 30 | Out-Null + +Add-Topic $topsPath $ctxId 'Citrix Farm Erneuerung' ` + "- Grundinstallation da, Stefan installiert Programme`n- Sophos Endpoint fehlt`n- GPOs + Netscaler Termin SVA`n- MFA und Citrix: Alternativen SVA ausstehend" 40 | Out-Null + +Add-Topic $topsPath $ctxId 'internal.lan / Access-Manager' ` + "- Nacharbeiten F+E laufen`n- Dennis + Ossenbuehl starten Vorbereitungen`n- Angebot Access-Manager ausstehend" 50 | Out-Null + +Add-Topic $topsPath $ctxId 'Greenbone / Schwachstellenmanagement' ` + "- Philip: Analyse-Ergebnis heisst nicht, alles selbst bearbeiten`n- Dokumentation in Arbeit`n- Greenbone ueberwacht auch Citrix-Umgebung (ja)" 60 | Out-Null + +Add-Topic $topsPath $ctxId 'Windows-Upgrade und EOL-Systeme' ` + "- Win10 bei KAS: isoliert? Hardwareauswahl? Ende 14. Okt.`n- XP-Maschinen fluten Netzwerk`n- Admin-Rechte HKR abschalten`n- Rechner Fertigung: IT-Richtlinie" 70 | Out-Null + +Add-Topic $topsPath $ctxId 'Netzwerk und Core-Switch' ` + "- Core-Switch Nacharbeiten (VLANs stecken, alten Switch ausbauen)`n- Rückbau IPSec Regeln`n- Aufbau Server-VLAN`n- NAC: Liste Geraete vorbereiten" 80 | Out-Null + +Add-Topic $topsPath $ctxId 'DocuSnap und Notfallhandbuch' ` + "- Notfallhandbuch HKR`n- DocuSnap: Termin mit Kelobit`n- Verzeichnis Verarbeitungstaetigkeiten`n- V14 eingespielt, Sammler gefixt" 90 | Out-Null + +Add-Topic $topsPath $ctxId 'KI-Richtlinie und IT-Governance' ` + "- KI-Governance: Philip`n- KI-Workshop: Eray`n- Server kuenftig nur auf Englisch (IT-Richtlinie)`n- KAS: keine Admin-Rechte mehr" 100 | Out-Null + +Add-Topic $topsPath $ctxId 'Teams und Kommunikation' ` + "- Teams-Probleme HKR: Netzwerkkarten bestellt`n- BZ-Ampere Ticket #507535`n- KRAH-App Service-Status" 110 | Out-Null + +Add-Topic $topsPath $ctxId 'Diverses und Aufgaben' ` + "- deletion of old user: ZAD, Job einplanen`n- IT Kram WITEC`n- SnipeIT, Teleport, CLM Einfuehrung`n- Passwort-DB ausrollen" 120 | Out-Null + +# ── 5. Create history entries (attached to Sitzungsprotokoll) ───────────────── +Write-Host "`n[5] Creating history entries..." -ForegroundColor Cyan + +Add-HistoryEntry $journalId '2026-02-20' "## Next`n`n- TISAX: Sperren Produktionsrechner (Benutzergruppe kein Internet + VLAN shared)`n- IT Kram WITEC`n- deletion of old user: ZAD aktivieren, Job einplanen`n- Windows ressourcenhungriger -> Leasing pruefen`n- Ticket #507535 BZ-Ampere" | Out-Null + +Add-HistoryEntry $journalId '2026-02-18' "## 2026-02-18`n`n- HKR: Warum 10 separate Banfen?`n- TISAX: Sperren Produktionsrechner`n- Greenbone: Analyse-Ergebnis heisst nicht, dass Philip alle bearbeitet`n- Ticket LQS, alte SharePoint Daten" | Out-Null + +Add-HistoryEntry $journalId '2026-02-11' "## 2026-02-11`n`n- Teams-Problem HKR: Steffen bestellt Netzwerkkarten`n- Angebot DL fuer AM: Jenni umgezogen`n- Ticket Ossenbuehl Citrix: alter IE, warten auf neue Farm`n- TISAX Situationen simulieren" | Out-Null + +Add-HistoryEntry $journalId '2026-02-03' "## 2026-02-03`n`n- Pen-Test vor TISAX (ueber RES)`n- Steffen: neue Hardware (PFSense nicht auf alter Sophos)`n- TISAX Produktionsrechner: VLAN, PC-0259`n- Doku Backup-Prozess noch nicht gestartet`n- DocuSnap Termin" | Out-Null + +Add-HistoryEntry $journalId '2026-01-28' "## 2026-01-28`n`n- viflow in Arbeit`n- Notebook Karsten: Rücksprache ML/TG`n- Focus TISAX Doku SteFi`n- Azure Mails an IT-Team / Helpdesk`n- ShutDown Stromverteiler Sa wieder anlaufen`n- B+W Server Aufstellung (EOL, Greenbone, Risiko)" | Out-Null + +Add-HistoryEntry $journalId '2026-01-13' "## 2026-01-13`n`n- Hees in Migration Domaene einbinden`n- Backup-Aufgaben in Plane`n- USV-Analyse mit Hees starten`n- Admin-Rechte HKR abschalten`n- Wildcard-Zertifikat ueberall?`n- NAC: Liste Geraete vorbereiten" | Out-Null + +Add-HistoryEntry $journalId '2026-01-07' "## 2026-01-07`n`n- PC QS: Ticket #393176`n- USB-Sticks sperren HKR (Start KW2 2026)`n- Status Festplattenbestellung IBM?`n- Backlogs AtWork bearbeiten" | Out-Null + +Add-HistoryEntry $journalId '2025-12-11' "## 2025-12-11`n`n- Access to Snipe-IT lost`n- Qlik: Sales + FI, Florian + Steffen einbinden`n- Teleport: Problem Setup`n- KPI Database: Phase 1 Qlik Ersatz, VIZLIB Plugin Test`n- Keine Admin-Rechte mehr fuer KAS" | Out-Null + +Add-HistoryEntry $journalId '2025-12-10' "## 2025-12-10`n`n- KI-Workshop IT-Team: Eray, KI-Governance Philip`n- DC-Problem 2025: warten auf SVA Statement`n- TISAX 5.3.2 Netzwerkdienste`n- Admin-Rechte HKR entfernen (lokale Gruppe NW-Konfig)`n- Reparatur-Prozess: Aufkleber Ticketnummer" | Out-Null + +Add-HistoryEntry $journalId '2025-11-25' "## 2025-11-25`n`n- Neue viFlow Server einrichten`n- USB-Sticks sperren ab 01. Dez`n- Server kuenftig nur auf Englisch`n- Findings HKR: XP fluten Netz, Sky GO`n- Bestellung Kelobit DocuSnap Server" | Out-Null + +Add-HistoryEntry $journalId '2025-11-19' "## 2025-11-19`n`n- Snipe-IT fuer alle (Ausgabe-, Wareneingangs-, Nachbestellprozess)`n- Netzwerkdokumentation aktualisieren`n- Ordnung in Teleport`n- Verrechnung IT-Kosten (SAN, Teleport + GIT -> KST Server)" | Out-Null + +Add-HistoryEntry $journalId '2025-11-12' "## 2025-11-12`n`n- WWP Bollenbach: NiceLabel -> Printer3`n- Win10 PCs HKR: Steffen banft`n- WITEC-Telefone: H+Ue kontaktiert Hersteller" | Out-Null + +Add-HistoryEntry $journalId '2025-11-05' "## 2025-11-05`n`n- Teams: aktuell laeuft es`n- HKR: SIP-Trunk 08. Dez Thalheim`n- BitSight Treffen`n- Visitor process: Android-Tool`n- Praktikanten Bewerbungen" | Out-Null + +Add-HistoryEntry $journalId '2025-10-28' "## 2025-10-28`n`n- Telekom Hardware waehrend Urlaub?`n- Server AccessManager`n- Citrix: Start Testphase?`n- WITEC final zurueckgebaut?`n- Abschaltung altes Intranet Jahresende`n- Angebote Hardware Bechtle vs. Hees" | Out-Null + +# ── 6. Upload ───────────────────────────────────────────────────────────────── +Write-Host "`n[6] Uploading..." -ForegroundColor Cyan +$ulArgs = @('-File', "$scripts\upload.ps1") +if ($Prod) { $ulArgs += '-Prod' } +if ($Force) { $ulArgs += '-Force' } +& powershell @ulArgs +if ($LASTEXITCODE -ne 0) { throw "Upload failed (exit $LASTEXITCODE)" } + +Write-Host "`n[DONE] JF Sysadmins import complete." -ForegroundColor Green diff --git a/ka-note/scripts/unlock.ps1 b/ka-note/scripts/unlock.ps1 index 5e30cbc..841f202 100644 --- a/ka-note/scripts/unlock.ps1 +++ b/ka-note/scripts/unlock.ps1 @@ -71,9 +71,12 @@ if (-not $LockToken) { if ($Prod) { $BaseUrl = "https://ka-note.azurewebsites.net" - $BearerToken = if ($Token) { $Token } else { $env:KA_NOTE_TOKEN } + $BearerToken = if ($Token) { $Token } elseif ($env:KA_NOTE_TOKEN) { $env:KA_NOTE_TOKEN } else { + Write-Host " Acquiring token via MSAL..." -ForegroundColor DarkGray + & "$PSScriptRoot\get-token.ps1" + } if (-not $BearerToken) { - Write-Err "Prod mode requires a Bearer token. Pass -Token or set `$env:KA_NOTE_TOKEN." + Write-Err "Failed to acquire a Bearer token." exit 1 } $Headers = @{ Authorization = "Bearer $BearerToken"; "Content-Type" = "application/json" } diff --git a/ka-note/scripts/upload.ps1 b/ka-note/scripts/upload.ps1 index 5161187..fc2a1b0 100644 --- a/ka-note/scripts/upload.ps1 +++ b/ka-note/scripts/upload.ps1 @@ -65,9 +65,12 @@ if (-not $LockToken) { if ($Prod) { $BaseUrl = "https://ka-note.azurewebsites.net" - $BearerToken = if ($Token) { $Token } else { $env:KA_NOTE_TOKEN } + $BearerToken = if ($Token) { $Token } elseif ($env:KA_NOTE_TOKEN) { $env:KA_NOTE_TOKEN } else { + Write-Host " Acquiring token via MSAL..." -ForegroundColor DarkGray + & "$PSScriptRoot\get-token.ps1" + } if (-not $BearerToken) { - Write-Err "Prod mode requires a Bearer token. Pass -Token or set `$env:KA_NOTE_TOKEN." + Write-Err "Failed to acquire a Bearer token." exit 1 } $Headers = @{ Authorization = "Bearer $BearerToken"; "Content-Type" = "application/octet-stream" } @@ -89,7 +92,23 @@ Write-Host "" Write-Host " Compressing work/..." -ForegroundColor DarkGray if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force } -Compress-Archive -Path "$WorkDir\*" -DestinationPath $ZipPath + +# Use .NET ZipArchive directly to ensure forward-slash entry names (Compress-Archive uses backslashes on Windows) +Add-Type -Assembly 'System.IO.Compression' +Add-Type -Assembly 'System.IO.Compression.FileSystem' +$zipStream = [System.IO.File]::Open($ZipPath, [System.IO.FileMode]::Create) +$archive = [System.IO.Compression.ZipArchive]::new($zipStream, [System.IO.Compression.ZipArchiveMode]::Create) +Get-ChildItem -Path $WorkDir -Recurse -File | Where-Object { $_.FullName -ne $ZipPath } | ForEach-Object { + $entryName = $_.FullName.Substring($WorkDir.Length + 1).Replace('\', '/') + $entry = $archive.CreateEntry($entryName, [System.IO.Compression.CompressionLevel]::Optimal) + $entryStream = $entry.Open() + $fileStream = [System.IO.File]::OpenRead($_.FullName) + $fileStream.CopyTo($entryStream) + $fileStream.Dispose() + $entryStream.Dispose() +} +$archive.Dispose() +$zipStream.Dispose() $ZipSizeKb = [math]::Round((Get-Item $ZipPath).Length / 1KB, 1) Write-KV "zip size" "${ZipSizeKb} KB"