Created
September 15, 2025 11:01
-
-
Save CypherpunkSamurai/2ccc0f7d11c3abb73ae509374cbd2d97 to your computer and use it in GitHub Desktop.
Minimal Cloudflare Tunnel Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // tunnelmain.go - Standalone Cloudflare Quick Tunnel Creator | |
| // | |
| // This is a standalone implementation that creates a Cloudflare quick tunnel | |
| // based on the cloudflared codebase. It provides a simplified interface for | |
| // creating quick tunnels without the need for authentication or complex setup. | |
| // | |
| // Usage: tunnelmain <local-url> | |
| // Example: tunnelmain localhost:8000 | |
| // | |
| // Features: | |
| // - Creates an authenticated quick tunnel on trycloudflare.com | |
| // - Automatically handles tunnel registration and connection | |
| // - Provides detailed call stack logging for debugging | |
| // - Uses QUIC protocol by default for better performance | |
| // - Supports HTTP/HTTPS local services | |
| // | |
| // Based on the original cloudflared implementation: | |
| // https://github.com/cloudflare/cloudflared | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "net/http" | |
| "strings" | |
| "time" | |
| "github.com/google/uuid" | |
| "github.com/pkg/errors" | |
| "github.com/rs/zerolog" | |
| "github.com/urfave/cli/v2" | |
| "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" | |
| "github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel" | |
| "github.com/cloudflare/cloudflared/connection" | |
| ) | |
| const ( | |
| httpTimeout = 15 * time.Second | |
| disclaimer = "Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee, are subject to the Cloudflare Online Services Terms of Use (https://www.cloudflare.com/website-terms/), and Cloudflare reserves the right to investigate your use of Tunnels for violations of such terms. If you intend to use Tunnels in production you should use a pre-created named tunnel by following: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps" | |
| ) | |
| type QuickTunnelResponse struct { | |
| Success bool | |
| Result QuickTunnel | |
| Errors []QuickTunnelError | |
| } | |
| type QuickTunnelError struct { | |
| Code int | |
| Message string | |
| } | |
| type QuickTunnel struct { | |
| ID string `json:"id"` | |
| Name string `json:"name"` | |
| Hostname string `json:"hostname"` | |
| AccountTag string `json:"account_tag"` | |
| Secret []byte `json:"secret"` | |
| } | |
| // Print out the given lines in a nice ASCII box. | |
| func AsciiBox(lines []string, padding int) (box []string) { | |
| // Call stack logging: AsciiBox entry | |
| fmt.Printf("[CALL_STACK] AsciiBox(lines=%v, padding=%d) --> entry\n", lines, padding) | |
| maxLen := maxLen(lines) | |
| spacer := strings.Repeat(" ", padding) | |
| border := "+" + strings.Repeat("-", maxLen+(padding*2)) + "+" | |
| box = append(box, border) | |
| for _, line := range lines { | |
| box = append(box, "|"+spacer+line+strings.Repeat(" ", maxLen-len(line))+spacer+"|") | |
| } | |
| box = append(box, border) | |
| // Call stack logging: AsciiBox exit | |
| fmt.Printf("[CALL_STACK] AsciiBox() <-- returns box with %d lines\n", len(box)) | |
| return | |
| } | |
| func maxLen(lines []string) int { | |
| max := 0 | |
| for _, line := range lines { | |
| if len(line) > max { | |
| max = len(line) | |
| } | |
| } | |
| return max | |
| } | |
| // RunQuickTunnel requests a tunnel from the specified service. | |
| func RunQuickTunnel(ctx *cli.Context, log *zerolog.Logger, buildInfo *cliutil.BuildInfo) error { | |
| // Call stack logging: RunQuickTunnel entry | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel(ctx=%p, log=%p, buildInfo=%p) --> entry\n", ctx, log, buildInfo) | |
| log.Info().Msg(disclaimer) | |
| log.Info().Msg("Requesting new quick Tunnel on trycloudflare.com...") | |
| client := http.Client{ | |
| Transport: &http.Transport{ | |
| TLSHandshakeTimeout: httpTimeout, | |
| ResponseHeaderTimeout: httpTimeout, | |
| }, | |
| Timeout: httpTimeout, | |
| } | |
| quickServiceURL := ctx.String("quick-service") | |
| if quickServiceURL == "" { | |
| quickServiceURL = "https://api.trycloudflare.com" | |
| } | |
| // Call stack logging: RunQuickTunnel --> http.NewRequest | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() --> http.NewRequest(POST, %s, nil)\n", fmt.Sprintf("%s/tunnel", quickServiceURL)) | |
| req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/tunnel", quickServiceURL), nil) | |
| // Call stack logging: RunQuickTunnel <-- http.NewRequest | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- http.NewRequest returns (req=%p, err=%v)\n", req, err) | |
| if err != nil { | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- early exit (err=%v)\n", err) | |
| return errors.Wrap(err, "failed to build quick tunnel request") | |
| } | |
| req.Header.Add("Content-Type", "application/json") | |
| req.Header.Add("User-Agent", buildInfo.UserAgent()) | |
| // Call stack logging: RunQuickTunnel --> client.Do | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() --> client.Do(req=%p)\n", req) | |
| resp, err := client.Do(req) | |
| // Call stack logging: RunQuickTunnel <-- client.Do | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- client.Do returns (resp=%p, err=%v)\n", resp, err) | |
| if err != nil { | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- early exit (err=%v)\n", err) | |
| return errors.Wrap(err, "failed to request quick Tunnel") | |
| } | |
| defer resp.Body.Close() | |
| // This will read the entire response into memory so we can print it in case of error | |
| rsp_body, err := io.ReadAll(resp.Body) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to read quick-tunnel response") | |
| } | |
| // Call stack logging: RunQuickTunnel --> json.Unmarshal | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() --> json.Unmarshal(rsp_body, &data)\n") | |
| var data QuickTunnelResponse | |
| if err := json.Unmarshal(rsp_body, &data); err != nil { | |
| rsp_string := string(rsp_body) | |
| log.Err(err).Msgf("Error unmarshaling QuickTunnel response: %s", rsp_string) | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- json.Unmarshal failed (err=%v)\n", err) | |
| return errors.Wrap(err, "failed to unmarshal quick Tunnel") | |
| } | |
| // Call stack logging: RunQuickTunnel <-- json.Unmarshal | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- json.Unmarshal success (tunnel_id=%s, hostname=%s)\n", data.Result.ID, data.Result.Hostname) | |
| tunnelID, err := uuid.Parse(data.Result.ID) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to parse quick Tunnel ID") | |
| } | |
| credentials := connection.Credentials{ | |
| AccountTag: data.Result.AccountTag, | |
| TunnelSecret: data.Result.Secret, | |
| TunnelID: tunnelID, | |
| } | |
| url := data.Result.Hostname | |
| if !strings.HasPrefix(url, "https://") { | |
| url = "https://" + url | |
| } | |
| for _, line := range AsciiBox([]string{ | |
| "Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):", | |
| url, | |
| }, 2) { | |
| log.Info().Msg(line) | |
| } | |
| // Set default protocol to QUIC for quick tunnels | |
| if !ctx.IsSet("protocol") { | |
| _ = ctx.Set("protocol", "quic") | |
| } | |
| // Override the number of connections used. Quick tunnels shouldn't be used for production usage, | |
| // so, use a single connection instead. | |
| _ = ctx.Set("ha-connections", "1") | |
| // Call stack logging: RunQuickTunnel --> tunnel.StartServer | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() --> tunnel.StartServer(ctx=%p, buildInfo, &connection.TunnelProperties{...}, log)\n", ctx) | |
| err = tunnel.StartServer( | |
| ctx, | |
| buildInfo, | |
| &connection.TunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname}, | |
| log, | |
| ) | |
| // Call stack logging: RunQuickTunnel <-- tunnel.StartServer | |
| fmt.Printf("[CALL_STACK] RunQuickTunnel() <-- tunnel.StartServer returns err=%v\n", err) | |
| return err | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "os" | |
| "strings" | |
| "time" | |
| "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" | |
| "github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel" | |
| "github.com/cloudflare/cloudflared/logger" | |
| cli "github.com/urfave/cli/v2" | |
| ) | |
| func main() { | |
| fmt.Println("Starting Tunnelling...") | |
| // Call stack logging: main() entry | |
| fmt.Printf("[CALL_STACK] main() --> entry point (args=%v)\n", os.Args) | |
| // Create CLI app | |
| app := &cli.App{ | |
| Name: "tunnelmain", | |
| Usage: "Standalone Cloudflare Quick Tunnel", | |
| Flags: []cli.Flag{ | |
| &cli.StringFlag{ | |
| Name: "url", | |
| Usage: "Connect to the local webserver at URL.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "loglevel", | |
| Value: "info", | |
| Usage: "Application logging level {debug, info, warn, error, fatal, panic, disabled}", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "protocol", | |
| Value: "quic", | |
| Usage: "Protocol implementation to connect with Cloudflare's edge network.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "ha-connections", | |
| Value: "1", | |
| Usage: "Number of concurrent connections to keep with the edge", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "quick-service", | |
| Value: "https://api.trycloudflare.com", | |
| Usage: "URL for a service which manages unauthenticated 'quick' tunnels.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "metrics", | |
| Value: "localhost:0", | |
| Usage: "Listen address for metrics reporting.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "edge-ip-version", | |
| Value: "4", | |
| Usage: "Cloudflare Edge IP address version to connect with. {4, 6, auto}", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "edge-bind-address", | |
| Value: "", | |
| Usage: "Bind to IP address for outgoing connections to Cloudflare Edge.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "region", | |
| Value: "", | |
| Usage: "Cloudflare Edge region to connect to. Omit or set to empty to connect to the global region.", | |
| }, | |
| &cli.IntFlag{ | |
| Name: "retries", | |
| Value: 5, | |
| Usage: "Maximum number of retries for connection/protocol errors.", | |
| }, | |
| &cli.DurationFlag{ | |
| Name: "grace-period", | |
| Value: 30 * time.Second, | |
| Usage: "Maximum amount of time that cloudflared waits to shut down if it is still serving requests.", | |
| }, | |
| &cli.DurationFlag{ | |
| Name: "rpc-timeout", | |
| Value: 30 * time.Second, | |
| Usage: "Maximum time to wait for RPC requests to the edge.", | |
| }, | |
| &cli.StringFlag{ | |
| Name: "management-hostname", | |
| Value: "management.argotunnel.com", | |
| Usage: "Management hostname to signify incoming management requests", | |
| Hidden: true, | |
| }, | |
| }, | |
| Action: func(ctx *cli.Context) error { | |
| localURL := ctx.String("url") | |
| if localURL == "" { | |
| if ctx.NArg() == 0 { | |
| fmt.Println("Usage: tunnelmain <local-url> [options]") | |
| fmt.Println("Example: tunnelmain localhost:8000 --loglevel debug") | |
| return cli.ShowAppHelp(ctx) | |
| } | |
| localURL = ctx.Args().Get(0) | |
| } | |
| if !strings.HasPrefix(localURL, "http://") && !strings.HasPrefix(localURL, "https://") { | |
| localURL = "http://" + localURL | |
| } | |
| // Set the URL in context | |
| ctx.Set("url", localURL) | |
| // Create logger | |
| log := logger.CreateLoggerFromContext(ctx, logger.EnableTerminalLog) | |
| // Create build info | |
| buildInfo := cliutil.GetBuildInfo("DEV", "DEV") | |
| // Initialize tunnel module with graceful shutdown channel | |
| graceShutdownC := make(chan struct{}) | |
| tunnel.Init(buildInfo, graceShutdownC) | |
| // Call stack logging: main() --> RunQuickTunnel | |
| fmt.Printf("[CALL_STACK] main() --> RunQuickTunnel(ctx=%p, log=%p, buildInfo=%p)\n", ctx, log, buildInfo) | |
| // Run the quick tunnel | |
| if err := RunQuickTunnel(ctx, log, buildInfo); err != nil { | |
| log.Err(err).Msg("Failed to run quick tunnel") | |
| fmt.Printf("[CALL_STACK] main() <-- exit with error (err=%v)\n", err) | |
| return err | |
| } | |
| // Call stack logging: main() exit | |
| fmt.Printf("[CALL_STACK] main() <-- exit\n") | |
| return nil | |
| }, | |
| } | |
| // Run the CLI app | |
| if err := app.Run(os.Args); err != nil { | |
| fmt.Printf("Error: %v\n", err) | |
| os.Exit(1) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment