Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
# Keep the SHA-pinned GitHub Actions current.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

# NuGet packages across the solution.
- package-ecosystem: nuget
directory: /
schedule:
interval: weekly
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '8.0.x'

Expand Down
187 changes: 159 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,173 @@
name: Release

# Pushing a version tag (v0.2.0) builds and publishes the Windows + Linux installers. Building on a
# private repo bills Actions minutes with steep multipliers (macOS 10x, Windows 2x), so releases are
# deliberate — tag when you mean it — rather than one per merge. macOS carries the 10x multiplier and
# is built only on demand via workflow_dispatch, which attaches the .dmg to the release for that
# version.
on:
push:
tags: [ 'v*' ]
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: Version to build the macOS .dmg for (e.g. 0.2.0), matching an existing v-tag release.
required: true

permissions:
contents: write

env:
PUBLISH_FLAGS: >-
-c Release --self-contained true
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
-p:DebugType=none -p:DebugSymbols=false

jobs:
publish:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
rid: win-x64
asset: lagebuch-win-x64.exe
artifact: Feuerwehr.App.exe
- os: ubuntu-latest
rid: linux-x64
asset: lagebuch-linux-x64
artifact: Feuerwehr.App
runs-on: ${{ matrix.os }}
version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
steps:
# tag push -> strip the leading 'v' from the tag; dispatch -> the supplied input.
- id: v
env:
REF_NAME: ${{ github.ref_name }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ "${{ github.event_name }}" = "push" ]; then
echo "version=${REF_NAME#v}" >> "$GITHUB_OUTPUT"
else
echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
fi

windows:
needs: version
if: github.event_name == 'push'
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '8.0.x'
- name: Publish
run: >
dotnet publish src/Feuerwehr.App/Feuerwehr.App.csproj -r win-x64 ${{ env.PUBLISH_FLAGS }}
-p:Version=${{ needs.version.outputs.version }} -o publish
- name: Install WiX
run: |
dotnet tool install --global wix --version 5.0.2
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
- name: Build MSI
# Absolute paths: WiX resolves <Files Include> relative to the .wxs file's own directory,
# not the working directory, so a bare "publish" would look under packaging/windows.
run: >
wix build packaging/windows/Lagebuch.wxs
-d Version=${{ needs.version.outputs.version }}
-d "PublishDir=${{ github.workspace }}\publish"
-d "IconFile=${{ github.workspace }}\src\Feuerwehr.App\Assets\icon.ico"
-o "lagebuch-${{ needs.version.outputs.version }}-x64.msi"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: msi
path: '*.msi'
if-no-files-found: error

linux:
needs: version
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '8.0.x'
- name: Publish
run: >
dotnet publish src/Feuerwehr.App/Feuerwehr.App.csproj -r linux-x64 ${{ env.PUBLISH_FLAGS }}
-p:Version=${{ needs.version.outputs.version }} -o publish
- name: Build .deb
run: |
chmod +x packaging/linux/build-deb.sh
packaging/linux/build-deb.sh \
"${{ needs.version.outputs.version }}" \
publish \
src/Feuerwehr.App/Assets/icon-1024.png \
dist
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: deb
path: dist/*.deb
if-no-files-found: error

release:
needs: [version, windows, linux]
runs-on: ubuntu-latest
# Publish the Windows + Linux installers to the release for the pushed tag.
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
- uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: v${{ needs.version.outputs.version }}
name: Lagebuch v${{ needs.version.outputs.version }}
prerelease: true
files: dist/**/*
body: |
Getaggter Release-Build.

| Plattform | Datei |
|-----------|-------|
| Windows | `lagebuch-${{ needs.version.outputs.version }}-x64.msi` |
| Linux | `lagebuch_${{ needs.version.outputs.version }}_amd64.deb` |

Ein macOS-Build (Apple Silicon, `.dmg`) wird auf Anfrage bereitgestellt und dann hier
angehängt.

**Die Pakete sind nicht signiert.** Beim ersten Start:
- **Windows**: SmartScreen → *Weitere Informationen* → *Trotzdem ausführen*.
- **Linux**: `sudo dpkg -i lagebuch_*.deb` (oder `sudo apt install ./lagebuch_*.deb`).
- **macOS**: Rechtsklick auf Lagebuch.app → *Öffnen* (einmalig bestätigen),
oder `xattr -dr com.apple.quarantine /Applications/Lagebuch.app`.

