Skip to content

Commit 6d97be9

Browse files
committed
fix(daemon): port probe 5000-5019 + exec bit on single-file binaries
v0.2.8 — three issues surfaced during E2E testing of v0.2.7: 1. Daemon hardcoded to port 5000. macOS Sequoia's AirPlay Receiver (ControlCenter) binds that port with SO_REUSEADDR so Kestrel fails with AddressInUseException on startup — app launches, daemon dies silently, user sees "waiting for backend" forever. Now probes 5000 through 5019 and uses the first free port; Electron already reads the actual port from the tmp port file so reconnection is automatic. 2. BinaryDownloader.ExtractAsync's single-file path (hint=bin/exe or no extension) called File.Copy with no chmod — GitHub serves the file as 0644, so ~/.wdc/binaries/mkcert/1.4.4/mkcert-v1.4.4-darwin-arm64 arrived without the execute bit. Every mkcert CLI invocation died with EACCES until the user manually chmod+x'd it. SSL site creation silently failed because of this — cert.pem was never written. Now sets UserExecute|GroupExecute|OtherExecute on Unix after the copy. 3. Frontend + daemon bumped to 0.2.8 to ship the fix. Pulls nks.wdc.apache-v1.0.8 via stage-plugins: Apache now re-renders httpd.conf on graceful reload, so `Listen 443` is emitted when an SSL cert exists (v0.2.7 had the 443 vhost but no Listen binding → ERR_CONN on https).
1 parent 1861790 commit 6d97be9

5 files changed

Lines changed: 45 additions & 7 deletions

File tree

src/daemon/NKS.WebDevConsole.Daemon/Binaries/BinaryDownloader.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ public async Task<string> ExtractAsync(
110110
: guessedName;
111111
var dest = Path.Combine(destinationDir, canonical);
112112
await Task.Run(() => File.Copy(archivePath, dest, overwrite: true), ct);
113+
// Bare-binary downloads arrive from GitHub as 0644 — no executable
114+
// bit. Without this the `chmod +x` step every user would otherwise
115+
// need to do manually, mkcert / cloudflared etc. fail with
116+
// EACCES the first time the plugin tries to run them. Tar/zip
117+
// extractors already honour in-archive mode bits; this path had
118+
// nothing to copy the bit from, so set it explicitly.
119+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
120+
{
121+
try
122+
{
123+
var current = File.GetUnixFileMode(dest);
124+
File.SetUnixFileMode(dest,
125+
current | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute);
126+
}
127+
catch (Exception ex)
128+
{
129+
_logger.LogDebug(ex, "Could not set executable bit on {Path}", dest);
130+
}
131+
}
113132
return destinationDir;
114133
}
115134

src/daemon/NKS.WebDevConsole.Daemon/NKS.WebDevConsole.Daemon.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
<!-- F92: single source of truth for daemon version. Keep in sync with
3434
src/frontend/package.json on every bump so /api/status and
3535
/api/system return the real shipped version instead of hardcoded "0.1.0". -->
36-
<Version>0.2.7</Version>
37-
<AssemblyVersion>0.2.7.0</AssemblyVersion>
38-
<FileVersion>0.2.7.0</FileVersion>
39-
<InformationalVersion>0.2.7</InformationalVersion>
36+
<Version>0.2.8</Version>
37+
<AssemblyVersion>0.2.8.0</AssemblyVersion>
38+
<FileVersion>0.2.8.0</FileVersion>
39+
<InformationalVersion>0.2.8</InformationalVersion>
4040
<!-- Default release manifest: requireAdministrator (one UAC at
4141
launch, every child op inherits elevation). CI smoke test
4242
jobs pass -p:CiBuild=true to swap in app.ci.manifest which

src/daemon/NKS.WebDevConsole.Daemon/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,25 @@
210210
var migrationRunner = new MigrationRunner(earlyLoggerFactory.CreateLogger<MigrationRunner>());
211211
migrationRunner.Run(database.ConnectionString);
212212

213+
// Port probing: 5000 is ASP.NET's default but macOS Sequoia's AirPlay
214+
// Receiver (ControlCenter) squats on it with SO_REUSEADDR so the daemon
215+
// can't bind. Try 5000–5019 and use whichever is free. Electron reads
216+
// the actual port from the tmp port file so it reconnects correctly.
217+
int chosenPort = 5000;
218+
for (int p = 5000; p < 5020; p++)
219+
{
220+
try
221+
{
222+
using var probe = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, p);
223+
probe.Start();
224+
probe.Stop();
225+
chosenPort = p;
226+
break;
227+
}
228+
catch (System.Net.Sockets.SocketException) { /* try next */ }
229+
}
230+
builder.WebHost.UseUrls($"http://127.0.0.1:{chosenPort}");
231+
213232
var app = builder.Build();
214233
app.UseWebSockets();
215234
app.UseCors();

src/frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nks-hub/webdev-console",
3-
"version": "0.2.7",
3+
"version": "0.2.8",
44
"description": "Unified local development environment for Windows, macOS, and Linux \u2014 manage Apache/Nginx, PHP versions, MySQL/MariaDB, and SSL from one interface.",
55
"author": {
66
"name": "NKS Hub",

0 commit comments

Comments
 (0)