main.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. package main
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "flag"
  6. "fmt"
  7. "log"
  8. "net"
  9. "net/http"
  10. "os"
  11. "os/signal"
  12. release "debtik"
  13. "sync"
  14. "syscall"
  15. "time"
  16. "debtik/internal/api"
  17. "debtik/internal/auth"
  18. "debtik/internal/backup"
  19. "debtik/internal/bridge"
  20. "debtik/internal/bwtest"
  21. "debtik/internal/certs"
  22. "debtik/internal/cli"
  23. "debtik/internal/dhcp"
  24. "debtik/internal/dns"
  25. "debtik/internal/dynamicrouting"
  26. "debtik/internal/firewall"
  27. "debtik/internal/logging"
  28. "debtik/internal/network"
  29. "debtik/internal/reverseproxy"
  30. "debtik/internal/routing"
  31. "debtik/internal/sdwan"
  32. "debtik/internal/services"
  33. "debtik/internal/tools"
  34. "debtik/internal/vpn"
  35. "debtik/internal/vrrp"
  36. "debtik/internal/web"
  37. )
  38. func main() {
  39. addr := flag.String("addr", ":8080", "HTTP service address (e.g. :8080 or :80)")
  40. cliAddr := flag.String("cli-addr", ":8282", "CLI service address for Telnet and debtik-cli (e.g. :8282)")
  41. metaPath := flag.String("meta", "/etc/debtik/metadata.json", "Path to interface metadata storage file")
  42. flag.Parse()
  43. if os.Geteuid() == 0 {
  44. if err := release.RefreshUpdateHelpers("/usr/local/libexec"); err != nil {
  45. log.Printf("Cannot refresh updater helpers: %v", err)
  46. }
  47. }
  48. // Initialize Logging Ring Buffer
  49. _ = logging.GetLogger()
  50. // Initialize components
  51. metaMgr := network.NewMetadataManager(*metaPath)
  52. netMgr := network.NewManager(metaMgr)
  53. persistMgr := network.NewPersistenceManager(netMgr)
  54. vpnMgr := vpn.NewManager()
  55. defer vpnMgr.AeroTUN.StopAll()
  56. fwMgr := firewall.NewFirewallManager()
  57. routingMgr := routing.NewRoutingManager()
  58. defer routingMgr.Stop()
  59. dynRoutingMgr := dynamicrouting.NewManager("")
  60. bridgeMgr := bridge.NewBridgeManager()
  61. toolsMgr := tools.NewManager()
  62. dhcpMgr := dhcp.NewManager()
  63. dhcpServerMgr := dhcp.NewServerManager()
  64. vrrpMgr := vrrp.NewManager()
  65. bwtestMgr := bwtest.NewManager("")
  66. defer bwtestMgr.Stop()
  67. dnsMgr := dns.NewManager()
  68. authMgr := auth.NewManager("")
  69. servicesMgr := services.NewManager("")
  70. certMgr, err := certs.NewCertManager("")
  71. if err != nil {
  72. log.Fatalf("Failed to initialize certificate manager: %v", err)
  73. }
  74. renewCtx, stopRenewal := context.WithCancel(context.Background())
  75. defer stopRenewal()
  76. go certMgr.RunRenewal(renewCtx)
  77. proxyMgr := reverseproxy.NewManager("", certMgr)
  78. defer proxyMgr.Stop()
  79. backupMgr := backup.NewManager("", "", "", nil)
  80. sdwanMgr := sdwan.NewManager()
  81. defer sdwanMgr.Stop()
  82. webHandler := web.Handler()
  83. // Build Unified RouterOS CLI Executor
  84. cliExecutor := cli.NewExecutor(
  85. authMgr,
  86. netMgr,
  87. routingMgr,
  88. dynRoutingMgr,
  89. fwMgr,
  90. dhcpMgr,
  91. dhcpServerMgr,
  92. dnsMgr,
  93. bridgeMgr,
  94. vpnMgr,
  95. vrrpMgr,
  96. bwtestMgr,
  97. servicesMgr,
  98. certMgr,
  99. proxyMgr,
  100. backupMgr,
  101. sdwanMgr,
  102. )
  103. // Determine Telnet & SSH addresses from services config or flags
  104. telnetAddr := *cliAddr
  105. if tSvc, ok := servicesMgr.GetService("telnet"); ok && tSvc.Port > 0 {
  106. telnetAddr = fmt.Sprintf(":%d", tSvc.Port)
  107. }
  108. sshAddr := ":2222"
  109. if sSvc, ok := servicesMgr.GetService("ssh"); ok && sSvc.Port > 0 {
  110. sshAddr = fmt.Sprintf(":%d", sSvc.Port)
  111. }
  112. cliTelnetServer := cli.NewServer(telnetAddr, authMgr, cliExecutor)
  113. cliSSHServer := cli.NewSSHServer(sshAddr, authMgr, cliExecutor)
  114. // Register hooks for dynamic service start/stop & port changes
  115. servicesMgr.RegisterHook("telnet", func(serviceName string, port int, disabled bool) error {
  116. cliTelnetServer.Stop()
  117. if !disabled {
  118. cliTelnetServer.SetAddr(fmt.Sprintf(":%d", port))
  119. return waitListener(cliTelnetServer.Start, cliTelnetServer.IsRunning)
  120. }
  121. return nil
  122. })
  123. servicesMgr.RegisterHook("ssh", func(serviceName string, port int, disabled bool) error {
  124. cliSSHServer.Stop()
  125. if !disabled {
  126. cliSSHServer.SetAddr(fmt.Sprintf(":%d", port))
  127. return waitListener(cliSSHServer.Start, cliSSHServer.IsRunning)
  128. }
  129. return nil
  130. })
  131. // Start enabled CLI servers
  132. if tSvc, ok := servicesMgr.GetService("telnet"); !ok || !tSvc.Disabled {
  133. go func() {
  134. if err := cliTelnetServer.Start(); err != nil {
  135. log.Printf("[CLI-Telnet] Server listener error: %v", err)
  136. }
  137. }()
  138. }
  139. defer cliTelnetServer.Stop()
  140. if sSvc, ok := servicesMgr.GetService("ssh"); !ok || !sSvc.Disabled {
  141. go func() {
  142. if err := cliSSHServer.Start(); err != nil {
  143. log.Printf("[CLI-SSH] Server listener error: %v", err)
  144. }
  145. }()
  146. }
  147. defer cliSSHServer.Stop()
  148. // API & Web UI Server
  149. srv := api.NewServer(
  150. netMgr,
  151. persistMgr,
  152. metaMgr,
  153. vpnMgr,
  154. fwMgr,
  155. routingMgr,
  156. dynRoutingMgr,
  157. bridgeMgr,
  158. toolsMgr,
  159. dhcpMgr,
  160. dhcpServerMgr,
  161. vrrpMgr,
  162. bwtestMgr,
  163. dnsMgr,
  164. authMgr,
  165. servicesMgr,
  166. certMgr,
  167. proxyMgr,
  168. backupMgr,
  169. sdwanMgr,
  170. webHandler,
  171. )
  172. // Standalone HTTPS Proxy listener when www-ssl is disabled
  173. var (
  174. proxyHTTPSServer *http.Server
  175. proxyHTTPSMu sync.Mutex
  176. proxyHTTPSRunning bool
  177. )
  178. startOrRestartProxyHTTPS := func() error {
  179. proxyHTTPSMu.Lock()
  180. defer proxyHTTPSMu.Unlock()
  181. if proxyHTTPSRunning && proxyHTTPSServer != nil {
  182. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  183. _ = proxyHTTPSServer.Shutdown(ctx)
  184. cancel()
  185. proxyHTTPSRunning = false
  186. proxyHTTPSServer = nil
  187. }
  188. // If www-ssl service is running, port 443 is already served by it
  189. sslSvc, ok := servicesMgr.GetService("www-ssl")
  190. if ok && !sslSvc.Disabled {
  191. return nil
  192. }
  193. cfg := proxyMgr.GetConfig()
  194. if !cfg.Enabled || cfg.HTTPSPort <= 0 {
  195. return nil
  196. }
  197. tlsConfig := &tls.Config{
  198. GetCertificate: proxyMgr.GetTLSCertificate,
  199. }
  200. listenAddr := fmt.Sprintf(":%d", cfg.HTTPSPort)
  201. newSrv := &http.Server{
  202. Addr: listenAddr,
  203. Handler: srv,
  204. TLSConfig: tlsConfig,
  205. ReadTimeout: 60 * time.Second,
  206. WriteTimeout: 60 * time.Second,
  207. IdleTimeout: 120 * time.Second,
  208. }
  209. listener, err := tls.Listen("tcp", listenAddr, tlsConfig)
  210. if err != nil {
  211. return err
  212. }
  213. proxyHTTPSServer = newSrv
  214. proxyHTTPSRunning = true
  215. go func() {
  216. log.Printf("[Proxy-HTTPS] Standalone reverse proxy HTTPS server listening on %s", listenAddr)
  217. if err := newSrv.Serve(listener); err != nil && err != http.ErrServerClosed {
  218. log.Printf("[Proxy-HTTPS] Server error: %v", err)
  219. proxyHTTPSMu.Lock()
  220. proxyHTTPSRunning = false
  221. proxyHTTPSMu.Unlock()
  222. }
  223. }()
  224. return nil
  225. }
  226. defer func() {
  227. proxyHTTPSMu.Lock()
  228. if proxyHTTPSServer != nil {
  229. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  230. _ = proxyHTTPSServer.Shutdown(ctx)
  231. cancel()
  232. }
  233. proxyHTTPSMu.Unlock()
  234. }()
  235. // HTTPS (www-ssl) Dynamic Server Listener
  236. var (
  237. httpsServer *http.Server
  238. httpsMu sync.Mutex
  239. httpsRunning bool
  240. )
  241. startOrRestartHTTPS := func(port int, disabled bool) error {
  242. httpsMu.Lock()
  243. defer httpsMu.Unlock()
  244. if httpsRunning && httpsServer != nil {
  245. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  246. _ = httpsServer.Shutdown(ctx)
  247. cancel()
  248. httpsRunning = false
  249. httpsServer = nil
  250. }
  251. if disabled {
  252. log.Printf("[HTTPS] Web SSL (www-ssl) listener stopped")
  253. return nil
  254. }
  255. sslSvc, ok := servicesMgr.GetService("www-ssl")
  256. if !ok || sslSvc.Certificate == "" {
  257. return fmt.Errorf("HTTPS requires a configured certificate")
  258. }
  259. tlsCert, err := certMgr.GetTLSCertificate(sslSvc.Certificate)
  260. if err != nil {
  261. return fmt.Errorf("HTTPS certificate %s: %w", sslSvc.Certificate, err)
  262. }
  263. tlsConfig := &tls.Config{
  264. Certificates: []tls.Certificate{tlsCert},
  265. GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
  266. // Priority: Check if reverse proxy has a rule matching this SNI domain with a certificate
  267. if cert, err := proxyMgr.GetTLSCertificate(hello); err == nil {
  268. return cert, nil
  269. }
  270. curSvc, ok := servicesMgr.GetService("www-ssl")
  271. if ok && curSvc.Certificate != "" {
  272. if c, err := certMgr.GetTLSCertificate(curSvc.Certificate); err == nil {
  273. return &c, nil
  274. }
  275. }
  276. return &tlsCert, nil
  277. },
  278. }
  279. listenAddr := fmt.Sprintf(":%d", port)
  280. newSrv := &http.Server{
  281. Addr: listenAddr,
  282. Handler: srv,
  283. TLSConfig: tlsConfig,
  284. ReadTimeout: 60 * time.Second,
  285. WriteTimeout: 60 * time.Second,
  286. IdleTimeout: 120 * time.Second,
  287. }
  288. listener, err := tls.Listen("tcp", listenAddr, tlsConfig)
  289. if err != nil {
  290. return err
  291. }
  292. httpsServer = newSrv
  293. httpsRunning = true
  294. go func() {
  295. log.Printf("[HTTPS] Web SSL (www-ssl) server listening on %s with certificate %q", listenAddr, sslSvc.Certificate)
  296. if err := newSrv.Serve(listener); err != nil && err != http.ErrServerClosed {
  297. log.Printf("[HTTPS] Server error: %v", err)
  298. httpsMu.Lock()
  299. httpsRunning = false
  300. httpsMu.Unlock()
  301. }
  302. }()
  303. return nil
  304. }
  305. servicesMgr.RegisterHook("www-ssl", func(serviceName string, port int, disabled bool) error {
  306. err := startOrRestartHTTPS(port, disabled)
  307. _ = startOrRestartProxyHTTPS()
  308. return err
  309. })
  310. if sslSvc, ok := servicesMgr.GetService("www-ssl"); ok && !sslSvc.Disabled {
  311. if err := startOrRestartHTTPS(sslSvc.Port, sslSvc.Disabled); err != nil {
  312. log.Printf("HTTPS startup failed: %v", err)
  313. }
  314. } else {
  315. // If www-ssl is disabled, start standalone proxy HTTPS listener
  316. if err := startOrRestartProxyHTTPS(); err != nil {
  317. log.Printf("Standalone HTTPS proxy startup note: %v", err)
  318. }
  319. }
  320. defer func() {
  321. httpsMu.Lock()
  322. if httpsRunning && httpsServer != nil {
  323. _ = httpsServer.Close()
  324. }
  325. httpsMu.Unlock()
  326. }()
  327. var httpMu sync.Mutex
  328. var httpServer *http.Server
  329. host, _, _ := net.SplitHostPort(*addr)
  330. startHTTP := func(port int, disabled bool) error {
  331. httpMu.Lock()
  332. defer httpMu.Unlock()
  333. old := httpServer
  334. // If www is disabled for management, keep HTTP port 80 listening if Reverse Proxy is enabled,
  335. // so that reverse proxy rules and Let's Encrypt ACME HTTP-01 challenges continue functioning.
  336. proxyActive := (proxyMgr != nil && proxyMgr.IsEnabled())
  337. if disabled && !proxyActive {
  338. httpServer = nil
  339. if old != nil {
  340. go old.Shutdown(context.Background())
  341. }
  342. return nil
  343. }
  344. address := net.JoinHostPort(host, fmt.Sprint(port))
  345. if old != nil && old.Addr == address {
  346. return nil
  347. }
  348. listener, err := net.Listen("tcp", address)
  349. if err != nil {
  350. return err
  351. }
  352. next := &http.Server{Addr: address, Handler: srv, ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second}
  353. httpServer = next
  354. go func() {
  355. if err := next.Serve(listener); err != nil && err != http.ErrServerClosed {
  356. log.Printf("HTTP listener failed: %v", err)
  357. }
  358. }()
  359. if old != nil {
  360. go old.Shutdown(context.Background())
  361. }
  362. return nil
  363. }
  364. servicesMgr.RegisterHook("www", func(_ string, port int, disabled bool) error { return startHTTP(port, disabled) })
  365. _, httpPortText, _ := net.SplitHostPort(*addr)
  366. var httpPort int
  367. _, _ = fmt.Sscan(httpPortText, &httpPort)
  368. httpDisabled := false
  369. explicitAddr := false
  370. flag.Visit(func(f *flag.Flag) {
  371. if f.Name == "addr" {
  372. explicitAddr = true
  373. }
  374. })
  375. if svc, ok := servicesMgr.GetService("www"); ok {
  376. httpDisabled = svc.Disabled
  377. if !explicitAddr {
  378. httpPort = svc.Port
  379. }
  380. }
  381. if err := startHTTP(httpPort, httpDisabled); err != nil {
  382. log.Fatalf("HTTP startup failed: %v", err)
  383. }
  384. // Print startup banner
  385. fmt.Println("========================================================")
  386. fmt.Println(" ⚡ DebTik Appliance Controller ⚡ ")
  387. fmt.Println("========================================================")
  388. fmt.Printf(" Web UI & API : http://0.0.0.0%s\n", *addr)
  389. fmt.Printf(" CLI Telnet Service : 0.0.0.0%s (debtik-cli / telnet)\n", telnetAddr)
  390. fmt.Printf(" CLI SSH Service : 0.0.0.0%s (ssh admin@<router-ip> -p 2222)\n", sshAddr)
  391. fmt.Printf(" Metadata Storage : %s\n", *metaPath)
  392. fmt.Printf(" Debian Config Path : %s\n", network.DebianVlansCfgPath)
  393. if ifaces, err := netMgr.ListInterfaces(); err == nil {
  394. fmt.Printf(" Discovered Links : %d interfaces\n", len(ifaces))
  395. for _, i := range ifaces {
  396. fmt.Printf(" - %-10s (%s) [%s] MTU:%d\n", i.Name, i.Type, i.State, i.MTU)
  397. }
  398. }
  399. fmt.Println("========================================================")
  400. fmt.Println(" DebTik Appliance ready. Press Ctrl+C to terminate.")
  401. // Graceful shutdown handling
  402. stopChan := make(chan os.Signal, 1)
  403. signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)
  404. <-stopChan
  405. fmt.Println("\nShutting down DebTik Controller gracefully...")
  406. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  407. defer cancel()
  408. httpMu.Lock()
  409. currentHTTP := httpServer
  410. httpMu.Unlock()
  411. if currentHTTP != nil {
  412. if err := currentHTTP.Shutdown(ctx); err != nil {
  413. log.Printf("Server shutdown error: %v", err)
  414. }
  415. }
  416. fmt.Println("DebTik Controller stopped.")
  417. }
  418. func waitListener(start func() error, running func() bool) error {
  419. result := make(chan error, 1)
  420. go func() { result <- start() }()
  421. timer := time.NewTimer(5 * time.Second)
  422. defer timer.Stop()
  423. ticker := time.NewTicker(20 * time.Millisecond)
  424. defer ticker.Stop()
  425. for {
  426. select {
  427. case err := <-result:
  428. return err
  429. case <-timer.C:
  430. return fmt.Errorf("listener did not become ready")
  431. case <-ticker.C:
  432. if running() {
  433. return nil
  434. }
  435. }
  436. }
  437. }