diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1c3d71..5e3f50e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,13 @@ jobs: build: strategy: matrix: - arch: [amd64, arm64] + include: + - os: linux + arch: amd64 + - os: linux + arch: arm64 + - os: windows + arch: amd64 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -20,16 +26,20 @@ jobs: cache: true - name: Build yatund + env: + EXE: ${{ matrix.os == 'windows' && '.exe' || '' }} run: | - GOOS=linux GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o build/yatund-${{ matrix.arch }} ./cmd/yatund/ + GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o build/yatund-${{ matrix.os }}-${{ matrix.arch }}${EXE} ./cmd/yatund/ - name: Build yatun + env: + EXE: ${{ matrix.os == 'windows' && '.exe' || '' }} run: | - GOOS=linux GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o build/yatun-${{ matrix.arch }} ./cmd/yatun/ + GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o build/yatun-${{ matrix.os }}-${{ matrix.arch }}${EXE} ./cmd/yatun/ - uses: actions/upload-artifact@v4 with: - name: binaries-${{ matrix.arch }} + name: binaries-${{ matrix.os }}-${{ matrix.arch }} path: build/ release: diff --git a/README.md b/README.md index 8cbade6..3996b0b 100644 --- a/README.md +++ b/README.md @@ -26,35 +26,57 @@ --- -## Quick start +## Installation -### Prerequisites +### Via Go install -- Go 1.26+ -- A server with a public IP (for yatund) +```bash +# For the client +go install github.com/KatIsCoding/yatun/cmd/yatun@latest +# For the server +go install github.com/KatIsCoding/yatun/cmd/yatund@latest +``` -### Run the server +### Download binary -```bash -# Optional: set your public domain so addresses are reported as domain.com:port -export DOMAIN=https://tunnel.example.com +Pre-built binaries are available on the [Releases](https://github.com/KatIsCoding/yatun/releases) page. -go run ./cmd/yatund/ -``` +--- -The server listens on port `5678` for agent connections. +## Quick start -### Run the agent +### Client (agent) + +Expose a local service through the public relay at `yatun.snowdev.one` — no server setup needed. ```bash -# Expose local port 8080 through the tunnel -go run ./cmd/yatun/ --port 8080 --server your-server.com +# Expose local port 8080 +yatun --port 8080 ``` The agent opens a TUI showing connection status, the public address, active connections, and ping latency. - Press `q` or `Ctrl+C` to quit. - Press `c` to copy the public address to your clipboard. +Use `--server` to point at a different relay: + +```bash +yatun --port 8080 --server your-server.com +``` + +### Server (relay) + +Run your own relay on a machine with a public IP. + +```bash +# Optional: set your public domain so addresses are reported as domain.com:port +export DOMAIN=https://tunnel.example.com + +yatund +``` + +The server listens on port `5678` for agent connections. + --- ## Configuration @@ -65,6 +87,8 @@ The agent opens a TUI showing connection status, the public address, active conn |----------|----------|---------|-------------| | `DOMAIN` | No | `""` | Public-facing domain. When set, the server reports addresses as `yourdomain.com:` instead of raw IP:port. | | `TLS` | No | `0` (off) | Enable TLS for incoming external connections. Set to any non‑`0` value. Requires certificate files. | +| `CERT_PATH` | No | `certs/cert.cer` | Path to the TLS certificate (fullchain). Only used when `TLS` is enabled. | +| `KEY_PATH` | No | `certs/cert_key.key` | Path to the TLS private key. Only used when `TLS` is enabled. | ### Agent flags @@ -77,7 +101,7 @@ The agent opens a TUI showing connection status, the public address, active conn ## Docker deployment -A Dockerfile and compose file are provided for deploying the server. +A Dockerfile and compose file are provided for a simple example of how the relay could be deployed with docker. ```bash cd deploy @@ -93,62 +117,12 @@ DOMAIN=https://tunnel.example.com # TLS (set to 1 to enable) TLS=1 -# Paths to your certificate files on the host +# Paths to your certificate files on the host (For the purposes of the example in the deploy/ folder) HOST_CER_FILE=/root/.acme.sh/example.com_ecc/fullchain.cer HOST_KEY_FILE=/root/.acme.sh/example.com_ecc/example.com.key ``` -The server looks for certificates at `certs/cert.cer` (fullchain) and `certs/cert_key.key` (private key) inside the container. The compose file maps host paths to those locations. - ---- - -## TLS setup - -yatund supports conditional TLS on external connections. When `TLS=1` is set, the server inspects the first byte of each incoming connection. If it's `0x16` (TLS handshake), the connection is transparently upgraded to TLS. Plain TCP connections are forwarded as-is on the same port. - -### Obtaining certificates with acme.sh - -Install acme.sh (a single-command helper is provided): - -```bash -./ssl/install.sh your@email.com -``` - -Issue a certificate using DNS validation (Cloudflare example): - -```bash -export CF_Token="your_cloudflare_api_token" -export CF_Zone_ID="your_zone_id" - -acme.sh --issue --dns dns_cf -d yourdomain.com --server letsencrypt -``` - -> `--server letsencrypt` uses the **production** Let's Encrypt endpoint. You must include this flag — the default may be staging, which browsers do not trust. If you see `stg` in the CA Issuers URL inside your certificate, you used the staging server. Re-issue with `--server letsencrypt`. - -After issuance, acme.sh prints the certificate paths. Your files will be under something like: - -``` -~/.acme.sh/yourdomain.com_ecc/ -├── yourdomain.com.key # private key -├── yourdomain.com.cer # leaf certificate only -└── fullchain.cer # full chain (leaf + intermediates) ← use this -``` - -Make yatund point to `fullchain.cer` (not the leaf-only `.cer`) and the `.key` file. The server must serve the full certificate chain or browsers will reject it. - -### Testing your certificate - -```bash -# Check the certificate chain contains intermediates -openssl x509 -in fullchain.cer -text -noout | grep "CA Issuers" - -# Verify the key matches the certificate -openssl x509 -noout -pubkey -in fullchain.cer | openssl md5 -openssl ec -noout -pubkey -in yourdomain.com.key | openssl md5 # ECC key -openssl rsa -noout -pubkey -in yourdomain.com.key | openssl md5 # RSA key -``` - -The two hashes must match. +The server looks for certificates at `certs/cert.cer` (fullchain) and `certs/cert_key.key` (private key) inside the container by default (can be changed over env vars). The compose file maps host paths to those locations. --- diff --git a/internal/tls/tlsutil.go b/internal/tls/tlsutil.go index d97fa70..296b444 100644 --- a/internal/tls/tlsutil.go +++ b/internal/tls/tlsutil.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "log" "net" + "os" ) type TLSStore struct { @@ -21,7 +22,19 @@ func (t TLSStore) Wrap(conn net.Conn) (*tls.Conn, error) { } func LoadTLSCerts() (TLSStore, error) { - cert, err := tls.LoadX509KeyPair("certs/cert.cer", "certs/cert_key.key") + cerLoc := "certs/cert.cer" + keyLoc := "certs/cert_key.key" + + if envCL, ok := os.LookupEnv("CERT_PATH"); ok { + cerLoc = envCL + } + if envK, ok := os.LookupEnv("KEY_PATH"); ok { + cerLoc = envK + } + + log.Printf("Reading cert from %v and key from %v", cerLoc, keyLoc) + + cert, err := tls.LoadX509KeyPair(cerLoc, keyLoc) if err != nil { log.Printf("Failed to load certificates: %v", err) return TLSStore{}, err