frontend with settings and restarting app
This commit is contained in:
239
app/config.go
239
app/config.go
@@ -1,138 +1,153 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Config contains runtime configuration for the honeypot
|
||||
type Config struct {
|
||||
LogMode string `json:"log_mode"` // "file" | "stdout" | "sqlite"
|
||||
LogPath string `json:"log_path"`
|
||||
LogMode string `json:"log_mode"` // "file" | "stdout" | "sqlite"
|
||||
LogPath string `json:"log_path"`
|
||||
|
||||
Web struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Bind string `json:"bind"`
|
||||
Port int `json:"port"`
|
||||
} `json:"web"`
|
||||
Web struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Bind string `json:"bind"`
|
||||
Port int `json:"port"`
|
||||
} `json:"web"`
|
||||
|
||||
Services struct {
|
||||
HTTP bool `json:"http"`
|
||||
HTTPS bool `json:"https"`
|
||||
SSH bool `json:"ssh"`
|
||||
FTP bool `json:"ftp"`
|
||||
SMTP bool `json:"smtp"`
|
||||
IMAP bool `json:"imap"`
|
||||
Telnet bool `json:"telnet"`
|
||||
MySQL bool `json:"mysql"`
|
||||
PostgreSQL bool `json:"postgresql"`
|
||||
MongoDB bool `json:"mongodb"`
|
||||
RDP bool `json:"rdp"`
|
||||
SMB bool `json:"smb"`
|
||||
SIP bool `json:"sip"`
|
||||
VNC bool `json:"vnc"`
|
||||
Generic []int `json:"generic"`
|
||||
} `json:"services"`
|
||||
Services struct {
|
||||
HTTP bool `json:"http"`
|
||||
HTTPS bool `json:"https"`
|
||||
SSH bool `json:"ssh"`
|
||||
FTP bool `json:"ftp"`
|
||||
SMTP bool `json:"smtp"`
|
||||
IMAP bool `json:"imap"`
|
||||
Telnet bool `json:"telnet"`
|
||||
MySQL bool `json:"mysql"`
|
||||
PostgreSQL bool `json:"postgresql"`
|
||||
MongoDB bool `json:"mongodb"`
|
||||
RDP bool `json:"rdp"`
|
||||
SMB bool `json:"smb"`
|
||||
SIP bool `json:"sip"`
|
||||
VNC bool `json:"vnc"`
|
||||
Generic []int `json:"generic"`
|
||||
} `json:"services"`
|
||||
|
||||
Ports struct {
|
||||
HTTP int `json:"http"`
|
||||
HTTPS int `json:"https"`
|
||||
SSH int `json:"ssh"`
|
||||
FTP int `json:"ftp"`
|
||||
SMTP int `json:"smtp"`
|
||||
IMAP int `json:"imap"`
|
||||
Telnet int `json:"telnet"`
|
||||
MySQL int `json:"mysql"`
|
||||
PostgreSQL int `json:"postgresql"`
|
||||
MongoDB int `json:"mongodb"`
|
||||
RDP int `json:"rdp"`
|
||||
SMB int `json:"smb"`
|
||||
SIP int `json:"sip"`
|
||||
VNC int `json:"vnc"`
|
||||
} `json:"ports"`
|
||||
Ports struct {
|
||||
HTTP int `json:"http"`
|
||||
HTTPS int `json:"https"`
|
||||
SSH int `json:"ssh"`
|
||||
FTP int `json:"ftp"`
|
||||
SMTP int `json:"smtp"`
|
||||
IMAP int `json:"imap"`
|
||||
Telnet int `json:"telnet"`
|
||||
MySQL int `json:"mysql"`
|
||||
PostgreSQL int `json:"postgresql"`
|
||||
MongoDB int `json:"mongodb"`
|
||||
RDP int `json:"rdp"`
|
||||
SMB int `json:"smb"`
|
||||
SIP int `json:"sip"`
|
||||
VNC int `json:"vnc"`
|
||||
} `json:"ports"`
|
||||
|
||||
// Certificates allows overriding default certificate/key locations.
|
||||
Certificates struct {
|
||||
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
||||
// If empty, a persistent key will be created in the same directory as LogPath.
|
||||
SSHHostKeyPath string `json:"ssh_host_key_path"`
|
||||
// TLSCertPath and TLSKeyPath are used by TLS-capable services if provided.
|
||||
// If empty, a self-signed certificate will be generated and stored next to LogPath.
|
||||
TLSCertPath string `json:"tls_cert_path"`
|
||||
TLSKeyPath string `json:"tls_key_path"`
|
||||
} `json:"certificates"`
|
||||
// Certificates allows overriding default certificate/key locations.
|
||||
Certificates struct {
|
||||
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
||||
// If empty, a persistent key will be created in the same directory as LogPath.
|
||||
SSHHostKeyPath string `json:"ssh_host_key_path"`
|
||||
// TLSCertPath and TLSKeyPath are used by TLS-capable services if provided.
|
||||
// If empty, a self-signed certificate will be generated and stored next to LogPath.
|
||||
TLSCertPath string `json:"tls_cert_path"`
|
||||
TLSKeyPath string `json:"tls_key_path"`
|
||||
} `json:"certificates"`
|
||||
}
|
||||
|
||||
// LastConfigPath holds the last path used to load/save config.json
|
||||
var LastConfigPath string
|
||||
|
||||
// EnsureConfig writes a default config file if the given path doesn't exist
|
||||
func EnsureConfig(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return fmt.Errorf("create config dir: %w", err)
|
||||
}
|
||||
def := defaultConfig()
|
||||
b, _ := json.MarshalIndent(def, "", " ")
|
||||
if err := os.WriteFile(path, b, 0644); err != nil {
|
||||
return fmt.Errorf("write default config: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return fmt.Errorf("create config dir: %w", err)
|
||||
}
|
||||
def := defaultConfig()
|
||||
b, _ := json.MarshalIndent(def, "", " ")
|
||||
if err := os.WriteFile(path, b, 0644); err != nil {
|
||||
return fmt.Errorf("write default config: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfig loads JSON config from path
|
||||
func LoadConfig(path string) (Config, error) {
|
||||
var cfg Config
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
return cfg, nil
|
||||
var cfg Config
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
LastConfigPath = path
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// SaveConfig writes the given config to the provided path
|
||||
func SaveConfig(path string, cfg Config) error {
|
||||
b, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, b, 0644)
|
||||
}
|
||||
|
||||
func defaultConfig() Config {
|
||||
var c Config
|
||||
c.LogMode = "file"
|
||||
c.LogPath = "honeypot.log"
|
||||
c.Web.Enabled = true
|
||||
c.Web.Bind = "127.0.0.1"
|
||||
c.Web.Port = 6333
|
||||
var c Config
|
||||
c.LogMode = "file"
|
||||
c.LogPath = "honeypot.log"
|
||||
c.Web.Enabled = true
|
||||
c.Web.Bind = "127.0.0.1"
|
||||
c.Web.Port = 6333
|
||||
|
||||
// Enable common services by default
|
||||
c.Services.HTTP = true
|
||||
c.Services.HTTPS = false
|
||||
c.Services.SSH = true
|
||||
c.Services.FTP = true
|
||||
c.Services.SMTP = true
|
||||
c.Services.Telnet = true
|
||||
c.Services.MySQL = false
|
||||
c.Services.PostgreSQL = false
|
||||
c.Services.MongoDB = false
|
||||
c.Services.IMAP = false
|
||||
c.Services.RDP = false
|
||||
c.Services.SMB = false
|
||||
c.Services.SIP = false
|
||||
c.Services.VNC = false
|
||||
c.Services.Generic = []int{}
|
||||
// Enable common services by default
|
||||
c.Services.HTTP = true
|
||||
c.Services.HTTPS = false
|
||||
c.Services.SSH = true
|
||||
c.Services.FTP = true
|
||||
c.Services.SMTP = true
|
||||
c.Services.Telnet = true
|
||||
c.Services.MySQL = false
|
||||
c.Services.PostgreSQL = false
|
||||
c.Services.MongoDB = false
|
||||
c.Services.IMAP = false
|
||||
c.Services.RDP = false
|
||||
c.Services.SMB = false
|
||||
c.Services.SIP = false
|
||||
c.Services.VNC = false
|
||||
c.Services.Generic = []int{}
|
||||
|
||||
// Standard ports
|
||||
c.Ports.HTTP = 8080
|
||||
c.Ports.HTTPS = 8443
|
||||
c.Ports.SSH = 2222
|
||||
c.Ports.FTP = 2121
|
||||
c.Ports.SMTP = 2525
|
||||
c.Ports.IMAP = 1143
|
||||
c.Ports.Telnet = 2323
|
||||
c.Ports.MySQL = 3306
|
||||
c.Ports.PostgreSQL = 5432
|
||||
c.Ports.MongoDB = 27017
|
||||
c.Ports.RDP = 3389
|
||||
c.Ports.SMB = 4450
|
||||
c.Ports.SIP = 5060
|
||||
c.Ports.VNC = 5900
|
||||
// Standard ports
|
||||
c.Ports.HTTP = 8080
|
||||
c.Ports.HTTPS = 8443
|
||||
c.Ports.SSH = 2222
|
||||
c.Ports.FTP = 2121
|
||||
c.Ports.SMTP = 2525
|
||||
c.Ports.IMAP = 1143
|
||||
c.Ports.Telnet = 2323
|
||||
c.Ports.MySQL = 3306
|
||||
c.Ports.PostgreSQL = 5432
|
||||
c.Ports.MongoDB = 27017
|
||||
c.Ports.RDP = 3389
|
||||
c.Ports.SIP = 5060
|
||||
c.Ports.VNC = 5900
|
||||
|
||||
return c
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ type App struct {
|
||||
mu sync.Mutex
|
||||
listeners []net.Listener
|
||||
conns map[net.Conn]struct{}
|
||||
// restart channel
|
||||
restartCh chan struct{}
|
||||
}
|
||||
|
||||
// HTTPS honeypot using self-signed or configured certificate
|
||||
@@ -203,7 +205,7 @@ func NewApp(cfg Config) (*App, error) {
|
||||
|
||||
// Root context for the App used for shutdown signalling
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
a := &App{cfg: cfg, logger: l, threatIntel: ti, ctx: ctx, cancel: cancel, conns: make(map[net.Conn]struct{})}
|
||||
a := &App{cfg: cfg, logger: l, threatIntel: ti, ctx: ctx, cancel: cancel, conns: make(map[net.Conn]struct{}), restartCh: make(chan struct{}, 1)}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -331,6 +333,17 @@ func (a *App) Run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Restart() {
|
||||
select {
|
||||
case a.restartCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) RestartChan() <-chan struct{} {
|
||||
return a.restartCh
|
||||
}
|
||||
|
||||
func (a *App) Shutdown() {
|
||||
a.cancel()
|
||||
// attempt to close all http servers if running
|
||||
|
||||
31
app/templates/blacklist.html
Normal file
31
app/templates/blacklist.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{{ define "blacklist_title" }}Blacklist{{ end }}
|
||||
{{ define "blacklist_content" }}
|
||||
<h1 class="text-2xl font-semibold text-white mb-4">Blacklisted IPs</h1>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">IP Address</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||
{{ range .Blacklisted }}
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ . }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">
|
||||
<!-- Actions could be added here (unblacklist) -->
|
||||
<span class="text-gray-500">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td class="px-4 py-4 text-sm text-gray-400" colspan="2">No blacklisted IPs.</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -1,5 +1,5 @@
|
||||
{{ define "title" }}Honeypot Dashboard{{ end }}
|
||||
{{ define "content" }}
|
||||
{{ define "index_title" }}Honeypot Dashboard{{ end }}
|
||||
{{ define "index_content" }}
|
||||
<div class="space-y-8">
|
||||
<h1 class="text-2xl font-semibold text-white">Honeypot Overview</h1>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@@ -31,4 +31,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ define "index" }}{{ template "layout.html" . }}{{ end }}
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ block "title" . }}Honeypot Dashboard{{ end }}</title>
|
||||
<title>
|
||||
{{ if eq .PageTitle "index_title" }}Honeypot Dashboard
|
||||
{{ else if eq .PageTitle "logs_title" }}Recent Logs
|
||||
{{ else if eq .PageTitle "settings_title" }}Settings
|
||||
{{ else if eq .PageTitle "stats_title" }}Statistics
|
||||
{{ else if eq .PageTitle "blacklist_title" }}Blacklist
|
||||
{{ else if eq .PageTitle "threats_title" }}Top Threats
|
||||
{{ else }}Honeypot Dashboard{{ end }}
|
||||
</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
@@ -27,18 +35,31 @@
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex h-16 items-center justify-between">
|
||||
<div class="flex items-center space-x-8">
|
||||
<a href="/" class="text-primary-400 hover:text-primary-300 font-semibold">🍯 Honeypot</a>
|
||||
<a href="/" class="text-primary-400 hover:text-primary-300 font-semibold">Honeypot Dashboard</a>
|
||||
<a href="/logs" class="text-gray-300 hover:text-white">Recent Logs</a>
|
||||
<a href="/threats" class="text-gray-300 hover:text-white">Top Threats</a>
|
||||
<a href="/blacklist" class="text-gray-300 hover:text-white">Blacklist</a>
|
||||
<a href="/stats" class="text-gray-300 hover:text-white">Statistics</a>
|
||||
<a href="/settings" class="text-gray-300 hover:text-white">Settings</a>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">{{ .Now }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
||||
{{ block "content" . }}{{ end }}
|
||||
{{ if eq .PageContent "index_content" }}
|
||||
{{ template "index_content" . }}
|
||||
{{ else if eq .PageContent "logs_content" }}
|
||||
{{ template "logs_content" . }}
|
||||
{{ else if eq .PageContent "settings_content" }}
|
||||
{{ template "settings_content" . }}
|
||||
{{ else if eq .PageContent "stats_content" }}
|
||||
{{ template "stats_content" . }}
|
||||
{{ else if eq .PageContent "blacklist_content" }}
|
||||
{{ template "blacklist_content" . }}
|
||||
{{ else if eq .PageContent "threats_content" }}
|
||||
{{ template "threats_content" . }}
|
||||
{{ end }}
|
||||
</main>
|
||||
<footer class="border-t border-gray-800 py-6 text-center text-gray-500 text-sm">Honeypot Dashboard</footer>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{ define "title" }}Recent Logs{{ end }}
|
||||
{{ define "content" }}
|
||||
{{ define "logs_title" }}Recent Logs{{ end }}
|
||||
{{ define "logs_content" }}
|
||||
<h1 class="text-2xl font-semibold text-white mb-4">Recent Logs</h1>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
@@ -26,4 +26,3 @@
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ define "logs" }}{{ template "layout.html" . }}{{ end }}
|
||||
|
||||
193
app/templates/settings.html
Normal file
193
app/templates/settings.html
Normal file
@@ -0,0 +1,193 @@
|
||||
{{ define "settings_title" }}Settings{{ end }}
|
||||
{{ define "settings_content" }}
|
||||
<h1 class="text-2xl font-semibold text-white mb-6">Settings</h1>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Services</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{{/* Toggle component */}}
|
||||
{{ $svc := .Cfg.Services }}
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">HTTP</span>
|
||||
<input id="svc-http" type="checkbox" class="h-5 w-5" {{ if $svc.HTTP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">HTTPS</span>
|
||||
<input id="svc-https" type="checkbox" class="h-5 w-5" {{ if $svc.HTTPS }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">SSH</span>
|
||||
<input id="svc-ssh" type="checkbox" class="h-5 w-5" {{ if $svc.SSH }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">FTP</span>
|
||||
<input id="svc-ftp" type="checkbox" class="h-5 w-5" {{ if $svc.FTP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">SMTP</span>
|
||||
<input id="svc-smtp" type="checkbox" class="h-5 w-5" {{ if $svc.SMTP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">IMAP</span>
|
||||
<input id="svc-imap" type="checkbox" class="h-5 w-5" {{ if $svc.IMAP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">Telnet</span>
|
||||
<input id="svc-telnet" type="checkbox" class="h-5 w-5" {{ if $svc.Telnet }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">MySQL</span>
|
||||
<input id="svc-mysql" type="checkbox" class="h-5 w-5" {{ if $svc.MySQL }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">PostgreSQL</span>
|
||||
<input id="svc-postgresql" type="checkbox" class="h-5 w-5" {{ if $svc.PostgreSQL }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">MongoDB</span>
|
||||
<input id="svc-mongodb" type="checkbox" class="h-5 w-5" {{ if $svc.MongoDB }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">RDP</span>
|
||||
<input id="svc-rdp" type="checkbox" class="h-5 w-5" {{ if $svc.RDP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">SMB</span>
|
||||
<input id="svc-smb" type="checkbox" class="h-5 w-5" {{ if $svc.SMB }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">SIP</span>
|
||||
<input id="svc-sip" type="checkbox" class="h-5 w-5" {{ if $svc.SIP }}checked{{ end }}>
|
||||
</label>
|
||||
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||
<span class="text-gray-200">VNC</span>
|
||||
<input id="svc-vnc" type="checkbox" class="h-5 w-5" {{ if $svc.VNC }}checked{{ end }}>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Ports</h2>
|
||||
{{ $p := .Cfg.Ports }}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<label class="block">
|
||||
<span class="text-gray-300">HTTP</span>
|
||||
<input id="port-http" type="number" value="{{ $p.HTTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">HTTPS</span>
|
||||
<input id="port-https" type="number" value="{{ $p.HTTPS }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">SSH</span>
|
||||
<input id="port-ssh" type="number" value="{{ $p.SSH }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">FTP</span>
|
||||
<input id="port-ftp" type="number" value="{{ $p.FTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">SMTP</span>
|
||||
<input id="port-smtp" type="number" value="{{ $p.SMTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">IMAP</span>
|
||||
<input id="port-imap" type="number" value="{{ $p.IMAP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">Telnet</span>
|
||||
<input id="port-telnet" type="number" value="{{ $p.Telnet }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">MySQL</span>
|
||||
<input id="port-mysql" type="number" value="{{ $p.MySQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">PostgreSQL</span>
|
||||
<input id="port-postgresql" type="number" value="{{ $p.PostgreSQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">MongoDB</span>
|
||||
<input id="port-mongodb" type="number" value="{{ $p.MongoDB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">RDP</span>
|
||||
<input id="port-rdp" type="number" value="{{ $p.RDP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">SMB</span>
|
||||
<input id="port-smb" type="number" value="{{ $p.SMB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">SIP</span>
|
||||
<input id="port-sip" type="number" value="{{ $p.SIP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-300">VNC</span>
|
||||
<input id="port-vnc" type="number" value="{{ $p.VNC }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex items-center gap-3">
|
||||
<button id="btn-save" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded text-white">Save Settings</button>
|
||||
<button id="btn-restart" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-white">Restart App</button>
|
||||
<span id="save-status" class="text-sm text-gray-400"></span>
|
||||
</div>
|
||||
<script>
|
||||
async function saveSettings() {
|
||||
const payload = {
|
||||
services: {
|
||||
http: document.getElementById('svc-http').checked,
|
||||
https: document.getElementById('svc-https').checked,
|
||||
ssh: document.getElementById('svc-ssh').checked,
|
||||
ftp: document.getElementById('svc-ftp').checked,
|
||||
smtp: document.getElementById('svc-smtp').checked,
|
||||
imap: document.getElementById('svc-imap').checked,
|
||||
telnet: document.getElementById('svc-telnet').checked,
|
||||
mysql: document.getElementById('svc-mysql').checked,
|
||||
postgresql: document.getElementById('svc-postgresql').checked,
|
||||
mongodb: document.getElementById('svc-mongodb').checked,
|
||||
rdp: document.getElementById('svc-rdp').checked,
|
||||
smb: document.getElementById('svc-smb').checked,
|
||||
sip: document.getElementById('svc-sip').checked,
|
||||
vnc: document.getElementById('svc-vnc').checked,
|
||||
},
|
||||
ports: {
|
||||
http: parseInt(document.getElementById('port-http').value, 10),
|
||||
https: parseInt(document.getElementById('port-https').value, 10),
|
||||
ssh: parseInt(document.getElementById('port-ssh').value, 10),
|
||||
ftp: parseInt(document.getElementById('port-ftp').value, 10),
|
||||
smtp: parseInt(document.getElementById('port-smtp').value, 10),
|
||||
imap: parseInt(document.getElementById('port-imap').value, 10),
|
||||
telnet: parseInt(document.getElementById('port-telnet').value, 10),
|
||||
mysql: parseInt(document.getElementById('port-mysql').value, 10),
|
||||
postgresql: parseInt(document.getElementById('port-postgresql').value, 10),
|
||||
mongodb: parseInt(document.getElementById('port-mongodb').value, 10),
|
||||
rdp: parseInt(document.getElementById('port-rdp').value, 10),
|
||||
smb: parseInt(document.getElementById('port-smb').value, 10),
|
||||
sip: parseInt(document.getElementById('port-sip').value, 10),
|
||||
vnc: parseInt(document.getElementById('port-vnc').value, 10),
|
||||
}
|
||||
};
|
||||
const res = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
const out = await res.json().catch(() => ({}));
|
||||
const el = document.getElementById('save-status');
|
||||
if (res.ok) {
|
||||
el.textContent = 'Saved. You may need to restart to apply port changes.';
|
||||
el.className = 'text-sm text-green-400';
|
||||
} else {
|
||||
el.textContent = out.error || 'Save failed';
|
||||
el.className = 'text-sm text-red-400';
|
||||
}
|
||||
}
|
||||
async function restartApp() {
|
||||
const res = await fetch('/api/restart', { method: 'POST' });
|
||||
if (res.ok) {
|
||||
document.getElementById('save-status').textContent = 'Restarting...';
|
||||
setTimeout(() => location.reload(), 1200);
|
||||
}
|
||||
}
|
||||
document.getElementById('btn-save').addEventListener('click', saveSettings);
|
||||
document.getElementById('btn-restart').addEventListener('click', restartApp);
|
||||
</script>
|
||||
{{ end }}
|
||||
47
app/templates/stats.html
Normal file
47
app/templates/stats.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{{ define "stats_title" }}Statistics{{ end }}
|
||||
{{ define "stats_content" }}
|
||||
<h1 class="text-2xl font-semibold text-white mb-4">Statistics</h1>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-400">Total IPs</div>
|
||||
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_ips" }}</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-400">Blacklisted</div>
|
||||
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "blacklisted_ips" }}</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-400">Connections</div>
|
||||
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_connections" }}</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-400">Auth Attempts</div>
|
||||
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_auth_attempts" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||
<h2 class="text-xl font-semibold text-white mb-3">Service Activity</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Service</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Connections</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||
{{ range $svc, $cnt := .ServiceStats }}
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ $svc }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ $cnt }}</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td class="px-4 py-4 text-sm text-gray-400" colspan="2">No data.</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
32
app/templates/threats.html
Normal file
32
app/templates/threats.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{{ define "threats_title" }}Top Threats{{ end }}
|
||||
{{ define "threats_content" }}
|
||||
<h1 class="text-2xl font-semibold text-white mb-4">Top Threats</h1>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">IP</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Threat Score</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Connections</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Auth Attempts</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Services</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Last Seen</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Blacklisted</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||
{{ range .Threats }}
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ .IP }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300 font-semibold">{{ .ThreatScore }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ .TotalConnections }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ .AuthAttempts }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ len .Services }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ .LastSeen.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-300">{{ if .IsBlacklisted }}<span class="text-red-400">Yes</span>{{ else }}No{{ end }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
185
app/web.go
185
app/web.go
@@ -25,7 +25,16 @@ func initTemplates() error {
|
||||
},
|
||||
}
|
||||
// Parse layout first, then pages
|
||||
t, err := template.New("layout.html").Funcs(funcMap).ParseFS(templatesFS, "templates/layout.html", "templates/index.html", "templates/logs.html")
|
||||
t, err := template.New("layout.html").Funcs(funcMap).ParseFS(
|
||||
templatesFS,
|
||||
"templates/layout.html",
|
||||
"templates/index.html",
|
||||
"templates/logs.html",
|
||||
"templates/threats.html",
|
||||
"templates/blacklist.html",
|
||||
"templates/stats.html",
|
||||
"templates/settings.html",
|
||||
)
|
||||
if err != nil { return err }
|
||||
templates = t
|
||||
return nil
|
||||
@@ -49,13 +58,125 @@ func (a *App) startWeb() {
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Stats": stats,
|
||||
"PageTitle": "index_title",
|
||||
"PageContent": "index_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "index", data)
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
})
|
||||
|
||||
// Settings UI
|
||||
mux.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Cfg": a.cfg,
|
||||
"PageTitle": "settings_title",
|
||||
"PageContent": "settings_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
})
|
||||
// API to read/update settings (services + ports)
|
||||
mux.HandleFunc("/api/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
_ = json.NewEncoder(w).Encode(a.cfg)
|
||||
return
|
||||
case http.MethodPost:
|
||||
var in struct {
|
||||
Services struct {
|
||||
HTTP bool `json:"http"`
|
||||
HTTPS bool `json:"https"`
|
||||
SSH bool `json:"ssh"`
|
||||
FTP bool `json:"ftp"`
|
||||
SMTP bool `json:"smtp"`
|
||||
IMAP bool `json:"imap"`
|
||||
Telnet bool `json:"telnet"`
|
||||
MySQL bool `json:"mysql"`
|
||||
PostgreSQL bool `json:"postgresql"`
|
||||
MongoDB bool `json:"mongodb"`
|
||||
RDP bool `json:"rdp"`
|
||||
SMB bool `json:"smb"`
|
||||
SIP bool `json:"sip"`
|
||||
VNC bool `json:"vnc"`
|
||||
} `json:"services"`
|
||||
Ports struct {
|
||||
HTTP int `json:"http"`
|
||||
HTTPS int `json:"https"`
|
||||
SSH int `json:"ssh"`
|
||||
FTP int `json:"ftp"`
|
||||
SMTP int `json:"smtp"`
|
||||
IMAP int `json:"imap"`
|
||||
Telnet int `json:"telnet"`
|
||||
MySQL int `json:"mysql"`
|
||||
PostgreSQL int `json:"postgresql"`
|
||||
MongoDB int `json:"mongodb"`
|
||||
RDP int `json:"rdp"`
|
||||
SMB int `json:"smb"`
|
||||
SIP int `json:"sip"`
|
||||
VNC int `json:"vnc"`
|
||||
} `json:"ports"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error":"bad json"})
|
||||
return
|
||||
}
|
||||
// Update in-memory
|
||||
a.cfg.Services.HTTP = in.Services.HTTP
|
||||
a.cfg.Services.HTTPS = in.Services.HTTPS
|
||||
a.cfg.Services.SSH = in.Services.SSH
|
||||
a.cfg.Services.FTP = in.Services.FTP
|
||||
a.cfg.Services.SMTP = in.Services.SMTP
|
||||
a.cfg.Services.IMAP = in.Services.IMAP
|
||||
a.cfg.Services.Telnet = in.Services.Telnet
|
||||
a.cfg.Services.MySQL = in.Services.MySQL
|
||||
a.cfg.Services.PostgreSQL = in.Services.PostgreSQL
|
||||
a.cfg.Services.MongoDB = in.Services.MongoDB
|
||||
a.cfg.Services.RDP = in.Services.RDP
|
||||
a.cfg.Services.SMB = in.Services.SMB
|
||||
a.cfg.Services.SIP = in.Services.SIP
|
||||
a.cfg.Services.VNC = in.Services.VNC
|
||||
|
||||
a.cfg.Ports.HTTP = in.Ports.HTTP
|
||||
a.cfg.Ports.HTTPS = in.Ports.HTTPS
|
||||
a.cfg.Ports.SSH = in.Ports.SSH
|
||||
a.cfg.Ports.FTP = in.Ports.FTP
|
||||
a.cfg.Ports.SMTP = in.Ports.SMTP
|
||||
a.cfg.Ports.IMAP = in.Ports.IMAP
|
||||
a.cfg.Ports.Telnet = in.Ports.Telnet
|
||||
a.cfg.Ports.MySQL = in.Ports.MySQL
|
||||
a.cfg.Ports.PostgreSQL = in.Ports.PostgreSQL
|
||||
a.cfg.Ports.MongoDB = in.Ports.MongoDB
|
||||
a.cfg.Ports.RDP = in.Ports.RDP
|
||||
a.cfg.Ports.SMB = in.Ports.SMB
|
||||
a.cfg.Ports.SIP = in.Ports.SIP
|
||||
a.cfg.Ports.VNC = in.Ports.VNC
|
||||
|
||||
// Persist to ./config.json
|
||||
if b, err := json.MarshalIndent(a.cfg, "", " "); err == nil {
|
||||
_ = os.WriteFile("config.json", b, 0644)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"status":"ok"})
|
||||
return
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
})
|
||||
// Restart endpoint: triggers app restart
|
||||
mux.HandleFunc("/api/restart", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed); return }
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"status":"restarting"})
|
||||
go func(){ time.Sleep(700*time.Millisecond); a.Restart() }()
|
||||
})
|
||||
mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
|
||||
// display last 200 logs
|
||||
var rows []Record
|
||||
@@ -102,9 +223,67 @@ func (a *App) startWeb() {
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Rows": rows,
|
||||
"PageTitle": "logs_title",
|
||||
"PageContent": "logs_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "logs", data)
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
})
|
||||
mux.HandleFunc("/threats", func(w http.ResponseWriter, r *http.Request) {
|
||||
var threats []*IPThreatInfo
|
||||
if a.threatIntel != nil {
|
||||
threats = a.threatIntel.GetTopThreats(50)
|
||||
}
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Threats": threats,
|
||||
"PageTitle": "threats_title",
|
||||
"PageContent": "threats_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
})
|
||||
mux.HandleFunc("/blacklist", func(w http.ResponseWriter, r *http.Request) {
|
||||
var bl []string
|
||||
if a.threatIntel != nil {
|
||||
bl = a.threatIntel.GetBlacklistedIPs()
|
||||
}
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Blacklisted": bl,
|
||||
"PageTitle": "blacklist_title",
|
||||
"PageContent": "blacklist_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
})
|
||||
mux.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||
stats := map[string]any{}
|
||||
var svc map[string]int
|
||||
if a.threatIntel != nil {
|
||||
stats = a.threatIntel.GetStats()
|
||||
if v, ok := stats["service_stats"].(map[string]int); ok {
|
||||
svc = v
|
||||
}
|
||||
}
|
||||
data := map[string]any{
|
||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||
"Stats": stats,
|
||||
"ServiceStats": svc,
|
||||
"PageTitle": "stats_title",
|
||||
"PageContent": "stats_content",
|
||||
}
|
||||
if templates != nil {
|
||||
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||
return
|
||||
}
|
||||
http.Error(w, "templates not loaded", 500)
|
||||
|
||||
70
main.go
70
main.go
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"honeydany/app"
|
||||
)
|
||||
@@ -16,36 +17,55 @@ func main() {
|
||||
cfgPath := flag.String("config", "config.json", "path to config.json")
|
||||
flag.Parse()
|
||||
|
||||
// ensure config exists (auto-generate default if missing)
|
||||
if err := app.EnsureConfig(*cfgPath); err != nil {
|
||||
log.Fatalf("ensure config: %v", err)
|
||||
}
|
||||
for {
|
||||
// ensure config exists (auto-generate default if missing)
|
||||
if err := app.EnsureConfig(*cfgPath); err != nil {
|
||||
log.Fatalf("ensure config: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := app.LoadConfig(*cfgPath)
|
||||
if err != nil {
|
||||
log.Fatalf("load config: %v", err)
|
||||
}
|
||||
cfg, err := app.LoadConfig(*cfgPath)
|
||||
if err != nil {
|
||||
log.Fatalf("load config: %v", err)
|
||||
}
|
||||
|
||||
a, err := app.NewApp(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("create app: %v", err)
|
||||
}
|
||||
a, err := app.NewApp(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("create app: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// handle signals and restart
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
var shouldRestart bool
|
||||
go func() {
|
||||
select {
|
||||
case <-sigCh:
|
||||
fmt.Println("signal received, shutting down")
|
||||
cancel()
|
||||
a.Shutdown()
|
||||
case <-a.RestartChan():
|
||||
fmt.Println("restart requested, reloading configuration")
|
||||
shouldRestart = true
|
||||
cancel()
|
||||
a.Shutdown()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := a.Run(ctx); err != nil {
|
||||
log.Fatalf("app run: %v", err)
|
||||
}
|
||||
|
||||
// handle signals
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigCh
|
||||
fmt.Println("signal received, shutting down")
|
||||
cancel()
|
||||
a.Shutdown()
|
||||
}()
|
||||
|
||||
if err := a.Run(ctx); err != nil {
|
||||
log.Fatalf("app run: %v", err)
|
||||
|
||||
if !shouldRestart {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println("restarting honeypot...")
|
||||
time.Sleep(1 * time.Second) // Brief pause before restart
|
||||
}
|
||||
|
||||
fmt.Println("honeypot stopped")
|
||||
|
||||
Reference in New Issue
Block a user