Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8f68456
feat(webapi): support HTTP-only listener and configurable WebDAV probe
Papyszoo May 14, 2026
81a65ee
feat(asset-processor): add hardware-acceleration toggle
Papyszoo May 14, 2026
808c349
feat: add native desktop launcher (Electron)
Papyszoo May 14, 2026
728a809
ci: build and test native installers on all three platforms
Papyszoo May 14, 2026
85a7336
docs: document native installer alongside Docker
Papyszoo May 14, 2026
a9a3cda
feat(desktop): tray host with service status window
Papyszoo Jun 3, 2026
bac256d
feat(desktop): host configuration panel + headless-safe tray
Papyszoo Jun 3, 2026
1bd7910
feat(desktop-client): standalone Electron client app
Papyszoo Jun 3, 2026
d59ad8e
ci+docs: build desktop client installers and document host/client split
Papyszoo Jun 3, 2026
283ffb9
chore(desktop): drop unused loading screen
Papyszoo Jun 3, 2026
0f90ad6
feat(desktop): host checks GitHub Releases for updates
Papyszoo Jun 3, 2026
f668d43
ci: temporarily build installers on push to this branch
Papyszoo Jun 3, 2026
e8639e4
ci: fix cross-platform installer build (Node path export + Linux home…
Papyszoo Jun 3, 2026
10c6aa5
fix(asset-processor): use @napi-rs/canvas instead of node-canvas
Papyszoo Jun 4, 2026
e871b51
ci: set deb maintainer email and cancel superseded installer runs
Papyszoo Jun 4, 2026
729fb4a
fix(asset-processor): regenerate lockfile in sync for npm ci
Papyszoo Jun 4, 2026
9ea1003
ci: retry flaky Chocolatey PostgreSQL install on Windows
Papyszoo Jun 4, 2026
722965e
fix(desktop): e2e blockers on Linux/macOS/Windows
Papyszoo Jun 4, 2026
9e981f3
ci: bundle self-contained PostgreSQL from Maven Central (Zonky)
Papyszoo Jun 4, 2026
730c8b3
fix(desktop): resolve bundled PostgreSQL shared libs on Linux/macOS
Papyszoo Jun 4, 2026
242a3eb
fix(desktop): dereference PostgreSQL lib symlinks when staging bundle
Papyszoo Jun 4, 2026
057929a
fix(desktop): point WebApi storage paths at userData
Papyszoo Jun 4, 2026
f5b9c0b
fix(desktop): address CodeQL findings (cert validation, rate limiting)
Papyszoo Jun 4, 2026
a66a633
fix(asset-processor): keep lockfile devDeps pinned to main (Prettier …
Papyszoo Jun 4, 2026
8bb885f
test(e2e): de-flake pack-filter and demo sound checks
Papyszoo Jun 4, 2026
21a06a1
fix(webapi): create the database on first boot in native installs
Papyszoo Jun 5, 2026
0bb1306
test(installer): run the full Playwright e2e suite against the instal…
Papyszoo Jun 5, 2026
80a53c3
fix(desktop): stop edge server from eating proxied request bodies
Papyszoo Jun 5, 2026
0e4687a
fix(desktop): default to software rendering for thumbnails
Papyszoo Jun 5, 2026
4b7e035
diag(asset-processor): log render-template page/console/request errors
Papyszoo Jun 5, 2026
0859f68
fix(asset-processor): move import map before module scripts in render…
Papyszoo Jun 5, 2026
8f12814
fix(desktop): share a worker API key between WebApi and asset processor
Papyszoo Jun 5, 2026
73e0393
feat(desktop): default to a CPU-aware asset-worker pool
Papyszoo Jun 5, 2026
2081f94
test(e2e): make suite portable to a non-Docker deployment
Papyszoo Jun 5, 2026
169a022
ci(native): run full E2E suite on Windows and macOS too
Papyszoo Jun 5, 2026
b397f19
fix(asset-processor): render via Metal on macOS (swiftshader has no W…
Papyszoo Jun 5, 2026
72c16bc
ci(native): launch the Windows app without stream redirection
Papyszoo Jun 5, 2026
63f3bed
fix(asset-processor): render via D3D11 (WARP) on Windows software path
Papyszoo Jun 5, 2026
e5429cd
ci(native): skip the demo phase for the installed-app E2E run
Papyszoo Jun 5, 2026
8d28984
ci(native): raise installed-app E2E ceiling to 180m for the slow Wind…
Papyszoo Jun 5, 2026
681ddd4
ci(native): reduce browser parallelism and raise test timeout on host…
Papyszoo Jun 5, 2026
ce042e4
feat(desktop): configurable ports + data folder, and a safe Restart
Papyszoo Jun 6, 2026
6602884
feat(client): explicit host + port connection settings for LAN hosts
Papyszoo Jun 6, 2026
759deca
feat(desktop): one-click Desktop Client install (download + launch)
Papyszoo Jun 6, 2026
c2c821a
feat(desktop+client): in-app auto-update via electron-updater
Papyszoo Jun 6, 2026
238b5df
fix(native): single worker process + single-worker E2E to stop races/…
Papyszoo Jun 6, 2026
6dd19d8
fix(thumbnails): make job claiming atomic so multiple workers are safe
Papyszoo Jun 6, 2026
95661bc
ci(native): non-blocking Chromium UI phase on Windows + extra retries
Papyszoo Jun 6, 2026
e6dd30d
ci(native): drop the temporary feat/native-installer push trigger
Papyszoo Jun 6, 2026
25c5a08
ci(native): publish manual builds to a draft release (separate, unzip…
Papyszoo Jun 6, 2026
83c6271
fix(desktop): honest active-vs-desired config so port changes can't lie
Papyszoo Jun 7, 2026
653e0b7
fix(desktop): address review findings — real edge test, safe worker r…
Papyszoo Jun 11, 2026
c54b89d
fix(desktop): restart no longer hangs ~90s or shows green dots while …
Papyszoo Jun 11, 2026
ad3f857
feat(desktop): detect installed client, warn on app-port change, poli…
Papyszoo Jun 11, 2026
d1d4b33
feat(desktop): simplified status window for non-technical users
Papyszoo Jun 11, 2026
30c4b9b
ci(native): event-tiered installed-app testing (main smoke + nightly …
Papyszoo Jun 11, 2026
9cf7a25
feat(desktop): confirm before quitting the host
Papyszoo Jun 11, 2026
82fb0a9
feat(desktop): auto-pick free ports and migrate data on a folder change
Papyszoo Jun 11, 2026
d654b95
feat(desktop): inform about the leftover data folder instead of delet…
Papyszoo Jun 11, 2026
623674c
test(desktop): host integration harness — data survives a folder change
Papyszoo Jun 11, 2026
9f575fb
feat(desktop): opt-in network access so LAN clients can connect
Papyszoo Jun 11, 2026
207e5ad
fix(release): put the client on its own update channel to avoid feed …
Papyszoo Jun 11, 2026
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
362 changes: 362 additions & 0 deletions .github/workflows/native-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
name: Native Installers

on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: write

jobs:
build-installers:
name: Build ${{ matrix.name }} Installer
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Windows
os: windows-latest
rid: win-x64
electronArch: x64
artifactGlob: src/desktop/dist/*.exe
- name: macOS
os: macos-14
rid: osx-arm64
electronArch: arm64
artifactGlob: src/desktop/dist/*.dmg
- name: Linux
os: ubuntu-latest
rid: linux-x64
electronArch: x64
artifactGlob: |
src/desktop/dist/*.AppImage
src/desktop/dist/*.deb

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x

- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20

- name: Export Node runtime path
run: node -e "const fs=require('fs'); fs.appendFileSync(process.env.GITHUB_ENV, `MODELIBR_NODE_EXECUTABLE=${process.execPath}\n`)"

- name: Prepare build folders
run: node -e "const fs=require('fs'); fs.mkdirSync('src/desktop/build-input/webapi',{recursive:true})"

- name: Install frontend dependencies
working-directory: src/frontend
run: npm ci

- name: Build frontend bundle for native runtime
working-directory: src/frontend
run: npm run build
env:
VITE_API_BASE_URL: /api

- name: Install asset processor runtime dependencies
working-directory: src/asset-processor
env:
PUPPETEER_CACHE_DIR: ${{ github.workspace }}/src/asset-processor/.cache/puppeteer
run: |
npm ci --omit=dev
npx puppeteer browsers install chrome

- name: Publish WebApi for native runtime
run: >-
dotnet publish src/WebApi/WebApi.csproj
-c Release
-r ${{ matrix.rid }}
--self-contained true
-o ${{ github.workspace }}/src/desktop/build-input/webapi

- name: Install PostgreSQL runtime (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
choco install postgresql --params '"/Password:ModelibrBuild123!"' --no-progress -y
$pgRoot = Get-ChildItem 'C:\Program Files\PostgreSQL' | Sort-Object Name -Descending | Select-Object -First 1
if (-not $pgRoot) { throw 'Embedded PostgreSQL runtime not found' }
"MODELIBR_POSTGRES_RUNTIME_DIR=$($pgRoot.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8

- name: Install PostgreSQL runtime (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
brew install postgresql@16
echo "MODELIBR_POSTGRES_RUNTIME_DIR=$(brew --prefix postgresql@16)" >> "$GITHUB_ENV"

- name: Install PostgreSQL runtime (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y postgresql-16
POSTGRES_RUNTIME_DIR="$GITHUB_WORKSPACE/src/desktop/build-input/postgres"
mkdir -p "$POSTGRES_RUNTIME_DIR"
cp -R /usr/lib/postgresql/16/bin "$POSTGRES_RUNTIME_DIR/bin"
cp -R /usr/lib/postgresql/16/lib "$POSTGRES_RUNTIME_DIR/lib"
cp -R /usr/share/postgresql/16 "$POSTGRES_RUNTIME_DIR/share"
echo "MODELIBR_POSTGRES_RUNTIME_DIR=$POSTGRES_RUNTIME_DIR" >> "$GITHUB_ENV"

- name: Install desktop packaging dependencies
working-directory: src/desktop
run: npm ci

- name: Build native installers
working-directory: src/desktop
env:
MODELIBR_WEBAPI_PUBLISH_DIR: ${{ github.workspace }}/src/desktop/build-input/webapi
run: npm run dist -- --${{ matrix.electronArch }}

- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: native-installers-${{ matrix.name }}
path: ${{ matrix.artifactGlob }}

- name: Upload release assets
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.artifactGlob }}

test-installers:
name: Test ${{ matrix.name }} Installer
needs: build-installers
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: Windows
os: windows-latest
- name: macOS
os: macos-14
- name: Linux
os: ubuntu-latest

steps:
- name: Download installer artifact
uses: actions/download-artifact@v4
with:
name: native-installers-${{ matrix.name }}
path: installer

# ───── Install ─────
- name: Install (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y xvfb
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
sudo dpkg -i installer/*.deb || sudo apt-get install -f -y

- name: Install (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
DMG=$(ls installer/*.dmg | head -n 1)
hdiutil attach "$DMG" -mountpoint /Volumes/Modelibr -nobrowse -quiet
cp -R "/Volumes/Modelibr/Modelibr.app" /Applications/
hdiutil detach /Volumes/Modelibr -quiet
# Strip quarantine attribute — installer isn't notarized in CI
sudo xattr -dr com.apple.quarantine /Applications/Modelibr.app

- name: Install (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$installer = Get-ChildItem installer/*.exe | Select-Object -First 1
if (-not $installer) { throw "Installer not found in artifact" }
Start-Process -Wait -FilePath $installer.FullName -ArgumentList '/S'

# ───── Start app ─────
- name: Start app (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
APP_BIN=$(find /opt -type f -executable \( -name 'modelibr*' -o -name 'Modelibr*' \) 2>/dev/null | head -n 1)
if [ -z "$APP_BIN" ]; then
echo "Could not locate installed binary under /opt"
ls -la /opt || true
exit 1
fi
echo "Starting $APP_BIN"
Xvfb :99 -screen 0 1280x1024x24 > /tmp/xvfb.log 2>&1 &
export DISPLAY=:99
nohup "$APP_BIN" --no-sandbox > /tmp/modelibr.log 2>&1 &
disown || true

- name: Start app (macOS)
if: runner.os == 'macOS'
shell: bash
run: open /Applications/Modelibr.app

- name: Start app (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$candidates = @(
"$env:LOCALAPPDATA\Programs\Modelibr\Modelibr.exe",
"$env:PROGRAMFILES\Modelibr\Modelibr.exe"
)
$exe = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $exe) { throw "Modelibr.exe not found in expected locations" }
Start-Process -FilePath $exe

# ───── Wait for HTTP server ─────
- name: Wait for app ready
shell: bash
run: |
for i in $(seq 1 60); do
if curl -sf http://127.0.0.1:3010 -o /dev/null; then
echo "App responding after ${i} attempts"
exit 0
fi
echo "Waiting for app... ($i/60)"
sleep 3
done
echo "App did not become ready within 180s"
exit 1

# ───── Smoke tests ─────
- name: Smoke — frontend served
shell: bash
run: curl -sf http://127.0.0.1:3010 -o /dev/null

- name: Smoke — native runtime API
shell: bash
run: |
response=$(curl -sf http://127.0.0.1:3010/api/native/runtime)
echo "Runtime snapshot: $response"
echo "$response" | grep -q publicAppUrl
echo "$response" | grep -q workerHealthUrls
echo "$response" | grep -q dataDirectory

- name: Smoke — WebApi health (proxied)
shell: bash
run: |
# /api/* is rewritten to /* by the edge server, so this hits WebApi /health
curl -sf http://127.0.0.1:3010/api/health -o /dev/null

# ───── Stop app ─────
- name: Stop app (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
pkill -f -i modelibr || true
sleep 5

- name: Stop app (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
osascript -e 'quit app "Modelibr"' || true
sleep 3
pkill -f Modelibr || true
sleep 3

- name: Stop app (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Stop-Process -Name Modelibr -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5

# ───── Uninstall ─────
- name: Uninstall (Linux)
if: runner.os == 'Linux'
run: sudo dpkg -r modelibr-desktop || sudo dpkg -r modelibr

- name: Uninstall (macOS)
if: runner.os == 'macOS'
run: sudo rm -rf /Applications/Modelibr.app

- name: Uninstall (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$uninst = Get-ChildItem -Path "$env:LOCALAPPDATA\Programs\Modelibr\Uninstall*.exe","$env:PROGRAMFILES\Modelibr\Uninstall*.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $uninst) { throw "Uninstaller not found" }
Start-Process -Wait -FilePath $uninst.FullName -ArgumentList '/S'
Start-Sleep -Seconds 5

# ───── Verify removal ─────
- name: Verify app binary removed (Linux)
if: runner.os == 'Linux'
run: |
if [ -e /opt/Modelibr ] || [ -e /opt/modelibr ] || [ -e /opt/modelibr-desktop ]; then
echo "App directory still present after uninstall"
ls -la /opt
exit 1
fi

- name: Verify app binary removed (macOS)
if: runner.os == 'macOS'
run: |
if [ -e /Applications/Modelibr.app ]; then
echo "Modelibr.app still present after uninstall"
exit 1
fi

- name: Verify app binary removed (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$leftover = @(
"$env:LOCALAPPDATA\Programs\Modelibr\Modelibr.exe",
"$env:PROGRAMFILES\Modelibr\Modelibr.exe"
) | Where-Object { Test-Path $_ }
if ($leftover) { throw "App binary still present: $leftover" }

# ───── Verify user data preserved (by design) ─────
- name: Verify user data preserved (Linux)
if: runner.os == 'Linux'
run: |
if [ ! -d "$HOME/.config/Modelibr" ]; then
echo "Warning: user data directory was not preserved"
exit 1
fi

- name: Verify user data preserved (macOS)
if: runner.os == 'macOS'
run: |
if [ ! -d "$HOME/Library/Application Support/Modelibr" ]; then
echo "Warning: user data directory was not preserved"
exit 1
fi

- name: Verify user data preserved (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
if (-not (Test-Path "$env:APPDATA\Modelibr")) {
throw "User data directory was not preserved"
}

# ───── Capture logs on failure ─────
- name: Upload diagnostics on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-diagnostics-${{ matrix.name }}
path: |
/tmp/modelibr.log
/tmp/xvfb.log
if-no-files-found: ignore
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

Modelibr is a self-hosted game asset library. It keeps **models**, **texture sets**, **environment maps**, **sprites**, and **sounds** in one place, lets you preview them in the browser, and helps you organize them into **projects** and reusable **packs**.

Native installers for Windows, macOS, and Linux are published in the GitHub Releases page. They bundle the local web UI, WebApi, asset processor, and PostgreSQL so non-technical users can run Modelibr without setting up Docker.

**[Main Site](https://papyszoo.github.io/Modelibr/)** | **[Documentation](https://papyszoo.github.io/Modelibr/docs)** | **[Live Demo](https://papyszoo.github.io/Modelibr/demo/)** | **[Discord](https://discord.gg/KgwgTDVP3F)** | **[GitHub Issues](https://github.com/Papyszoo/Modelibr/issues)**

The live demo stores its data in your browser, so what you add there is visible only to you.
Expand All @@ -33,6 +35,17 @@ The live demo stores its data in your browser, so what you add there is visible

## Quick start

### Native installers

Download the installer for your platform from the GitHub Releases page and run it.

- The native build bundles the local database and worker runtime.
- The app is exposed on a local configurable port.
- WebDAV is exposed from the same local runtime.
- Worker process count and GPU acceleration can be adjusted from the in-app `Settings > Native Runtime` section.

### Docker

```bash
git clone https://github.com/Papyszoo/Modelibr.git
cd Modelibr
Expand All @@ -48,6 +61,7 @@ Open **https://localhost:3010** in your browser. The first visit uses a self-sig

- WebDAV gives Modelibr a more file-browser style workflow, which is useful when you want the library to sit closer to art-pipeline tools.
- Environment maps are exposed through WebDAV globally and inside packs/projects, alongside the rest of the library.
- Native installers expose WebDAV through the local launcher port by default.
- Blender-related flows are part of the repository, and Blender CLI can be downloaded at runtime from the Settings page when you want that workflow.
- If you want more detail, start with the [main site](https://papyszoo.github.io/Modelibr/) and the [documentation](https://papyszoo.github.io/Modelibr/docs).

Expand Down
Loading
Loading