Skip to content

[Feat] Windows Service / Tray / Installer#3

Draft
bryanchriswhite wants to merge 84 commits intoCypherpunk-Labs:v0.1.3_g4ffrom
Episk-pos:feat/windows-installer
Draft

[Feat] Windows Service / Tray / Installer#3
bryanchriswhite wants to merge 84 commits intoCypherpunk-Labs:v0.1.3_g4ffrom
Episk-pos:feat/windows-installer

Conversation

@bryanchriswhite
Copy link

Windows Installer & Service Infrastructure

Summary

This PR introduces a complete Windows installation and service management infrastructure for PinShare, including:

  • Windows Service Wrapper (pinsharesvc) - Manages IPFS daemon and PinShare backend as a Windows service
  • System Tray Application (pinshare-tray) - User-friendly tray icon for service status monitoring and control
  • WiX 6 Installer - Professional MSI installer with configuration wizard
  • Build Infrastructure - Batch scripts and PowerShell for building all Windows components

Key Changes

New Components

  • cmd/pinsharesvc/ - Windows service that:

    • Manages IPFS daemon lifecycle
    • Runs PinShare backend API
    • Handles graceful shutdown and process cleanup
    • Loads configuration from registry/config files
  • cmd/pinshare-tray/ - System tray application that:

    • Displays real-time service status (Running/Stopped/Starting)
    • Shows IPFS and PinShare component health
    • Provides Start/Stop/Restart controls (with UAC elevation)
    • Uses Windows SCM API directly for status checks (no process spawning)
    • Temporarily disables "Open PinShare UI" menu item (commented for future re-enablement)
  • installer/ - WiX 6 MSI installer with:

    • Configuration wizard for upload directory and settings
    • Service installation and automatic startup
    • Proper uninstallation cleanup
    • GPLv3 license display

Build System

  • build-windows.bat / build-windows.ps1 - Automated build scripts
  • installer/build-wix6.bat - WiX 6 installer compilation
  • GitHub Actions workflow for CI/CD

Configuration & P2P Improvements

  • Feature flags for relay and transport options
  • IPv6 localhost publishing fix
  • Expanded allowed file types (CAD formats)
  • /api/health endpoint for service health checks

Technical Notes

  • Tray app uses golang.org/x/sys/windows SCM API with minimal permissions (SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) to avoid UI flickering from process spawning
  • Service control operations use PowerShell UAC elevation (-Verb RunAs)
  • Status polling occurs every 10 seconds via in-process Windows API calls

Documentation

  • WINDOWS_SERVICE.md - Service architecture documentation
  • docs/windows/ - Build, testing, and troubleshooting guides
  • INSTALLER-QUICKSTART.md - Quick start for building installer

Test Plan

  • Build all components with build-windows.bat
  • Install MSI on clean Windows machine
  • Verify service starts automatically after install
  • Verify tray app shows correct status
  • Test Start/Stop/Restart from tray menu
  • Verify clean uninstallation
  • Test Windows 11

claude and others added 30 commits December 3, 2025 09:50
Implements a complete Windows service wrapper for PinShare with:

- Native Windows service using golang.org/x/sys/windows/svc
- Auto-start on Windows boot
- Process management for IPFS and PinShare backends
- Health monitoring with automatic restart (max 3 attempts)
- Embedded UI server serving React static files
- Reverse proxy for API requests (/api -> backend, /ipfs-api -> IPFS)
- Configuration via Windows Registry or JSON file
- Windows Event Log integration
- Graceful shutdown handling with context cancellation

- System tray icon with context menu
- Service control (Start/Stop/Restart)
- Status monitoring (Running/Stopped/Starting/etc.)
- One-click UI access via default browser
- Log directory access
- Auto-start with Windows (via installer)

- Professional MSI package
- Installs binaries to Program Files
- Creates data directories in ProgramData
- Registers Windows service with auto-start
- Sets up registry configuration
- Adds tray app to startup folder
- Creates Start Menu shortcuts
- Configures service recovery options
- Automated build via build.bat

