User-facing copy only; internal API names (Backups collection,
BackupEntry, list-backups op, etc.) stay the same to avoid churn
through the wire protocol and existing on-disk files. The new
phrasing makes the auto-snapshot-before-save model more discoverable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for the v0.1.0 install experience:
1. Embed app.manifest with requestedExecutionLevel=requireAdministrator
so the GUI always elevates. The named pipe is ACL'd to SYSTEM and
the Administrators group, but UAC token splitting puts Admins in
deny-only on the standard token, so launching the GUI from the
Start Menu fails to connect with "Access is denied". The manifest
forces UAC to elevate, surfaces the shield icon on the shortcut,
and matches the reality that the GUI cannot function without
admin rights.
2. Add a [Code] PrepareToInstall hook to webhook-server.iss that runs
`sc stop WebhookServer` before file copy. Upgrade installs were
failing on locked binaries because the running service held the
exes open. sc returns non-zero on fresh installs (no service yet)
which we ignore.
Bumps Version to 0.1.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ConfigStore.SaveAsync now snapshots the previous config to
%ProgramData%\WebhookServer\backups\config-<timestamp>.json before
overwriting, retaining the last 30. Failures are silent so a
backup-write hiccup never blocks an actual save.
Three new admin pipe ops:
- list-backups: returns newest 50 entries with timestamps and sizes
- restore-backup: takes a fileName, refuses path-traversal chars,
loads the named backup over the live config (which itself triggers
a fresh backup of the current state via the SaveAsync hook)
- import-config: replaces the current config with a GUI-supplied
ServerConfig, merging encrypted secrets where the GUI didn't supply
new plaintext
GUI File menu items are wired:
- Import config: file picker -> ImportConfigAsync
- Export config: SaveFileDialog writes the current config as JSON
- Backups: dynamic submenu auto-refreshed when opened, listing
backups with timestamp + size; click to confirm-and-restore
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GUI csproj enables UseWindowsForms (NotifyIcon lives in WinForms even
in .NET 8). New Services/TrayIcon.cs wraps NotifyIcon with a context
menu (Open / Restart service / Exit) and the embedded webhook-server
icon. MainWindow creates the TrayIcon, hides itself on minimize and
restores on tray double-click.
Adds GlobalUsings.cs to alias the WPF defaults for types that exist
in both WPF and WinForms (Application, MessageBox, TextBox, Binding,
etc.) so existing code keeps compiling without per-file changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/generate-icons.ps1 renders the icon programmatically with
System.Drawing - rounded teal square (#0E7C66) with a stylized white
hook glyph - at 16/24/32/48/64/128/256 px and assembles a proper
multi-resolution Microsoft ICO. The PNG and ICO outputs land in
resources/. The script is the source of truth; re-run after editing
the design.
GUI csproj uses ApplicationIcon for the EXE icon and embeds the .ico
+ .png as Resources so MainWindow and AboutDialog can use them via
WPF's resource URI scheme.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Directory.Build.props sets Version=0.1.0 (semver pre-1.0 = beta) plus
Authors / Product / RepositoryUrl, picked up by all three projects.
- MainWindow gets a real menu bar (File / Server / Help) replacing the
old toolbar. File: New endpoint / Import / Export / Backups (last
three are stubs for the next phase) / Exit. Server: Settings /
Restart service. Help: About.
- Drop the Refresh button - the 3 s polling loop covers it.
- DataGridRow gets a right-click context menu: Edit / Copy URL /
toggle Enabled / Delete.
- New About dialog reads AssemblyInformationalVersion at runtime and
links jpaul.me + the GitHub repo via clickable hyperlinks.
- Ctrl+N input binding for new-endpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ServerConfig grows two fields:
- BindAddresses: list of IPs Kestrel binds to (empty = all interfaces,
current behavior). Listening only on a subset is useful when the host
has multiple NICs and the webhook should not be reachable on all of
them.
- DisplayHost: the hostname/IP the GUI splices into the URL column and
Copy URL button. Cosmetic; doesn't affect what the server accepts.
Server Settings dialog gains a "Network" section: a checkbox for "all
interfaces" plus per-NIC checkboxes auto-detected via NetworkInterface.
GetAllNetworkInterfaces, and an editable ComboBox for the display host
pre-populated with detected IPs and the machine name.
Listener restart fires on BindAddresses change but not on DisplayHost
change (cosmetic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native per-endpoint identity instead of the schtasks bridge:
- Service (default) keeps the existing path - hooks inherit the service
account (SYSTEM by default, or whatever you installed under).
- SpecificUser binds ProcessStartInfo.UserName / Password / Domain so
the hook runs in a batch logon session as the named account. Useful
for AD-write hooks that should NOT run as SYSTEM.
- InteractiveUser uses WTSQueryUserToken(WTSGetActiveConsoleSessionId)
+ DuplicateTokenEx + CreateProcessAsUser to drop the child into the
logged-in user's session with their environment block. This is the
real fix for "calc.exe should pop up on my desktop" - no Task
Scheduler bridge required. Stdio is captured via inheritable
anonymous pipes so the hook still returns stdout/stderr to the
caller normally.
Implementation:
- New RunAsMode enum + RunAsConfig model on EndpointConfig
- ConfigStore round-trips RunAs.Password through DPAPI alongside
bearer/HMAC/PFX secrets
- AdminPipeServer's secret-merge logic preserves the encrypted blob
when the GUI saves an endpoint without re-typing the password
- New WebhookServer.Core.Execution.Native namespace with NativeMethods
(P/Invoke) and InteractiveProcessLauncher (token-based launcher)
- ProcessExecutor branches on RunAs.Mode; the Service/SpecificUser
paths share .NET's Process; InteractiveUser uses the launcher
- GUI editor gets a "Run as" section: dropdown + conditional
username/password/load-profile fields under SpecificUser
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GUI:
- URL column in endpoint grid + Copy URL toolbar button so the full
http://host:port/hook/<slug> is one click away
- Double-click a row to open the edit dialog
- Bearer/HMAC sections in the editor hide when the auth mode doesn't
use them, and reappear with previously-entered values when switched
back
- Log panel auto-scroll checkbox (default on) plus 3s polling so log
entries stream in without manual refresh
- Secret fields are now plain text with a Copy button. Anyone who can
open the admin-pipe-ACL'd GUI is already SYSTEM-equivalent on the
host, so masking the value just made recovery harder. PFX password
in Server Settings gets the same treatment.
Service:
- Admin pipe ops log info-level lines on every mutation
(create/update/delete/enable/disable/update-config/bind-https) so
GUI activity is visible in the Serilog file
- /hook/{slug} accepts GET as well as POST so a browser smoke-test
works without curl
- /favicon.ico returns 204 so browser hits don't pollute logs with 404s
- AdminPipeServer no longer strips plaintext secrets when sending
config to the GUI; the pipe ACL already restricts to SYSTEM/Admins
Scripts:
- New deploy.ps1: stops + republishes + copies binaries to
C:\Program Files\WebhookServer + (re)installs the Windows Service
- install-service.ps1 now uses sc.exe argv splatting consistently for
both create and config paths
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the .NET 8 solution scaffolded against PLAN.md. Three projects share
WebhookServer.Core (models, auth, execution, storage, IPC, callbacks)
and WebhookServer.Service hosts an embedded Kestrel listener plus the
named-pipe admin server. WebhookServer.Gui is a thin MVVM client over
the pipe. Includes 25 unit tests covering HMAC verification, bearer
auth, IP allowlist parsing, arg-template rendering, DPAPI round-trip,
and the encrypt-on-save config store.
Install/uninstall PowerShell scripts default to LocalSystem and accept
a domain user or gMSA via -ServiceAccount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>