Compare commits

...

3 commits

Author SHA1 Message Date
03991fdea5 Update window title handling and ensure consistent version retrieval in configuration
Some checks failed
Tests & Quality Checks / Test on Python 3.11 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.11-1 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12-1 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.10 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.11-2 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12-2 (push) Has been cancelled
Tests & Quality Checks / Build Artifacts (push) Has been cancelled
Tests & Quality Checks / Build Artifacts-1 (push) Has been cancelled
2026-02-20 12:36:24 +01:00
e0b316fe65 Update README.md to clarify Windows and macOS support details and enhance installer instructions 2026-02-20 12:32:18 +01:00
9d39ed8201 Enhance release script to support optional executable uploads and improve error handling
- Added -SkipExe switch to allow skipping the upload of the executable and its checksum.
- Updated paths for the executable and checksum files to reflect new directory structure.
- Improved file existence checks with warnings instead of errors for optional artifacts.
- Enhanced release body to include checksum information conditionally based on executable upload.
- Refined upload process for MSI and executable, including better error handling and logging.
2026-02-20 12:30:02 +01:00
8 changed files with 2993 additions and 2981 deletions

View file

@ -19,7 +19,7 @@ WebDrop Bridge embeds a web application in a Qt container with full filesystem a
## Features ## Features
- ✅ **Qt-based Architecture** - Native Windows & macOS support via PySide6 - ✅ **Qt-based Architecture** - Professional Windows support via PySide6 (macOS support planned)
- ✅ **Embedded Web App** - QtWebEngine provides Chromium without browser limitations - ✅ **Embedded Web App** - QtWebEngine provides Chromium without browser limitations
- ✅ **Drag Interception** - Converts text paths to native file operations - ✅ **Drag Interception** - Converts text paths to native file operations
- ✅ **Path Whitelist** - Security-conscious file system access control - ✅ **Path Whitelist** - Security-conscious file system access control
@ -35,7 +35,7 @@ WebDrop Bridge embeds a web application in a Qt container with full filesystem a
### Requirements ### Requirements
- Python 3.10+ - Python 3.10+
- Windows 10/11 or macOS 12+ - Windows 10/11
- 200 MB disk space (includes Chromium from PyInstaller) - 200 MB disk space (includes Chromium from PyInstaller)
### Installation from Source ### Installation from Source
@ -47,7 +47,6 @@ cd webdrop-bridge
# Create and activate virtual environment # Create and activate virtual environment
python -m venv venv python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate.ps1 # Windows (PowerShell) # venv\Scripts\activate.ps1 # Windows (PowerShell)
# venv\Scripts\activate.bat # Windows (cmd.exe) # venv\Scripts\activate.bat # Windows (cmd.exe)
@ -71,8 +70,7 @@ pytest tests -v
tox tox
# Build installers # Build installers
python build/scripts/build_windows.py # Windows MSI python build/scripts/build_windows.py --msi # Windows MSI
bash build/scripts/build_macos.sh # macOS DMG
``` ```
## Project Structure ## Project Structure
@ -92,7 +90,6 @@ webdrop-bridge/
│ └── conftest.py # Pytest configuration │ └── conftest.py # Pytest configuration
├── build/ ├── build/
│ ├── windows/ # Windows-specific build configs │ ├── windows/ # Windows-specific build configs
│ ├── macos/ # macOS-specific build configs
│ └── scripts/ # Build automation scripts │ └── scripts/ # Build automation scripts
├── webapp/ # Embedded web application ├── webapp/ # Embedded web application
├── resources/ ├── resources/
@ -225,48 +222,27 @@ See [CHANGELOG.md](CHANGELOG.md) for release notes.
### Windows MSI Installer ### Windows MSI Installer
```bash ```bash
# Simple build (creates standalone .exe) # Build with MSI installer (recommended)
python build/scripts/build_windows.py
# Build with MSI installer
python build/scripts/build_windows.py --msi python build/scripts/build_windows.py --msi
# Build and sign executable # Build with code signing (requires certificate)
python build/scripts/build_windows.py --sign python build/scripts/build_windows.py --msi --code-sign
``` ```
Output: Output:
- Standalone executable: `build/dist/windows/WebDropBridge.exe` (~195 MB) - Portable executable: `build/dist/windows/WebDropBridge/WebDropBridge.exe` (~195 MB)
- Optional MSI installer: `build/dist/windows/WebDropBridge.msi` - Professional MSI installer: `build/dist/windows/WebDropBridge-{version}-Setup.msi`
- SHA256 checksum: `build/dist/windows/WebDropBridge.exe.sha256` - SHA256 checksum: `build/dist/windows/WebDropBridge/WebDropBridge.exe.sha256`
### macOS DMG Installer **Note on macOS**: Build scripts exist for macOS (DMG generation), but have never been built or tested. macOS support is theoretical at this point. The Qt/PySide6 architecture should support macOS, but platform-specific testing and validation would be required.
```bash
# Build DMG (requires macOS)
bash build/scripts/build_macos.sh
# Build with code signing
SIGN_APP=true bash build/scripts/build_macos.sh
# Build with notarization
NOTARIZE_APP=true bash build/scripts/build_macos.sh
```
Output:
- DMG installer: `build/dist/macos/WebDropBridge.dmg`
- App bundle: `build/dist/macos/WebDropBridge.app`
### Creating Releases ### Creating Releases
For Forgejo/GitHub releases: For Forgejo/GitHub releases:
```bash ```bash
# Windows - Create release with executable # Windows - Create release with MSI installer
powershell -ExecutionPolicy Bypass -File build/scripts/create_release.ps1 powershell -ExecutionPolicy Bypass -File build/scripts/create_release.ps1
# macOS - Create release with DMG
bash build/scripts/create_release.sh
``` ```
## Development Workflow ## Development Workflow
@ -314,9 +290,10 @@ bash build/scripts/create_release.sh
| Platform | Version | Status | Notes | | Platform | Version | Status | Notes |
|----------|---------|--------|-------| |----------|---------|--------|-------|
| Windows | 10, 11 | ✅ Full | Tested on x64 | | Windows | 10, 11 | ✅ Full | Tested on x64, MSI installer support |
| macOS | 12, 13, 14 | ✅ Full | Intel & Apple Silicon | | macOS | 12+ | ⚠️ **Untested** | Possible via Qt/PySide6, but never built or tested. Theoretical support only. |
| Linux | Ubuntu 22.04+ | ⚠️ Partial | Limited testing |
**Note**: WebDrop Bridge is currently developed and tested exclusively on Windows. While the Qt/PySide6 framework supports macOS, we cannot guarantee functionality without actual macOS testing and validation. Contributions for macOS support validation are welcome.
## Contributing ## Contributing

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:ui="http://schemas.microsoft.com/wix/2010/ui"> xmlns:ui="http://schemas.microsoft.com/wix/2010/ui">
<Product Id="*" Name="WebDrop Bridge" Language="1033" Version="0.5.0" <Product Id="*" Name="WebDrop Bridge" Language="1033" Version="0.6.0"
Manufacturer="HIM-Tools" Manufacturer="HIM-Tools"
UpgradeCode="12345678-1234-1234-1234-123456789012"> UpgradeCode="12345678-1234-1234-1234-123456789012">

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -16,11 +16,12 @@ param(
[switch]$ClearCredentials, [switch]$ClearCredentials,
[switch]$SkipExe,
[string]$ForgejoUrl = "https://git.him-tools.de", [string]$ForgejoUrl = "https://git.him-tools.de",
[string]$Repo = "HIM-public/webdrop-bridge", [string]$Repo = "HIM-public/webdrop-bridge",
[string]$ExePath = "build\dist\windows\WebDropBridge.exe", [string]$ExePath = "build\dist\windows\WebDropBridge\WebDropBridge.exe",
[string]$ChecksumPath = "build\dist\windows\WebDropBridge.exe.sha256", [string]$ChecksumPath = "build\dist\windows\WebDropBridge\WebDropBridge.exe.sha256"
[string]$MsiPath = "build\dist\windows\WebDropBridge-1.0.0-Setup.msi"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@ -107,32 +108,44 @@ if (-not $Version) {
Write-Host "Using version: $Version" -ForegroundColor Green Write-Host "Using version: $Version" -ForegroundColor Green
} }
# Verify files exist # Define MSI path with resolved version
if (-not (Test-Path $ExePath)) { $MsiPath = Join-Path $projectRoot "build\dist\windows\WebDropBridge-$Version-Setup.msi"
Write-Host "ERROR: Executable not found at $ExePath" -ForegroundColor Red
exit 1 # Verify files exist (exe/checksum optional, MSI required)
if (-not $SkipExe) {
if (-not (Test-Path $ExePath)) {
Write-Host "WARNING: Executable not found at $ExePath" -ForegroundColor Yellow
Write-Host " Use -SkipExe flag to skip exe upload" -ForegroundColor Gray
$SkipExe = $true
}
if (-not $SkipExe -and -not (Test-Path $ChecksumPath)) {
Write-Host "WARNING: Checksum file not found at $ChecksumPath" -ForegroundColor Yellow
Write-Host " Exe will not be uploaded" -ForegroundColor Gray
$SkipExe = $true
}
} }
if (-not (Test-Path $ChecksumPath)) { # MSI is the primary release artifact
Write-Host "ERROR: Checksum file not found at $ChecksumPath" -ForegroundColor Red if (-not (Test-Path $MsiPath)) {
Write-Host "ERROR: MSI installer not found at $MsiPath" -ForegroundColor Red
Write-Host "Please build with MSI support:" -ForegroundColor Yellow
Write-Host " python build\scripts\build_windows.py --msi" -ForegroundColor Cyan
exit 1 exit 1
} }
# MSI is optional (only available on Windows after build)
$hasMsi = Test-Path $MsiPath
Write-Host "Creating WebDropBridge $Version release on Forgejo..." -ForegroundColor Cyan Write-Host "Creating WebDropBridge $Version release on Forgejo..." -ForegroundColor Cyan
# Get file info # Get file info
$exeSize = (Get-Item $ExePath).Length / 1MB $msiSize = (Get-Item $MsiPath).Length / 1MB
$checksum = Get-Content $ChecksumPath -Raw Write-Host "Primary Artifact: WebDropBridge-$Version-Setup.msi ($([math]::Round($msiSize, 2)) MB)"
Write-Host "File: WebDropBridge.exe ($([math]::Round($exeSize, 2)) MB)" if (-not $SkipExe) {
if ($hasMsi) { $exeSize = (Get-Item $ExePath).Length / 1MB
$msiSize = (Get-Item $MsiPath).Length / 1MB $checksum = Get-Content $ChecksumPath -Raw
Write-Host "File: WebDropBridge-1.0.0-Setup.msi ($([math]::Round($msiSize, 2)) MB)" Write-Host "Optional Artifact: WebDropBridge.exe ($([math]::Round($exeSize, 2)) MB)"
Write-Host " Checksum: $($checksum.Substring(0, 16))..."
} }
Write-Host "Checksum: $($checksum.Substring(0, 16))..."
# Create basic auth header # Create basic auth header
$auth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${ForgejoUser}:${ForgejoPW}")) $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${ForgejoUser}:${ForgejoPW}"))
@ -146,10 +159,17 @@ $headers = @{
Write-Host "`nCreating release v$Version..." -ForegroundColor Yellow Write-Host "`nCreating release v$Version..." -ForegroundColor Yellow
$releaseUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases" $releaseUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases"
# Build release body with checksum info if exe is being uploaded
$releaseBody = "WebDropBridge v$Version`n`n**Release Artifacts:**`n- MSI Installer (Windows Setup)`n"
if (-not $SkipExe) {
$checksum = Get-Content $ChecksumPath -Raw
$releaseBody += "- Portable Executable`n`n**Checksum:**`n$checksum`n"
}
$releaseData = @{ $releaseData = @{
tag_name = "v$Version" tag_name = "v$Version"
name = "WebDropBridge v$Version" name = "WebDropBridge v$Version"
body = "WebDropBridge v$Version`n`nChecksum: $checksum" body = $releaseBody
draft = $false draft = $false
prerelease = $false prerelease = $false
} | ConvertTo-Json } | ConvertTo-Json
@ -172,56 +192,14 @@ catch {
exit 1 exit 1
} }
# Step 2: Upload executable as asset using curl # Setup curl authentication
Write-Host "Uploading executable asset..." -ForegroundColor Yellow $curlAuth = "$ForgejoUser`:$ForgejoPW"
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
# Step 2: Upload MSI installer as primary artifact
Write-Host "`nUploading MSI installer (primary artifact)..." -ForegroundColor Yellow
try { try {
$curlAuth = "$ForgejoUser`:$ForgejoPW"
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
$response = curl.exe -s -X POST `
-u $curlAuth `
-F "attachment=@$ExePath" `
$uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "ERROR uploading executable: $response" -ForegroundColor Red
exit 1
}
Write-Host "[OK] Executable uploaded" -ForegroundColor Green
}
catch {
Write-Host "ERROR uploading executable: $_" -ForegroundColor Red
exit 1
}
# Step 3: Upload checksum as asset using curl
Write-Host "Uploading checksum asset..." -ForegroundColor Yellow
try {
$response = curl.exe -s -X POST `
-u $curlAuth `
-F "attachment=@$ChecksumPath" `
$uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "ERROR uploading checksum: $response" -ForegroundColor Red
exit 1
}
Write-Host "[OK] Checksum uploaded" -ForegroundColor Green
}
catch {
Write-Host "ERROR uploading checksum: $_" -ForegroundColor Red
exit 1
}
# Step 4: Upload MSI as asset (if available)
if ($hasMsi) {
Write-Host "Uploading MSI installer asset..." -ForegroundColor Yellow
try {
$response = curl.exe -s -X POST ` $response = curl.exe -s -X POST `
-u $curlAuth ` -u $curlAuth `
-F "attachment=@$MsiPath" ` -F "attachment=@$MsiPath" `
@ -232,11 +210,52 @@ if ($hasMsi) {
exit 1 exit 1
} }
Write-Host "[OK] MSI uploaded" -ForegroundColor Green Write-Host "[OK] MSI installer uploaded" -ForegroundColor Green
} }
catch { catch {
Write-Host "ERROR uploading MSI: $_" -ForegroundColor Red Write-Host "ERROR uploading MSI: $_" -ForegroundColor Red
exit 1 exit 1
}
# Step 3: Upload executable as optional artifact (if available)
if (-not $SkipExe) {
Write-Host "`nUploading executable (optional portable version)..." -ForegroundColor Yellow
try {
$response = curl.exe -s -X POST `
-u $curlAuth `
-F "attachment=@$ExePath" `
$uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "WARNING: Could not upload executable: $response" -ForegroundColor Yellow
}
else {
Write-Host "[OK] Executable uploaded" -ForegroundColor Green
}
}
catch {
Write-Host "WARNING: Could not upload executable: $_" -ForegroundColor Yellow
}
# Step 4: Upload checksum as asset
Write-Host "Uploading checksum..." -ForegroundColor Yellow
try {
$response = curl.exe -s -X POST `
-u $curlAuth `
-F "attachment=@$ChecksumPath" `
$uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "WARNING: Could not upload checksum: $response" -ForegroundColor Yellow
}
else {
Write-Host "[OK] Checksum uploaded" -ForegroundColor Green
}
}
catch {
Write-Host "WARNING: Could not upload checksum: $_" -ForegroundColor Yellow
} }
} }

View file

@ -137,7 +137,22 @@ class Config:
log_file = Config.get_default_log_path() log_file = Config.get_default_log_path()
app_name = data.get("app_name", "WebDrop Bridge") app_name = data.get("app_name", "WebDrop Bridge")
window_title = data.get("window_title", f"{app_name} v{__version__}") stored_window_title = data.get("window_title", "")
# Regenerate default window titles on version upgrade
# If the stored title matches the pattern "{app_name} v{version}", regenerate it
# with the current version. This ensures the title updates automatically on upgrades.
import re
version_pattern = re.compile(rf"^{re.escape(app_name)}\s+v[\d.]+$")
if stored_window_title and version_pattern.match(stored_window_title):
# Detected a default-pattern title with old version, regenerate
window_title = f"{app_name} v{__version__}"
elif stored_window_title:
# Custom window title, keep it as-is
window_title = stored_window_title
else:
# No window title specified, use default
window_title = f"{app_name} v{__version__}"
return cls( return cls(
app_name=app_name, app_name=app_name,
@ -178,12 +193,10 @@ class Config:
# Extract and validate configuration values # Extract and validate configuration values
app_name = os.getenv("APP_NAME", "WebDrop Bridge") app_name = os.getenv("APP_NAME", "WebDrop Bridge")
# Version comes from __init__.py (lazy import to avoid circular imports) # Version always comes from __init__.py for consistency
if not os.getenv("APP_VERSION"):
from webdrop_bridge import __version__ from webdrop_bridge import __version__
app_version = __version__ app_version = __version__
else:
app_version = os.getenv("APP_VERSION")
log_level = os.getenv("LOG_LEVEL", "INFO").upper() log_level = os.getenv("LOG_LEVEL", "INFO").upper()
log_file_str = os.getenv("LOG_FILE", None) log_file_str = os.getenv("LOG_FILE", None)
allowed_roots_str = os.getenv("ALLOWED_ROOTS", "Z:/,C:/Users/Public") allowed_roots_str = os.getenv("ALLOWED_ROOTS", "Z:/,C:/Users/Public")

View file

@ -52,8 +52,11 @@ class TestConfigFromEnv:
# Load config (env vars from file, not system) # Load config (env vars from file, not system)
config = Config.from_env(str(env_file)) config = Config.from_env(str(env_file))
# Version always comes from package __init__.py, not from APP_VERSION env var
from webdrop_bridge import __version__
assert config.app_name == "TestApp" assert config.app_name == "TestApp"
assert config.app_version == "2.0.0" assert config.app_version == __version__ # Uses package version, not env var
assert config.log_level == "DEBUG" assert config.log_level == "DEBUG"
assert config.allowed_roots == [root1.resolve(), root2.resolve()] assert config.allowed_roots == [root1.resolve(), root2.resolve()]
assert config.allowed_urls == ["example.com", "*.test.org"] assert config.allowed_urls == ["example.com", "*.test.org"]