Track cmd/tunneld; scope gitignore to the built binary
The bare 'tunneld' ignore pattern also matched the cmd/tunneld directory, so the server's main.go was never committed and the Coolify build failed on COPY cmd. Also update deploy docs for the Coolify/Traefik topology (API via HTTPS router, tunnel traffic via TCP SNI passthrough).
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// tunneld is a single-node, self-hosted OpenTunnel server: it speaks the
|
||||
// wire protocol from docs/protocol.md, so the official opentunnel CLI and
|
||||
// SDK work against it unchanged.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.tzgyn.dev/TZGyn/tunnel/internal/server"
|
||||
)
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func defaultDataDir() string {
|
||||
if xdg := os.Getenv("XDG_DATA_HOME"); xdg != "" {
|
||||
return filepath.Join(xdg, "tunneld")
|
||||
}
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
return filepath.Join(home, ".local", "share", "tunneld")
|
||||
}
|
||||
return ".local"
|
||||
}
|
||||
|
||||
func main() {
|
||||
dataDefault := filepath.Join(defaultDataDir(), "data.json")
|
||||
|
||||
domain := flag.String("domain", envOr("OPENTUNNEL_DOMAIN", "localhost"), "API domain; tunnels are <id>.<domain>")
|
||||
tlsListen := flag.String("tls-listen", envOr("TLS_LISTEN", "127.0.0.1:8443"), "public TLS listen address (SNI router)")
|
||||
httpListen := flag.String("http-listen", envOr("HTTP_LISTEN", "127.0.0.1:8080"), `HTTP listen address ("off" disables)`)
|
||||
httpMode := flag.String("http-mode", envOr("HTTP_MODE", "serve"), `"serve" runs the whole app on plain HTTP, "redirect" 301s to the domain`)
|
||||
dataPath := flag.String("data", envOr("TUNNELD_DATA", dataDefault), "tunnel store file (created 0600)")
|
||||
caFile := flag.String("ca-file", envOr("LOCAL_CA_FILE", ""), "write the local CA certificate here for clients to trust")
|
||||
flag.Parse()
|
||||
|
||||
if *caFile == "" {
|
||||
*caFile = filepath.Join(filepath.Dir(*dataPath), "ca.pem")
|
||||
}
|
||||
|
||||
store, err := server.OpenStore(*dataPath)
|
||||
if err != nil {
|
||||
log.Fatalf("open store: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(*caFile), 0o700); err != nil {
|
||||
log.Fatalf("ca file directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(*caFile, []byte(store.CACertPEM()), 0o644); err != nil {
|
||||
log.Fatalf("write CA file: %v", err)
|
||||
}
|
||||
certPEM, keyPEM, err := store.ServerCertificate(*domain)
|
||||
if err != nil {
|
||||
log.Fatalf("server certificate: %v", err)
|
||||
}
|
||||
serverCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
log.Fatalf("parse server certificate: %v", err)
|
||||
}
|
||||
|
||||
hub := server.NewHub(store)
|
||||
api := &server.API{Store: store, Hub: hub, Domain: *domain}
|
||||
|
||||
// Public TLS listener: SNI routing, with API terminations handed to the
|
||||
// shared HTTP server.
|
||||
ln, err := net.Listen("tcp", *tlsListen)
|
||||
if err != nil {
|
||||
log.Fatalf("tls listen: %v", err)
|
||||
}
|
||||
apiListener := server.NewChanListener(64, ln.Addr())
|
||||
relay := &server.Relay{
|
||||
Domain: *domain,
|
||||
Store: store,
|
||||
Hub: hub,
|
||||
Server: &serverCert,
|
||||
APIConns: apiListener.Conns(),
|
||||
}
|
||||
go relay.Serve(ln)
|
||||
go func() {
|
||||
if err := http.Serve(apiListener, api); err != nil {
|
||||
log.Printf("api over tls: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Printf("tls: listening on %s (domain %s)", ln.Addr(), *domain)
|
||||
|
||||
var httpLn net.Listener
|
||||
if *httpListen != "off" {
|
||||
httpLn, err = net.Listen("tcp", *httpListen)
|
||||
if err != nil {
|
||||
log.Fatalf("http listen: %v", err)
|
||||
}
|
||||
var handler http.Handler = api
|
||||
if *httpMode == "redirect" {
|
||||
handler = server.RedirectHandler(*domain)
|
||||
}
|
||||
go func() {
|
||||
if err := http.Serve(httpLn, handler); err != nil {
|
||||
log.Printf("http: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Printf("http: listening on %s (mode %s)", httpLn.Addr(), *httpMode)
|
||||
}
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-signals
|
||||
log.Printf("received %s; draining bridges", sig)
|
||||
hub.DrainAll()
|
||||
ln.Close()
|
||||
if httpLn != nil {
|
||||
httpLn.Close()
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
log.Printf("bye")
|
||||
}
|
||||
Reference in New Issue
Block a user