Skip to content

Commit

Permalink
Add -T/--no-tunnel option to fedify inbox command
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed May 1, 2024
1 parent 9011ce0 commit 2ac508c
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 57 deletions.
101 changes: 49 additions & 52 deletions cli/inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const logger = getLogger(["fedify", "cli", "inbox"]);

export const command = new Command()
.description(
"Spins up an ephemeral ActivityPub server and receives activities. " +
"Spins up an ephemeral server that serves the ActivityPub inbox with " +
"an one-time actor, through a short-lived public DNS with HTTPS. " +
"You can monitor the incoming activities in real-time.",
)
.option(
Expand All @@ -49,15 +50,23 @@ export const command = new Command()
"requests will be accepted.",
{ collect: true },
)
.option(
"-T, --no-tunnel",
"Do not tunnel the ephemeral ActivityPub server to the public Internet.",
)
.action(async (options) => {
const spinner = ora({
text: "Spinning up an ephemeral ActivityPub server...",
discardStdin: false,
}).start();
const server = await spawnTemporaryServer(fetch);
const server = await spawnTemporaryServer(fetch, {
noTunnel: !options.tunnel,
});
spinner.succeed(
`The ephemeral ActivityPub server is up and running: ${
colors.green(server.url.href)
colors.green(
server.url.href,
)
}`,
);
Deno.addSignalListener("SIGINT", () => {
Expand Down Expand Up @@ -90,7 +99,7 @@ export const command = new Command()
object: actor.id,
}),
);
spinner.succeed(`Followed ${colors.green(uri)}`);
spinner.succeed(`Sent follow request to ${colors.green(uri)}.`);
spinner.start();
}
}
Expand All @@ -101,7 +110,6 @@ export const command = new Command()
const federation = new Federation<number>({
kv: new MemoryKvStore(),
queue: new InProcessMessageQueue(),
treatHttps: true,
documentLoader: await getDocumentLoader(),
});

Expand Down Expand Up @@ -167,16 +175,14 @@ federation
if (!isActor(follower)) return;
const accepts = await acceptsFollowFrom(follower);
if (!accepts) {
logger.debug(
"Does not accept follow from {actor}.",
{ actor: follower.id?.href },
);
logger.debug("Does not accept follow from {actor}.", {
actor: follower.id?.href,
});
return;
}
logger.debug(
"Accepting follow from {actor}.",
{ actor: follower.id?.href },
);
logger.debug("Accepting follow from {actor}.", {
actor: follower.id?.href,
});
await ctx.sendActivity(
{ handle },
follower,
Expand All @@ -192,7 +198,7 @@ function printServerInfo(fedCtx: Context<number>): void {
new Table(
[
new Cell("Actor handle:").align("right"),
colors.green(`i@${fedCtx.getActorUri("i").hostname}`),
colors.green(`i@${fedCtx.getActorUri("i").host}`),
],
[
new Cell("Actor URI:").align("right"),
Expand All @@ -206,22 +212,19 @@ function printServerInfo(fedCtx: Context<number>): void {
new Cell("Shared inbox:").align("right"),
colors.green(fedCtx.getInboxUri().href),
],
).chars(tableStyle).border().render();
)
.chars(tableStyle)
.border()
.render();
}

function printActivityEntry(
idx: number,
entry: ActivityEntry,
): void {
function printActivityEntry(idx: number, entry: ActivityEntry): void {
const request = entry.request.clone();
const response = entry.response?.clone();
const url = new URL(request.url);
const activity = entry.activity;
new Table(
[
new Cell("Request #:").align("right"),
colors.bold(idx.toString()),
],
[new Cell("Request #:").align("right"), colors.bold(idx.toString())],
[
new Cell("Activity type:").align("right"),
activity == null
Expand All @@ -236,42 +239,36 @@ function printActivityEntry(
: colors.red(request.method)
} ${url.pathname + url.search}`,
],
...(response == null ? [] : [[
new Cell("HTTP response:").align("right"),
`${
response.ok
? colors.green(response.status.toString())
: colors.red(response.status.toString())
} ${response.statusText}`,
]]),
[
new Cell("Details").align("right"),
new URL(`/r/${idx}`, url).href,
],
).chars(tableStyle).border().render();
...(response == null ? [] : [
[
new Cell("HTTP response:").align("right"),
`${
response.ok
? colors.green(response.status.toString())
: colors.red(response.status.toString())
} ${response.statusText}`,
],
]),
[new Cell("Details").align("right"), new URL(`/r/${idx}`, url).href],
)
.chars(tableStyle)
.border()
.render();
}

const app = new Hono();

app.get("/", (c) => c.redirect("/r"));

app.get("/r", (c) =>
c.html(
<ActivityListPage entries={activities} />,
));
app.get("/r", (c) => c.html(<ActivityListPage entries={activities} />));

app.get(
"/r/:idx{[0-9]+}",
(c) => {
const idx = parseInt(c.req.param("idx"));
const tab = c.req.query("tab") ?? "request";
const activity = activities[idx];
if (activity == null) return c.notFound();
return c.html(
<ActivityEntryPage idx={idx} entry={activity} tabPage={tab} />,
);
},
);
app.get("/r/:idx{[0-9]+}", (c) => {
const idx = parseInt(c.req.param("idx"));
const tab = c.req.query("tab") ?? "request";
const activity = activities[idx];
if (activity == null) return c.notFound();
return c.html(<ActivityEntryPage idx={idx} entry={activity} tabPage={tab} />);
});

async function fetch(request: Request): Promise<Response> {
const timestamp = Temporal.Now.instant();
Expand Down
47 changes: 46 additions & 1 deletion cli/tempserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,62 @@ import { getLogger } from "@logtape/logtape";

const logger = getLogger(["fedify", "cli", "tempserver"]);

export interface SpawnTemporaryServerOptions {
noTunnel?: boolean;
}

export interface TemporaryServer {
url: URL;
close(): Promise<void>;
}

export function spawnTemporaryServer(
handler: Deno.ServeHandler,
options: SpawnTemporaryServerOptions = {},
): Promise<TemporaryServer> {
if (options.noTunnel) {
return new Promise((resolve) => {
const server = Deno.serve({
handler,
port: 0,
hostname: "::",
onListen({ port }) {
logger.debug("Temporary server is listening on port {port}.", {
port,
});
resolve({
url: new URL(`http://localhost:${port}`),
async close() {
await server.shutdown();
},
});
},
});
});
}
return new Promise((resolve) => {
const server = Deno.serve({
handler,
async handler(request: Request, info: Deno.ServeHandlerInfo) {
const url = new URL(request.url);
url.protocol = "https:";
request = new Request(url, {
method: request.method,
headers: request.headers,
body: request.method === "GET" || request.method === "HEAD"
? null
: await request.blob(),
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
integrity: request.integrity,
keepalive: request.keepalive,
signal: request.signal,
});
return await handler(request, info);
},
port: 0,
hostname: "::",
onListen({ port }) {
Expand Down
74 changes: 70 additions & 4 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,11 @@ Person {
`fedify inbox`: Ephemeral inbox server
--------------------------------------

The `fedify inbox` command is used to spin up an ephemeral inbox server that
serves the ActivityPub inbox with an one-time actor. This is useful when you
want to test and debug the outgoing activities of your server. To start
an ephemeral inbox server, run the below command:
The `fedify inbox` command is used to spin up an ephemeral server that serves
the ActivityPub inbox with an one-time actor, through a short-lived public DNS
with HTTPS. This is useful when you want to test and debug the outgoing
activities of your server. To start an ephemeral inbox server,
run the below command:

~~~~ sh
fedify inbox
Expand Down Expand Up @@ -465,6 +466,71 @@ You can also see the details of the incoming activities by visiting the

![The details of the incoming activities](cli/fedify-inbox-web.png)

### `-f`/`--follow`: Follow an actor

The `-f`/`--follow` option is used to follow an actor. You can specify the
actor handle or URI to follow. For example, to follow the actor with the
handle *@john@doe.com* and *@jane@doe.com*, run the below command:

~~~~ sh
fedify inbox -f @john@doe.com -f @jane@doe.com
~~~~

> [!NOTE]
> Although `-f`/`--follow` option sends `Follow` activities to the specified
> actors, it does not guarantee that they will accept the follow requests.
> If the actors accept the follow requests, you will receive the `Accept`
> activities in the inbox server, and the server will log them to the console:
>
> ~~~~
> ╭────────────────┬─────────────────────────────────────╮
> │ Request #: │ 0 │
> ├────────────────┼─────────────────────────────────────┤
> │ Activity type: │ Accept │
> ├────────────────┼─────────────────────────────────────┤
> │ HTTP request: │ POST /i/inbox │
> ├────────────────┼─────────────────────────────────────┤
> │ HTTP response: │ 202 │
> ├────────────────┼─────────────────────────────────────┤
> │ Details │ https://876f71397f5c31.lhr.life/r/0 │
> ╰────────────────┴─────────────────────────────────────╯
> ~~~~
### `-a`/`--accept-follow`: Accept follow requests

The `-a`/`--accept-follow` option is used to accept follow requests from
actors. You can specify the actor handle or URI to accept follow requests.
Or you can accept all follow requests by specifying the wildcard `*`.
For example, to accept follow requests from the actor with the handle
*@john@doe.com* and *@jane@doe.com*, run the below command:

~~~~ sh
fedify inbox -a @john@doe.com -a @jane@doe.com
~~~~

When the follow requests are received from the specified actors, the server
will immediately send the `Accept` activities to them. Otherwise, the server
will just log the `Follow` activities to the console without sending the
`Accept` activities.

### `-T`/`--no-tunnel`: Local server without tunneling

The `-T`/`--no-tunnel` option is used to disable the tunneling feature of the
inbox server. By default, the inbox server tunnels the local server to the
public internet, so that the server is accessible from the outside. If you
want to disable the tunneling feature, run the below command:

~~~~ sh
fedify inbox --no-tunnel
~~~~

It would be useful when you want to test the server locally but are worried
about the security implications of exposing the server to the public internet.

> [!NOTE]
> If you disable the tunneling feature, the ephemeral ActivityPub instance will
> be served via HTTP instead of HTTPS.

Shell completions
-----------------
Expand Down

0 comments on commit 2ac508c

Please sign in to comment.