diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..cefb898 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,27 @@ +name: docs +on: + push: + branches: + - main +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set outputs + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + - name: build docs site + run: make ssg + - name: publish to pgs + uses: picosh/pgs-action@v3 + with: + user: hey + key: ${{ secrets.PRIVATE_KEY }} + src: './public/' + project: "sish-${{ steps.vars.outputs.sha_short }}" + promote: "sish-prod" + retain: "sish-" diff --git a/.gitignore b/.gitignore index ec1f2e3..90fce30 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ deploy/ dist/ sish __debug_bin +docs/public/* +!docs/public/.gitkeep diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8b36002 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +clean: + rm -rf ./docs/public/* + echo "" > ./docs/public/.gitkeep +.PHONY: clean + +ssg: + go run ./docs/cmd + cp ./docs/static/* ./docs/public +.PHONY: ssg + +docs: ssg + rsync -vr ./docs/public/ hey@pgs.sh:/sish-local +.PHONY: docs + +docs-prod: ssg + rsync -vr ./docs/public/ hey@pgs.sh:/sish-prod +.PHONY: docs-prod + +dev: + go run main.go --http-address localhost:3000 --domain testing.ssi.sh +.PHONY: dev diff --git a/README.md b/README.md index 812321f..629fc1b 100644 --- a/README.md +++ b/README.md @@ -2,425 +2,35 @@ An open source serveo/ngrok alternative. -## Deploy +[Read the docs.](https://docs.ssi.sh) -Builds are made automatically for each commit to the repo and are pushed to Dockerhub. Builds are tagged using a commit sha, -branch name, tag, latest if released on main. You can find a list [here](https://hub.docker.com/r/antoniomika/sish/tags). -Each release builds separate `sish` binaries that can be downloaded from [here](https://github.com/antoniomika/sish/releases) for various OS/archs. Feel free to either use the automated binaries or to build your own. If you submit a PR, images are -not built by default and will require a retag from a maintainer to be built. +## dev -1. Pull the Docker image - - `docker pull antoniomika/sish:latest` -2. Run the image - - - ```bash - docker run -itd --name sish \ - -v ~/sish/ssl:/ssl \ - -v ~/sish/keys:/keys \ - -v ~/sish/pubkeys:/pubkeys \ - --net=host antoniomika/sish:latest \ - --ssh-address=:22 \ - --http-address=:80 \ - --https-address=:443 \ - --https=true \ - --https-certificate-directory=/ssl \ - --authentication-keys-directory=/pubkeys \ - --private-keys-directory=/keys \ - --bind-random-ports=false - ``` - -3. SSH to your host to communicate with sish - - `ssh -p 2222 -R 80:localhost:8080 ssi.sh` - -## Docker Compose - -You can also use Docker Compose to setup your sish instance. This includes taking -care of SSL via Let's Encrypt for you. This uses the -[adferrand/dnsrobocert](https://github.com/adferrand/dnsrobocert) container to handle issuing wildcard -certifications over DNS. For more information on how to use this, head to that link above. Generally, you -can deploy your service like so: +Clone the `sish` repo: ```bash -docker-compose -f deploy/docker-compose.yml up -d +git clone git@github.com:antoniomika/sish.git +cd sish ``` -The domain and DNS auth info in `deploy/docker-compose.yml` and `deploy/le-config.yml` should be updated -to reflect your needs. You will also need to create a symlink that points to your domain's -Let's Encrypt certificates like: +Add your SSH public key: ```bash -ln -s /etc/letsencrypt/live//fullchain.pem deploy/ssl/.crt -ln -s /etc/letsencrypt/live//privkey.pem deploy/ssl/.key +cp ~/.ssh/id_ed25519.pub ./deploy/pubkeys ``` -Careful: the symlinks need to point to `/etc/letsencrypt`, not a relative path. The symlinks will -not resolve on the host filesystem, but they will resolve inside of the sish container because it mounts -the letsencrypt files in /etc/letsencrypt, _not_ ./letsencrypt. - -I use these files in my deployment of `ssi.sh` and have included them here for consistency. - -## Google Cloud Platform - -There is a tutorial for creating an instance in Google Cloud Platform -with sish fully setup that can be found [here](https://github.com/antoniomika/sish/blob/main/deploy/gcloud.md). -It can be accessed through [Google Cloud Shell](https://cloud.google.com/shell). - -[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://ssh.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fantoniomika%2Fsish&cloudshell_git_branch=main&cloudshell_tutorial=deploy%2Fgcloud.md) - -## How it works - -SSH can normally forward local and remote ports. This service implements -an SSH server that only handles forwarding and nothing else. The service supports -multiplexing connections over HTTP/HTTPS with WebSocket support. Just assign a -remote port as port `80` to proxy HTTP traffic and `443` to proxy HTTPS traffic. -If you use any other remote port, the server will listen to the port for TCP connections, -but only if that port is available. - -You can choose your own subdomain instead of relying on a randomly assigned one -by setting the `--bind-random-subdomains` option to `false` and then selecting a -subdomain by prepending it to the remote port specifier: - -`ssh -p 2222 -R foo:80:localhost:8080 ssi.sh` - -If the selected subdomain is not taken, it will be assigned to your connection. - -## Supported forwarding types - -### HTTP forwarding - -sish can forward any number of HTTP connections through SSH. It also provides logging the connections -to the connected client that has forwarded the connection and a web interface to see full request and -responses made to each forwarded connection. Each webinterface can be unique to the forwarded connection or -use a unified access token. To make use of HTTP forwarding, ports `[80, 443]` are used to tell sish that a -HTTP connection is being forwarded and that HTTP virtualhosting should be defined for the service. For -example, let's say I'm -developing a HTTP webservice on my laptop at port `8080` that uses websockets and I want to show one of my -coworkers who is not near me. I can forward the connection like so: +Run the binary: ```bash -ssh -R hereiam:80:localhost:8080 ssi.sh +go run main.go --http-address localhost:3000 --domain testing.ssi.sh ``` -And then share the link `https://hereiam.ssi.sh` with my coworker. They should be able to access the service -seamlessly over HTTPS, with full websocket support working fine. Let's say `hereiam.ssi.sh` isn't available, -then sish will generate a random subdomain and give that to me. - -### TCP forwarding - -Any TCP based service can be used with sish for TCP and alias forwarding. TCP forwarding -will establish a remote port on the server that you deploy sish to and will forward all connections -to that port through the SSH connection and to your local device. For example, if I was to run -a SSH server on my laptop with port `22` and want to be able to access it from anywhere at `ssi.sh:2222`, -I can use an SSH command on my laptop like so to forward the connection: - -```bash -ssh -R 2222:localhost:22 ssi.sh -``` - -I can use the forwarded connection to then access my laptop from anywhere: - -```bash -ssh -p 2222 ssi.sh -``` - -### SNI forwarding - -Sometimes, you may have multiple TCP services running on the same port. -If these services support [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication), you can have sish -route TLS connections to different backends based on the SNI name provided. For example, I have two webservices -(servers) and I want to offload TLS to each without sish offloading SSL. This can be achieved by disabling sish's -internal HTTPS service (you won't be able to use the service console for this however). Then, I can start a ssh -connection from each server like so: - -From server A - -```bash -ssh -R servera.example.com:443:localhost:443 ssi.sh sni-proxy=true -``` - -From server B - -```bash -ssh -R serverb.example.com:443:localhost:443 ssi.sh sni-proxy=true -``` - -As long as server{a,b}.example.com points to where sish is hosted and a user can bind those hosts, TLS connections to -servera.example.com:443 will be forwarded to server A and TLS connections to serverb.example.com:443 will -be forwarded to server B. It is then up to each server to complete the TLS handshake and the subsequent request. - -### TCP alias forwarding - -Let's say instead I don't want the service to be accessible by the rest of the world, you can then use a TCP -alias. A TCP alias is a type of forwarded TCP connection that only exists inside of sish. You can gain access -to the alias by using SSH with the `-W` flag, which will forwarding the SSH process' stdin/stdout to the -fowarded TCP connection. In combination with authentication, this will guarantee your remote service is safe -from the rest of the world because you need to login to sish before you can access it. Changing the example -above for this would mean running the following command on my laptop: - -```bash -ssh -R mylaptop:22:localhost:22 ssi.sh -``` - -sish won't publish port 22 or 2222 to the rest of the world anymore, instead it'll retain a pointer saying -that TCP connections made from within SSH after a user has authenticated to `mylaptop:22` should be -forwarded to the forwarded TCP tunnel. Then I can use the forwarded connection access my laptop from -anywhere using: - -```bash -ssh -o ProxyCommand="ssh -W %h:%p ssi.sh" mylaptop -``` +We have an alias `make dev` for running the binary. -Shorthand for which is this with newer SSH versions: +SSH to your host to communicate with sish: ```bash -ssh -J ssi.sh mylaptop -``` - -You can also use TCP aliases with any port you would like. If for example you wanted to use an alias -with port `80` or `443` (default to a HTTP tunnel), provide the command `tcp-alias=true` to the ssh command: - -```bash -ssh -R service:80:localhost:80 ssi.sh tcp-alias=true -``` - -Aliases can be accessed on a different computer using SSH local forwards also. For the above, I could use: - -```bash -ssh -L 80:service:80 ssi.sh -``` - -to then access the forwarded server service at `localhost:80` on the client side of the computer I am on. - -## Authentication - -If you want to use this service privately, it supports both public key and password -authentication. To enable authentication, set `--authentication=true` as one of your CLI -options and be sure to configure `--authentication-password` or `--authentication-keys-directory` to your -liking. The directory provided by `--authentication-keys-directory` is watched for changes and will reload -the authorized keys automatically. The authorized cert index is regenerated on directory -modification, so removed public keys will also automatically be removed. Files in this -directory can either be single key per file, or multiple keys per file separated by newlines, -similar to `authorized_keys`. Password auth can be disabled by setting `--authentication-password=""` as a -CLI option. - -One of my favorite ways of using this for authentication is like so: - -```bash -sish@sish0:~/sish/pubkeys# curl https://github.com/antoniomika.keys > antoniomika -``` - -This will load my public keys from GitHub, place them in the directory that sish is watching, -and then load the pubkey. As soon as this command is run, I can SSH normally and it will authorize me. - -## Custom domains - -sish supports allowing users to bring custom domains to the service, but SSH key auth is required to be -enabled. To use this feature, you must setup TXT and CNAME/A records for the domain/subdomain you would -like to use for your forwarded connection. The CNAME/A record must point to the domain or IP that is hosting -sish. The TXT record must be be a `key=val` string that looks like: - -```text -sish=SSHKEYFINGERPRINT -``` - -Where `SSHKEYFINGERPRINT` is the fingerprint of the key used for logging into the server. You can set -multiple TXT records and sish will check all of them to ensure at least one is a match. You can retrieve -your key fingerprint by running: - -```bash -ssh-keygen -lf ~/.ssh/id_rsa | awk '{print $2}' -``` - -If you trust the users connecting to sish and would like to allow any domain to be used with sish -(bypassing verification), there are a few added flags to aid in this. This is especially useful when -adding multiple wildcard certificates to sish in order to not need to automatically provision Let's -Encrypt certs. To disable verfication, set `--bind-any-host=true`, which will allow and subdomain/domain -combination to be used. To only allow subdomains of a certain subset of domains, you can set `--bind-hosts` -to a comma separated list of domains that are allowed to be bound. - -To add certficates for sish to use, configure the `--https-certificate-directory` flag to point to a dir -that is accessible by sish. In the directory, sish will look for a combination of files that look like -`name.crt` and `name.key`. `name` can be arbitrary in either case, it just needs to be unique to the cert -and key pair to allow them to be loaded into sish. - -## Load balancing - -sish can load balance any type of forwarded connection, but this needs to be enabled when starting sish -using the `--http-load-balancer`, -`--tcp-load-balancer`, and `--alias-load-balancer` flags. Let's say you have a few edge nodes -(raspberry pis) that are running a service internally but you want to be able to balance load across these -devices from the outside world. By enabling load balancing in sish, this happens automatically when a -device with the same forwarded TCP port, alias, or HTTP subdomain connects to sish. Connections will then be -evenly distributed to whatever nodes are connected to sish that match the forwarded connection. - -## Whitelisting IPs - -Whitelisting IP ranges or countries is also possible. Whole CIDR ranges can be -specified with the `--whitelisted-ips` option that accepts a comma-separated -string like "192.30.252.0/22,185.199.108.0/22". If you want to whitelist a single -IP, use the `/32` range. - -To whitelist countries, use `--whitelisted-countries` with a comma-separated -string of countries in ISO format (for example, "pt" for Portugal). You'll also -need to set `--geodb` to `true`. - -## DNS Setup - -To use sish, you need to add a wildcard DNS record that is used for multiplexed subdomains. -Adding an `A` record with `*` as the subdomain to the IP address of your server is the simplest way to achieve this configuration. - -## Notes - -1. This is by no means production ready in any way. This was hacked together and solves a fairly specific -use case. - - You can help it get production ready by submitting PRs/reviewing code/writing tests/etc -2. This is a fairly simple implementation, I've intentionally cut corners in some places to make it easier -to write. -3. If you have any questions or comments, feel free to reach out via email -[me@antoniomika.me](mailto:me@antoniomika.me) -or on [freenode IRC #sish](https://kiwiirc.com/client/chat.freenode.net:6697/#sish) - -## Upgrading to v2.0 - -v2 introduces only a few breaking changes, namely around authentication. v2 enables authentication by default. If you were -an authenticated instance before, be sure to set `--authentication` accordingly. v2 also brings support for multiple -SSH host private keys, which allows you to use different encryption schemes. This changed the `--private-key-location` to -`--private-keys-directory`. Keys generated or previously used in sish will work as normal, just be sure to update this -argument if it was changed from the default. - -## Upgrading to v1.0 - -There are numerous breaking changes in sish between pre-1.0 and post-1.0 versions. The largest changes are -found in the mapping of command flags and configuration params. Those have changed drastically, but it should be easy -to find the new counterpart. The other change is SSH keys that are supported for host key auth. sish -continues to support most modern keys, but by default if a host key is not found, it will create an OpenSSH -ED25519 key to use. Previous versions of sish would aes encrypt the pem block of this private key, but we -have since moved to using the native -[OpenSSH private key format](https://github.com/openssh/openssh-portable/blob/master/sshkey.c) to allow for -easy interop between OpenSSH tools. For this reason, you will either have to manually convert an AES -encrypted key or generate a new one. - -## CLI Flags - -```text -sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and load balancing. -It can handle multiple vhosting and reverse tunneling endpoints for a large number of clients. - -Usage: - sish [flags] - -Flags: - --admin-console Enable the admin console accessible at http(s)://domain/_sish/console?x-authorization=admin-console-token - -j, --admin-console-token string The token to use for admin console access if it's enabled - --alias-load-balancer Enable the alias load balancer (multiple clients can bind the same alias) - --append-user-to-subdomain Append the SSH user to the subdomain. This is useful in multitenant environments - --append-user-to-subdomain-separator string The token to use for separating username and subdomain selection in a virtualhost (default "-") - --authentication Require authentication for the SSH service (default true) - --authentication-key-request-timeout duration Duration to wait for a response from the authentication key request (default 5s) - --authentication-key-request-url string A url to validate public keys for public key authentication. - sish will make an HTTP POST request to this URL with a JSON body containing an - OpenSSH 'authorized key' formatted public key, username, - and ip address. E.g.: - {"auth_key": string, "user": string, "remote_addr": string} - A response with status code 200 indicates approval of the auth key - -k, --authentication-keys-directory string Directory where public keys for public key authentication are stored. - sish will watch this directory and automatically load new keys and remove keys - from the authentication list (default "deploy/pubkeys/") - --authentication-keys-directory-watch-interval duration The interval to poll for filesystem changes for SSH keys (default 200ms) - -u, --authentication-password string Password to use for SSH server password authentication - --banned-aliases string A comma separated list of banned aliases that users are unable to bind - -o, --banned-countries string A comma separated list of banned countries. Applies to HTTP, TCP, and SSH connections - -x, --banned-ips string A comma separated list of banned ips that are unable to access the service. Applies to HTTP, TCP, and SSH connections - -b, --banned-subdomains string A comma separated list of banned subdomains that users are unable to bind (default "localhost") - --bind-any-host Allow binding any host when accepting an HTTP listener - --bind-hosts string A comma separated list of other hosts a user can bind. Requested hosts should be subdomains of a host in this list - --bind-http-auth Allow binding http auth on a forwarded host (default true) - --bind-http-path Allow binding specific paths on a forwarded host (default true) - --bind-random-aliases Force bound alias tunnels to use random aliases instead of user provided ones (default true) - --bind-random-aliases-length int The length of the random alias to generate if a alias is unavailable or if random aliases are enforced (default 3) - --bind-random-ports Force TCP tunnels to bind a random port, where the kernel will randomly assign it (default true) - --bind-random-subdomains Force bound HTTP tunnels to use random subdomains instead of user provided ones (default true) - --bind-random-subdomains-length int The length of the random subdomain to generate if a subdomain is unavailable or if random subdomains are enforced (default 3) - --bind-root-domain Allow binding the root domain when accepting an HTTP listener - --bind-wildcards Allow binding wildcards when accepting an HTTP listener - --cleanup-unauthed Cleanup unauthed SSH connections after a set timeout (default true) - --cleanup-unauthed-timeout duration Duration to wait before cleaning up an unauthed connection (default 5s) - --cleanup-unbound Cleanup unbound (unforwarded) SSH connections after a set timeout - --cleanup-unbound-timeout duration Duration to wait before cleaning up an unbound (unforwarded) connection (default 5s) - -c, --config string Config file (default "config.yml") - --debug Enable debugging information - --debug-interval duration Duration to wait between each debug loop output if debug is true (default 2s) - -d, --domain string The root domain for HTTP(S) multiplexing that will be appended to subdomains (default "ssi.sh") - --force-all-https Redirect all requests to the https server - --force-https Allow indiviual binds to request for https to be enforced - --force-requested-aliases Force the aliases used to be the one that is requested. Will fail the bind if it exists already - --force-requested-ports Force the ports used to be the one that is requested. Will fail the bind if it exists already - --force-requested-subdomains Force the subdomains used to be the one that is requested. Will fail the bind if it exists already - --force-tcp-address Force the address used for the TCP interface to be the one defined by --tcp-address - --geodb Use a geodb to verify country IP address association for IP filtering - -h, --help help for sish - -i, --http-address string The address to listen for HTTP connections (default "localhost:80") - --http-load-balancer Enable the HTTP load balancer (multiple clients can bind the same domain) - --http-port-override int The port to use for http command output. This does not affect ports used for connecting, it's for cosmetic use only - --http-request-port-override int The port to use for http requests. Will default to 80, then http-port-override. Otherwise will use this value - --https Listen for HTTPS connections. Requires a correct --https-certificate-directory - -t, --https-address string The address to listen for HTTPS connections (default "localhost:443") - -s, --https-certificate-directory string The directory containing HTTPS certificate files (name.crt and name.key). There can be many crt/key pairs (default "deploy/ssl/") - --https-certificate-directory-watch-interval duration The interval to poll for filesystem changes for HTTPS certificates (default 200ms) - --https-ondemand-certificate Enable retrieving certificates on demand via Let's Encrypt - --https-ondemand-certificate-accept-terms Accept the Let's Encrypt terms - --https-ondemand-certificate-email string The email to use with Let's Encrypt for cert notifications. Can be left blank - --https-port-override int The port to use for https command output. This does not affect ports used for connecting, it's for cosmetic use only - --https-request-port-override int The port to use for https requests. Will default to 443, then https-port-override. Otherwise will use this value - --idle-connection Enable connection idle timeouts for reads and writes (default true) - --idle-connection-timeout duration Duration to wait for activity before closing a connection for all reads and writes (default 5s) - --load-templates Load HTML templates. This is required for admin/service consoles (default true) - --load-templates-directory string The directory and glob parameter for templates that should be loaded (default "templates/*") - --localhost-as-all Enable forcing localhost to mean all interfaces for tcp listeners (default true) - --log-to-client Enable logging HTTP and TCP requests to the client - --log-to-file Enable writing log output to file, specified by log-to-file-path - --log-to-file-compress Enable compressing log output files - --log-to-file-max-age int The maxium number of days to store log output in a file (default 28) - --log-to-file-max-backups int The maxium number of rotated logs files to keep (default 3) - --log-to-file-max-size int The maximum size of outputed log files in megabytes (default 500) - --log-to-file-path string The file to write log output to (default "/tmp/sish.log") - --log-to-stdout Enable writing log output to stdout (default true) - --ping-client Send ping requests to the underlying SSH client. - This is useful to ensure that SSH connections are kept open or close cleanly (default true) - --ping-client-interval duration Duration representing an interval to ping a client to ensure it is up (default 5s) - --ping-client-timeout duration Duration to wait for activity before closing a connection after sending a ping to a client (default 5s) - -n, --port-bind-range string Ports or port ranges that sish will allow to be bound when a user attempts to use TCP forwarding (default "0,1024-65535") - -p, --private-key-passphrase string Passphrase to use to encrypt the server private key (default "S3Cr3tP4$$phrAsE") - -l, --private-keys-directory string The location of other SSH server private keys. sish will add these as valid auth methods for SSH. Note, these need to be unencrypted OR use the private-key-passphrase (default "deploy/keys") - --proxy-protocol Use the proxy-protocol while proxying connections in order to pass-on IP address and port information - --proxy-protocol-listener Use the proxy-protocol to resolve ip addresses from user connections - --proxy-protocol-policy string What to do with the proxy protocol header. Can be use, ignore, reject, or require (default "use") - --proxy-protocol-timeout duration The duration to wait for the proxy proto header (default 200ms) - --proxy-protocol-use-timeout Use a timeout for the proxy-protocol read - -q, --proxy-protocol-version string What version of the proxy protocol to use. Can either be 1, 2, or userdefined. - If userdefined, the user needs to add a command to SSH called proxyproto=version (ie proxyproto=1) (default "1") - --redirect-root Redirect the root domain to the location defined in --redirect-root-location (default true) - -r, --redirect-root-location string The location to redirect requests to the root domain - to instead of responding with a 404 (default "https://github.com/antoniomika/sish") - --rewrite-host-header Force rewrite the host header if the user provides host-header=host.com (default true) - --service-console Enable the service console for each service and send the info to connected clients - --service-console-max-content-length int The max content length before we stop reading the response body (default -1) - -m, --service-console-token string The token to use for service console access. Auto generated if empty for each connected tunnel - --sni-load-balancer Enable the SNI load balancer (multiple clients can bind the same SNI domain/port) - --sni-proxy Enable the use of SNI proxying - --sni-proxy-https Enable the use of SNI proxying on the HTTPS port - -a, --ssh-address string The address to listen for SSH connections (default "localhost:2222") - --strip-http-path Strip the http path from the forward (default true) - --tcp-address string The address to listen for TCP connections - --tcp-aliases Enable the use of TCP aliasing - --tcp-aliases-allowed-users any Enable setting allowed users to access tcp aliases. - Can provide tcp-aliases-allowed-users in the ssh command set to a comma separated list of ssh fingerprints that can access an alias. - Provide any for all. - --tcp-load-balancer Enable the TCP load balancer (multiple clients can bind the same port) - --time-format string The time format to use for both HTTP and general log messages (default "2006/01/02 - 15:04:05") - --verify-dns Verify DNS information for hosts and ensure it matches a connecting users sha256 key fingerprint (default true) - --verify-ssl Verify SSL certificates made on proxied HTTP connections (default true) - -v, --version version for sish - -y, --whitelisted-countries string A comma separated list of whitelisted countries. Applies to HTTP, TCP, and SSH connections - -w, --whitelisted-ips string A comma separated list of whitelisted ips. Applies to HTTP, TCP, and SSH connections +ssh -p 2222 -R 80:localhost:8080 testing.ssi.sh ``` +> The `testing.ssi.sh` DNS record points to `localhost` so anyone can use it for +> development diff --git a/deploy/gcloud.md b/deploy/gcloud.md index 82cc9bd..6358a5c 100644 --- a/deploy/gcloud.md +++ b/deploy/gcloud.md @@ -1,20 +1,23 @@ # sish installation -sish is an open source serveo/ngrok alternative that can be used to open a tunnel -to localhost that is accessible to the open internet using only SSH. sish implements -an SSH server that can handle multiplexing of HTTP(S), TCP, and TCP Aliasing +sish is an open source serveo/ngrok alternative that can be used to open a +tunnel to localhost that is accessible to the open internet using only SSH. sish +implements an SSH server that can handle multiplexing of HTTP(S), TCP, and TCP +Aliasing ([more about this can be found in the README](https://github.com/antoniomika/sish/blob/main/README.md)) This tutorial will teach you how to: -* Setup an instance in Google Cloud using the [free tier](https://cloud.google.com/free) -* Add and modify authentication for users -* Access sish from a remote computer +- Setup an instance in Google Cloud using the + [free tier](https://cloud.google.com/free) +- Add and modify authentication for users +- Access sish from a remote computer ## Project selection -You first need to select a project to host the resources created in this tutorial. -I'd suggest creating a new project at this time where your sish instance will live. +You first need to select a project to host the resources created in this +tutorial. I'd suggest creating a new project at this time where your sish +instance will live. ## Access Google Cloud Shell @@ -23,14 +26,18 @@ I'd suggest creating a new project at this time where your sish instance will li ## Create the instance running the container -Here is a command to create the instance running the sish container. This will start the container -on a hardened [Container Optimized OS](https://cloud.google.com/container-optimized-os/docs) and start -the service. This is just a starting command that runs sish on port `2222`, `80`, and `443`. If you -accept the [Let's Encrypt TOS](https://letsencrypt.org/repository/), you can enable automatic SSL cert loading. -This command does *NOT* include authentication and it is up to you to properly tune these parameters based on -the documentation [here](https://github.com/antoniomika/sish#cli-flags). Make sure to update `YOURDOMAIN` -to the actual domain you own. You will also need to setup the DNS records as described below. Also feel free -to change the `--zone` used for these commands. +Here is a command to create the instance running the sish container. This will +start the container on a hardened +[Container Optimized OS](https://cloud.google.com/container-optimized-os/docs) +and start the service. This is just a starting command that runs sish on port +`2222`, `80`, and `443`. If you accept the +[Let's Encrypt TOS](https://letsencrypt.org/repository/), you can enable +automatic SSL cert loading. This command does _NOT_ include authentication and +it is up to you to properly tune these parameters based on the documentation +[here](https://github.com/antoniomika/sish#cli-flags). Make sure to update +`YOURDOMAIN` to the actual domain you own. You will also need to setup the DNS +records as described below. Also feel free to change the `--zone` used for these +commands. ```bash gcloud compute instances create-with-container sish \ @@ -82,8 +89,8 @@ gcloud compute firewall-rules create allow-all-tcp-sish \ Get the external IP address of your machine and create two DNS records -* An `A` record for YOURDOMAIN pointing it to the output below -* An `A` record for *.YOURDOMAIN pointing it to the output below +- An `A` record for YOURDOMAIN pointing it to the output below +- An `A` record for *.YOURDOMAIN pointing it to the output below ```bash gcloud compute instances describe sish \ @@ -91,8 +98,9 @@ gcloud compute instances describe sish \ --format='get(networkInterfaces[0].accessConfigs[0].natIP)' ``` -To confirm that the DNS records were created appropriately, you can run: -`dig @1.1.1.1 abcxyz.YOURDOMAIN` and `dig @1.1.1.1 YOURDOMAIN`. Both should get an answer with `status: NOERROR`. +To confirm that the DNS records were created appropriately, you can run:\ +`dig @1.1.1.1 abcxyz.YOURDOMAIN` and `dig @1.1.1.1 YOURDOMAIN`. Both should get +an answer with `status: NOERROR`. ## Using sish diff --git a/docs/cmd/ssg.go b/docs/cmd/ssg.go new file mode 100644 index 0000000..02f4e5f --- /dev/null +++ b/docs/cmd/ssg.go @@ -0,0 +1,94 @@ +package main + +import ( + "github.com/picosh/pdocs" +) + +func main() { + pager := pdocs.Pager("./docs/posts") + sitemap := []*pdocs.Sitemap{ + {Text: "Home", Href: "/", Page: pager("home.md")}, + {Text: "Sitemap", Href: "/sitemap", Page: pager("sitemap.md")}, + { + Text: "Getting Started", + Href: "/getting-started", + Page: pager("getting-started.md"), + Tag: "Help", + Children: []*pdocs.Sitemap{ + {Text: "Managed"}, + {Text: "DNS"}, + {Text: "Docker Compose"}, + {Text: "Docker"}, + {Text: "Google Cloud Platform"}, + {Text: "Authentication"}, + }, + }, + { + Text: "How it Works", + Href: "/how-it-works", + Page: pager("how-it-works.md"), + Tag: "Help", + Children: []*pdocs.Sitemap{ + {Text: "Port Forward"}, + {Text: "Traditional VPN"}, + {Text: "sish Public"}, + {Text: "sish Private"}, + {Text: "Additional Details"}, + }, + }, + { + Text: "Forwarding Types", + Href: "/forwarding-types", + Page: pager("forwarding-types.md"), + Tag: "Help", + Children: []*pdocs.Sitemap{ + {Text: "HTTP"}, + {Text: "TCP"}, + {Text: "TCP Alias"}, + {Text: "SNI"}, + }, + }, + { + Text: "Cheatsheet", + Href: "/cheatsheet", + Page: pager("cheatsheet.md"), + Tag: "Help", + Children: []*pdocs.Sitemap{ + {Text: "Remote forward SSH tunnels"}, + {Text: "Local forward SSH tunnels"}, + {Text: "HTTPS public access"}, + {Text: "HTTPS private access"}, + {Text: "Websocket"}, + {Text: "TCP public access"}, + {Text: "TCP private access"}, + }, + }, + {Text: "CLI", Href: "/cli", Page: pager("cli.md"), Tag: "CLI"}, + { + Text: "Advanced", + Href: "/advanced", + Page: pager("advanced.md"), + Children: []*pdocs.Sitemap{ + {Text: "Choose your own subdomain"}, + {Text: "Websocket Support"}, + {Text: "Allowlist IPs"}, + {Text: "Custom Domains"}, + {Text: "Load Balancing"}, + }, + Tag: "Help", + }, + {Text: "FAQ", Href: "/faq", Page: pager("faq.md"), Tag: "Help"}, + } + + config := &pdocs.DocConfig{ + Sitemap: sitemap, + Out: "./docs/public", + Tmpl: "./docs/tmpl", + PageTmpl: "post.page.tmpl", + } + + err := config.GenSite() + if err != nil { + panic(err) + } +} diff --git a/docs/posts/advanced.md b/docs/posts/advanced.md new file mode 100644 index 0000000..5428e2d --- /dev/null +++ b/docs/posts/advanced.md @@ -0,0 +1,81 @@ +--- +title: Advanced +description: How to customize sish +keywords: [sish, advanced, custom, domains, load, balancing, allowlist, ip] +--- + +# Choose your own subdomain + +You can choose your own subdomain instead of relying on a randomly assigned one +by setting the `--bind-random-subdomains` option to `false` and then selecting a +subdomain by prepending it to the remote port specifier: + +`ssh -p 2222 -R foo:80:localhost:8080 tuns.sh` + +If the selected subdomain is not taken, it will be assigned to your connection. + +# Websocket Support + +The service supports multiplexing connections over HTTP/HTTPS with WebSocket +support. Just assign a remote port as port `80` to proxy HTTP traffic and `443` +to proxy HTTPS traffic. If you use any other remote port, the server will listen +to the port for TCP connections, but only if that port is available. + +# Allowlist IPs + +Whitelisting IP ranges or countries is also possible. Whole CIDR ranges can be +specified with the `--whitelisted-ips` option that accepts a comma-separated +string like "192.30.252.0/22,185.199.108.0/22". If you want to whitelist a +single IP, use the `/32` range. + +To whitelist countries, use `--whitelisted-countries` with a comma-separated +string of countries in ISO format (for example, "pt" for Portugal). You'll also +need to set `--geodb` to `true`. + +# Custom domains + +sish supports allowing users to bring custom domains to the service, but SSH key +auth is required to be enabled. To use this feature, you must setup TXT and +CNAME/A records for the domain/subdomain you would like to use for your +forwarded connection. The CNAME/A record must point to the domain or IP that is +hosting sish. The TXT record must be be a `key=val` string that looks like: + +```text +sish=SSHKEYFINGERPRINT +``` + +Where `SSHKEYFINGERPRINT` is the fingerprint of the key used for logging into +the server. You can set multiple TXT records and sish will check all of them to +ensure at least one is a match. You can retrieve your key fingerprint by +running: + +```bash +ssh-keygen -lf ~/.ssh/id_rsa | awk '{print $2}' +``` + +If you trust the users connecting to sish and would like to allow any domain to +be used with sish (bypassing verification), there are a few added flags to aid +in this. This is especially useful when adding multiple wildcard certificates to +sish in order to not need to automatically provision Let's Encrypt certs. To +disable verfication, set `--bind-any-host=true`, which will allow and +subdomain/domain combination to be used. To only allow subdomains of a certain +subset of domains, you can set `--bind-hosts` to a comma separated list of +domains that are allowed to be bound. + +To add certficates for sish to use, configure the +`--https-certificate-directory` flag to point to a dir that is accessible by +sish. In the directory, sish will look for a combination of files that look like +`name.crt` and `name.key`. `name` can be arbitrary in either case, it just needs +to be unique to the cert and key pair to allow them to be loaded into sish. + +# Load balancing + +sish can load balance any type of forwarded connection, but this needs to be +enabled when starting sish using the `--http-load-balancer`, +`--tcp-load-balancer`, and `--alias-load-balancer` flags. Let's say you have a +few edge nodes (raspberry pis) that are running a service internally but you +want to be able to balance load across these devices from the outside world. By +enabling load balancing in sish, this happens automatically when a device with +the same forwarded TCP port, alias, or HTTP subdomain connects to sish. +Connections will then be evenly distributed to whatever nodes are connected to +sish that match the forwarded connection. diff --git a/docs/posts/cheatsheet.md b/docs/posts/cheatsheet.md new file mode 100644 index 0000000..1091997 --- /dev/null +++ b/docs/posts/cheatsheet.md @@ -0,0 +1,121 @@ +--- +title: Cheatsheet +description: sish usage reference +keywords: [sish, reference, cheatsheet] +--- + +[More info about forwarding types](/forwarding-types) + +# Remote forward SSH tunnels + +Full example: + +```bash +ssh -R subdomain:80:localhost:3000 tuns.sh +# |__| +# remote forward + +ssh -R subdomain:80:localhost:3000 tuns.sh +# |_________| +# subdomain.tuns.sh + +ssh -R subdomain:80:localhost:3000 tuns.sh +# |______________| +# local web server +``` + +Dropping the subdomain: + +```bash +ssh -R 80:localhost:3000 tuns.sh +# |__| +# autogenerated.tuns.sh +# access local server over http (443 for https) + +ssh -R 80:localhost:3000 tuns.sh +# |______________| +# local web server over http +``` + +# Local forward SSH tunnels + +Given remote forward to `subdomain.tuns.sh:80` + +```bash +ssh -L 3000:subdomain:80 tuns.sh +# |__| +# local forward + +ssh -L 3000:subdomain:80 tuns.sh +# |____| +# access tunnel at localhost:3000 + +ssh -L 3000:subdomain:80 tuns.sh +# |____________| +# subdomain.tuns.sh:80 +``` + +# HTTPS public access + +[More info](/forwarding-types#http) + +- Eric has a web server running on `localhost:3000` +- Eric wants to share with anyone +- Tony wants to access it + +Eric sets up remote forward: + +```bash +ssh -R 80:localhost:3000 tuns.sh +``` + +# HTTPS private access + +- Eric has a web server running on `localhost:3000` +- Eric only wants to share with Tony +- Tony wants to access it + +Tony provides Eric with pubkey fingerprint: + +```bash +ssh-keygen -lf ~/.ssh/id_ed25519 +256 SHA256:4vNGm4xvuVxYbaIE5JX1KgTgncaF3x3w2lk+JMLOfd8 your_email@example.com (ED25519) +``` + +Eric sets up remote forward using Tony's fingerprint: + +```bash +ssh -R private:3000:localhost:3000 tuns.sh tcp-aliases-allowed-users=SHA256:4vNGm4xvuVxYbaIE5JX1KgTgncaF3x3w2lk+JMLOfd8 +``` + +Tony sets up local forward: + +```bash +ssh -L 3000:private:3000 tuns.sh +``` + +Tony can access site at `http://localhost:3000` + +# Websocket + +Same method as [HTTPS public access](/cheatsheet#https-public-access). + +# TCP public access + +Expose SSH to the world + +```bash +ssh -R 2222:localhost:22 tuns.sh +``` + +I can use the forwarded connection to then access my laptop from anywhere: + +```bash +ssh -p 2222 tuns.sh +``` + +# TCP private access + +For example if you want to use `netcat` to send files between computers. + +[Setup a TCP alias](/forwarding-types#tcp-alias) diff --git a/docs/posts/cli.md b/docs/posts/cli.md new file mode 100644 index 0000000..56ffee3 --- /dev/null +++ b/docs/posts/cli.md @@ -0,0 +1,130 @@ +--- +title: CLI +description: How use sish's CLI +keywords: [sish, cli] +--- + +```text +```text +sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and load balancing. +It can handle multiple vhosting and reverse tunneling endpoints for a large number of clients. + +Usage: + sish [flags] + +Flags: + --admin-console Enable the admin console accessible at http(s)://domain/_sish/console?x-authorization=admin-console-token + -j, --admin-console-token string The token to use for admin console access if it's enabled + --alias-load-balancer Enable the alias load balancer (multiple clients can bind the same alias) + --append-user-to-subdomain Append the SSH user to the subdomain. This is useful in multitenant environments + --append-user-to-subdomain-separator string The token to use for separating username and subdomain selection in a virtualhost (default "-") + --authentication Require authentication for the SSH service (default true) + --authentication-key-request-timeout duration Duration to wait for a response from the authentication key request (default 5s) + --authentication-key-request-url string A url to validate public keys for public key authentication. + sish will make an HTTP POST request to this URL with a JSON body containing an + OpenSSH 'authorized key' formatted public key, username, + and ip address. E.g.: + {"auth_key": string, "user": string, "remote_addr": string} + A response with status code 200 indicates approval of the auth key + -k, --authentication-keys-directory string Directory where public keys for public key authentication are stored. + sish will watch this directory and automatically load new keys and remove keys + from the authentication list (default "deploy/pubkeys/") + --authentication-keys-directory-watch-interval duration The interval to poll for filesystem changes for SSH keys (default 200ms) + -u, --authentication-password string Password to use for SSH server password authentication + --banned-aliases string A comma separated list of banned aliases that users are unable to bind + -o, --banned-countries string A comma separated list of banned countries. Applies to HTTP, TCP, and SSH connections + -x, --banned-ips string A comma separated list of banned ips that are unable to access the service. Applies to HTTP, TCP, and SSH connections + -b, --banned-subdomains string A comma separated list of banned subdomains that users are unable to bind (default "localhost") + --bind-any-host Allow binding any host when accepting an HTTP listener + --bind-hosts string A comma separated list of other hosts a user can bind. Requested hosts should be subdomains of a host in this list + --bind-http-auth Allow binding http auth on a forwarded host (default true) + --bind-http-path Allow binding specific paths on a forwarded host (default true) + --bind-random-aliases Force bound alias tunnels to use random aliases instead of user provided ones (default true) + --bind-random-aliases-length int The length of the random alias to generate if a alias is unavailable or if random aliases are enforced (default 3) + --bind-random-ports Force TCP tunnels to bind a random port, where the kernel will randomly assign it (default true) + --bind-random-subdomains Force bound HTTP tunnels to use random subdomains instead of user provided ones (default true) + --bind-random-subdomains-length int The length of the random subdomain to generate if a subdomain is unavailable or if random subdomains are enforced (default 3) + --bind-root-domain Allow binding the root domain when accepting an HTTP listener + --bind-wildcards Allow binding wildcards when accepting an HTTP listener + --cleanup-unauthed Cleanup unauthed SSH connections after a set timeout (default true) + --cleanup-unauthed-timeout duration Duration to wait before cleaning up an unauthed connection (default 5s) + --cleanup-unbound Cleanup unbound (unforwarded) SSH connections after a set timeout + --cleanup-unbound-timeout duration Duration to wait before cleaning up an unbound (unforwarded) connection (default 5s) + -c, --config string Config file (default "config.yml") + --debug Enable debugging information + --debug-interval duration Duration to wait between each debug loop output if debug is true (default 2s) + -d, --domain string The root domain for HTTP(S) multiplexing that will be appended to subdomains (default "ssi.sh") + --force-all-https Redirect all requests to the https server + --force-https Allow indiviual binds to request for https to be enforced + --force-requested-aliases Force the aliases used to be the one that is requested. Will fail the bind if it exists already + --force-requested-ports Force the ports used to be the one that is requested. Will fail the bind if it exists already + --force-requested-subdomains Force the subdomains used to be the one that is requested. Will fail the bind if it exists already + --force-tcp-address Force the address used for the TCP interface to be the one defined by --tcp-address + --geodb Use a geodb to verify country IP address association for IP filtering + -h, --help help for sish + -i, --http-address string The address to listen for HTTP connections (default "localhost:80") + --http-load-balancer Enable the HTTP load balancer (multiple clients can bind the same domain) + --http-port-override int The port to use for http command output. This does not affect ports used for connecting, it's for cosmetic use only + --http-request-port-override int The port to use for http requests. Will default to 80, then http-port-override. Otherwise will use this value + --https Listen for HTTPS connections. Requires a correct --https-certificate-directory + -t, --https-address string The address to listen for HTTPS connections (default "localhost:443") + -s, --https-certificate-directory string The directory containing HTTPS certificate files (name.crt and name.key). There can be many crt/key pairs (default "deploy/ssl/") + --https-certificate-directory-watch-interval duration The interval to poll for filesystem changes for HTTPS certificates (default 200ms) + --https-ondemand-certificate Enable retrieving certificates on demand via Let's Encrypt + --https-ondemand-certificate-accept-terms Accept the Let's Encrypt terms + --https-ondemand-certificate-email string The email to use with Let's Encrypt for cert notifications. Can be left blank + --https-port-override int The port to use for https command output. This does not affect ports used for connecting, it's for cosmetic use only + --https-request-port-override int The port to use for https requests. Will default to 443, then https-port-override. Otherwise will use this value + --idle-connection Enable connection idle timeouts for reads and writes (default true) + --idle-connection-timeout duration Duration to wait for activity before closing a connection for all reads and writes (default 5s) + --load-templates Load HTML templates. This is required for admin/service consoles (default true) + --load-templates-directory string The directory and glob parameter for templates that should be loaded (default "templates/*") + --localhost-as-all Enable forcing localhost to mean all interfaces for tcp listeners (default true) + --log-to-client Enable logging HTTP and TCP requests to the client + --log-to-file Enable writing log output to file, specified by log-to-file-path + --log-to-file-compress Enable compressing log output files + --log-to-file-max-age int The maxium number of days to store log output in a file (default 28) + --log-to-file-max-backups int The maxium number of rotated logs files to keep (default 3) + --log-to-file-max-size int The maximum size of outputed log files in megabytes (default 500) + --log-to-file-path string The file to write log output to (default "/tmp/sish.log") + --log-to-stdout Enable writing log output to stdout (default true) + --ping-client Send ping requests to the underlying SSH client. + This is useful to ensure that SSH connections are kept open or close cleanly (default true) + --ping-client-interval duration Duration representing an interval to ping a client to ensure it is up (default 5s) + --ping-client-timeout duration Duration to wait for activity before closing a connection after sending a ping to a client (default 5s) + -n, --port-bind-range string Ports or port ranges that sish will allow to be bound when a user attempts to use TCP forwarding (default "0,1024-65535") + -p, --private-key-passphrase string Passphrase to use to encrypt the server private key (default "S3Cr3tP4$$phrAsE") + -l, --private-keys-directory string The location of other SSH server private keys. sish will add these as valid auth methods for SSH. Note, these need to be unencrypted OR use the private-key-passphrase (default "deploy/keys") + --proxy-protocol Use the proxy-protocol while proxying connections in order to pass-on IP address and port information + --proxy-protocol-listener Use the proxy-protocol to resolve ip addresses from user connections + --proxy-protocol-policy string What to do with the proxy protocol header. Can be use, ignore, reject, or require (default "use") + --proxy-protocol-timeout duration The duration to wait for the proxy proto header (default 200ms) + --proxy-protocol-use-timeout Use a timeout for the proxy-protocol read + -q, --proxy-protocol-version string What version of the proxy protocol to use. Can either be 1, 2, or userdefined. + If userdefined, the user needs to add a command to SSH called proxyproto=version (ie proxyproto=1) (default "1") + --redirect-root Redirect the root domain to the location defined in --redirect-root-location (default true) + -r, --redirect-root-location string The location to redirect requests to the root domain + to instead of responding with a 404 (default "https://github.com/antoniomika/sish") + --rewrite-host-header Force rewrite the host header if the user provides host-header=host.com (default true) + --service-console Enable the service console for each service and send the info to connected clients + --service-console-max-content-length int The max content length before we stop reading the response body (default -1) + -m, --service-console-token string The token to use for service console access. Auto generated if empty for each connected tunnel + --sni-load-balancer Enable the SNI load balancer (multiple clients can bind the same SNI domain/port) + --sni-proxy Enable the use of SNI proxying + --sni-proxy-https Enable the use of SNI proxying on the HTTPS port + -a, --ssh-address string The address to listen for SSH connections (default "localhost:2222") + --strip-http-path Strip the http path from the forward (default true) + --tcp-address string The address to listen for TCP connections + --tcp-aliases Enable the use of TCP aliasing + --tcp-aliases-allowed-users any Enable setting allowed users to access tcp aliases. + Can provide tcp-aliases-allowed-users in the ssh command set to a comma separated list of ssh fingerprints that can access an alias. + Provide any for all. + --tcp-load-balancer Enable the TCP load balancer (multiple clients can bind the same port) + --time-format string The time format to use for both HTTP and general log messages (default "2006/01/02 - 15:04:05") + --verify-dns Verify DNS information for hosts and ensure it matches a connecting users sha256 key fingerprint (default true) + --verify-ssl Verify SSL certificates made on proxied HTTP connections (default true) + -v, --version version for sish + -y, --whitelisted-countries string A comma separated list of whitelisted countries. Applies to HTTP, TCP, and SSH connections + -w, --whitelisted-ips string A comma separated list of whitelisted ips. Applies to HTTP, TCP, and SSH connections +``` +``` diff --git a/docs/posts/faq.md b/docs/posts/faq.md new file mode 100644 index 0000000..9c787be --- /dev/null +++ b/docs/posts/faq.md @@ -0,0 +1,26 @@ +--- +title: FAQ +description: Frequently asked questions for sish +keywords: [sish, faq] +--- + +# Where can I find latest releases? + +Builds are made automatically for each commit to the repo and are pushed to +Dockerhub. Builds are tagged using a commit sha, branch name, tag, `latest` if +released on `main`. + +- [Image Registry](https://hub.docker.com/r/antoniomika/sish/tags) +- [OS/arch binaries](https://github.com/antoniomika/sish/releases) + +# How does sish compare to ngrok? + +The goals are similar, but the underlying tech is different. With `sish` the +end-user doesn't need to install any cli tool in order to use it. We are simply +leveraging SSH to make the connections that the `ngrok` cli would use. + +# Who can I contact with questions? + +If you have any questions or comments, feel free to reach out via email +[me@antoniomika.me](mailto:me@antoniomika.me) or on libera IRC +[#sish](https://web.libera.chat/#sish) diff --git a/docs/posts/forwarding-types.md b/docs/posts/forwarding-types.md new file mode 100644 index 0000000..62999e1 --- /dev/null +++ b/docs/posts/forwarding-types.md @@ -0,0 +1,124 @@ +--- +title: Forwarding Types +description: The various forwarding types sish supports +keywords: [sish, forwarding, types, http, tcp, sni, alias] +--- + +# HTTP + +sish can forward any number of HTTP connections through SSH. It also provides +logging the connections to the connected client that has forwarded the +connection and a web interface to see full request and responses made to each +forwarded connection. Each webinterface can be unique to the forwarded +connection or use a unified access token. To make use of HTTP forwarding, ports +`[80, 443]` are used to tell sish that a HTTP connection is being forwarded and +that HTTP virtualhosting should be defined for the service. For example, let's +say I'm developing a HTTP webservice on my laptop at port `8080` that uses +websockets and I want to show one of my coworkers who is not near me. I can +forward the connection like so: + +```bash +ssh -R hereiam:80:localhost:8080 tuns.sh +``` + +And then share the link `https://hereiam.tuns.sh` with my coworker. They should +be able to access the service seamlessly over HTTPS, with full websocket support +working fine. Let's say `hereiam.tuns.sh` isn't available, then sish will +generate a random subdomain and give that to me. + +# TCP + +Any TCP based service can be used with sish for TCP and alias forwarding. TCP +forwarding will establish a remote port on the server that you deploy sish to +and will forward all connections to that port through the SSH connection and to +your local device. For example, if I was to run a SSH server on my laptop with +port `22` and want to be able to access it from anywhere at `tuns.sh:2222`, I +can use an SSH command on my laptop like so to forward the connection: + +```bash +ssh -R 2222:localhost:22 tuns.sh +``` + +I can use the forwarded connection to then access my laptop from anywhere: + +```bash +ssh -p 2222 tuns.sh +``` + +# TCP Alias + +Let's say instead I don't want the service to be accessible by the rest of the +world, you can then use a TCP alias. A TCP alias is a type of forwarded TCP +connection that only exists inside of sish. You can gain access to the alias by +using SSH with the `-W` flag, which will forwarding the SSH process' +stdin/stdout to the fowarded TCP connection. In combination with authentication, +this will guarantee your remote service is safe from the rest of the world +because you need to login to sish before you can access it. Changing the example +above for this would mean running the following command on my laptop: + +```bash +ssh -R mylaptop:22:localhost:22 tuns.sh +``` + +sish won't publish port 22 or 2222 to the rest of the world anymore, instead +it'll retain a pointer saying that TCP connections made from within SSH after a +user has authenticated to `mylaptop:22` should be forwarded to the forwarded TCP +tunnel. Then I can use the forwarded connection access my laptop from anywhere +using: + +```bash +ssh -o ProxyCommand="ssh -W %h:%p tuns.sh" mylaptop +``` + +Shorthand for which is this with newer SSH versions: + +```bash +ssh -J tuns.sh mylaptop +``` + +You can also use TCP aliases with any port you would like. If for example you +wanted to use an alias with port `80` or `443` (default to a HTTP tunnel), +provide the command `tcp-alias=true` to the ssh command: + +```bash +ssh -R service:80:localhost:80 tuns.sh tcp-alias=true +``` + +Aliases can be accessed on a different computer using SSH local forwards also. +For the above, I could use: + +```bash +ssh -L 80:service:80 tuns.sh +``` + +to then access the forwarded server service at `localhost:80` on the client side +of the computer I am on. + +# SNI + +Sometimes, you may have multiple TCP services running on the same port. If these +services support [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication), +you can have sish route TLS connections to different backends based on the SNI +name provided. For example, I have two webservices (servers) and I want to +offload TLS to each without sish offloading SSL. This can be achieved by +disabling sish's internal HTTPS service (you won't be able to use the service +console for this however). Then, I can start a ssh connection from each server +like so: + +From server A + +```bash +ssh -R servera.example.com:443:localhost:443 tuns.sh sni-proxy=true +``` + +From server B + +```bash +ssh -R serverb.example.com:443:localhost:443 tuns.sh sni-proxy=true +``` + +As long as server{a,b}.example.com points to where sish is hosted and a user can +bind those hosts, TLS connections to servera.example.com:443 will be forwarded +to server A and TLS connections to serverb.example.com:443 will be forwarded to +server B. It is then up to each server to complete the TLS handshake and the +subsequent request. diff --git a/docs/posts/getting-started.md b/docs/posts/getting-started.md new file mode 100644 index 0000000..2046f63 --- /dev/null +++ b/docs/posts/getting-started.md @@ -0,0 +1,155 @@ +--- +title: Getting Started +description: Learn how to use sish +keywords: [sish, guide, getting, started, how] +--- + +We have a managed service and **three** officially supported self-hosting +deployments for `sish`. + +Here are the guides related to self-hosting `sish`. + +# Managed + +The easiest way to get started with using sish is to use our managed service at +[tuns.sh](https://tuns.sh). This service manages `sish` for you so you don't +have to go through the process of setting `sish` up yourself. + +# DNS + +To use sish, you need to add a wildcard DNS record that is used for multiplexed +subdomains. Adding an `A` record with `*` as the subdomain to the IP address of +your server is the simplest way to achieve this configuration. + +For the purposes of our guides, we will use `tuns.sh` as our domain. + +# Docker Compose + +You can use Docker Compose to setup your sish instance. This includes taking +care of SSL via Let's Encrypt for you. This uses the +[adferrand/dnsrobocert](https://github.com/adferrand/dnsrobocert) container to +handle issuing wildcard certifications over DNS. For more information on how to +use this, head to that link above. + +We use +[sish/deploy](https://github.com/antoniomika/sish/tree/4ed42082289f6da8a9f873ed8110963290ea4ce9/deploy) +in our deployment of `tuns.sh` and are using them for this guide. + +Clone the `sish` repo: + +```bash +git clone git@github.com:antoniomika/sish.git +``` + +Then copy the `sish/deploy` folder: + +```bash +cp -R sish/deploy ~/sish +``` + +Edit `~/sish/docker-compose.yml` and `~/sish/le-config.yml` file with your +domain and DNS auth info. + +Then, create a symlink that points to your domain's Let's Encrypt certificates +like: + +```bash +ln -s /etc/letsencrypt/live//fullchain.pem deploy/ssl/.crt +ln -s /etc/letsencrypt/live//privkey.pem deploy/ssl/.key +``` + +> Careful: the symlinks need to point to `/etc/letsencrypt`, not a relative +> path. The symlinks will not resolve on the host filesystem, but they will +> resolve inside of the sish container because it mounts the letsencrypt files +> in /etc/letsencrypt, _not_ ./letsencrypt. + +Finally, you can deploy your service like so: + +```bash +docker-compose -f deploy/docker-compose.yml up -d +``` + +SSH to your host to communicate with sish + +```bash +ssh -p 2222 -R 80:localhost:8080 tuns.sh +``` + +# Docker + +[Find our latest releases.](/releases) + +Pull the Docker image + +```bash +docker pull antoniomika/sish:latest +``` + +Create folders to host your keys + +```bash +mkdir -p ~/sish/ssl ~/sish/keys ~/sish/pubkeys +``` + +Copy your public keys to `pubkeys` + +```bash +cp ~/.ssh/id_ed25519.pub ~/sish/pubkeys +``` + +Run the image + +```bash +docker run -itd --name sish \ + -v ~/sish/ssl:/ssl \ + -v ~/sish/keys:/keys \ + -v ~/sish/pubkeys:/pubkeys \ + --net=host antoniomika/sish:latest \ + --ssh-address=:2222 \ + --http-address=:80 \ + --https-address=:443 \ + --https=true \ + --https-certificate-directory=/ssl \ + --authentication-keys-directory=/pubkeys \ + --private-keys-directory=/keys \ + --bind-random-ports=false \ + --domain=tuns.sh +``` + +SSH to your host to communicate with sish + +```bash +ssh -p 2222 -R 80:localhost:8080 tuns.sh +``` + +# Google Cloud Platform + +There is a tutorial for creating an instance in Google Cloud Platform with sish +fully setup that can be found +[here](https://github.com/antoniomika/sish/blob/main/deploy/gcloud.md). It can +be accessed through [Google Cloud Shell](https://cloud.google.com/shell). + +[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://ssh.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fantoniomika%2Fsish&cloudshell_git_branch=main&cloudshell_tutorial=deploy%2Fgcloud.md) + +# Authentication + +If you want to use this service privately, it supports both public key and +password authentication. To enable authentication, set `--authentication=true` +as one of your CLI options and be sure to configure `--authentication-password` +or `--authentication-keys-directory` to your liking. The directory provided by +`--authentication-keys-directory` is watched for changes and will reload the +authorized keys automatically. The authorized cert index is regenerated on +directory modification, so removed public keys will also automatically be +removed. Files in this directory can either be single key per file, or multiple +keys per file separated by newlines, similar to `authorized_keys`. Password auth +can be disabled by setting `--authentication-password=""` as a CLI option. + +One of my favorite ways of using this for authentication is like so: + +```bash +sish@sish0:~/sish/pubkeys# curl https://github.com/antoniomika.keys > antoniomika +``` + +This will load my public keys from GitHub, place them in the directory that sish +is watching, and then load the pubkey. As soon as this command is run, I can SSH +normally and it will authorize me. diff --git a/docs/posts/home.md b/docs/posts/home.md new file mode 100644 index 0000000..5cd4fc3 --- /dev/null +++ b/docs/posts/home.md @@ -0,0 +1,6 @@ +--- +title: sish +description: something something darkside +slug: index +template: home.page.tmpl +--- diff --git a/docs/posts/how-it-works.md b/docs/posts/how-it-works.md new file mode 100644 index 0000000..014c7bc --- /dev/null +++ b/docs/posts/how-it-works.md @@ -0,0 +1,78 @@ +--- +title: How it works +description: Technical details for sish +keywords: [sish, how, works] +--- + +SSH can normally forward local and remote ports. This service implements an SSH +server that only handles forwarding and nothing else. + +But let's first take a step back and illustrate some basic examples of how +things work without `sish`. Let's start with a simple port forward: + +# Port Forward + +Here Eric has a web server hosted on its `localhost:3000`. Eric has to forward +its localhost connection to Tony in order for him to access the web server. + +
+ hiw-port-forward +
+ +This is manual, arduous, and sometimes difficult to get to work properly because +of firewalls. So many people opt to setup a VPN that both Eric and Tony can +connect to. + +# Traditional VPN + +Now both Eric and Tony connect to the VPN service and then Tony can access +Eric's web server via Eric's VPN IP. + +
+ hiw-vpn +
+ +Great! But this requires both Eric and Tony to connect to the VPN service. What +if Eric wants to share the web server with multiple users that are **not** +connected to the VPN? Sometimes it isn't feasible or appropriate to have +everyone connect to your VPN. + +# sish Public + +Enter `sish`. Using just SSH and a `sish` service, Eric can create an SSH remote +port forward to connect to `sish` which will automatically create a public URL +that **anyone** can access. + +
+ hiw-sish-public +
+ +Very nice! Tony doesn't have to worry about firewall issues, setting up and +connecting to a VPN, and anyone else can also access the web server via URL. +This is the real power of leveraging `sish`. + +But what if we want the web server to be private so only Tony can access the web +server using `sish`? + +# sish Private + +In this example both Eric and Tony setup an SSH tunnel to `sish`: + +- Eric sets up a remote port forward tunnel +- Tony sets up a local port forward tunnel + +
+ hiw-sish-private +
+ +> NOTE: The remote tunnel command needs to include `tcp-aliases-allowed-users` with +> Tony's pubkey fingerprint + +```bash +ssh -R private:3000:localhost:3000 tuns.sh tcp-aliases-allowed-users=SHA256:4vNGm4xvuVxYbaIE5JX1KgTgncaF3x3w2lk+JMLOfd8 +``` + +This creates a private connection between Eric and Tony that allows Tony to +access Eric's local web server without anyone else having access to it! + +[Learn more](/cheatsheet#https-private-access) diff --git a/docs/posts/sitemap.md b/docs/posts/sitemap.md new file mode 100644 index 0000000..2a26f2c --- /dev/null +++ b/docs/posts/sitemap.md @@ -0,0 +1,6 @@ +--- +title: sitemap +description: sish doc sitemap +slug: sitemap +template: sitemap.page.tmpl +--- diff --git a/docs/public/.gitkeep b/docs/public/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/public/.gitkeep @@ -0,0 +1 @@ + diff --git a/docs/sish-diagrams.excalidraw b/docs/sish-diagrams.excalidraw new file mode 100644 index 0000000..7b65a52 --- /dev/null +++ b/docs/sish-diagrams.excalidraw @@ -0,0 +1,2509 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 141, + "versionNonce": 1531914054, + "isDeleted": false, + "id": "m2e7OmKnDf26QMoCTrglU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 279, + "y": 224, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 1092983517, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "shWooKlNmJWjvzj5CEEt4" + }, + { + "id": "uqlLaGrvswKk44sT962fN", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 77, + "versionNonce": 571365402, + "isDeleted": false, + "id": "shWooKlNmJWjvzj5CEEt4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 360.75, + "y": 275.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.5, + "height": 25, + "seed": 2030755741, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Eric", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "m2e7OmKnDf26QMoCTrglU", + "originalText": "Eric", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 221, + "versionNonce": 246828678, + "isDeleted": false, + "id": "NpiOCUpneDstuARV_87Yv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 845, + "y": 224, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 741693181, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ScXH63ztqop3QHhvcfmjs" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 162, + "versionNonce": 427836634, + "isDeleted": false, + "id": "ScXH63ztqop3QHhvcfmjs", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 922.0583324432373, + "y": 275.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 45.88333511352539, + "height": 25, + "seed": 684420957, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Tony", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "NpiOCUpneDstuARV_87Yv", + "originalText": "Tony", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "line", + "version": 176, + "versionNonce": 1899400646, + "isDeleted": false, + "id": "KucwJ3g8lALjVEbmP-FWM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 504, + "y": 94, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 436, + "seed": 1937898557, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 436 + ] + ] + }, + { + "type": "line", + "version": 327, + "versionNonce": 1301715354, + "isDeleted": false, + "id": "US-fRQc--Px3EPsRAat4I", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 821.0428421211119, + "y": 93.53795454005709, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 436, + "seed": 303909011, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 436 + ] + ] + }, + { + "type": "text", + "version": 143, + "versionNonce": 1877796102, + "isDeleted": false, + "id": "kLvI9iqjURCUenKIHkYMR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 625, + "y": 273, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.63333129882812, + "height": 25, + "seed": 242220696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Internet", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Internet", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 160, + "versionNonce": 2105824858, + "isDeleted": false, + "id": "vn6_3WpC_XL4JPESdi6R2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 551, + "y": 12, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 235.56666564941406, + "height": 45, + "seed": 14665448, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Port Forward", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Port Forward", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 156, + "versionNonce": 1280726086, + "isDeleted": false, + "id": "q4qincULKxSnf_35KiPdz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 307, + "y": 370, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.86666870117188, + "height": 25, + "seed": 2039073176, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "localhost:3000", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "localhost:3000", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 167, + "versionNonce": 169638682, + "isDeleted": false, + "id": "sPImFlQg7nMup5LVFsvx2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 863, + "y": 350, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 398, + "height": 58, + "seed": 1480813288, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -186, + 53 + ], + [ + -398, + -5 + ] + ] + }, + { + "type": "arrow", + "version": 183, + "versionNonce": 273668998, + "isDeleted": false, + "id": "uqlLaGrvswKk44sT962fN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 468, + "y": 224, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 389, + "height": 55, + "seed": 327255272, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "m2e7OmKnDf26QMoCTrglU", + "focus": -0.4633121308496138, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 197, + -50 + ], + [ + 389, + 5 + ] + ] + }, + { + "type": "rectangle", + "version": 302, + "versionNonce": 1104283610, + "isDeleted": false, + "id": "Jb4hBafoNfhuyzx3C7X0f", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 268, + "y": 877.6247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 2029527960, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "rxnAonrTb687XY7DKaNhr" + }, + { + "id": "JCdPOcFdRv7rFPjNv9phb", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 236, + "versionNonce": 35027654, + "isDeleted": false, + "id": "rxnAonrTb687XY7DKaNhr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 349.75, + "y": 929.1247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.5, + "height": 25, + "seed": 39841944, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Eric", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Jb4hBafoNfhuyzx3C7X0f", + "originalText": "Eric", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 380, + "versionNonce": 750255258, + "isDeleted": false, + "id": "upyJFVrZGTRkwpRO1NHC1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 834, + "y": 877.6247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 122035608, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "sHkgU4jRwa9yoC-L7y_EY" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 321, + "versionNonce": 407319046, + "isDeleted": false, + "id": "sHkgU4jRwa9yoC-L7y_EY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 911.0583324432373, + "y": 929.1247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 45.88333511352539, + "height": 25, + "seed": 1222552216, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Tony", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "upyJFVrZGTRkwpRO1NHC1", + "originalText": "Tony", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "line", + "version": 335, + "versionNonce": 1484872026, + "isDeleted": false, + "id": "-RWiCxCxQwvc3BeO0bRsT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 493, + "y": 747.6247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 436, + "seed": 1105041304, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 436 + ] + ] + }, + { + "type": "line", + "version": 486, + "versionNonce": 1843331398, + "isDeleted": false, + "id": "ZlVQ9V_47VYRqT_RSBHN_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 810.0428421211118, + "y": 747.1627211220516, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 436, + "seed": 1987503256, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 436 + ] + ] + }, + { + "type": "text", + "version": 302, + "versionNonce": 612441626, + "isDeleted": false, + "id": "MWLHYCb82aJSkLghGI2WT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 614, + "y": 926.6247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.63333129882812, + "height": 25, + "seed": 1147048344, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Internet", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Internet", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 315, + "versionNonce": 909830278, + "isDeleted": false, + "id": "lp7Z4yOszfLewPlQqXLUT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 296, + "y": 1023.6247665819944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.86666870117188, + "height": 25, + "seed": 1114180504, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "localhost:3000", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "localhost:3000", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 155, + "versionNonce": 229264090, + "isDeleted": false, + "id": "i4rJWU_NkHUtihxaEEdO2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 583, + "y": 652, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 149, + "height": 88, + "seed": 264339176, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "2PrQu288_j3Uk2tfWzgw5" + }, + { + "id": "fKAIBMuh-xZhWDud4Icjh", + "type": "arrow" + }, + { + "id": "JnuFJgy4RlZePkl-cuatQ", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 109, + "versionNonce": 823650246, + "isDeleted": false, + "id": "2PrQu288_j3Uk2tfWzgw5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 639.283332824707, + "y": 683.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.43333435058594, + "height": 25, + "seed": 330632936, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "VPN", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "i4rJWU_NkHUtihxaEEdO2", + "originalText": "VPN", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 223, + "versionNonce": 2056249242, + "isDeleted": false, + "id": "JCdPOcFdRv7rFPjNv9phb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 453, + "y": 877, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 134, + "height": 139, + "seed": 1799068136, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Jb4hBafoNfhuyzx3C7X0f", + "focus": 0.14038446689859166, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 134, + -139 + ] + ] + }, + { + "type": "arrow", + "version": 134, + "versionNonce": 512950022, + "isDeleted": false, + "id": "h-IQQP7ZeTbAWoCMlFAxP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 841, + "y": 884, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 112, + "height": 147, + "seed": 202715624, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -112, + -147 + ] + ] + }, + { + "type": "text", + "version": 250, + "versionNonce": 124050522, + "isDeleted": false, + "id": "frAfUkiqr6rORkQ0faEdH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.546292725028556, + "x": 394, + "y": 813, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 100.83333587646484, + "height": 25, + "seed": 1403373032, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "192.168.1.3", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "192.168.1.3", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 372, + "versionNonce": 858336838, + "isDeleted": false, + "id": "p5DJQYDdSIPc0T-IuveZ_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.8960553845713379, + "x": 806.7365813931264, + "y": 807.1811456333146, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 100.01667022705078, + "height": 25, + "seed": 1175482600, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "192.168.1.4", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "192.168.1.4", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 254, + "versionNonce": 1375486234, + "isDeleted": false, + "id": "fKAIBMuh-xZhWDud4Icjh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 598, + "y": 742, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 136, + "height": 141, + "seed": 507265000, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "i4rJWU_NkHUtihxaEEdO2", + "focus": 0.12939321345179972, + "gap": 2 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -136, + 141 + ] + ] + }, + { + "type": "arrow", + "version": 243, + "versionNonce": 196572550, + "isDeleted": false, + "id": "JnuFJgy4RlZePkl-cuatQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 722, + "y": 741, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 114, + "height": 151, + "seed": 560920984, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "i4rJWU_NkHUtihxaEEdO2", + "focus": -0.2833912268297931, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 114, + 151 + ] + ] + }, + { + "type": "text", + "version": 332, + "versionNonce": 1170804186, + "isDeleted": false, + "id": "lv0UTocJqMobwO4tF9XqG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 617.216667175293, + "y": 578.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 65.5999984741211, + "height": 45, + "seed": 43817704, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "VPN", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "VPN", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 540, + "versionNonce": 561697990, + "isDeleted": false, + "id": "5DUgB__uuI9H67DUsx7RU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 191.01779413652372, + "y": 1606.4032631448385, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 647536280, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "83nNHtwYmMBGXjifaEqJQ" + }, + { + "id": "-wOk4ZictzlKfYTxihewP", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 470, + "versionNonce": 1742619290, + "isDeleted": false, + "id": "83nNHtwYmMBGXjifaEqJQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 272.7677941365237, + "y": 1657.9032631448385, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.5, + "height": 25, + "seed": 1010371480, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Eric", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5DUgB__uuI9H67DUsx7RU", + "originalText": "Eric", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 608, + "versionNonce": 1041255430, + "isDeleted": false, + "id": "daPAi7CPTae5lf9IsgQDs", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 841.5844767984399, + "y": 1609.0877902730758, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 2101598360, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "vsT5Ud72rYdfS82glQKi7" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 547, + "versionNonce": 586080090, + "isDeleted": false, + "id": "vsT5Ud72rYdfS82glQKi7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 918.6428092416772, + "y": 1660.5877902730758, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 45.88333511352539, + "height": 25, + "seed": 1298990488, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Tony", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "daPAi7CPTae5lf9IsgQDs", + "originalText": "Tony", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "line", + "version": 635, + "versionNonce": 1869929286, + "isDeleted": false, + "id": "MupmNEl7FcG2zpMVkhIzu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 480.00000000000006, + "y": 1500.0199256645815, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5.684341886080802e-14, + "height": 374.97835559084024, + "seed": 2045285016, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.684341886080802e-14, + 374.97835559084024 + ] + ] + }, + { + "type": "line", + "version": 600, + "versionNonce": 323871770, + "isDeleted": false, + "id": "OTHHwi5xnzG0bbPlBNRFV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 797.0428421211118, + "y": 1439.5205774241476, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 436, + "seed": 157974424, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 436 + ] + ] + }, + { + "type": "text", + "version": 416, + "versionNonce": 2080735878, + "isDeleted": false, + "id": "ceW6fF4ej0HaltAHEgSay", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 601, + "y": 1618.9826228840905, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.63333129882812, + "height": 25, + "seed": 203869336, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Internet", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Internet", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 549, + "versionNonce": 1391349978, + "isDeleted": false, + "id": "XjHMdKcC_gnwhrzOV33qx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 219.01779413652372, + "y": 1752.4032631448385, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.86666870117188, + "height": 25, + "seed": 1808890264, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "localhost:3000", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "localhost:3000", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 272, + "versionNonce": 1451180486, + "isDeleted": false, + "id": "rkv0Fw5cMej8JIsUWWlHN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 570, + "y": 1344.357856302096, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 149, + "height": 88, + "seed": 138539928, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "VtDjHpMkIeKf6VUMioK1T" + }, + { + "id": "u4Dj-bCeA_YJLIMAUIOYe", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 227, + "versionNonce": 1164125594, + "isDeleted": false, + "id": "VtDjHpMkIeKf6VUMioK1T", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 626.5, + "y": 1375.857856302096, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36, + "height": 25, + "seed": 670023832, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "sish", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rkv0Fw5cMej8JIsUWWlHN", + "originalText": "sish", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 1015, + "versionNonce": 301930758, + "isDeleted": false, + "id": "-wOk4ZictzlKfYTxihewP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 375.5698818507873, + "y": 1604.4888141245106, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 194.49275163453723, + "height": 178.06832433708996, + "seed": 173796760, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5DUgB__uuI9H67DUsx7RU", + "focus": 0.07391216951731348, + "gap": 1.914449020327993 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 194.49275163453723, + -178.06832433708996 + ] + ] + }, + { + "type": "text", + "version": 889, + "versionNonce": 1675584090, + "isDeleted": false, + "id": "tqDbOe7rW8gEgwURv6zUL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.546292725028556, + "x": 298.26255508758027, + "y": 1485.9307154488276, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 298.2833251953125, + "height": 20, + "seed": 901736344, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "ssh -R eric:80:localhost:3000 tuns.sh", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ssh -R eric:80:localhost:3000 tuns.sh", + "lineHeight": 1.25, + "baseline": 15 + }, + { + "type": "arrow", + "version": 735, + "versionNonce": 1442973766, + "isDeleted": false, + "id": "vLExBjcipxyfdXYAzteey", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 581.0626334853246, + "y": 1430.4204897874204, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.0291809774697, + "height": 182.3423484040925, + "seed": 1712087448, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -197.0291809774697, + 182.3423484040925 + ] + ] + }, + { + "type": "text", + "version": 513, + "versionNonce": 154535706, + "isDeleted": false, + "id": "aCwZtpo1tGWj9ceraemm2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 560.386101270985, + "y": 1271.8318688777474, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 174.6666717529297, + "height": 45, + "seed": 249748376, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "sish public", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "sish public", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 515, + "versionNonce": 1382354822, + "isDeleted": false, + "id": "B3C8biN6B0fyA0TGv3IYd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 545.5, + "y": 1474, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 202.00000000000003, + "height": 88, + "seed": 1860782824, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "49m37PT7xqPv0wh4dWOaj" + }, + { + "id": "u4Dj-bCeA_YJLIMAUIOYe", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 552, + "versionNonce": 1037702106, + "isDeleted": false, + "id": "49m37PT7xqPv0wh4dWOaj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 553.2083358764648, + "y": 1505.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 186.5833282470703, + "height": 25, + "seed": 1951378920, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "https://eric.tuns.sh", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "B3C8biN6B0fyA0TGv3IYd", + "originalText": "https://eric.tuns.sh", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 82, + "versionNonce": 862269126, + "isDeleted": false, + "id": "u4Dj-bCeA_YJLIMAUIOYe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 646, + "y": 1472, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 39.204711549874446, + "seed": 1583996056, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "B3C8biN6B0fyA0TGv3IYd", + "focus": -0.00495049504950495, + "gap": 2 + }, + "endBinding": { + "elementId": "rkv0Fw5cMej8JIsUWWlHN", + "focus": -0.020134228187919462, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -39.204711549874446 + ] + ] + }, + { + "type": "arrow", + "version": 186, + "versionNonce": 64915610, + "isDeleted": false, + "id": "RGCfSY3mBor7ePEoTk_om", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 844.6880304373697, + "y": 1619.1753867166083, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 102.36471714419258, + "height": 59.10029699871939, + "seed": 1093780456, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -102.36471714419258, + -59.10029699871939 + ] + ] + }, + { + "type": "rectangle", + "version": 617, + "versionNonce": 362767878, + "isDeleted": false, + "id": "_WIhPq2GycKkOz9pisQHG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 205.93512266003427, + "y": 2323.635467122728, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 2054044648, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "1XzaZqdRT9ImtH5H2YYke" + }, + { + "id": "oxyXCci2iQlkIXyRulnIT", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 547, + "versionNonce": 586573146, + "isDeleted": false, + "id": "1XzaZqdRT9ImtH5H2YYke", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 287.68512266003427, + "y": 2375.135467122728, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.5, + "height": 25, + "seed": 68058856, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Eric", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_WIhPq2GycKkOz9pisQHG", + "originalText": "Eric", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 797, + "versionNonce": 2047915334, + "isDeleted": false, + "id": "UOs--hxhnyjHXKmxPa800", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 927.6047233444946, + "y": 2323.3459816753143, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 200, + "height": 128, + "seed": 380527080, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Gtn6YzNCMdMfCJz2dVk_4" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 726, + "versionNonce": 1953802778, + "isDeleted": false, + "id": "Gtn6YzNCMdMfCJz2dVk_4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1004.6630557877319, + "y": 2374.8459816753143, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 45.88333511352539, + "height": 25, + "seed": 827974888, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Tony", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UOs--hxhnyjHXKmxPa800", + "originalText": "Tony", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "line", + "version": 712, + "versionNonce": 974527622, + "isDeleted": false, + "id": "Cj4QdQNmUhEIhyPclQzqE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 494.9173285235106, + "y": 2217.252129642471, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5.684341886080802e-14, + "height": 374.97835559084024, + "seed": 295503848, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.684341886080802e-14, + 374.97835559084024 + ] + ] + }, + { + "type": "line", + "version": 722, + "versionNonce": 793532122, + "isDeleted": false, + "id": "TaFMEFlJHXQqaa3sDuaRK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 811.9601706446224, + "y": 2218.0617751307095, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 374.6910062713282, + "seed": 1669581544, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 374.6910062713282 + ] + ] + }, + { + "type": "text", + "version": 493, + "versionNonce": 2070715334, + "isDeleted": false, + "id": "F9j7eJxUe9tyd3ChYsmbR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 615.9173285235106, + "y": 2336.2148268619803, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.63333129882812, + "height": 25, + "seed": 1905929704, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Internet", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Internet", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 626, + "versionNonce": 873491354, + "isDeleted": false, + "id": "xOB2MKC79q8aakX0ARDcq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 233.93512266003427, + "y": 2469.635467122728, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.86666870117188, + "height": 25, + "seed": 1424765160, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "localhost:3000", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "localhost:3000", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 350, + "versionNonce": 607204102, + "isDeleted": false, + "id": "2sVpIhgQmUULdktPT-keI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 584.9173285235106, + "y": 2061.5900602799857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 149, + "height": 88, + "seed": 621138920, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "JGmhR_EdmoUelsnMCqGb0" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 304, + "versionNonce": 1492379738, + "isDeleted": false, + "id": "JGmhR_EdmoUelsnMCqGb0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 641.4173285235106, + "y": 2093.0900602799857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36, + "height": 25, + "seed": 670242536, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "sish", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2sVpIhgQmUULdktPT-keI", + "originalText": "sish", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 1208, + "versionNonce": 1323360838, + "isDeleted": false, + "id": "oxyXCci2iQlkIXyRulnIT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 390.487210374298, + "y": 2321.7210181024, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 194.49275163453723, + "height": 178.06832433708996, + "seed": 37747176, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": { + "elementId": "_WIhPq2GycKkOz9pisQHG", + "focus": 0.07391216951731483, + "gap": 1.914449020327993 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 194.49275163453723, + -178.06832433708996 + ] + ] + }, + { + "type": "text", + "version": 1099, + "versionNonce": 902417690, + "isDeleted": false, + "id": "zaxOsNIpjhRRjYGz9HnSn", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.546292725028556, + "x": 287.730177745384, + "y": 2190.5710288539813, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 326.20001220703125, + "height": 20, + "seed": 1727560936, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "ssh -R private:80:localhost:3000 tuns.sh", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ssh -R private:80:localhost:3000 tuns.sh", + "lineHeight": 1.25, + "baseline": 15 + }, + { + "type": "arrow", + "version": 812, + "versionNonce": 1045997958, + "isDeleted": false, + "id": "9rbg4NLBpc3gIIlous3Ab", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 595.9799620088352, + "y": 2147.65269376531, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.0291809774697, + "height": 182.3423484040925, + "seed": 1202233320, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -197.0291809774697, + 182.3423484040925 + ] + ] + }, + { + "type": "text", + "version": 698, + "versionNonce": 1879382490, + "isDeleted": false, + "id": "mk_lt_LL_jCNnju8ADYB2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 546.083052524957, + "y": 1990.0380854312884, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 206.76666259765625, + "height": 45, + "seed": 73163496, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "sish private", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "sish private", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 213, + "versionNonce": 1372948678, + "isDeleted": false, + "id": "66aIT0LOev9HWvK53MeVk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 722.9510820385176, + "y": 2148.906882259468, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 208.43869118937562, + "height": 188.95843967634983, + "seed": 720979688, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 208.43869118937562, + 188.95843967634983 + ] + ] + }, + { + "type": "arrow", + "version": 442, + "versionNonce": 112774810, + "isDeleted": false, + "id": "8y_xmHLO8FRlEwGY2zhSK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 937.2606398109486, + "y": 2324.1655684159623, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 205.5434445915696, + "height": 183.05078676170433, + "seed": 355303656, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1706040903384, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -205.5434445915696, + -183.05078676170433 + ] + ] + }, + { + "type": "text", + "version": 1146, + "versionNonce": 2029289478, + "isDeleted": false, + "id": "BTZo9M4vx2WA6lWjopI-q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.7615596990965523, + "x": 723.9356114057408, + "y": 2198.7263791168, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 250.61666870117188, + "height": 20, + "seed": 42427800, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "8y_xmHLO8FRlEwGY2zhSK", + "type": "arrow" + } + ], + "updated": 1706040903384, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "ssh -L 3000:private:80 tuns.sh", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ssh -L 3000:private:80 tuns.sh", + "lineHeight": 1.25, + "baseline": 15 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#f5faff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/static/favicon-16x16.png b/docs/static/favicon-16x16.png new file mode 100644 index 0000000..9775c56 Binary files /dev/null and b/docs/static/favicon-16x16.png differ diff --git a/docs/static/favicon-32x32.png b/docs/static/favicon-32x32.png new file mode 100644 index 0000000..eb91b35 Binary files /dev/null and b/docs/static/favicon-32x32.png differ diff --git a/docs/static/hiw-port-forward.png b/docs/static/hiw-port-forward.png new file mode 100644 index 0000000..9d76148 Binary files /dev/null and b/docs/static/hiw-port-forward.png differ diff --git a/docs/static/hiw-sish-private.png b/docs/static/hiw-sish-private.png new file mode 100644 index 0000000..ee9726c Binary files /dev/null and b/docs/static/hiw-sish-private.png differ diff --git a/docs/static/hiw-sish-public.png b/docs/static/hiw-sish-public.png new file mode 100644 index 0000000..9427d8b Binary files /dev/null and b/docs/static/hiw-sish-public.png differ diff --git a/docs/static/hiw-vpn.png b/docs/static/hiw-vpn.png new file mode 100644 index 0000000..b645feb Binary files /dev/null and b/docs/static/hiw-vpn.png differ diff --git a/docs/static/main.css b/docs/static/main.css new file mode 100644 index 0000000..1f12965 --- /dev/null +++ b/docs/static/main.css @@ -0,0 +1,90 @@ +.sitemap { + column-count: 2; +} + +.hiw { + background-color: #282a36; +} + +.link-alt-adj, +.link-alt-adj:visited, +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + color: var(--link-color); + text-decoration: none; +} + +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + text-decoration: underline; +} + +.link-alt-hover, +.link-alt-hover:visited, +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + color: var(--hover); + text-decoration: none; +} + +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + text-decoration: underline; +} + +.link-alt, +.link-alt:visited, +.link-alt:visited:hover, +.link-alt:hover { + color: var(--white); + text-decoration: none; +} + +.link-alt:visited:hover, +.link-alt:hover { + text-decoration: underline; +} + +.hero { + padding: 5rem 0; +} + +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(225px, 1fr)); + gap: 1rem; +} + +.features h3 { + border: none; +} + +.mk-nav { + padding: 1rem; +} + +.text-hdr { + color: var(--hover); +} + +.text-underline-hdr { + border-bottom: 3px solid var(--hover); + padding-bottom: 3px; +} + +.current { + background-color: var(--blockquote-bg) !important; + border-right: 5px solid var(--blockquote); +} + +.current a { + color: var(--white); +} + +.current-page a { + color: var(--white); +} + +.pager { + min-width: 150px; +} diff --git a/docs/tmpl/base.layout.tmpl b/docs/tmpl/base.layout.tmpl new file mode 100644 index 0000000..41c1e2f --- /dev/null +++ b/docs/tmpl/base.layout.tmpl @@ -0,0 +1,21 @@ +{{define "base"}} + + + + {{template "title" .}} + + + + + + + + + {{template "meta" .}} + + + + {{template "body" .}} + + +{{end}} diff --git a/docs/tmpl/footer.partial.tmpl b/docs/tmpl/footer.partial.tmpl new file mode 100644 index 0000000..7361903 --- /dev/null +++ b/docs/tmpl/footer.partial.tmpl @@ -0,0 +1,5 @@ +{{define "footer"}} +
+

Built by Antonio Mika

+
+{{end}} diff --git a/docs/tmpl/home.page.tmpl b/docs/tmpl/home.page.tmpl new file mode 100644 index 0000000..a92e925 --- /dev/null +++ b/docs/tmpl/home.page.tmpl @@ -0,0 +1,61 @@ +{{template "base" .}} + +{{define "title"}}{{.Data.Title}}{{end}} + +{{define "meta"}} +{{end}} + +{{define "attrs"}}class="container"{{end}} + +{{define "body"}} +{{template "nav" .}} + +
+
+
+
+

sish

+
Access localhost using https
+ GET STARTED +
+
+ +
+
+

+ Tunnels to localhost using SSH +

+

Using SSH tunnels, we can forward requests to your localhost from https, wss, and tcp.

+
+ +
+

+ Self-hosted ngrok alternative +

+

With docker it is easier than ever to deploy sish to your own VM.

+
+ +
+

+ Fully managed service tuns.sh +

+

We also manage an instance of sish so users don't have to worry about managing it.

+
+
+ + +
+ +
+ + {{template "footer" .}} +
+{{end}} diff --git a/docs/tmpl/nav.partial.tmpl b/docs/tmpl/nav.partial.tmpl new file mode 100644 index 0000000..21937ac --- /dev/null +++ b/docs/tmpl/nav.partial.tmpl @@ -0,0 +1,18 @@ +{{define "nav"}} + +{{end}} diff --git a/docs/tmpl/post.page.tmpl b/docs/tmpl/post.page.tmpl new file mode 100644 index 0000000..730b11b --- /dev/null +++ b/docs/tmpl/post.page.tmpl @@ -0,0 +1,62 @@ +{{template "base" .}} + +{{define "title"}}{{.Data.Title}}{{end}} + +{{define "meta"}}{{end}} + +{{define "attrs"}}class="container-sm"{{end}} + +{{define "body"}} +{{template "nav" .}} + +
+

{{.Data.Title}}

+

{{.Data.Description}}

+ +
+ +
+ {{.Data.Html}} +
+ +
+ {{if .Prev}} +
+
+
<< PREV
+ {{.Prev.Text}} +
+
+ {{end}} + + {{if .Next}} +
+
+
+ NEXT >> +
+ {{.Next.Text}} +
+
+ {{end}} +
+
+ +
+{{range .Sitemap -}} +
+ {{- if (and $.Prev (eq $.Prev.GenHref .GenHref)) -}} + {{.Text}} + {{- else if (and $.Next (eq $.Next.GenHref .GenHref)) -}} + {{.Text}} + {{- else if (eq $.Href .GenHref) -}} + {{.Text}} + {{- else -}} + {{.Text}} + {{- end -}} +
+{{- end}} +
+ +{{template "footer" .}} +{{end}} diff --git a/docs/tmpl/sitemap.page.tmpl b/docs/tmpl/sitemap.page.tmpl new file mode 100644 index 0000000..c8f0748 --- /dev/null +++ b/docs/tmpl/sitemap.page.tmpl @@ -0,0 +1,22 @@ +{{template "base" .}} + +{{define "title"}}{{.Data.Title}}{{end}} + +{{define "meta"}}{{end}} + +{{define "attrs"}}class="container-sm"{{end}} + +{{define "body"}} +{{template "nav" .}} + +
+

{{.Data.Title}}

+

{{.Data.Description}}

+ +
+ + {{template "toc" .}} +
+ +{{template "footer" .}} +{{end}} diff --git a/docs/tmpl/toc.partial.tmpl b/docs/tmpl/toc.partial.tmpl new file mode 100644 index 0000000..9170376 --- /dev/null +++ b/docs/tmpl/toc.partial.tmpl @@ -0,0 +1,14 @@ +{{define "toc"}} +
+ {{range $key, $value := .SitemapByTag}} +
+

{{$key}}

+ +
+ {{end}} +
+{{end}} diff --git a/go.mod b/go.mod index 40d2db2..ffcd97a 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/jpillora/ipfilter v1.2.9 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a + github.com/picosh/pdocs v0.0.0-20240122053603-749f8ca95244 github.com/pires/go-proxyproto v0.7.0 github.com/radovskyb/watcher v1.0.7 github.com/sirupsen/logrus v1.9.3 @@ -24,10 +25,12 @@ require ( require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/bytedance/sonic v1.10.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -61,7 +64,11 @@ require ( github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/yuin/goldmark v1.6.0 // indirect + github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zeebo/blake3 v0.2.3 // indirect + go.abhg.dev/goldmark/anchor v0.1.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.7.0 // indirect diff --git a/go.sum b/go.sum index 3737492..f4eaf59 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY github.com/ScaleFT/sshkeys v1.2.0 h1:5BRp6rTVIhJzXT3VcUQrKgXR8zWA3sOsNeuyW15WUA8= github.com/ScaleFT/sshkeys v1.2.0/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/antoniomika/oxy v1.1.1-0.20210804032133-5924ea01c950 h1:AZcTu5Wwh+MJqW+m4eA+Bv9H9VV4xedv+lElnCfM1k0= github.com/antoniomika/oxy v1.1.1-0.20210804032133-5924ea01c950/go.mod h1:pJou3S+yPP9m4CrgeBtKj/4gl31Kbte2j5JS1iP0WVc= github.com/antoniomika/syncmap v1.0.0 h1:iFSfbQFQOvHZILFZF+hqWosO0no+W9+uF4y2VEyMKWU= @@ -31,6 +33,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -119,6 +123,8 @@ github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/phuslu/iploc v1.0.20230201/go.mod h1:gsgExGWldwv1AEzZm+Ki9/vGfyjkL33pbSr9HGpt2Xg= github.com/phuslu/iploc v1.0.20231229 h1:zZVEFTAJu7tQIKssTPtUomSqjpBjI32t44q37Zu3S7E= github.com/phuslu/iploc v1.0.20231229/go.mod h1:gsgExGWldwv1AEzZm+Ki9/vGfyjkL33pbSr9HGpt2Xg= +github.com/picosh/pdocs v0.0.0-20240122053603-749f8ca95244 h1:XMmcHS9/Je4ngtVy4+Dih4+1ohPtukPCOWgX+R3NYBk= +github.com/picosh/pdocs v0.0.0-20240122053603-749f8ca95244/go.mod h1:rh8n5EosoD8svAbVPUEuXWfcWBsGj3GPeG/8D/0Az3c= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -173,12 +179,21 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg= +github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= +github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.abhg.dev/goldmark/anchor v0.1.1 h1:NUH3hAzhfeymRqZKOkSoFReZlEAmfXBZlbXEzpD2Qgc= +go.abhg.dev/goldmark/anchor v0.1.1/go.mod h1:zYKiaHXTdugwVJRZqInVdmNGQRM3ZRJ6AGBC7xP7its= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=