Skip to content

Commit 84aa16b

Browse files
authored
Merge pull request #529 from sij411/feat/tunnel-service
Add --tunnel-service option to lookup, inbox, and relay commands
2 parents ec32aaa + bfde03a commit 84aa16b

7 files changed

Lines changed: 138 additions & 26 deletions

File tree

docs/cli.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,19 @@ the [ActivityPub HTTP Signatures] specification.
787787
[RFC 9421]: https://www.rfc-editor.org/rfc/rfc9421
788788
[ActivityPub HTTP Signatures]: https://swicg.github.io/activitypub-http-signature/
789789

790+
### `--tunnel-service`: Tunneling service for `-a`/`--authorized-fetch`
791+
792+
The `--tunnel-service` option is used to specify which tunneling service to use
793+
when using the `-a`/`--authorized-fetch` option. The authorized fetch feature
794+
requires a temporary server to be exposed to the public internet, and this
795+
option allows you to choose the tunneling service. Available services can be
796+
found in the output of the `fedify lookup --help` command. For example, to use
797+
serveo.net as the tunneling service:
798+
799+
~~~~ sh
800+
fedify lookup --authorized-fetch --tunnel-service serveo.net @user@example.com
801+
~~~~
802+
790803
### `-u`/`--user-agent`: Custom `User-Agent` header
791804

792805
*This option is available since Fedify 1.3.0.*
@@ -966,6 +979,20 @@ about the security implications of exposing the server to the public internet.
966979
> If you disable the tunneling feature, the ephemeral ActivityPub instance will
967980
> be served via HTTP instead of HTTPS.
968981
982+
### `--tunnel-service`: Tunneling service
983+
984+
The `--tunnel-service` option is used to specify which tunneling service to use
985+
for exposing the ephemeral inbox server to the public internet. Available
986+
services can be found in the output of the `fedify inbox --help` command.
987+
For example, to use serveo.net as the tunneling service:
988+
989+
~~~~ sh
990+
fedify inbox --tunnel-service serveo.net
991+
~~~~
992+
993+
> [!NOTE]
994+
> This option cannot be used together with `-T`/`--no-tunnel`.
995+
969996
### `-A`/`--authorized-fetch`: Authorized fetch mode
970997

971998
The `-A`/`--authorized-fetch` option enables authorized fetch mode on the
@@ -1126,11 +1153,13 @@ fedify tunnel 3000
11261153
11271154
[x-forwarded-fetch]: https://github.com/dahlia/x-forwarded-fetch
11281155

1129-
### `-s`/`--service`: The tunneling service
1156+
### `-s`/`--service`/`--tunnel-service`: The tunneling service
11301157

11311158
The `-s`/`--service` option is used to specify the tunneling service to use.
1132-
Available services can be found in the output of the `fedify tunnel --help`
1133-
command. For example, to use the serveo.net, run the below command:
1159+
The `--tunnel-service` is an alias for consistency with other commands that
1160+
support tunneling. Available services can be found in the output of the
1161+
`fedify tunnel --help` command. For example, to use serveo.net, run the below
1162+
command:
11341163

11351164
~~~~ sh
11361165
fedify tunnel --service serveo.net 3000

packages/cli/src/inbox.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import ora from "ora";
4444
import metadata from "../deno.json" with { type: "json" };
4545
import { getDocumentLoader } from "./docloader.ts";
4646
import { configureLogging, debugOption } from "./globals.ts";
47+
import { tunnelOption } from "./options.ts";
4748
import type { ActivityEntry } from "./inbox/entry.ts";
4849
import { ActivityEntryPage, ActivityListPage } from "./inbox/view.tsx";
4950
import { recordingSink } from "./log.ts";
@@ -86,14 +87,9 @@ export const inboxCommand = command(
8687
}),
8788
),
8889
),
89-
noTunnel: option(
90-
"-T",
91-
"--no-tunnel",
92-
{
93-
description:
94-
message`Do not tunnel the ephemeral ActivityPub server to the public Internet.`,
95-
},
96-
),
90+
}),
91+
tunnelOption,
92+
object({
9793
actorName: withDefault(
9894
option("--actor-name", string({ metavar: "NAME" }), {
9995
description: message`Customize the actor display name.`,
@@ -334,7 +330,8 @@ export async function runInbox(
334330
discardStdin: false,
335331
}).start();
336332
const server = await spawnTemporaryServer(fetch, {
337-
noTunnel: command.noTunnel,
333+
noTunnel: !command.tunnel,
334+
...(command.tunnel && { service: command.tunnelService }),
338335
});
339336
spinner.succeed(
340337
`The ephemeral ActivityPub server is up and running: ${

packages/cli/src/lookup.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,20 @@ import ora from "ora";
4242
import { getContextLoader, getDocumentLoader } from "./docloader.ts";
4343
import { configureLogging, debugOption } from "./globals.ts";
4444
import { renderImages } from "./imagerenderer.ts";
45+
import { tunnelServiceOption } from "./options.ts";
4546
import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts";
4647
import { colorEnabled, colors, formatObject } from "./utils.ts";
4748

4849
const logger = getLogger(["fedify", "cli", "lookup"]);
4950

50-
export const authorizedFetchOption = withDefault(
51+
export const authorizedFetchOption = or(
5152
object({
52-
authorizedFetch: flag("-a", "--authorized-fetch", {
53-
description: message`Sign the request with an one-time key.`,
54-
}),
53+
authorizedFetch: map(
54+
flag("-a", "--authorized-fetch", {
55+
description: message`Sign the request with an one-time key.`,
56+
}),
57+
() => true as const,
58+
),
5559
firstKnock: withDefault(
5660
option(
5761
"--first-knock",
@@ -64,8 +68,11 @@ export const authorizedFetchOption = withDefault(
6468
),
6569
"draft-cavage-http-signatures-12" as const,
6670
),
71+
tunnelService: tunnelServiceOption,
72+
}),
73+
object({
74+
authorizedFetch: constant(false as const),
6775
}),
68-
{ authorizedFetch: false } as const,
6976
);
7077

7178
const traverseOption = withDefault(
@@ -358,7 +365,7 @@ export async function runLookup(command: InferValue<typeof lookupCommand>) {
358365
}),
359366
{ contextLoader },
360367
);
361-
});
368+
}, { service: command.tunnelService });
362369
const baseAuthLoader = getAuthenticatedDocumentLoader(
363370
{
364371
keyId: new URL("#main-key", server.url),

packages/cli/src/options.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
choice,
3+
constant,
4+
flag,
5+
map,
6+
message,
7+
object,
8+
option,
9+
optional,
10+
or,
11+
valueSet,
12+
} from "@optique/core";
13+
14+
/**
15+
* Available tunneling services for exposing local servers to the public internet.
16+
*/
17+
export const TUNNEL_SERVICES = [
18+
"localhost.run",
19+
"serveo.net",
20+
"pinggy.io",
21+
] as const;
22+
23+
/**
24+
* Type representing a valid tunneling service.
25+
*/
26+
export type TunnelService = typeof TUNNEL_SERVICES[number];
27+
28+
/**
29+
* Option for selecting a tunneling service.
30+
* Use this when tunneling is implicit (e.g., in `lookup` with `-a`).
31+
*/
32+
export const tunnelServiceOption = optional(
33+
option(
34+
"--tunnel-service",
35+
choice(TUNNEL_SERVICES, { metavar: "SERVICE" }),
36+
{
37+
description: message`The tunneling service to use: ${
38+
valueSet(TUNNEL_SERVICES)
39+
}.`,
40+
},
41+
),
42+
);
43+
44+
/**
45+
* Combined option for enabling/disabling tunneling with service selection.
46+
* Use this when tunneling can be disabled (e.g., in `inbox` and `relay`).
47+
*
48+
* Returns either:
49+
* - `{ tunnel: false }` when `--no-tunnel` is specified
50+
* - `{ tunnel: true, tunnelService?: TunnelService }` otherwise
51+
*/
52+
export const tunnelOption = or(
53+
object({
54+
tunnel: map(
55+
flag("-T", "--no-tunnel", {
56+
description: message`Do not tunnel the server to the public Internet.`,
57+
}),
58+
() => false as const,
59+
),
60+
}),
61+
object({
62+
tunnel: constant(true),
63+
tunnelService: tunnelServiceOption,
64+
}),
65+
);

packages/cli/src/relay.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { DatabaseSync } from "node:sqlite";
2424
import process from "node:process";
2525
import ora from "ora";
2626
import { configureLogging, debugOption } from "./globals.ts";
27+
import { tunnelOption } from "./options.ts";
2728
import { tableStyle } from "./table.ts";
2829
import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts";
2930
import { colors, matchesActor } from "./utils.ts";
@@ -82,11 +83,8 @@ export const relayCommand = command(
8283
message`Reject follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be rejected.`,
8384
}),
8485
)),
85-
noTunnel: option("-T", "--no-tunnel", {
86-
description:
87-
message`Disable tunneling the relay server to the public internet. Local access only.`,
88-
}),
8986
}),
87+
tunnelOption,
9088
debugOption,
9189
),
9290
{
@@ -140,7 +138,11 @@ export async function runRelay(
140138

141139
server = await spawnTemporaryServer(async (request) => {
142140
return await relay.fetch(request);
143-
}, { noTunnel: command.noTunnel, port: command.port });
141+
}, {
142+
noTunnel: !command.tunnel,
143+
port: command.port,
144+
...(command.tunnel && { service: command.tunnelService }),
145+
});
144146

145147
relay = createRelay(
146148
command.protocol as RelayType,

packages/cli/src/tempserver.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { openTunnel } from "@hongminhee/localtunnel";
22
import { getLogger } from "@logtape/logtape";
33
import { serve } from "srvx";
4+
import type { TunnelService } from "./options.ts";
45

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

78
export type SpawnTemporaryServerOptions = {
89
noTunnel?: boolean;
910
port?: number;
11+
service?: TunnelService;
1012
};
1113

1214
export type TemporaryServer = {
@@ -80,7 +82,10 @@ export async function spawnTemporaryServer(
8082
const port = url.port;
8183

8284
logger.debug("Temporary server is listening on port {port}.", { port });
83-
const tun = await openTunnel({ port: parseInt(port) });
85+
const tun = await openTunnel({
86+
port: parseInt(port),
87+
service: options.service,
88+
});
8489
logger.debug("Temporary server is tunneled to {url}.", { url: tun.url.href });
8590

8691
return {

packages/cli/src/tunnel.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import {
1010
object,
1111
option,
1212
optional,
13+
valueSet,
1314
} from "@optique/core";
1415
import { choice } from "@optique/core/valueparser";
16+
import { TUNNEL_SERVICES } from "./options.ts";
1517
import { print, printError } from "@optique/run";
1618
import process from "node:process";
1719
import ora from "ora";
@@ -32,10 +34,15 @@ export const tunnelCommand = command(
3234
option(
3335
"-s",
3436
"--service",
35-
choice(["localhost.run", "serveo.net", "pinggy.io"], {
37+
"--tunnel-service",
38+
choice(TUNNEL_SERVICES, {
3639
metavar: "SERVICE",
3740
}),
38-
{ description: message`The tunneling service to use.` },
41+
{
42+
description: message`The tunneling service to use: ${
43+
valueSet(TUNNEL_SERVICES)
44+
}.`,
45+
},
3946
),
4047
),
4148
}),

0 commit comments

Comments
 (0)