Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6d5b9b1e | ||
|
|
6c1c651839 | ||
|
|
795a056564 | ||
|
|
53f3f6bd45 | ||
|
|
079e69963d | ||
|
|
3a807da54c | ||
|
|
71c35c0e45 | ||
|
|
6be767645d | ||
|
|
872cab095c | ||
|
|
3bc916b184 | ||
|
|
492e071f20 | ||
|
|
79463b7a72 | ||
|
|
a2e1c16128 | ||
|
|
dd8ad5e1b7 | ||
|
|
f66b8a6aa6 | ||
|
|
fbb9e29f9c | ||
|
|
c424f41b9f |
No files matched your search
@@ -0,0 +1,49 @@
|
||||
name: Update Scoop manifests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
code: write
|
||||
timeout-minutes: 55
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ gitea.event.repository.default_branch }}
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check Python runtime
|
||||
run: python3 -c "import sys; assert sys.version_info >= (3, 10), 'Python 3.10+ erforderlich'"
|
||||
|
||||
- name: Test manifest updater
|
||||
run: python3 -B -m unittest discover -s tests -v
|
||||
|
||||
- name: Update manifests
|
||||
id: update
|
||||
run: python3 -B scripts/update-manifests.py --write
|
||||
|
||||
# Gültige Updates auch dann speichern, wenn ein anderer Download scheitert.
|
||||
# Der fehlgeschlagene Update-Schritt bleibt im Workflow sichtbar.
|
||||
- name: Commit and push changed manifests
|
||||
if: ${{ always() && steps.update.outcome != 'skipped' }}
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ gitea.event.repository.default_branch }}
|
||||
run: |
|
||||
set -eu
|
||||
git add -- bucket/*.json
|
||||
if git diff --cached --quiet; then
|
||||
echo 'Keine Manifest-Änderungen.'
|
||||
exit 0
|
||||
fi
|
||||
git config user.name 'Gitea Actions'
|
||||
git config user.email 'actions@gitea.medelexis.ch'
|
||||
git commit -m 'Update Scoop manifests'
|
||||
git pull --rebase origin "$DEFAULT_BRANCH"
|
||||
git push origin "HEAD:$DEFAULT_BRANCH"
|
||||
@@ -1 +1,5 @@
|
||||
*.bak
|
||||
*.exe
|
||||
.vscode/*
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -5,7 +5,7 @@
|
||||
"depends": [
|
||||
"extras/element"
|
||||
],
|
||||
"url": "https://git.streichtech.ch/elexis/scoop/raw/branch/master/config/matrix/medelexis/config.json#/config.json",
|
||||
"url": "https://gitea.medelexis.ch/public/scoop/raw/branch/master/config/matrix/medelexis/config.json#/config.json",
|
||||
"hash": "b5ed72c551ff700d86a4ae293c871528d71fa53440c46d2594916a6d1273d131",
|
||||
"post_install": [
|
||||
"$configDir = Join-Path $env:APPDATA 'Element'",
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"description": "Configuration for Element (Matrix Chat) for Medelexis.",
|
||||
"url": "https://git.streichtech.ch/elexis/scoop/raw/branch/master/config/matrix/medelexis/config.json#/config.json",
|
||||
"hash": "5aa0e674ea7455ee41646307b95bc953d13ec3b7eb62a3f193edc15d6d4966ea",
|
||||
"post_install": [
|
||||
"$configDir = Join-Path $env:APPDATA 'Element'",
|
||||
"New-Item -ItemType Directory -Force -Path $configDir | Out-Null",
|
||||
"Copy-Item (Join-Path $dir 'config.json') (Join-Path $configDir 'config.json') -Force"
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"url": "https://download.medelexis.ch/myelexis-cli/windows/myelexis-cli-1.0.0-SNAPSHOT-runner.exe#/myelexis-cli.exe",
|
||||
"hash": "sha256:ea1258d043be5b45ef0ba1d68306eb3365e6a6b6243f26610351e20d38fdb9a1",
|
||||
"bin": [
|
||||
["myelexis-cli.exe", "ee"],
|
||||
["myelexis-cli.exe", "es"]
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"version": "13.5.0",
|
||||
"url": "https://git.streichtech.ch/elexis/ee-iso-builder/releases/download/v13.5.0/ElexisEnvironment.iso#/ElexisEnvironment.dat",
|
||||
"installer": {
|
||||
"script": [
|
||||
"Rename-Item \"$dir\\ElexisEnvironment.dat\" \"ElexisEnvironment.iso\"",
|
||||
"Copy-Item \"$dir\\ElexisEnvironment.iso\" \"$HOME\\Downloads\\ElexisEnvironment.iso\"",
|
||||
"Remove-Item \"$dir\\ElexisEnvironment.iso\"",
|
||||
"scoop uninstall ee_iso --purge"
|
||||
]
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://git.streichtech.ch/elexis/ee-iso-builder/releases/download/v$version/ElexisEnvironment.iso#/ElexisEnvironment.dat"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": "20241018T145846Z",
|
||||
"description": "Elexis 3.10",
|
||||
"url": "https://download.medelexis.ch/medelexis/3.10/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-20241018T145846Z.zip",
|
||||
"hash": "de4dafd270a875ceebdce054941b7c136d3a1853f0b6c4cf6cef3398e4db9228",
|
||||
"bin": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"e10",
|
||||
"-console"
|
||||
]
|
||||
],
|
||||
"shortcuts": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"Elexis 3.10",
|
||||
"-console"
|
||||
],
|
||||
[
|
||||
"Medelexis.ini",
|
||||
"ini 3.10"
|
||||
]
|
||||
],
|
||||
"post_install": [
|
||||
"$iniPath = \"$persist_dir\\Medelexis.ini\"",
|
||||
"$ini = Get-Content $iniPath",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.10', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$missing = @($insert | Where-Object { $_ -notin $ini })",
|
||||
"if ($missing.Count -gt 0) {",
|
||||
" $vmargs = $ini | Select-String '^-vmargs$' | Select-Object -First 1",
|
||||
" if (-not $vmargs) { throw '-vmargs wurde in Medelexis.ini nicht gefunden.' }",
|
||||
" $index = $vmargs.LineNumber",
|
||||
" $before = $ini[0..($index - 1)]",
|
||||
" $after = if ($index -lt $ini.Count) { $ini[$index..($ini.Count - 1)] } else { @() }",
|
||||
" $ini = @($before) + $missing + @($after)",
|
||||
" Set-Content $iniPath $ini",
|
||||
"}"
|
||||
],
|
||||
"persist": [
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": [
|
||||
"Elexis 3.10 wurde installiert.",
|
||||
"",
|
||||
"INI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.",
|
||||
"Programm startet mit Konsole.",
|
||||
"",
|
||||
"Konsolenbefehle:",
|
||||
"-> e10 = Start Elexis 3.10"
|
||||
],
|
||||
"checkver": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.10/products/",
|
||||
"script": [
|
||||
"$fileUrl = 'https://download.medelexis.ch/medelexis/3.10/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip'",
|
||||
"$response = Invoke-WebRequest -Uri $fileUrl -Method Head -UseBasicParsing",
|
||||
"$lastModified = [string]$response.Headers['Last-Modified']",
|
||||
"if ([string]::IsNullOrWhiteSpace($lastModified)) { throw 'Kein Last-Modified-Header vorhanden.' }",
|
||||
"$date = [DateTimeOffset]::ParseExact($lastModified, 'R', [Globalization.CultureInfo]::InvariantCulture)",
|
||||
"$date.UtcDateTime.ToString(\"yyyyMMdd'T'HHmmss'Z'\")"
|
||||
],
|
||||
"regex": "(?<version>\\d{8}T\\d{6}Z)"
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.10/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-$version.zip"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": "20260601T081039Z",
|
||||
"description": "Elexis 3.12",
|
||||
"url": "https://download.medelexis.ch/medelexis/3.12/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-20260601T081039Z.zip",
|
||||
"hash": "dc19c40dd1bcd66a6d823c1a59ced571542fe34a3e6c58e710f055cdbcc7ed38",
|
||||
"bin": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"e12",
|
||||
"-console"
|
||||
]
|
||||
],
|
||||
"shortcuts": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"Elexis 3.12",
|
||||
"-console"
|
||||
],
|
||||
[
|
||||
"Medelexis.ini",
|
||||
"ini 3.12"
|
||||
]
|
||||
],
|
||||
"post_install": [
|
||||
"$iniPath = \"$persist_dir\\Medelexis.ini\"",
|
||||
"$ini = Get-Content $iniPath",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.12', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$missing = @($insert | Where-Object { $_ -notin $ini })",
|
||||
"if ($missing.Count -gt 0) {",
|
||||
" $vmargs = $ini | Select-String '^-vmargs$' | Select-Object -First 1",
|
||||
" if (-not $vmargs) { throw '-vmargs wurde in Medelexis.ini nicht gefunden.' }",
|
||||
" $index = $vmargs.LineNumber",
|
||||
" $before = $ini[0..($index - 1)]",
|
||||
" $after = if ($index -lt $ini.Count) { $ini[$index..($ini.Count - 1)] } else { @() }",
|
||||
" $ini = @($before) + $missing + @($after)",
|
||||
" Set-Content $iniPath $ini",
|
||||
"}"
|
||||
],
|
||||
"persist": [
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": [
|
||||
"Elexis 3.12 wurde installiert.",
|
||||
"",
|
||||
"INI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.",
|
||||
"Programm startet mit Konsole.",
|
||||
"",
|
||||
"Konsolenbefehle:",
|
||||
"-> e12 = Start Elexis 3.12"
|
||||
],
|
||||
"checkver": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.12/products/",
|
||||
"script": [
|
||||
"$fileUrl = 'https://download.medelexis.ch/medelexis/3.12/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip'",
|
||||
"$response = Invoke-WebRequest -Uri $fileUrl -Method Head -UseBasicParsing",
|
||||
"$lastModified = [string]$response.Headers['Last-Modified']",
|
||||
"if ([string]::IsNullOrWhiteSpace($lastModified)) { throw 'Kein Last-Modified-Header vorhanden.' }",
|
||||
"$date = [DateTimeOffset]::ParseExact($lastModified, 'R', [Globalization.CultureInfo]::InvariantCulture)",
|
||||
"$date.UtcDateTime.ToString(\"yyyyMMdd'T'HHmmss'Z'\")"
|
||||
],
|
||||
"regex": "(?<version>\\d{8}T\\d{6}Z)"
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.12/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-$version.zip"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": "20261012T102907Z",
|
||||
"description": "Elexis 3.13",
|
||||
"url": "https://download.medelexis.ch/medelexis/3.13/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-20261012T102907Z.zip",
|
||||
"hash": "967629296b7bbdb2915c6045071c0918b8f87fec8388984bfed084046d2227eb",
|
||||
"bin": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"e13",
|
||||
"-console"
|
||||
]
|
||||
],
|
||||
"shortcuts": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"Elexis 3.13",
|
||||
"-console"
|
||||
],
|
||||
[
|
||||
"Medelexis.ini",
|
||||
"ini 3.13"
|
||||
]
|
||||
],
|
||||
"post_install": [
|
||||
"$iniPath = \"$persist_dir\\Medelexis.ini\"",
|
||||
"$ini = Get-Content $iniPath",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.13', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$missing = @($insert | Where-Object { $_ -notin $ini })",
|
||||
"if ($missing.Count -gt 0) {",
|
||||
" $vmargs = $ini | Select-String '^-vmargs$' | Select-Object -First 1",
|
||||
" if (-not $vmargs) { throw '-vmargs wurde in Medelexis.ini nicht gefunden.' }",
|
||||
" $index = $vmargs.LineNumber",
|
||||
" $before = $ini[0..($index - 1)]",
|
||||
" $after = if ($index -lt $ini.Count) { $ini[$index..($ini.Count - 1)] } else { @() }",
|
||||
" $ini = @($before) + $missing + @($after)",
|
||||
" Set-Content $iniPath $ini",
|
||||
"}"
|
||||
],
|
||||
"persist": [
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": [
|
||||
"Elexis 3.13 wurde installiert.",
|
||||
"",
|
||||
"INI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.",
|
||||
"Programm startet mit Konsole.",
|
||||
"",
|
||||
"Konsolenbefehle:",
|
||||
"-> e13 = Start Elexis 3.13"
|
||||
],
|
||||
"checkver": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.13/products/",
|
||||
"script": [
|
||||
"$fileUrl = 'https://download.medelexis.ch/medelexis/3.13/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip'",
|
||||
"$response = Invoke-WebRequest -Uri $fileUrl -Method Head -UseBasicParsing",
|
||||
"$lastModified = [string]$response.Headers['Last-Modified']",
|
||||
"if ([string]::IsNullOrWhiteSpace($lastModified)) { throw 'Kein Last-Modified-Header vorhanden.' }",
|
||||
"$date = [DateTimeOffset]::ParseExact($lastModified, 'R', [Globalization.CultureInfo]::InvariantCulture)",
|
||||
"$date.UtcDateTime.ToString(\"yyyyMMdd'T'HHmmss'Z'\")"
|
||||
],
|
||||
"regex": "(?<version>\\d{8}T\\d{6}Z)"
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://download.medelexis.ch/medelexis/3.13/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-$version.zip"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": "20260601T071724Z",
|
||||
"description": "Elexis master",
|
||||
"url": "https://download.medelexis.ch/medelexis/master/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-20260601T071724Z.zip",
|
||||
"hash": "325f38a7b7a297a315ddad03ddb34e3061780c3fd869c4856cbe0ab7bed51124",
|
||||
"bin": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"em",
|
||||
"-console"
|
||||
]
|
||||
],
|
||||
"shortcuts": [
|
||||
[
|
||||
"Medelexis.exe",
|
||||
"Elexis master",
|
||||
"-console"
|
||||
],
|
||||
[
|
||||
"Medelexis.ini",
|
||||
"ini master"
|
||||
]
|
||||
],
|
||||
"post_install": [
|
||||
"$iniPath = \"$persist_dir\\Medelexis.ini\"",
|
||||
"$ini = Get-Content $iniPath",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=master', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$missing = @($insert | Where-Object { $_ -notin $ini })",
|
||||
"if ($missing.Count -gt 0) {",
|
||||
" $vmargs = $ini | Select-String '^-vmargs$' | Select-Object -First 1",
|
||||
" if (-not $vmargs) { throw '-vmargs wurde in Medelexis.ini nicht gefunden.' }",
|
||||
" $index = $vmargs.LineNumber",
|
||||
" $before = $ini[0..($index - 1)]",
|
||||
" $after = if ($index -lt $ini.Count) { $ini[$index..($ini.Count - 1)] } else { @() }",
|
||||
" $ini = @($before) + $missing + @($after)",
|
||||
" Set-Content $iniPath $ini",
|
||||
"}"
|
||||
],
|
||||
"persist": [
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": [
|
||||
"Elexis master wurde installiert.",
|
||||
"",
|
||||
"INI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.",
|
||||
"Programm startet mit Konsole.",
|
||||
"",
|
||||
"Konsolenbefehle:",
|
||||
"-> em = Start Elexis master"
|
||||
],
|
||||
"checkver": {
|
||||
"url": "https://download.medelexis.ch/medelexis/master/products/",
|
||||
"script": [
|
||||
"$fileUrl = 'https://download.medelexis.ch/medelexis/master/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip'",
|
||||
"$response = Invoke-WebRequest -Uri $fileUrl -Method Head -UseBasicParsing",
|
||||
"$lastModified = [string]$response.Headers['Last-Modified']",
|
||||
"if ([string]::IsNullOrWhiteSpace($lastModified)) { throw 'Kein Last-Modified-Header vorhanden.' }",
|
||||
"$date = [DateTimeOffset]::ParseExact($lastModified, 'R', [Globalization.CultureInfo]::InvariantCulture)",
|
||||
"$date.UtcDateTime.ToString(\"yyyyMMdd'T'HHmmss'Z'\")"
|
||||
],
|
||||
"regex": "(?<version>\\d{8}T\\d{6}Z)"
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://download.medelexis.ch/medelexis/master/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-$version.zip"
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "3.10",
|
||||
"description": "Elexis 3.10",
|
||||
"url": [
|
||||
"https://download.medelexis.ch/medelexis/3.10/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-3.10.zip"
|
||||
],
|
||||
"bin": [
|
||||
["Medelexis.exe", "e10", "-console"]
|
||||
],
|
||||
"shortcuts": [
|
||||
["Medelexis.exe", "Elexis 3.10", "-console"],
|
||||
["Medelexis.ini", "ini 3.10"]
|
||||
],
|
||||
"post_install": [
|
||||
"$ini = Get-Content \"$persist_dir\\Medelexis.ini\"",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.10', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$index = ($ini | Select-String '-vmargs').LineNumber",
|
||||
"$ini = $ini[0..($index-1)] + $insert + $ini[$index..($ini.Length-1)]",
|
||||
"Set-Content \"$persist_dir\\Medelexis.ini\" $ini"
|
||||
],
|
||||
"persist": [
|
||||
"Medelexis.ini",
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": "Elexis 3.10 wurde installiert.\n\nINI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.\nProgramm Startet mit Konsole \n\nKonsole Befehle:\n -> e10 = Start Elexis 3.10\n -> e10ini = Öffne ini von Elexis 3.10"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "3.12",
|
||||
"description": "Elexis 3.12",
|
||||
"url": [
|
||||
"https://download.medelexis.ch/medelexis/3.12/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-3.12.zip"
|
||||
],
|
||||
"bin": [
|
||||
["Medelexis.exe", "e12", "-console"]
|
||||
],
|
||||
"shortcuts": [
|
||||
["Medelexis.exe", "Elexis 3.12", "-console"],
|
||||
["Medelexis.ini", "ini 3.12"]
|
||||
],
|
||||
"post_install": [
|
||||
"$ini = Get-Content \"$persist_dir\\Medelexis.ini\"",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.12', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$index = ($ini | Select-String '-vmargs').LineNumber",
|
||||
"$ini = $ini[0..($index-1)] + $insert + $ini[$index..($ini.Length-1)]",
|
||||
"Set-Content \"$persist_dir\\Medelexis.ini\" $ini"
|
||||
],
|
||||
"persist": [
|
||||
"Medelexis.ini",
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": "Elexis 3.12 wurde installiert.\n\nINI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.\nProgramm Startet mit Konsole \n\nKonsole Befehle:\n -> e12 = Start Elexis 3.12\n -> e12ini = Öffne ini von Elexis 3.12"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "3.13",
|
||||
"description": "Elexis 3.13",
|
||||
"url": [
|
||||
"https://download.medelexis.ch/medelexis/3.13/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-3.13.zip"
|
||||
],
|
||||
"bin": [
|
||||
["Medelexis.exe", "e13", "-console"]
|
||||
],
|
||||
"shortcuts": [
|
||||
["Medelexis.exe", "Elexis 3.13", "-console"],
|
||||
["Medelexis.ini", "ini 3.13"]
|
||||
],
|
||||
"post_install": [
|
||||
"$ini = Get-Content \"$persist_dir\\Medelexis.ini\"",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=3.13', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$index = ($ini | Select-String '-vmargs').LineNumber",
|
||||
"$ini = $ini[0..($index-1)] + $insert + $ini[$index..($ini.Length-1)]",
|
||||
"Set-Content \"$persist_dir\\Medelexis.ini\" $ini"
|
||||
],
|
||||
"persist": [
|
||||
"Medelexis.ini",
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": "Elexis 3.13 wurde installiert.\n\nINI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.\nProgramm Startet mit Konsole \n\nKonsole Befehle:\n -> e13 = Start Elexis 3.13\n -> e13ini = Öffne ini von Elexis 3.13"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "master",
|
||||
"description": "Elexis master",
|
||||
"url": [
|
||||
"https://download.medelexis.ch/medelexis/master/products/ch.medelexis.application.product.Medelexis-win32.win32.x86_64-signed.zip#/Elexis-master.zip"
|
||||
],
|
||||
"bin": [
|
||||
["Medelexis.exe", "em", "-console"]
|
||||
],
|
||||
"shortcuts": [
|
||||
["Medelexis.exe", "Elexis master", "-console"],
|
||||
["Medelexis.ini", "ini master"]
|
||||
],
|
||||
"post_install": [
|
||||
"$ini = Get-Content \"$persist_dir\\Medelexis.ini\"",
|
||||
"$insert = @('-Dprovisioning.UpdateRepository=master', '-DopenDBWizard', '-Dfop.printing.debug=true')",
|
||||
"$index = ($ini | Select-String '-vmargs').LineNumber",
|
||||
"$ini = $ini[0..($index-1)] + $insert + $ini[$index..($ini.Length-1)]",
|
||||
"Set-Content \"$persist_dir\\Medelexis.ini\" $ini"
|
||||
],
|
||||
"persist": [
|
||||
"Medelexis.ini",
|
||||
"workspace\\.metadata\\.plugins\\org.eclipse.e4.workbench"
|
||||
],
|
||||
"notes": "Elexis master wurde installiert.\n\nINI wurde mit Repo-Eintrag, DB-Abfrage und FOP-Debug ergänzt.\nProgramm Startet mit Konsole \n\nKonsole Befehle:\n -> em = Start Elexis master\n -> emini = Öffne ini von Elexis master"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": "20261020T080626Z",
|
||||
"description": "MyElexis CLI",
|
||||
"url": "https://download.medelexis.ch/myelexis-cli/windows/myelexis-cli-1.0.0-SNAPSHOT-runner.exe#/myelexis-cli.exe",
|
||||
"hash": "a397bae3e6c340f40b4b0a35ea22bafd3c30a02990e0b26bde7957a803902078",
|
||||
"bin": [
|
||||
[
|
||||
"myelexis-cli.exe",
|
||||
"ee"
|
||||
],
|
||||
[
|
||||
"myelexis-cli.exe",
|
||||
"es"
|
||||
]
|
||||
],
|
||||
"checkver": {
|
||||
"url": "https://download.medelexis.ch/myelexis-cli/windows/",
|
||||
"script": [
|
||||
"$fileUrl = 'https://download.medelexis.ch/myelexis-cli/windows/myelexis-cli-1.0.0-SNAPSHOT-runner.exe'",
|
||||
"$response = Invoke-WebRequest -Uri $fileUrl -Method Head -UseBasicParsing",
|
||||
"$lastModified = [string]$response.Headers['Last-Modified']",
|
||||
"if ([string]::IsNullOrWhiteSpace($lastModified)) { throw 'Kein Last-Modified-Header vorhanden.' }",
|
||||
"$date = [DateTimeOffset]::ParseExact($lastModified, 'R', [Globalization.CultureInfo]::InvariantCulture)",
|
||||
"$date.UtcDateTime.ToString('yyyyMMddTHHmmssZ')"
|
||||
],
|
||||
"regex": "(?<version>\\d{8}T\\d{6}Z)"
|
||||
},
|
||||
"autoupdate": {
|
||||
"url": "https://download.medelexis.ch/myelexis-cli/windows/myelexis-cli-1.0.0-SNAPSHOT-runner.exe#/myelexis-cli.exe"
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,13 @@
|
||||
"extras/firefox",
|
||||
"extras/keepass",
|
||||
"extras/teamviewer",
|
||||
"extras/rustdesk",
|
||||
|
||||
"rustdesk-config",
|
||||
"ee-cli",
|
||||
"elexis_3.10",
|
||||
"elexis_3.12",
|
||||
"elexis_3.13",
|
||||
"elexis_master",
|
||||
"chat_medelexis"
|
||||
"myelexis-cli",
|
||||
"elexis-3.10",
|
||||
"elexis-3.12",
|
||||
"elexis-3.13",
|
||||
"elexis-master",
|
||||
"chat-medelexis"
|
||||
],
|
||||
"url": "https://git.streichtech.ch/elexis/scoop/raw/branch/master/packages/p.intern.txt#/p.intern.txt"
|
||||
"url": "https://gitea.medelexis.ch/public/scoop/raw/branch/master/packages/p.intern.txt#/p.intern.txt"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"version": "1.4.9",
|
||||
"description": "Medelexis RustDesk",
|
||||
"url": "https://github.com/rustdesk/rustdesk/releases/download/1.4.9/rustdesk-1.4.9-x86_64.exe#/rd=0nI9AzNrs2dRl0c0p1KRJWdzInQFh3M3VVUlJjZoxkQudWbHNTOpBlcsRle58kI6ISeltmIsICaj5ycphXZsVGZl1mLrNXZkR3c1J3LvozcwRHdoJiOikGchJCLiIiOikXYsVmciwiIoNmLzlGelxWZkVWbus2clRGdzVnciojI0N3boJye.exe",
|
||||
"hash": "eaedeb0088e687bf46f7c46a9c6ea5493ce51f3134dfd6acbedb47b5b9136274",
|
||||
"bin": [["rd=0nI9AzNrs2dRl0c0p1KRJWdzInQFh3M3VVUlJjZoxkQudWbHNTOpBlcsRle58kI6ISeltmIsICaj5ycphXZsVGZl1mLrNXZkR3c1J3LvozcwRHdoJiOikGchJCLiIiOikXYsVmciwiIoNmLzlGelxWZkVWbus2clRGdzVnciojI0N3boJye.exe", "rustdesk-medelexis"]],
|
||||
"shortcuts": [["rd=0nI9AzNrs2dRl0c0p1KRJWdzInQFh3M3VVUlJjZoxkQudWbHNTOpBlcsRle58kI6ISeltmIsICaj5ycphXZsVGZl1mLrNXZkR3c1J3LvozcwRHdoJiOikGchJCLiIiOikXYsVmciwiIoNmLzlGelxWZkVWbus2clRGdzVnciojI0N3boJye.exe", "RustDesk Medelexis"]]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=0nI9AzNrs2dRl0c0p1KRJWdzInQFh3M3VVUlJjZoxkQudWbHNTOpBlcsRle58kI6ISeltmIsICaj5ycphXZsVGZl1mLrNXZkR3c1J3LvozcwRHdoJiOikGchJCLiIiOikXYsVmciwiIoNmLzlGelxWZkVWbus2clRGdzVnciojI0N3boJye
|
||||
@@ -0,0 +1,49 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Aktualisiert Scoop und alle benutzerspezifischen Pakete; elexis* bleibt gesperrt.
|
||||
.DESCRIPTION
|
||||
Fuehrt ausschliesslich den Update-Lauf aus. Die Aufgabe wird von update-bucket.ps1
|
||||
eingerichtet und startet dieses Skript verborgen. Der letzte Lauf steht in
|
||||
auto-update.log neben diesem Skript. Globale Pakete werden nicht aktualisiert.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
||||
$env:SCOOP = $ScoopRoot
|
||||
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
||||
if (-not (Test-Path -LiteralPath $scoopCommand -PathType Leaf)) {
|
||||
throw "Scoop wurde nicht gefunden: $scoopCommand"
|
||||
}
|
||||
|
||||
$transcriptStarted = $false
|
||||
$exitCode = 0
|
||||
try {
|
||||
Start-Transcript -LiteralPath (Join-Path $PSScriptRoot 'auto-update.log') -Force | Out-Null
|
||||
$transcriptStarted = $true
|
||||
# Ein absoluter Scoop-Pfad und die shims im PATH funktionieren auch ohne Profil.
|
||||
$env:PATH = (Join-Path $ScoopRoot 'shims') + [IO.Path]::PathSeparator + $env:PATH
|
||||
$installed = @(& $scoopCommand list)
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Installierte Scoop-Pakete konnten nicht ermittelt werden.' }
|
||||
foreach ($package in $installed) {
|
||||
if ($package.Name -notlike 'elexis*' -or $package.Info -match 'Global install|Install failed') { continue }
|
||||
& $scoopCommand hold $package.Name
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Updatesperre fuer '$($package.Name)' fehlgeschlagen. Kein Update ausgefuehrt."
|
||||
}
|
||||
}
|
||||
& $scoopCommand update
|
||||
if ($LASTEXITCODE -ne 0) { throw 'scoop update fehlgeschlagen.' }
|
||||
& $scoopCommand update '*'
|
||||
if ($LASTEXITCODE -ne 0) { throw 'scoop update * fehlgeschlagen.' }
|
||||
} catch {
|
||||
Write-Error $_ -ErrorAction Continue
|
||||
$exitCode = 1
|
||||
} finally {
|
||||
if ($transcriptStarted) { Stop-Transcript | Out-Null }
|
||||
}
|
||||
exit $exitCode
|
||||
@@ -0,0 +1,52 @@
|
||||
# 1. SCOOP CHECK & INSTALL
|
||||
$bucketList = scoop bucket list 2>$null | Out-String
|
||||
|
||||
if ($bucketList) {
|
||||
Write-Host "Scoop ist bereits installiert." -ForegroundColor White
|
||||
}
|
||||
else {
|
||||
Write-Host "Scoop wird installiert..." -ForegroundColor Yellow
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
|
||||
$scoopInstallationsSkript = Invoke-RestMethod -Uri "https://get.scoop.sh"
|
||||
Invoke-Expression $scoopInstallationsSkript
|
||||
$bucketList = scoop bucket list 2>$null | Out-String
|
||||
}
|
||||
|
||||
# 2. GIT CHECK & INSTALL
|
||||
if ((git --version 2>$null) -match "git version") {
|
||||
Write-Host "Git ist bereits installiert." -ForegroundColor White
|
||||
}
|
||||
else {
|
||||
Write-Host "Git wird installiert..." -ForegroundColor Yellow
|
||||
scoop install git
|
||||
if ((git --version 2>$null) -match "git version") {
|
||||
Write-Host "Git wurde erfolgreich installiert." -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "Error: Git konnte nicht installiert werden." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 3. BUCKET CHECKS
|
||||
if ($bucketList -match "elexis") {
|
||||
Write-Host "elexis Bucket ist bereits vorhanden." -ForegroundColor White
|
||||
}
|
||||
else {
|
||||
scoop bucket add elexis https://gitea.medelexis.ch/public/scoop
|
||||
Write-Host "elexis Bucket hinzugefügt." -ForegroundColor Green
|
||||
}
|
||||
|
||||
if ($bucketList -match "extras") {
|
||||
Write-Host "Extras Bucket ist bereits vorhanden." -ForegroundColor White
|
||||
}
|
||||
else {
|
||||
scoop bucket add extras
|
||||
Write-Host "Extras Bucket hinzugefügt." -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Fertig! Verfügbare Apps anzeigen mit: scoop search" -ForegroundColor Cyan
|
||||
Write-Host "Bsp. scoop search myelexis-cli" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Read-Host "Enter drücken zum Beenden"
|
||||
@@ -0,0 +1,69 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fragt nach und richtet optional die taeglichen Scoop-Auto-Updates ein.
|
||||
.DESCRIPTION
|
||||
Eigenstaendig ausfuehrbar, ohne Bucket-Migration. Bei y wird auto-update.ps1
|
||||
heruntergeladen und eine Aufgabe fuer taeglich 03:00 Uhr erstellt.
|
||||
Die Aufgabe startet das Updateskript verborgen und holt verpasste Termine nach,
|
||||
sobald die Benutzersitzung und eine Netzwerkverbindung verfuegbar sind.
|
||||
Bei n wird nichts eingerichtet; bestehende Aufgaben bleiben unveraendert.
|
||||
.EXAMPLE
|
||||
.\scripts\setup-auto-update.ps1
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
||||
$repository = 'https://gitea.medelexis.ch/public/scoop'
|
||||
|
||||
do {
|
||||
$autoUpdateAnswer = (Read-Host 'Scoop Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
||||
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||
|
||||
if ($autoUpdateAnswer -eq 'y') {
|
||||
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
||||
if (-not (Test-Path -LiteralPath $scoopCommand -PathType Leaf)) {
|
||||
throw "Scoop wurde nicht gefunden: $scoopCommand"
|
||||
}
|
||||
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||
New-Item -ItemType Directory -Path $autoUpdateDirectory -Force | Out-Null
|
||||
$autoUpdateScript = Join-Path $autoUpdateDirectory 'auto-update.ps1'
|
||||
$downloadPath = Join-Path $autoUpdateDirectory ('auto-update-{0}.download' -f [guid]::NewGuid())
|
||||
try {
|
||||
$autoUpdateUrl = "$repository/raw/branch/master/scripts/auto-update.ps1"
|
||||
Invoke-WebRequest -Uri $autoUpdateUrl -OutFile $downloadPath -UseBasicParsing
|
||||
$parseTokens = $null
|
||||
$parseErrors = $null
|
||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gueltiges Updateskript.'
|
||||
}
|
||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||
$taskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
||||
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
$arguments = '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -ScoopRoot "{1}"' -f $autoUpdateScript, $ScoopRoot
|
||||
$action = New-ScheduledTaskAction -Execute $powershell -Argument $arguments
|
||||
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force `
|
||||
-Description 'Scoop taeglich um 03:00 aktualisieren; verpasste Termine nachholen; elexis* auf hold setzen.' | Out-Null
|
||||
Write-Host "Scoop Auto-Update eingeschaltet: $taskName (taeglich 03:00, verpasste Termine nachholen)."
|
||||
Write-Host "Ausfuehrung verborgen unter $userId, sobald der Benutzer angemeldet ist; kein Aufwecken des PCs."
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||
}
|
||||
} else {
|
||||
Write-Host 'Auto-Update nicht eingerichtet. Eine eventuell bestehende Aufgabe bleibt unveraendert.'
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stellt bestehende Scoop-Installationen von elexis/streichtech auf medelexis um.
|
||||
.DESCRIPTION
|
||||
Ohne -Apply nur ein Probelauf. Keine Sicherungen oder Git-Pushes.
|
||||
Bearbeitet alle Versionsverzeichnisse der benutzerspezifischen Scoop-Installation.
|
||||
Ein passender bestehender Bucket wird umbenannt. Danach werden elexis und streichtech entfernt.
|
||||
Andere Buckets wie main und extras bleiben bestehen.
|
||||
Alte Paketnamen mit Unterstrich werden auf passende Manifestnamen mit Bindestrich umgestellt.
|
||||
ee-iso/ee_iso und ee-cli werden deinstalliert. MyElexis CLI wird neu installiert.
|
||||
Mit -Apply wird abschliessend gefragt, ob taegliche Auto-Updates um 03:00 Uhr
|
||||
eingerichtet werden sollen. Das separate Skript wird aus dem Repository geladen.
|
||||
.EXAMPLE
|
||||
.\scripts\update-bucket.ps1
|
||||
.EXAMPLE
|
||||
.\scripts\update-bucket.ps1 -Apply
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch] $Apply,
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repository = 'https://gitea.medelexis.ch/public/scoop'
|
||||
$targetName = 'medelexis'
|
||||
$target = Join-Path $ScoopRoot "buckets\$targetName"
|
||||
$apps = Join-Path $ScoopRoot 'apps'
|
||||
if (-not (Test-Path -LiteralPath $apps -PathType Container)) {
|
||||
throw "Kein Scoop-Apps-Verzeichnis gefunden: $apps"
|
||||
}
|
||||
|
||||
function Test-MedelexisRepository([string] $Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Container)) { return $false }
|
||||
$remote = & git -C $Path remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { return $false }
|
||||
try { $uri = [Uri] ([string] $remote).Trim() } catch { return $false }
|
||||
return ($uri.Scheme -eq 'https' -and $uri.Host -eq 'gitea.medelexis.ch' -and
|
||||
$uri.AbsolutePath.TrimEnd('/').Replace('.git', '') -eq '/public/scoop')
|
||||
}
|
||||
|
||||
function Get-PackageRenames([string] $BucketPath) {
|
||||
if (-not $BucketPath -or -not (Test-Path -LiteralPath $BucketPath)) { return }
|
||||
foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) {
|
||||
if ($app.Name -in @('ee-iso', 'ee_iso')) { continue }
|
||||
if (-not $app.Name.Contains('_')) { continue }
|
||||
$newName = $app.Name.Replace('_', '-')
|
||||
$newManifest = Join-Path $BucketPath "bucket\$newName.json"
|
||||
if (-not (Test-Path -LiteralPath $newManifest)) {
|
||||
$newManifest = Join-Path $BucketPath "$newName.json"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $newManifest)) { continue }
|
||||
$current = Join-Path $app.FullName 'current'
|
||||
$installFile = Join-Path $current 'install.json'
|
||||
if (-not (Test-Path -LiteralPath $installFile)) { continue }
|
||||
$install = [IO.File]::ReadAllText($installFile) | ConvertFrom-Json
|
||||
if ($install.bucket -notin @('elexis', 'streichtech', 'medelexis')) { continue }
|
||||
$destination = Join-Path $apps $newName
|
||||
if (Test-Path -LiteralPath $destination) {
|
||||
throw "'$($app.Name)' und '$newName' sind beide installiert. Keine automatische Zusammenführung."
|
||||
}
|
||||
$link = Get-Item -LiteralPath $current -Force
|
||||
if ($link.LinkType -ne 'Junction') {
|
||||
throw "Für '$($app.Name)' wird eine Scoop-current-Junction erwartet. Keine automatische Umbenennung."
|
||||
}
|
||||
$versionPath = [IO.Path]::GetFullPath([string] @($link.Target)[0])
|
||||
if ((Split-Path $versionPath -Parent) -ne $app.FullName) {
|
||||
throw "Die current-Verknüpfung von '$($app.Name)' zeigt ausserhalb des Paketordners."
|
||||
}
|
||||
$persistSource = Join-Path $ScoopRoot "persist\$($app.Name)"
|
||||
$persistTarget = Join-Path $ScoopRoot "persist\$newName"
|
||||
if ((Test-Path -LiteralPath $persistSource) -and (Test-Path -LiteralPath $persistTarget)) {
|
||||
throw "Beide Persistenzordner für '$($app.Name)' und '$newName' existieren. Keine automatische Zusammenführung."
|
||||
}
|
||||
Write-Host "Paket umbenennen: $($app.Name) -> $newName"
|
||||
[pscustomobject]@{
|
||||
OldName = $app.Name; NewName = $newName; Source = $app.FullName
|
||||
Destination = $destination; Version = (Split-Path $versionPath -Leaf)
|
||||
PersistSource = $persistSource; PersistTarget = $persistTarget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$installCli = (Test-Path -LiteralPath (Join-Path $apps 'ee-cli')) -or
|
||||
-not (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli'))
|
||||
$obsoleteApps = @(@('ee-iso', 'ee_iso', 'ee-cli') | Where-Object {
|
||||
Test-Path -LiteralPath (Join-Path $apps $_)
|
||||
})
|
||||
if ($installCli -and (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli'))) {
|
||||
$obsoleteApps += 'myelexis-cli'
|
||||
}
|
||||
foreach ($obsolete in $obsoleteApps) {
|
||||
Write-Host "Veraltetes Paket deinstallieren: $obsolete"
|
||||
}
|
||||
if ($installCli) { Write-Host 'MyElexis CLI wird aus medelexis neu installiert.' }
|
||||
|
||||
$renameSource = $null
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
if (-not (Test-MedelexisRepository $target)) {
|
||||
throw 'Der vorhandene Bucket medelexis zeigt auf ein anderes Repository. Keine Änderung.'
|
||||
}
|
||||
Write-Host 'Bucket medelexis ist bereits korrekt eingerichtet.'
|
||||
} else {
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
$candidate = Join-Path $ScoopRoot "buckets\$oldName"
|
||||
if (Test-MedelexisRepository $candidate) {
|
||||
$renameSource = $candidate
|
||||
Write-Host "Bucket umbenennen: $oldName -> $targetName"
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $renameSource) { Write-Host "Bucket hinzufügen: $targetName -> $repository" }
|
||||
}
|
||||
|
||||
$changes = @(
|
||||
foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) {
|
||||
if ($app.Name -in $obsoleteApps) { continue }
|
||||
foreach ($version in Get-ChildItem -LiteralPath $app.FullName -Directory) {
|
||||
# current und andere Verknüpfungen nicht nochmals bearbeiten.
|
||||
if ($version.Attributes -band [IO.FileAttributes]::ReparsePoint) { continue }
|
||||
$file = Join-Path $version.FullName 'install.json'
|
||||
if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue }
|
||||
$original = [IO.File]::ReadAllText($file)
|
||||
$data = $original | ConvertFrom-Json
|
||||
if ($data.bucket -notin @('elexis', 'streichtech')) { continue }
|
||||
$pattern = '(?i)("bucket"\s*:\s*)"(?:elexis|streichtech)"'
|
||||
$updated = [regex]::Replace($original, $pattern, '${1}"medelexis"')
|
||||
if (($updated | ConvertFrom-Json).bucket -ne $targetName) {
|
||||
throw "Bucket-Zuordnung konnte nicht angepasst werden: $file"
|
||||
}
|
||||
Write-Host "$($app.Name) / $($version.Name): $($data.bucket) -> $targetName"
|
||||
[pscustomobject]@{ File = $file; Original = $original; Updated = $updated; App = $app.Name }
|
||||
}
|
||||
}
|
||||
)
|
||||
$manifestRoot = $(if (Test-Path -LiteralPath $target) { $target } else { $renameSource })
|
||||
$renames = @(Get-PackageRenames $manifestRoot)
|
||||
|
||||
if (-not $Apply) {
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
if (Test-Path -LiteralPath (Join-Path $ScoopRoot "buckets\$oldName")) {
|
||||
Write-Host "Alter Bucket wird nach der Umstellung entfernt oder umbenannt: $oldName"
|
||||
}
|
||||
}
|
||||
Write-Host "Probelauf: $($changes.Count) install.json-Dateien würden geändert."
|
||||
Write-Host "Probelauf: $($renames.Count) Paketnamen würden umgestellt."
|
||||
Write-Host "Probelauf: $(@($obsoleteApps).Count) alte Pakete würden deinstalliert."
|
||||
Write-Host 'Mit -Apply wird optional ein Auto-Update um 03:00 Uhr mit Nachholen eingerichtet (y/n).'
|
||||
Write-Host 'Mit -Apply anwenden. Es werden keine Sicherungen erstellt.'
|
||||
return
|
||||
}
|
||||
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'ScoopRoot muss der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $target)) {
|
||||
if ($renameSource) {
|
||||
$bucketRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'buckets')).Path
|
||||
$sourcePath = (Resolve-Path -LiteralPath $renameSource).Path
|
||||
$targetPath = [IO.Path]::GetFullPath($target)
|
||||
if ((Split-Path $sourcePath -Parent) -ne $bucketRoot -or
|
||||
(Split-Path $targetPath -Parent) -ne $bucketRoot) {
|
||||
throw 'Bucket-Pfade liegen ausserhalb des erwarteten Verzeichnisses. Keine Umbenennung.'
|
||||
}
|
||||
[IO.Directory]::Move($sourcePath, $targetPath)
|
||||
} else {
|
||||
# scoop verwendet dieselbe Root-Variable für benutzerdefinierte Installationen.
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne
|
||||
[IO.Path]::GetFullPath($(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })).TrimEnd('\')) {
|
||||
throw 'ScoopRoot weicht von der aktiven Scoop-Installation ab; Bucket bitte dort zuerst einrichten.'
|
||||
}
|
||||
& scoop bucket add $targetName $repository
|
||||
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $target)) {
|
||||
throw 'Bucket konnte nicht hinzugefügt werden. Installationsmetadaten bleiben unverändert.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Falls der Ziel-Bucket erst hinzugefügt wurde, stehen seine Manifestnamen jetzt bereit.
|
||||
if (-not $manifestRoot) { $renames = @(Get-PackageRenames $target) }
|
||||
|
||||
# Vor dem ersten Schreiben prüfen, ob während des Probelaufs Dateien geändert wurden.
|
||||
foreach ($change in $changes) {
|
||||
if ([IO.File]::ReadAllText($change.File) -cne $change.Original) {
|
||||
throw 'Installationsmetadaten wurden zwischenzeitlich geändert. Skript bitte erneut starten.'
|
||||
}
|
||||
}
|
||||
foreach ($change in $changes) {
|
||||
[IO.File]::WriteAllText($change.File, $change.Updated, [Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
if ($installCli -and -not (Test-Path -LiteralPath (Join-Path $target 'bucket\myelexis-cli.json')) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $target 'myelexis-cli.json'))) {
|
||||
throw 'Manifest myelexis-cli fehlt im Ziel-Bucket. Keine Deinstallation.'
|
||||
}
|
||||
foreach ($obsolete in $obsoleteApps) {
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Für Deinstallationen muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
& scoop uninstall $obsolete
|
||||
if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath (Join-Path $apps $obsolete))) {
|
||||
throw "'$obsolete' konnte nicht deinstalliert werden."
|
||||
}
|
||||
}
|
||||
if ($installCli) {
|
||||
Write-Host 'MyElexis CLI aus medelexis neu installieren ...'
|
||||
& scoop install 'medelexis/myelexis-cli'
|
||||
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli\current\myelexis-cli.exe'))) {
|
||||
throw 'MyElexis CLI konnte nicht vollständig installiert werden.'
|
||||
}
|
||||
}
|
||||
foreach ($rename in $renames) {
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Für Paketumbenennungen muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
$appsRoot = (Resolve-Path -LiteralPath $apps).Path
|
||||
if ((Split-Path $rename.Source -Parent) -ne $appsRoot -or
|
||||
(Split-Path $rename.Destination -Parent) -ne $appsRoot) {
|
||||
throw 'Paketpfade liegen ausserhalb des Apps-Verzeichnisses.'
|
||||
}
|
||||
if (Test-Path -LiteralPath $rename.PersistSource) {
|
||||
$persistRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'persist')).Path
|
||||
if ((Split-Path $rename.PersistSource -Parent) -ne $persistRoot -or
|
||||
(Split-Path $rename.PersistTarget -Parent) -ne $persistRoot) {
|
||||
throw 'Persistenzpfade liegen ausserhalb des Persist-Verzeichnisses.'
|
||||
}
|
||||
[IO.Directory]::Move($rename.PersistSource, $rename.PersistTarget)
|
||||
}
|
||||
$oldCurrent = Join-Path $rename.Source 'current'
|
||||
& attrib -R /L $oldCurrent
|
||||
if ($LASTEXITCODE -ne 0) { throw 'current-Verknüpfung konnte nicht freigegeben werden.' }
|
||||
# Nur die Junction entfernen, ohne rekursiv ihren Inhalt zu löschen.
|
||||
[IO.Directory]::Delete($oldCurrent)
|
||||
[IO.Directory]::Move($rename.Source, $rename.Destination)
|
||||
$newCurrent = Join-Path $rename.Destination 'current'
|
||||
$newVersion = Join-Path $rename.Destination $rename.Version
|
||||
New-Item -ItemType Junction -Path $newCurrent -Target $newVersion | Out-Null
|
||||
Write-Host "Startverknüpfungen für $($rename.NewName) erneuern ..."
|
||||
& scoop reset "$($rename.NewName)@$($rename.Version)"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Scoop reset für '$($rename.NewName)' fehlgeschlagen." }
|
||||
}
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
$oldPath = Join-Path $ScoopRoot "buckets\$oldName"
|
||||
if (-not (Test-Path -LiteralPath $oldPath)) { continue }
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Zum Entfernen alter Buckets muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
Write-Host "Alten Bucket entfernen: $oldName"
|
||||
& scoop bucket rm $oldName
|
||||
if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath $oldPath)) {
|
||||
throw "Alter Bucket '$oldName' konnte nicht entfernt werden. Die Umstellung kann erneut ausgeführt werden."
|
||||
}
|
||||
}
|
||||
foreach ($name in @((Get-ChildItem -LiteralPath $apps -Directory).Name)) {
|
||||
$installFile = Join-Path $apps "$name\current\install.json"
|
||||
if (-not (Test-Path -LiteralPath $installFile)) { continue }
|
||||
if (([IO.File]::ReadAllText($installFile) | ConvertFrom-Json).bucket -ne $targetName) { continue }
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $target "bucket\$name.json")) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $target "$name.json"))) {
|
||||
Write-Warning "Für '$name' fehlt weiterhin ein gleichnamiges Manifest."
|
||||
}
|
||||
}
|
||||
Write-Host "Fertig: $($changes.Count) install.json-Dateien auf medelexis umgestellt."
|
||||
Write-Host "Paketnamen umgestellt: $($renames.Count)."
|
||||
Write-Host 'Von elexis/streichtech/medelexis bleibt nur medelexis bestehen. Prüfung: scoop status --local'
|
||||
|
||||
do {
|
||||
$autoUpdateAnswer = (Read-Host 'Scoop Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
||||
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||
|
||||
if ($autoUpdateAnswer -eq 'y') {
|
||||
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||
New-Item -ItemType Directory -Path $autoUpdateDirectory -Force | Out-Null
|
||||
$autoUpdateScript = Join-Path $autoUpdateDirectory 'auto-update.ps1'
|
||||
$downloadPath = Join-Path $autoUpdateDirectory ('auto-update-{0}.download' -f [guid]::NewGuid())
|
||||
try {
|
||||
$autoUpdateUrl = "$repository/raw/branch/master/scripts/auto-update.ps1"
|
||||
Invoke-WebRequest -Uri $autoUpdateUrl -OutFile $downloadPath -UseBasicParsing
|
||||
$parseTokens = $null
|
||||
$parseErrors = $null
|
||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gueltiges Updateskript.'
|
||||
}
|
||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||
$taskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
||||
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
$arguments = '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -ScoopRoot "{1}"' -f $autoUpdateScript, $ScoopRoot
|
||||
$action = New-ScheduledTaskAction -Execute $powershell -Argument $arguments
|
||||
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force `
|
||||
-Description 'Scoop taeglich um 03:00 aktualisieren; verpasste Termine nachholen; elexis* auf hold setzen.' | Out-Null
|
||||
Write-Host "Scoop Auto-Update eingeschaltet: $taskName (taeglich 03:00, verpasste Termine nachholen)."
|
||||
Write-Host "Ausfuehrung verborgen unter $userId, sobald der Benutzer angemeldet ist; kein Aufwecken des PCs."
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||
}
|
||||
} else {
|
||||
Write-Host 'Auto-Update nicht eingerichtet. Eine eventuell bestehende Aufgabe bleibt unveraendert.'
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
"""Update timestamp and release manifests using only Python 3.10+.
|
||||
|
||||
Without --write, calculate changes without saving them. Never commit or push.
|
||||
PowerShell statements in manifests are not executed.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from datetime import timezone
|
||||
from email.utils import parsedate_to_datetime
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urldefrag, urlsplit
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
BUCKET = Path(__file__).resolve().parents[1] / 'bucket'
|
||||
TIMESTAMP_REGEX = r'(?<version>\d{8}T\d{6}Z)'
|
||||
|
||||
|
||||
def open_url(url, method='GET', headers=None):
|
||||
parsed = urlsplit(url)
|
||||
if parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password:
|
||||
raise ValueError('Nur HTTPS-URLs ohne eingebettete Zugangsdaten sind erlaubt.')
|
||||
return urlopen(Request(url, method=method, headers={
|
||||
'User-Agent': 'Medelexis-Scoop-Updater', 'Cache-Control': 'no-cache',
|
||||
'Accept-Encoding': 'identity', **(headers or {})}), timeout=180)
|
||||
|
||||
|
||||
def metadata(headers):
|
||||
return {key: headers.get(key, '') for key in ('Last-Modified', 'ETag')}
|
||||
|
||||
|
||||
def read_head(url):
|
||||
with open_url(url, 'HEAD') as response:
|
||||
return metadata(response.headers)
|
||||
|
||||
|
||||
def release_version(checkver):
|
||||
if checkver.get('jsonpath') != '$.tag_name' or not checkver.get('regex'):
|
||||
raise ValueError('Unterstützt werden Last-Modified oder jsonpath $.tag_name mit regex.')
|
||||
with open_url(checkver['url']) as response:
|
||||
tag = json.load(response)['tag_name']
|
||||
pattern = checkver['regex'].replace('(?<version>', '(?P<version>')
|
||||
match = re.search(pattern, tag)
|
||||
if not match:
|
||||
raise ValueError('Release-Tag passt nicht zum Versionsmuster.')
|
||||
version = match.groupdict().get('version') or match.group(1)
|
||||
if not re.fullmatch(r'[0-9A-Za-z][0-9A-Za-z._+-]*', version):
|
||||
raise ValueError('Ungültige Release-Version.')
|
||||
return version
|
||||
|
||||
|
||||
def download_file(url, observed, target):
|
||||
conditions = {}
|
||||
if observed['ETag'] and not observed['ETag'].startswith('W/'):
|
||||
conditions['If-Match'] = observed['ETag']
|
||||
elif observed['Last-Modified']:
|
||||
conditions['If-Unmodified-Since'] = observed['Last-Modified']
|
||||
size = 0
|
||||
prefix = b''
|
||||
with open_url(url, headers=conditions) as response:
|
||||
if metadata(response.headers) != observed:
|
||||
raise ValueError('Datei hat sich zwischen HEAD und GET geändert; erneut versuchen.')
|
||||
if response.headers.get('Content-Type', '').split(';')[0].lower() in (
|
||||
'text/html', 'application/json'):
|
||||
raise ValueError('Download liefert HTML/JSON statt einer Programmdatei.')
|
||||
expected = response.headers.get('Content-Length')
|
||||
total = int(expected) if expected is not None else None
|
||||
last_report = time.monotonic()
|
||||
if total is not None:
|
||||
print(f' Download gestartet: {total / 1048576:.1f} MiB.', flush=True)
|
||||
else:
|
||||
print(' Download gestartet; Gesamtgrösse unbekannt.', flush=True)
|
||||
with target.open('wb') as output:
|
||||
while chunk := response.read(1024 * 1024):
|
||||
if not size:
|
||||
prefix = chunk[:4]
|
||||
output.write(chunk)
|
||||
size += len(chunk)
|
||||
now = time.monotonic()
|
||||
if now - last_report >= 5:
|
||||
progress = f'{size / 1048576:.1f} MiB'
|
||||
if total:
|
||||
progress += f' / {total / 1048576:.1f} MiB ({size / total:.0%})'
|
||||
print(f' Heruntergeladen: {progress}', flush=True)
|
||||
last_report = now
|
||||
if not size or (expected is not None and size != int(expected)):
|
||||
raise ValueError('Leerer oder unvollständiger Download.')
|
||||
suffix = Path(urlsplit(url).path).suffix.lower()
|
||||
if suffix == '.exe' and not prefix.startswith(b'MZ'):
|
||||
raise ValueError('Download hat keine Windows-EXE-Signatur.')
|
||||
if suffix == '.zip' and not prefix.startswith(b'PK'):
|
||||
raise ValueError('Download hat keine ZIP-Signatur.')
|
||||
print(f' Download abgeschlossen: {size / 1048576:.1f} MiB.', flush=True)
|
||||
return size
|
||||
|
||||
|
||||
def hash_file(path):
|
||||
total = path.stat().st_size
|
||||
print(f' SHA-256-Berechnung gestartet: {total / 1048576:.1f} MiB.', flush=True)
|
||||
digest = hashlib.sha256()
|
||||
size = 0
|
||||
last_report = time.monotonic()
|
||||
with path.open('rb') as source:
|
||||
while chunk := source.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
size += len(chunk)
|
||||
now = time.monotonic()
|
||||
if now - last_report >= 5:
|
||||
print(f' Gehasht: {size / 1048576:.1f} / {total / 1048576:.1f} MiB '
|
||||
f'({size / total:.0%})', flush=True)
|
||||
last_report = now
|
||||
print(' SHA-256-Berechnung abgeschlossen.', flush=True)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def download_hash(url, observed):
|
||||
with tempfile.TemporaryDirectory(prefix='scoop-update-') as directory:
|
||||
target = Path(directory) / 'download.bin'
|
||||
size = download_file(url, observed, target)
|
||||
return hash_file(target), size
|
||||
|
||||
|
||||
def save_manifest(path, manifest):
|
||||
original = path.read_bytes()
|
||||
newline = '\r\n' if b'\r\n' in original else '\n'
|
||||
content = json.dumps(manifest, ensure_ascii=False, indent=4) + '\n'
|
||||
temporary = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(dir=path.parent, suffix='.tmp', delete=False) as output:
|
||||
temporary = Path(output.name)
|
||||
output.write(content.replace('\n', newline).encode('utf-8'))
|
||||
temporary.replace(path)
|
||||
finally:
|
||||
if temporary is not None:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def update_manifest(path, write=False):
|
||||
manifest = json.loads(path.read_text(encoding='utf-8-sig'))
|
||||
checkver, autoupdate = manifest.get('checkver'), manifest.get('autoupdate')
|
||||
if not checkver or not autoupdate:
|
||||
print(f'{path.stem}: kein automatisches Update konfiguriert; übersprungen.')
|
||||
return False
|
||||
if not isinstance(checkver, dict) or not isinstance(autoupdate.get('url'), str):
|
||||
raise ValueError('Diese checkver/autoupdate-Konfiguration wird noch nicht unterstützt.')
|
||||
|
||||
template = autoupdate['url']
|
||||
observed = None
|
||||
if checkver.get('regex') == TIMESTAMP_REGEX:
|
||||
source = urldefrag(manifest['url'])[0]
|
||||
print(f'{path.stem}: HEAD-Anfrage an {source} ...', flush=True)
|
||||
observed = read_head(source)
|
||||
if not observed['Last-Modified']:
|
||||
raise ValueError('Last-Modified-Header fehlt.')
|
||||
date = parsedate_to_datetime(observed['Last-Modified'])
|
||||
if date.tzinfo is None:
|
||||
raise ValueError('Last-Modified enthält keine Zeitzone.')
|
||||
version = date.astimezone(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
|
||||
if urldefrag(template.replace('$version', version))[0] != source:
|
||||
raise ValueError('Zeitstempel-Updates benötigen eine feste Download-URL.')
|
||||
elif 'script' not in checkver:
|
||||
print(f'{path.stem}: Release-Version abfragen: {checkver.get("url", "")} ...', flush=True)
|
||||
version = release_version(checkver)
|
||||
else:
|
||||
raise ValueError('Dieses checkver-Skript wird nicht ausgeführt oder interpretiert.')
|
||||
|
||||
if version == manifest['version']:
|
||||
print(f'{path.stem}: {version} unverändert; kein Download.')
|
||||
return False
|
||||
url = template.replace('$version', version)
|
||||
if '$' in url:
|
||||
raise ValueError('Unterstützt wird nur der Platzhalter $version.')
|
||||
source = urldefrag(url)[0]
|
||||
if observed is None:
|
||||
print(f'{path.stem}: HEAD-Anfrage an {source} ...', flush=True)
|
||||
observed = read_head(source)
|
||||
print(f'{path.stem}: neue Version {version}; GET-Download ...', flush=True)
|
||||
print(' Für den SHA-256-Hash muss die gesamte Datei heruntergeladen werden. '
|
||||
'Danach wird der Hash berechnet. Bei grossen Dateien kann dies mehrere Minuten dauern, '
|
||||
'auch im Probelauf.', flush=True)
|
||||
digest, size = download_hash(source, observed)
|
||||
print(f'{path.stem}: {manifest["version"]} -> {version}; {size} Bytes; SHA-256 {digest}')
|
||||
manifest.update(version=version, url=url, hash=digest)
|
||||
if write:
|
||||
save_manifest(path, manifest)
|
||||
print(f'{path.stem}: Manifest gespeichert.')
|
||||
else:
|
||||
print(f'{path.stem}: Probelauf; Manifest bleibt unverändert.')
|
||||
return True
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--bucket', type=Path, default=BUCKET)
|
||||
parser.add_argument('--app', action='append', help='Manifestname; mehrfach möglich, sonst alle.')
|
||||
parser.add_argument('--write', action='store_true', help='Geprüfte Änderungen speichern.')
|
||||
args = parser.parse_args(argv)
|
||||
if not args.bucket.is_dir():
|
||||
parser.error('Bucket-Verzeichnis existiert nicht.')
|
||||
if args.app and any(not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]*', app) for app in args.app):
|
||||
parser.error('--app erwartet einen Manifestnamen ohne Pfad oder .json-Endung.')
|
||||
paths = ([args.bucket / f'{app}.json' for app in args.app] if args.app else
|
||||
sorted(args.bucket.glob('*.json')))
|
||||
errors = 0
|
||||
for path in paths:
|
||||
try:
|
||||
update_manifest(path, args.write)
|
||||
except HTTPError as error:
|
||||
print(f'{path.stem}: HTTP {error.code}; Manifest unverändert.', file=sys.stderr)
|
||||
errors += 1
|
||||
except (ValueError, KeyError, TypeError, OSError, URLError) as error:
|
||||
print(f'{path.stem}: {error}; Manifest unverändert.', file=sys.stderr)
|
||||
errors += 1
|
||||
return int(errors > 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,154 @@
|
||||
#requires -Version 5.1
|
||||
# Isolated behavioural tests: no real downloads, Scoop updates or scheduled tasks.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$projectRoot = Split-Path $PSScriptRoot -Parent
|
||||
$fixtureRoot = Join-Path $PSScriptRoot ('.auto-update-test-' + [guid]::NewGuid())
|
||||
$oldScoop = $env:SCOOP
|
||||
$oldPath = $env:PATH
|
||||
$oldFixture = $env:SCOOP_AUTO_UPDATE_TEST_ROOT
|
||||
$oldFailure = $env:SCOOP_AUTO_UPDATE_TEST_FAILURE
|
||||
|
||||
function Assert-True($Condition, [string] $Message) {
|
||||
if (-not $Condition) { throw $Message }
|
||||
}
|
||||
|
||||
# These stand-ins capture scheduling settings without changing Windows.
|
||||
function New-ScheduledTaskAction { param($Execute, $Argument) [pscustomobject]$PSBoundParameters }
|
||||
function New-ScheduledTaskTrigger { param([switch]$Daily, $At) [pscustomobject]$PSBoundParameters }
|
||||
function New-ScheduledTaskPrincipal { param($UserId, $LogonType, $RunLevel) [pscustomobject]$PSBoundParameters }
|
||||
function New-ScheduledTaskSettingsSet {
|
||||
param([switch]$StartWhenAvailable, [switch]$RunOnlyIfNetworkAvailable,
|
||||
[switch]$AllowStartIfOnBatteries, [switch]$DontStopIfGoingOnBatteries,
|
||||
$MultipleInstances, $ExecutionTimeLimit)
|
||||
[pscustomobject]$PSBoundParameters
|
||||
}
|
||||
function Register-ScheduledTask {
|
||||
param($TaskName, $Action, $Trigger, $Principal, $Settings, [switch]$Force, $Description)
|
||||
$global:AutoUpdateTestTask = [pscustomobject]$PSBoundParameters
|
||||
}
|
||||
function Read-Host {
|
||||
param($Prompt)
|
||||
$global:AutoUpdateTestAnswers.Dequeue()
|
||||
}
|
||||
function Invoke-WebRequest {
|
||||
param($Uri, $OutFile, [switch]$UseBasicParsing)
|
||||
Assert-True ($Uri -in @('https://example.test/scoop/raw/branch/master/scripts/auto-update.ps1',
|
||||
'https://gitea.medelexis.ch/public/scoop/raw/branch/master/scripts/auto-update.ps1')) 'Unexpected download URL.'
|
||||
if ($global:AutoUpdateTestInvalidDownload) {
|
||||
'This is not the setup script.' | Set-Content -LiteralPath $OutFile
|
||||
} else {
|
||||
@'
|
||||
param([string] $ScoopRoot)
|
||||
throw 'The downloaded runner must not execute during setup.'
|
||||
'@ | Set-Content -LiteralPath $OutFile
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
# Spaces in the installation path exercise the scheduled command's quoting.
|
||||
$scoopRoot = Join-Path $fixtureRoot 'scoop with spaces'
|
||||
$shims = Join-Path $scoopRoot 'shims'
|
||||
New-Item -ItemType Directory -Path $shims -Force | Out-Null
|
||||
$scriptPath = Join-Path $fixtureRoot 'auto-update.ps1'
|
||||
Copy-Item -LiteralPath (Join-Path $projectRoot 'scripts\auto-update.ps1') -Destination $scriptPath
|
||||
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $fixtureRoot
|
||||
$commandLog = Join-Path $fixtureRoot 'commands.log'
|
||||
@'
|
||||
param([Parameter(ValueFromRemainingArguments = $true)][string[]] $ScoopArguments)
|
||||
Add-Content -LiteralPath (Join-Path $env:SCOOP_AUTO_UPDATE_TEST_ROOT 'commands.log') -Value ($ScoopArguments -join ' ')
|
||||
if ($env:SCOOP_AUTO_UPDATE_TEST_FAILURE -eq ($ScoopArguments -join ' ')) { exit 1 }
|
||||
if ($ScoopArguments[0] -eq 'list') {
|
||||
[pscustomobject]@{ Name = 'elexis'; Info = '' }
|
||||
[pscustomobject]@{ Name = 'Elexis-test'; Info = 'Held package' }
|
||||
[pscustomobject]@{ Name = 'myelexis-cli'; Info = '' }
|
||||
[pscustomobject]@{ Name = 'git'; Info = '' }
|
||||
[pscustomobject]@{ Name = 'elexis-global'; Info = 'Global install' }
|
||||
[pscustomobject]@{ Name = 'elexis-broken'; Info = 'Install failed' }
|
||||
}
|
||||
exit 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shims 'scoop.ps1') -Encoding UTF8
|
||||
|
||||
& $scriptPath -ScoopRoot $scoopRoot
|
||||
Assert-True ($LASTEXITCODE -eq 0) 'Expected successful update.'
|
||||
Assert-True (((Get-Content $commandLog) -join '|') -eq 'list|hold elexis|hold Elexis-test|update|update *') 'Expected only local elexis* held before updates.'
|
||||
Assert-True (Test-Path (Join-Path $fixtureRoot 'auto-update.log')) 'Expected update log.'
|
||||
|
||||
foreach ($failure in @('list', 'hold elexis', 'update', 'update *')) {
|
||||
Remove-Item -LiteralPath $commandLog
|
||||
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $failure
|
||||
# Expected errors are logged; do not let test ErrorActionPreference terminate the caller.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $scriptPath -ScoopRoot $scoopRoot 2>$null
|
||||
$result = $LASTEXITCODE
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Assert-True ($result -eq 1) "Expected failure status for $failure."
|
||||
$commands = @(Get-Content $commandLog)
|
||||
Assert-True ($commands[-1] -eq $failure) "Must abort immediately after $failure."
|
||||
}
|
||||
# Exercise the actual opt-in/download block without running the bucket migration.
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
$bucketAst = [Management.Automation.Language.Parser]::ParseFile((Join-Path $projectRoot 'scripts\update-bucket.ps1'), [ref]$tokens, [ref]$errors)
|
||||
Assert-True ($errors.Count -eq 0) 'Invalid bucket script syntax.'
|
||||
$optInBlock = [scriptblock]::Create((($bucketAst.EndBlock.Statements | Select-Object -Last 2 | ForEach-Object { $_.Extent.Text }) -join "`n"))
|
||||
$repository = 'https://example.test/scoop'
|
||||
Remove-Item -LiteralPath $commandLog
|
||||
$global:AutoUpdateTestAnswers = [Collections.Generic.Queue[string]]::new()
|
||||
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||
& $optInBlock
|
||||
$downloadDirectory = Join-Path $scoopRoot 'scripts\medelexis'
|
||||
Assert-True (-not (Test-Path $downloadDirectory)) 'Declining must not download or set up anything.'
|
||||
$global:AutoUpdateTestAnswers.Enqueue('invalid')
|
||||
$global:AutoUpdateTestAnswers.Enqueue('Y')
|
||||
& $optInBlock
|
||||
$downloadedPath = Join-Path $downloadDirectory 'auto-update.ps1'
|
||||
Assert-True (Test-Path $downloadedPath) 'Expected downloaded runner.'
|
||||
$task = $global:AutoUpdateTestTask
|
||||
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00') 'Expected daily 03:00 schedule.'
|
||||
Assert-True ($task.Settings.StartWhenAvailable -and $task.Settings.RunOnlyIfNetworkAvailable) 'Expected catch-up and network settings.'
|
||||
Assert-True ($task.Settings.MultipleInstances -eq 'IgnoreNew') 'Expected overlap protection.'
|
||||
Assert-True ($task.Principal.LogonType -eq 'Interactive' -and $task.Principal.RunLevel -eq 'Limited') 'Expected normal user session.'
|
||||
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-NonInteractive')) 'Expected background execution.'
|
||||
Assert-True ($task.Action.Argument.Contains('-File "' + $downloadedPath + '"') -and $task.Action.Argument.Contains('-ScoopRoot "' + $scoopRoot + '"')) 'Expected quoted paths pointing to the downloaded runner.'
|
||||
Assert-True (-not (Test-Path $commandLog)) 'Setup must not execute Scoop.'
|
||||
$taskName = $task.TaskName
|
||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||
& $optInBlock
|
||||
Assert-True ($global:AutoUpdateTestTask.TaskName -eq $taskName -and $global:AutoUpdateTestTask.Force) 'Repeated setup must replace the same task.'
|
||||
$originalDownload = [IO.File]::ReadAllText($downloadedPath)
|
||||
Remove-Variable AutoUpdateTestTask -Scope Global
|
||||
$global:AutoUpdateTestInvalidDownload = $true
|
||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||
$rejected = $false
|
||||
try { & $optInBlock } catch { $rejected = $true }
|
||||
Assert-True $rejected 'Invalid download must be rejected.'
|
||||
Assert-True ([IO.File]::ReadAllText($downloadedPath) -ceq $originalDownload) 'Invalid download must preserve the existing script.'
|
||||
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Invalid download must not register a task.'
|
||||
Assert-True (@(Get-ChildItem $downloadDirectory -Filter '*.download').Count -eq 0) 'Temporary downloads must be removed.'
|
||||
# The additional standalone installer must offer the same setup without migration.
|
||||
$standaloneSetup = Join-Path $projectRoot 'scripts\setup-auto-update.ps1'
|
||||
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||
& $standaloneSetup -ScoopRoot $scoopRoot
|
||||
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Standalone n must not register a task.'
|
||||
$global:AutoUpdateTestInvalidDownload = $false
|
||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||
& $standaloneSetup -ScoopRoot $scoopRoot
|
||||
$task = $global:AutoUpdateTestTask
|
||||
Assert-True ($task.TaskName -eq $taskName -and $task.Force) 'Standalone setup must use the same task.'
|
||||
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00' -and $task.Settings.StartWhenAvailable) 'Standalone setup must schedule daily 03:00 with catch-up.'
|
||||
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-File "' + $downloadedPath + '"')) 'Standalone task must invoke the downloaded runner hidden.'
|
||||
Assert-True (-not (Test-Path $commandLog)) 'Standalone setup must not run Scoop or migrations.'
|
||||
Write-Host 'PASS: schedule, repeat setup, hold filtering, command order, logging, failures, opt-in and download setup.'
|
||||
} finally {
|
||||
$env:SCOOP = $oldScoop
|
||||
$env:PATH = $oldPath
|
||||
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $oldFixture
|
||||
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $oldFailure
|
||||
Remove-Variable AutoUpdateTestTask, AutoUpdateTestAnswers, AutoUpdateTestInvalidDownload -Scope Global -ErrorAction SilentlyContinue
|
||||
# Delete only this test's unique directory, after checking its absolute parent.
|
||||
if ((Test-Path -LiteralPath $fixtureRoot) -and
|
||||
[IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($fixtureRoot)) -eq [IO.Path]::GetFullPath($PSScriptRoot) -and
|
||||
[IO.Path]::GetFileName($fixtureRoot).StartsWith('.auto-update-test-')) {
|
||||
Remove-Item -LiteralPath $fixtureRoot -Recurse -Force
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location('updater', ROOT / 'scripts/update-manifests.py')
|
||||
updater = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(updater)
|
||||
MODIFIED = 'Wed, 21 Oct 2026 06:00:00 GMT'
|
||||
HEADERS = {'Last-Modified': MODIFIED, 'ETag': '"example"'}
|
||||
|
||||
|
||||
def response(data=b'', headers=None):
|
||||
result = io.BytesIO(data)
|
||||
result.headers = dict(headers or HEADERS)
|
||||
return result
|
||||
|
||||
|
||||
class UpdaterTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.path = Path(self.temp.name) / 'myelexis-cli.json'
|
||||
self.manifest = json.loads((ROOT / 'bucket/myelexis-cli.json').read_text(encoding='utf-8'))
|
||||
self.path.write_text(json.dumps(self.manifest, indent=4) + '\n', encoding='utf-8')
|
||||
self.original = self.path.read_bytes()
|
||||
|
||||
def test_same_version_only_uses_head(self):
|
||||
self.manifest['version'] = '20261021T060000Z'
|
||||
self.path.write_text(json.dumps(self.manifest), encoding='utf-8')
|
||||
before = self.path.read_bytes()
|
||||
with patch.object(updater, 'open_url', return_value=response()) as request:
|
||||
self.assertFalse(updater.update_manifest(self.path, write=True))
|
||||
self.assertEqual(request.call_count, 1)
|
||||
self.assertEqual(request.call_args.args[1], 'HEAD')
|
||||
self.assertEqual(self.path.read_bytes(), before)
|
||||
|
||||
def test_dry_run_downloads_and_hashes_without_writing(self):
|
||||
binary = b'MZexample'
|
||||
with patch.object(updater, 'open_url', side_effect=[response(), response(binary)]) as request:
|
||||
self.assertTrue(updater.update_manifest(self.path))
|
||||
self.assertEqual(request.call_args.kwargs['headers']['If-Match'], HEADERS['ETag'])
|
||||
self.assertEqual(self.path.read_bytes(), self.original)
|
||||
|
||||
def test_write_updates_only_version_url_and_hash(self):
|
||||
binary = b'MZexample'
|
||||
with patch.object(updater, 'open_url', side_effect=[response(), response(binary)]):
|
||||
updater.update_manifest(self.path, write=True)
|
||||
actual = json.loads(self.path.read_text())
|
||||
expected = {**self.manifest, 'version': '20261021T060000Z',
|
||||
'hash': hashlib.sha256(binary).hexdigest()}
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_download_errors_leave_manifest_unchanged(self):
|
||||
cases = [
|
||||
response(b'MZexample', {**HEADERS, 'ETag': '"changed"'}),
|
||||
response(b'MZexample', {**HEADERS, 'Content-Length': '100'}),
|
||||
response(b'<html>login</html>', {**HEADERS, 'Content-Type': 'text/html'}),
|
||||
response(b'not-an-executable'),
|
||||
]
|
||||
for download in cases:
|
||||
with self.subTest(headers=download.headers):
|
||||
with patch.object(updater, 'open_url', side_effect=[response(), download]):
|
||||
with self.assertRaises(ValueError):
|
||||
updater.update_manifest(self.path, write=True)
|
||||
self.assertEqual(self.path.read_bytes(), self.original)
|
||||
|
||||
def test_missing_last_modified_leaves_manifest_unchanged(self):
|
||||
with patch.object(updater, 'open_url', return_value=response(headers={'ETag': 'x'})):
|
||||
with self.assertRaises(ValueError):
|
||||
updater.update_manifest(self.path, write=True)
|
||||
self.assertEqual(self.path.read_bytes(), self.original)
|
||||
|
||||
def test_hash_runs_after_download_and_temporary_file_is_removed(self):
|
||||
binary = b'MZexample'
|
||||
temporary_paths = []
|
||||
original_hash = updater.hash_file
|
||||
|
||||
def inspect_download(path):
|
||||
self.assertEqual(path.read_bytes(), binary)
|
||||
temporary_paths.append(path)
|
||||
return original_hash(path)
|
||||
|
||||
with patch.object(updater, 'open_url', return_value=response(binary)), \
|
||||
patch.object(updater, 'hash_file', side_effect=inspect_download):
|
||||
digest, size = updater.download_hash('https://example.org/tool.exe', HEADERS)
|
||||
self.assertEqual(digest, hashlib.sha256(binary).hexdigest())
|
||||
self.assertEqual(size, len(binary))
|
||||
self.assertFalse(temporary_paths[0].parent.exists())
|
||||
|
||||
def test_existing_release_manifest_substitutes_version(self):
|
||||
manifest = {
|
||||
'version': '13.6.0',
|
||||
'url': 'https://example.org/releases/download/v13.6.0/tool.exe',
|
||||
'hash': 'a' * 64,
|
||||
'checkver': {'url': 'https://example.org/api/releases/latest',
|
||||
'jsonpath': '$.tag_name', 'regex': r'^v(?<version>[\d.]+)$'},
|
||||
'autoupdate': {'url': 'https://example.org/releases/download/v$version/tool.exe'},
|
||||
}
|
||||
self.path.write_text(json.dumps(manifest), encoding='utf-8')
|
||||
binary = b'MZexample executable'
|
||||
with patch.object(updater, 'open_url', side_effect=[
|
||||
response(b'{"tag_name":"v13.7.0"}'), response(), response(binary)]) as request:
|
||||
updater.update_manifest(self.path, write=True)
|
||||
updated = json.loads(self.path.read_text())
|
||||
self.assertEqual(updated['version'], '13.7.0')
|
||||
self.assertIn('/v13.7.0/', updated['url'])
|
||||
self.assertNotIn('#', request.call_args.args[0])
|
||||
self.assertEqual(updated['hash'], hashlib.sha256(binary).hexdigest())
|
||||
|
||||
def test_archive_filename_is_updated_but_download_url_has_no_fragment(self):
|
||||
manifest = json.loads((ROOT / 'bucket/elexis-3.13.json').read_text(encoding='utf-8'))
|
||||
self.path.write_text(json.dumps(manifest), encoding='utf-8')
|
||||
with patch.object(updater, 'open_url', side_effect=[response(), response(b'PKexample')]) as request:
|
||||
updater.update_manifest(self.path, write=True)
|
||||
self.assertNotIn('#', request.call_args.args[0])
|
||||
self.assertTrue(json.loads(self.path.read_text())['url'].endswith('#/Elexis-20261021T060000Z.zip'))
|
||||
|
||||
def test_http_error_is_reported_and_does_not_stop_other_manifests(self):
|
||||
other = self.path.with_name('other.json')
|
||||
other.write_text(json.dumps({'version': '1.0.0'}), encoding='utf-8')
|
||||
error = updater.HTTPError('https://example.org/file', 401, 'Unauthorized', {}, None)
|
||||
with patch.object(updater, 'open_url', side_effect=error):
|
||||
self.assertEqual(updater.main(['--bucket', self.temp.name, '--write']), 1)
|
||||
self.assertEqual(self.path.read_bytes(), self.original)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user