Files
ozan 0488cdda72
Build tinqs-git / build (push) Failing after 16m16s
feat: migrate bot service from arikigame.com to tinqs.com domain
- Proxy: add bot.tinqs.com route alongside legacy bot.arikigame.com
- Bot: add /api/v1/ai/* rewrite alias for inference proxy (Cursor endpoint)
- Auth: update Gitea URL defaults from git.arikigame.com to tinqs.com
- UI: update all landing page, team-tool, callback URLs to tinqs.com
- Libs: update gitea.ts, design.ts, docs-search.ts, handoffs.ts,
  mcp-handler.ts, image-gen-context.ts to tinqs.com API base
- Config: add tinqs-ai provider entry in deeptinqs providers.json
- Tests: update smoke test default URL to bot.tinqs.com

All endpoints work on both domains during transition.
Old bot.arikigame.com stays in proxy routes for backwards compat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 09:38:50 +01:00

167 lines
4.7 KiB
Go

package main
import (
"crypto/tls"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
"golang.org/x/crypto/acme/autocert"
)
var version = "1.2.0"
// Routes: hostname → localhost:port.
// Compiled in — these change maybe once a quarter.
var routes = map[string]string{
"git.arikigame.com": "http://127.0.0.1:4100", // Gitea
"bot.arikigame.com": "http://127.0.0.1:5500", // bot platform (legacy — use bot.tinqs.com)
"bot.tinqs.com": "http://127.0.0.1:5500", // bot platform (new domain)
"taco.arikigame.com": "http://127.0.0.1:5500", // meeting assistant (same process as bot)
"admin.arikigame.com": "http://127.0.0.1:7700", // admin portal
"langfuse.arikigame.com": "http://127.0.0.1:3100", // Langfuse LLM observability
"staging.tinqs.com": "http://127.0.0.1:3115", // Tinqs Platform (staging) — pm2 platform-staging
"platform.tinqs.com": "http://127.0.0.1:3120", // Tinqs Platform (production) — pm2 platform
}
// Tailscale-only hosts — unreachable from public internet. Defense-in-depth.
var tailscaleOnly = map[string]bool{
"admin.arikigame.com": true,
"langfuse.arikigame.com": true,
}
// tailscaleCIDR is 100.64.0.0/10 (CGNAT range Tailscale uses).
var tailscaleCIDR *net.IPNet
func init() {
_, tailscaleCIDR, _ = net.ParseCIDR("100.64.0.0/10")
}
func isTailscaleIP(remoteAddr string) bool {
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
host = remoteAddr
}
ip := net.ParseIP(host)
return ip != nil && tailscaleCIDR.Contains(ip)
}
func realIP(r *http.Request) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return host
}
func main() {
proxies := make(map[string]*httputil.ReverseProxy, len(routes))
domains := make([]string, 0, len(routes))
for host, target := range routes {
u, err := url.Parse(target)
if err != nil {
log.Fatalf("bad target for %s: %v", host, err)
}
rp := &httputil.ReverseProxy{
Director: func(h string, u *url.URL) func(req *http.Request) {
return func(req *http.Request) {
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
req.Host = h
ip := realIP(req)
req.Header.Set("X-Real-IP", ip)
req.Header.Set("X-Forwarded-For", ip)
req.Header.Set("X-Forwarded-Proto", "https")
}
}(host, u),
ErrorLog: log.New(os.Stderr, "["+host+"] ", log.LstdFlags),
// Flush immediately for SSE / streaming responses
FlushInterval: -1,
}
proxies[host] = rp
domains = append(domains, host)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host := strings.ToLower(strings.Split(r.Host, ":")[0])
proxy, ok := proxies[host]
if !ok {
http.Error(w, "unknown host", http.StatusNotFound)
return
}
if tailscaleOnly[host] && !isTailscaleIP(r.RemoteAddr) {
http.Error(w, "Tailscale required — install at https://tailscale.com/download and sign in with @tinqs.com", http.StatusForbidden)
return
}
proxy.ServeHTTP(w, r)
})
// Certificate storage
certDir := "/var/lib/tinqs-proxy/certs"
if d := os.Getenv("CERT_DIR"); d != "" {
certDir = d
}
mgr := &autocert.Manager{
Cache: autocert.DirCache(certDir),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domains...),
}
// :80 — ACME HTTP-01 challenges, Tailscale-only hosts served directly, rest redirect to HTTPS
go func() {
httpSrv := &http.Server{
Addr: ":80",
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
Handler: mgr.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host := strings.Split(r.Host, ":")[0]
// Tailscale-only hosts: serve on HTTP directly (WireGuard is already encrypted)
if tailscaleOnly[host] {
proxy, ok := proxies[host]
if ok && isTailscaleIP(r.RemoteAddr) {
proxy.ServeHTTP(w, r)
return
}
http.Error(w, "Tailscale required", http.StatusForbidden)
return
}
target := "https://" + r.Host + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusMovedPermanently)
})),
}
log.Printf("tinqs-proxy :80 (ACME + redirect)")
if err := httpSrv.ListenAndServe(); err != nil {
log.Fatalf(":80 failed: %v", err)
}
}()
// :443 — TLS with autocert
srv := &http.Server{
Addr: ":443",
Handler: handler,
TLSConfig: &tls.Config{
GetCertificate: mgr.GetCertificate,
MinVersion: tls.VersionTLS12,
},
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Minute, // 37 GB repos need time
IdleTimeout: 5 * time.Minute,
}
log.Printf("tinqs-proxy v%s starting on :443", version)
for _, d := range domains {
log.Printf(" %s → %s", d, routes[d])
}
log.Fatal(srv.ListenAndServeTLS("", ""))
}