- Cross-compilation support (Linux/macOS -> Windows)
- Automated builds for all components
- IPFS Kubo binary download
- React UI production build
- Installer generation
- Clean targets

- Complete installation guide (docs/windows/README.md)
- Detailed build instructions (docs/windows/BUILD.md)
- Troubleshooting and configuration
- Implementation overview (WINDOWS_SERVICE.md)

- Native execution (no Docker required)
- Service wraps IPFS daemon and PinShare backend
- Health checker monitors both components (30s intervals)
- Embedded UI server on localhost:8888
- All state in C:\ProgramData\PinShare
- Binaries in C:\Program Files\PinShare

- Primary: Windows Registry (HKLM\SOFTWARE\PinShare)
- Fallback: JSON file (C:\ProgramData\PinShare\config.json)
- Configurable ports, paths, feature flags
- Organization and group names

Dependencies added:
- github.com/getlantern/systray for system tray
- golang.org/x/sys/windows for Windows service APIs
…ucture

## Critical Security Fixes

1. **Secure encryption key generation** (config.go)
   - Replaced hardcoded placeholder key with crypto/rand generated key
   - Uses 32 cryptographically secure random bytes
   - Panics on failure as this is critical for security

2. **Fix health checker race condition** (service.go)
   - Initialize health checker before starting IPFS/PinShare processes
   - Fixes nil pointer dereference in waitForIPFS() and waitForPinShare()
   - Health checker now available during startup health checks

3. **Replace WiX installer placeholder GUID** (Product.wxs)
   - Generated proper UpgradeCode GUID: 24D1704E-0ACD-4370-BCF9-ED664A546060
   - Required for Windows installer upgrades to work correctly

4. **Fix debug.New() return value handling** (service.go)
   - Corrected openEventLog() to match actual API (returns single value)

## Testing Infrastructure

Added comprehensive Windows testing automation:

- **Test-PinShare.ps1**: PowerShell automation script with modular test suites
  - Build, Service, Health, API, UI, Integration, All, Cleanup suites
  - Automated service installation and lifecycle testing
  - Health check verification for IPFS, PinShare API, and UI
  - Detailed logging with JSON results export

- **TESTING.md**: Complete testing guide and strategy
  - Quick start and usage instructions
  - Manual testing procedures and troubleshooting guides
  - Development workflow patterns for rapid iteration
  - CI/CD integration examples (GitHub Actions, Azure DevOps)
  - Performance testing strategies

These changes address critical security vulnerabilities identified in code review
and provide the tooling needed for rapid Windows development iteration.
Updates the Windows installer to use modern WiX 6 tooling:

## Changes

### New Files
- **Package.wxs**: WiX 6 format installer definition
  - Uses new namespace: http://wixtoolset.org/schemas/v4/wxs
  - <Package> element instead of <Product>
  - <StandardDirectory> for system folders
  - Bitness="always64" on all components
  - FileRef instead of FileKey for custom actions

- **PinShare.wixproj**: MSBuild SDK-style project
  - Uses WixToolset.Sdk/6.0.2
  - Built-in HarvestDirectory for auto-harvesting UI files
  - No need for manual heat.exe calls

- **build-wix6.bat / build-wix6.sh**: Modern build scripts
  - Checks for .NET SDK and WiX tool
  - Auto-installs WiX if missing
  - Uses 'dotnet build' instead of candle/light

- **README-WIX6.md**: Comprehensive WiX 6 documentation

## Why WiX 6?

WiX 3 is deprecated. WiX 6 is the current version with:
- .NET tool installation: dotnet tool install --global wix
- MSBuild integration: dotnet build PinShare.wixproj
- Simplified syntax and auto-harvesting
- Better CI/CD integration

## Building

Prerequisites:
1. .NET SDK 6+ (dotnet.microsoft.com)
2. WiX tool: dotnet tool install --global wix

Build:
```bash
make -f Makefile.windows windows-all
cd installer
build-wix6.bat  # or ./build-wix6.sh on Linux
```

Output: bin/Release/PinShare-Setup.msi

## Migration Notes

- Old build.bat (WiX 3) is kept for reference but deprecated
- Package.wxs replaces Product.wxs with WiX 6 syntax
- All custom actions and components preserved
- Service installation/startup logic unchanged
Provides alternative build methods for Windows users:

New Files:
- build-windows.ps1: PowerShell build script with interactive prompts
- build-windows.bat: Batch script for Git Bash/CMD
- BUILD-WINDOWS-SIMPLE.md: Simple build guide without make

Features:
- No make dependency required
- Builds all components (backend, service, tray, UI)
- Auto-downloads IPFS if missing
- Optional installer build with prompt
- Works in PowerShell, CMD, and Git Bash
- Clear progress output and error handling

Usage:
  PowerShell: .\build-windows.ps1
  Batch: build-windows.bat
  Manual: See BUILD-WINDOWS-SIMPLE.md

This is easier for Windows developers who don't have make installed.
…ucture

Fix tray application compilation error:
- Replace non-existent svc.Start with proper service.Start() call
- Create separate startService() function for starting services
- Keep controlService() for stop/pause/continue commands

The Windows service control API doesn't have a svc.Start command.
Starting a service requires calling service.Start() directly on the
service object, while other control commands (Stop, Pause, Continue)
use service.Control(cmd).

This fixes the compilation errors:
  cmd\pinshare-tray\tray.go:138:31: undefined: svc.Start
  cmd\pinshare-tray\tray.go:173:31: undefined: svc.Start

Verified: Cross-compiled successfully for windows/amd64
Ignore dist/, bin/, and *.msi files as these are build artifacts
that should not be committed to version control.
The pinshare-ui is on the infra/refactor branch and will be merged later.
For now, disable all UI-related components to allow the installer to build:

Changes:
- PinShare.wixproj: Comment out HarvestDirectory for UI files
- Package.wxs: Comment out UIComponents ComponentGroupRef
- Package.wxs: Comment out UIFolder directory definition
- Package.wxs: Comment out UIComponents Fragment

The installer now builds only the service wrapper, backend, and tray app.
UI can be re-enabled once pinshare-ui is merged from infra/refactor.
The UI is temporarily disabled in the installer configuration,
so the build script shouldn't check for UI files either.

This allows the installer to build with just:
- pinsharesvc.exe
- pinshare.exe
- pinshare-tray.exe
- ipfs.exe

UI will be re-enabled when pinshare-ui is merged from infra/refactor.
Previously the script would always print 'Build Complete!' even if
the installer build failed. Now it properly checks:
- If 'cd installer' succeeds
- If 'build-wix6.bat' succeeds

If either fails, the script exits with an error code instead of
falsely claiming success.
WiX 4+ requires conditions to be in a 'Condition' attribute, not as inner text.

Changed from:
  <Custom Action="..." Before="...">NOT Installed</Custom>

To:
  <Custom Action="..." Before="..." Condition="NOT Installed" />

This fixes the WIX0004 errors:
- The Custom element contains illegal inner text

Fixes 4 compilation errors.
…pVT handling

- Add install directory to PATH when starting PinShare subprocess so `ipfs`
  command is found
- Add environment variable loading for path configs (PS_UPLOAD_FOLDER,
  PS_CACHE_FOLDER, PS_REJECT_FOLDER, PS_METADATA_FILE, PS_IDENTITY_KEY_FILE)
- Add environment variable loading for feature flags (PS_FF_ARCHIVE_NODE,
  PS_FF_CACHE)
- When FFSkipVT=true, bypass security capability checks and set capability=2
  to allow startup without virus scanning infrastructure
- Fix uploads.go and downloads.go to check FFSkipVT flag before any security
  scanning, not just for SecurityCapability==4