# macOS carries the 10x minute multiplier, so it is not built on every tag. Run this workflow
# manually (workflow_dispatch) with the version of an existing release to add the .dmg to it.
macos:
needs: version
if: github.event_name == 'workflow_dispatch'
runs-on: macos-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '8.0.x'
- name: Publish
run: >
dotnet publish src/Feuerwehr.App/Feuerwehr.App.csproj
-c Release -r ${{ matrix.rid }} --self-contained true
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
-o publish
- name: Rename artifact
shell: bash
run: cp "publish/${{ matrix.artifact }}" "${{ matrix.asset }}"
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.asset }}
dotnet publish src/Feuerwehr.App/Feuerwehr.App.csproj -r osx-arm64 ${{ env.PUBLISH_FLAGS }}
-p:Version=${{ needs.version.outputs.version }} -o publish
- name: Build .dmg
run: |
chmod +x packaging/macos/build-dmg.sh
packaging/macos/build-dmg.sh \
"${{ needs.version.outputs.version }}" \
publish \
src/Feuerwehr.App/Assets/icon-1024.png \
dist
# Always downloadable from the run; also attach to the matching release if one exists.
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dmg
path: dist/*.dmg
if-no-files-found: error
- name: Attach .dmg to the release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.version.outputs.version }}
run: |
if gh release view "v${VERSION}" >/dev/null 2>&1; then
gh release upload "v${VERSION}" dist/*.dmg --clobber
else
echo "No release v${VERSION} yet — .dmg is available as a workflow artifact only."
fi
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ Replaces a legacy macro-enabled Excel template with a robust, offline, cross-pla

Under construction. See plans under `docs/` (note: design/plan artifacts are not committed).

## Install

Pushing a version tag publishes a release under [Releases](../../releases) with an installer per
platform:

```bash
git tag v0.2.0 && git push origin v0.2.0
```

| Platform | File | Built |
|----------|------|-------|
| Windows | `lagebuch-<version>-x64.msi` | on every tag |
| Linux (Debian/Ubuntu) | `lagebuch_<version>_amd64.deb` | on every tag |
| macOS (Apple Silicon) | `lagebuch-<version>-macos-arm64.dmg` | on request |

macOS runners bill Actions minutes at 10× on a private repo, so the `.dmg` is built on demand rather
than on every tag: run the **Release** workflow manually (Actions → Release → *Run workflow*) with
the release version, and the `.dmg` is attached to that release.

The packages are **not code-signed**, so the OS warns on first launch:

- **Windows** — run the `.msi`; if SmartScreen appears, *More info → Run anyway*.
- **macOS** — open the `.dmg`, drag Lagebuch to Applications, then **right-click the app → Open**
once (or `xattr -dr com.apple.quarantine /Applications/Lagebuch.app`).
- **Linux** — `sudo dpkg -i lagebuch_*.deb` (or `sudo apt install ./lagebuch_*.deb`).

All builds are self-contained; no .NET runtime needs to be installed separately.

## Build & Test

```bash
Expand Down
66 changes: 66 additions & 0 deletions packaging/linux/build-deb.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Build a .deb from a published self-contained Lagebuch. Reproducible on any Linux box with
# dpkg-deb, so the CI leg and a local smoke test run the exact same steps.
#
# build-deb.sh <version> <publish-dir> <icon-png> <out-dir>
set -euo pipefail

VERSION="$1"
PUBLISH_DIR="$2"
ICON_PNG="$3"
OUT_DIR="$4"

PKG="lagebuch"
ROOT="$(mktemp -d)"
trap 'rm -rf "$ROOT"' EXIT

# --- filesystem layout -----------------------------------------------------------------------
# The self-contained payload lives under /usr/lib/lagebuch; /usr/bin/lagebuch is a thin launcher
# so the binary is on PATH under a friendly name regardless of the publish output's filename.
install -d "$ROOT/usr/lib/$PKG"
cp -r "$PUBLISH_DIR"/. "$ROOT/usr/lib/$PKG/"
chmod +x "$ROOT/usr/lib/$PKG/Feuerwehr.App"

install -d "$ROOT/usr/bin"
cat > "$ROOT/usr/bin/$PKG" <<'LAUNCH'
#!/bin/sh
exec /usr/lib/lagebuch/Feuerwehr.App "$@"
LAUNCH
chmod +x "$ROOT/usr/bin/$PKG"

install -Dm644 "$ICON_PNG" "$ROOT/usr/share/icons/hicolor/512x512/apps/$PKG.png"

install -d "$ROOT/usr/share/applications"
cat > "$ROOT/usr/share/applications/$PKG.desktop" <<DESKTOP
[Desktop Entry]
Type=Application
Name=Lagebuch
Comment=Einsatzdokumentation für die Feuerwehr
Exec=$PKG
Icon=$PKG
Terminal=false
Categories=Office;Utility;
DESKTOP

# --- control metadata ------------------------------------------------------------------------
# Installed-Size is in KiB, what apt shows before installing.
SIZE_KB=$(du -sk "$ROOT/usr" | cut -f1)
install -d "$ROOT/DEBIAN"
cat > "$ROOT/DEBIAN/control" <<CONTROL
Package: $PKG
Version: $VERSION
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Feuerwehr <noreply@github.com>
Installed-Size: $SIZE_KB
Description: Lagebuch — Einsatzdokumentation
Digitales Einsatztagebuch und Lagedarstellung für die Feuerwehr.
Self-contained; no .NET runtime required.
CONTROL

mkdir -p "$OUT_DIR"
DEB="$OUT_DIR/${PKG}_${VERSION}_amd64.deb"
# dpkg-deb's progress line goes to stderr so stdout is only the artifact path.
dpkg-deb --root-owner-group --build "$ROOT" "$DEB" 1>&2
echo "$DEB"
63 changes: 63 additions & 0 deletions packaging/macos/build-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Build an unsigned Lagebuch.app and wrap it in a drag-to-Applications .dmg. macOS-only
# (iconutil / hdiutil / sips / codesign). Runs on the macos-latest CI runner.
#
# build-dmg.sh <version> <publish-dir> <icon-png-1024> <out-dir>
set -euo pipefail

VERSION="$1"
PUBLISH_DIR="$2"
ICON_PNG="$3"
OUT_DIR="$4"

APP="Lagebuch.app"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT

APP_DIR="$STAGE/$APP"
install -d "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources"

# --- payload ---------------------------------------------------------------------------------
cp -R "$PUBLISH_DIR"/. "$APP_DIR/Contents/MacOS/"
chmod +x "$APP_DIR/Contents/MacOS/Feuerwehr.App"

# --- icon: 1024 png -> multi-size .icns ------------------------------------------------------
ICONSET="$STAGE/Lagebuch.iconset"
mkdir -p "$ICONSET"
for s in 16 32 64 128 256 512; do
sips -z $s $s "$ICON_PNG" --out "$ICONSET/icon_${s}x${s}.png" >/dev/null
sips -z $((s*2)) $((s*2)) "$ICON_PNG" --out "$ICONSET/icon_${s}x${s}@2x.png" >/dev/null
done
iconutil -c icns "$ICONSET" -o "$APP_DIR/Contents/Resources/Lagebuch.icns"

# --- Info.plist ------------------------------------------------------------------------------
cat > "$APP_DIR/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key> <string>Lagebuch</string>
<key>CFBundleDisplayName</key> <string>Lagebuch</string>
<key>CFBundleIdentifier</key> <string>de.feuerwehr.lagebuch</string>
<key>CFBundleVersion</key> <string>$VERSION</string>
<key>CFBundleShortVersionString</key><string>$VERSION</string>
<key>CFBundleExecutable</key> <string>Feuerwehr.App</string>
<key>CFBundleIconFile</key> <string>Lagebuch</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>LSMinimumSystemVersion</key> <string>11.0</string>
<key>NSHighResolutionCapable</key> <true/>
</dict>
</plist>
PLIST

# --- ad-hoc sign -----------------------------------------------------------------------------
# No Developer ID, so sign with the ad-hoc identity "-". This is what lets the app launch at all
# once the user allows it in Gatekeeper; without any signature Gatekeeper reports "damaged".
codesign --force --deep --sign - "$APP_DIR"

# --- .dmg (drag-to-Applications) -------------------------------------------------------------
ln -s /Applications "$STAGE/Applications"
mkdir -p "$OUT_DIR"
DMG="$OUT_DIR/lagebuch-${VERSION}-macos-arm64.dmg"
hdiutil create -volname "Lagebuch" -srcfolder "$STAGE" -ov -format UDZO "$DMG" 1>&2
echo "$DMG"
Loading