Plain is headed towards 1.0! Subscribe for development updates →

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