Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion apps/pi-extension/server/network.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, test } from "bun:test";
import { getServerPort, isRemoteSession } from "./network";
import { getServerHostname, getServerPort, isRemoteSession } from "./network";

const savedEnv: Record<string, string | undefined> = {};
const envKeys = ["PLANNOTATOR_REMOTE", "PLANNOTATOR_PORT", "SSH_TTY", "SSH_CONNECTION"];
Expand Down Expand Up @@ -94,3 +94,16 @@ describe("pi port selection", () => {
expect(getServerPort()).toEqual({ port: 9999, portSource: "env" });
});
});

describe("pi server hostname", () => {
test("binds local sessions to loopback", () => {
clearEnv();
expect(getServerHostname()).toBe("127.0.0.1");
});

test("binds remote sessions to all interfaces", () => {
clearEnv();
process.env.PLANNOTATOR_REMOTE = "1";
expect(getServerHostname()).toBe("0.0.0.0");
});
});
7 changes: 6 additions & 1 deletion apps/pi-extension/server/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Server } from "node:http";
import { release } from "node:os";

const DEFAULT_REMOTE_PORT = 19432;
const LOOPBACK_HOST = "127.0.0.1";

/**
* Check if running in a remote session (SSH, devcontainer, etc.)
Expand Down Expand Up @@ -67,6 +68,10 @@ export function getServerPort(): {
return { port: 0, portSource: "random" };
}

export function getServerHostname(): string {
return isRemoteSession() ? "0.0.0.0" : LOOPBACK_HOST;
}

const MAX_RETRIES = 5;
const RETRY_DELAY_MS = 500;

Expand All @@ -81,7 +86,7 @@ export async function listenOnPort(
server.once("error", reject);
server.listen(
result.port,
isRemoteSession() ? "0.0.0.0" : "127.0.0.1",
getServerHostname(),
() => {
server.removeListener("error", reject);
resolve();
Expand Down
3 changes: 2 additions & 1 deletion packages/server/annotate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
*/

import { isRemoteSession, getServerPort } from "./remote";
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
import { getRepoInfo } from "./repo";
import type { Origin } from "@plannotator/shared/agents";
import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon } from "./shared-handlers";
Expand Down Expand Up @@ -131,6 +131,7 @@ export async function startAnnotateServer(
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
server = Bun.serve({
hostname: getServerHostname(),
port: configuredPort,

async fetch(req, server) {
Expand Down
3 changes: 2 additions & 1 deletion packages/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import type { Origin } from "@plannotator/shared/agents";
import { resolve } from "path";
import { isRemoteSession, getServerPort } from "./remote";
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
import { openEditorDiff } from "./ide";
import {
saveToObsidian,
Expand Down Expand Up @@ -200,6 +200,7 @@ export async function startPlannotatorServer(
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
server = Bun.serve({
hostname: getServerHostname(),
port: configuredPort,

async fetch(req, server) {
Expand Down
15 changes: 14 additions & 1 deletion packages/server/remote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { afterEach, describe, expect, test } from "bun:test";
import { isRemoteSession, getServerPort } from "./remote";
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";

// Save and restore env between tests
const savedEnv: Record<string, string | undefined> = {};
Expand Down Expand Up @@ -135,3 +135,16 @@ describe("getServerPort", () => {
expect(getServerPort()).toBe(0);
});
});

describe("getServerHostname", () => {
test("returns loopback for local sessions", () => {
clearEnv();
expect(getServerHostname()).toBe("127.0.0.1");
});

test("returns all interfaces for remote sessions", () => {
clearEnv();
process.env.PLANNOTATOR_REMOTE = "1";
expect(getServerHostname()).toBe("0.0.0.0");
});
});
9 changes: 9 additions & 0 deletions packages/server/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

const DEFAULT_REMOTE_PORT = 19432;
const LOOPBACK_HOST = "127.0.0.1";

function getRemoteOverride(): boolean | null {
const remote = process.env.PLANNOTATOR_REMOTE;
Expand Down Expand Up @@ -63,3 +64,11 @@ export function getServerPort(): number {
// Remote sessions use fixed port for port forwarding; local uses random
return isRemoteSession() ? DEFAULT_REMOTE_PORT : 0;
}

/**
* Bind local sessions to loopback, but keep remote sessions reachable via the
* container or host network interface for SSH/devcontainer/Docker forwarding.
*/
export function getServerHostname(): string {
return isRemoteSession() ? "0.0.0.0" : LOOPBACK_HOST;
}
3 changes: 2 additions & 1 deletion packages/server/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
*/

import { isRemoteSession, getServerPort } from "./remote";
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
import type { Origin } from "@plannotator/shared/agents";
import { type DiffType, type GitContext, runVcsDiff, getVcsFileContentsForDiff, canStageFiles, stageFile, unstageFile, resolveVcsCwd, validateFilePath } from "./vcs";
import { getRepoInfo } from "./repo";
Expand Down Expand Up @@ -348,6 +348,7 @@ export async function startReviewServer(
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
server = Bun.serve({
hostname: getServerHostname(),
port: configuredPort,

async fetch(req, server) {
Expand Down