- Add /api/health endpoint to API server for service health monitoring
- Increase PinShare startup timeout from 30s to 60s to allow for libp2p
  initialization and peer discovery
- Use SCRIPT_DIR variable with absolute paths throughout
- Replace cd with pushd/popd for reliable directory navigation
- Fix installer directory path to use absolute path
- Now works correctly when run from any directory
- Add comprehensive docs/windows-architecture.md documenting process hierarchy,
  components, data flow, and configuration
- Fix pinshare-tray icon not displaying by generating a valid 16x16 ICO
  format icon at runtime instead of the malformed 62-byte placeholder
- Create ConfigDialog.wxs with custom TextStyle definitions (PinShare_Font_Bold)
  to avoid undefined WixUI_Font_Bold errors when WixUI extension was disabled
- Enable WixToolset.UI.wixext in PinShare.wixproj for proper installer UI
- Add WixUI_Minimal reference and license RTF in Package.wxs
- Define configurable properties (ORGNAME, GROUPNAME, SKIPVIRUSTOTAL, ENABLECACHE)
  that can be customized during installation
- Update registry entries to use the configurable properties

The ConfigDialog is defined and ready for integration into the install
sequence, but currently uses WixUI_Minimal for simplicity.
Reverts to the working state from the 01NLPuCxVe8RG15ksJTz1N3E branch:
- Remove ConfigDialog.wxs (UI temporarily disabled until infra/refactor merge)
- Disable WixToolset.UI.wixext in PinShare.wixproj
- Revert Package.wxs to use hardcoded registry values
- Keep WixUI_Minimal commented out for now

This fixes the WIX0204 errors about undefined TextStyle WixUI_Font_Bold.
…ssing

Changed from exit with error to graceful skip using goto :skip_ui
when the pinshare-ui directory doesn't exist. UI will be added later
from the infra/refactor branch.
Restore installer configuration:
- Restore ConfigDialog.wxs with full configuration wizard UI
  (Network Identity, Ports, Features, Startup Options)
- Restore Package.wxs with WixUI_InstallDir integration and
  configurable properties (ORG_NAME, ports, SKIP_VIRUSTOTAL, etc.)
- Enable WixToolset.UI.wixext in PinShare.wixproj

Add dynamic version support:
- build-windows.bat: Detect version from git tags (git describe)
- build-wix6.bat: Accept version parameter, pass to dotnet build
- PinShare.wixproj: Accept ProductVersion via command line
- Package.wxs: Use preprocessor variable with fallback

Add GitHub Actions workflow:
- New windows-build.yml workflow for automated builds
- Triggers on version tags (v*) or manual dispatch
- Builds all Windows binaries and MSI installer
- Uploads versioned MSI to GitHub Releases

The installer now creates PinShare-{version}-Setup.msi with the
version embedded in both the filename and MSI metadata.
When no version tag (v*) exists, fall back to 1.0.0 instead of using
the commit hash. MSI versions must be numeric X.Y.Z or X.Y.Z.W format.

- Use --match "v[0-9]*" to only match version tags
- Fall back to 1.0.0 when no version tags exist
- Properly validate commit count is numeric before appending
Define WixUI_Font_Normal, WixUI_Font_Bold, and WixUI_Font_Title
in our custom UI fragment since they aren't inherited from the
WixUI extension when using a separate UI element.
ConfigDialog.wxs:
- Rename TextStyles to PinShare_Font_* to avoid conflicts with WixUI
- Add new "Directories" section with Upload folder field
- Expand dialog height to accommodate new field
- Adjust Y positions of all elements

Package.wxs:
- Add UPLOAD_DIR property with default path
- Update registry to use UPLOAD_DIR property
pm.logError("Failed to kill PinShare process", err)
}
// Kill the process tree using taskkill
pm.killProcessByPID(pid, "PinShare")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would be able to use the appName const here as well.

Comment on lines 22 to 24
var count int64
var wg sync.WaitGroup
semaphore := make(chan struct{}, maxConcurrentUploads)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a brief comment to explain how these are used together to facilitate limited concurrency and synchronization.

- uploads.go: rename variables (f→filename), add concurrency comments,
  extract processFileWithLimit(), use filepath.Join, use fmt.Printf
- downloads.go: fix SecurityCapability type casting consistency
- process.go: use dir constants, fix comment wording, update TODO ref
- config.go: promote appName to module-level, remove duplicate fallback
- tray.go: refactor handleRestartService to use handler methods
- settings_dialog.go: extract magic numbers to constants, use
  winservice.Default*Port for labels
- Update TODO references to GitHub issue #10

on:
# TEMP: Enable PR trigger so this workflow runs in the PR that introduces it.
# TODO_IN_THIS_PR(@bryanchriswhite): Remove before merging to avoid running on every PR.
Copy link
Author

@bryanchriswhite bryanchriswhite Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bryanchriswhite TODO: revert pull_request line

- Create internal/types package for shared SecurityCapability type
- Update config.go to use types.SecurityCapability instead of int
- Remove type casting in p2p/downloads.go and p2p/uploads.go
- Consolidate tray.go constants with winservice package
- Update SERVICE.md backup/restore examples with concrete paths
- Update PR3_REVIEW_COMMENTS.md tracking file with completion status
Comment on lines +22 to +23
defaultOrgName = "MyOrganization"
defaultGroupName = "MyGroup"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These still need to be set

When user clicks Exit in the tray menu, show a Yes/No/Cancel dialog
asking whether to stop the service before exiting:
- Yes: Stop service, then exit tray
- No: Exit tray (service continues running)
- Cancel: Stay in tray

Addresses PR #3 review comment about tray exit behavior.
- Use dirLogs constant instead of hardcoded "logs" string in process.go
- Fix GitHub URLs from Episk-pos to Cypherpunk-Labs in issue references
- Remove stale git checkout line from SERVICE.md clone instructions
- Migrate pinsharesvc from switch-based CLI to Cobra commands
- Add proper subcommands: install, uninstall, start, stop, restart, debug
- Add --auto-start flag for install command
- Improve help text and command descriptions
- Remove "walk" naming from settings dialog functions
Extract tab page definitions into separate methods:
- createNetworkPortsTab()
- createOrganizationTab()
- createFeaturesTab()
- createSecurityTab()
- createInfoTab()
- createButtonsBar()

Improves readability by reducing the main Dialog{} declaration
from ~270 lines to ~20 lines.
Create cmd/pinshare-tray/constants.go with:
- App identity: appName, appTooltip
- Windows MessageBox flags: MB_OK, MB_YESNO, MB_YESNOCANCEL, MB_ICON*
- MessageBox return values: IDYES, IDNO, IDCANCEL
- Directory names: dirIPFS, dirPinShare, dirUpload, dirCache, dirRejected, dirLogs
- File names: fileConfig, fileSession
- Environment variables: envLocalAppData, envUserProfile, envProgramData, envUsername
- Default paths: defaultLocalAppDataPath, defaultProgramDataPath

Remove duplicate constants from main.go and settings.go.
Update all files to use centralized constants.
Move duplicate constants to internal/winservice/constants.go:
- AppName: application name for directory paths
- DirIPFS, DirPinShare, DirUpload, DirCache, DirRejected, DirLogs
- FileConfig, FileSession, FileServiceLog
- EnvLocalAppData, EnvUserProfile, EnvProgramData, EnvProgramFiles, EnvUsername
- DefaultLocalAppDataPath, DefaultProgramDataPath, DefaultProgramFilesPath

Update cmd/pinsharesvc to use winservice.* constants directly.
Update cmd/pinshare-tray to alias winservice constants for convenience.

This eliminates duplication between pinsharesvc and pinshare-tray packages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants