v0.165.0
 1import getpass
 2import random
 3import string
 4
 5import click
 6from plain.cli import register_cli
 7
 8from .client import TunnelClient
 9
10
11@register_cli("tunnel")
12@click.command()
13@click.argument("destination")
14@click.option(
15    "--subdomain",
16    help="The subdomain to use for the tunnel.",
17    envvar="PLAIN_TUNNEL_SUBDOMAIN",
18)
19@click.option(
20    "--tunnel-host", envvar="PLAIN_TUNNEL_HOST", hidden=True, default="plaintunnel.com"
21)
22@click.option("--debug", "log_level", flag_value="DEBUG", help="Enable debug logging.")
23@click.option(
24    "--quiet", "log_level", flag_value="WARNING", help="Only log warnings and errors."
25)
26def cli(
27    destination: str, subdomain: str | None, tunnel_host: str, log_level: str | None
28) -> None:
29    """Create a public tunnel to local server"""
30    if not destination.startswith("http://") and not destination.startswith("https://"):
31        destination = f"https://{destination}"
32
33    # Strip trailing slashes from the destination URL (maybe even enforce no path at all?)
34    destination = destination.rstrip("/")
35
36    if not log_level:
37        log_level = "INFO"
38
39    if not subdomain:
40        # Generate a subdomain using the system username + 7 random characters
41        random_chars = "".join(random.choices(string.ascii_lowercase, k=7))
42        subdomain = f"{getpass.getuser()}-{random_chars}"
43
44    tunnel = TunnelClient(
45        destination_url=destination,
46        subdomain=subdomain,
47        tunnel_host=tunnel_host,
48        log_level=log_level,
49    )
50    click.secho(f"Tunneling {tunnel.tunnel_http_url} -> {destination}", bold=True)
51    tunnel.run()