Merge pull request #1 from shinyzero00/master
All checks were successful
build / build (push) Successful in 2m25s

refactoring pr by shinyzero00
This commit is contained in:
Ilya Chernishev
2026-02-15 13:17:01 +03:00
committed by GitHub
8 changed files with 199 additions and 189 deletions

View File

@@ -16,13 +16,14 @@ var (
port int port int
protocol string protocol string
) )
var UnbanCmd = &cobra.Command{ var UnbanCmd = &cobra.Command{
Use: "unban", Use: "unban",
Short: "Unban IP", Short: "Unban IP",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := func() error {
if len(args) == 0 { if len(args) == 0 {
fmt.Println("IP can't be empty") return fmt.Errorf("IP can't be empty")
os.Exit(1)
} }
if ttl_fw == "" { if ttl_fw == "" {
ttl_fw = "1y" ttl_fw = "1y"
@@ -30,39 +31,38 @@ var UnbanCmd = &cobra.Command{
ip := args[0] ip := args[0]
db, err := storage.NewBanWriter() db, err := storage.NewBanWriter()
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
cfg, err := config.LoadConfig() cfg, err := config.LoadConfig()
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
fw := cfg.Firewall.Name fw := cfg.Firewall.Name
b := blocker.GetBlocker(fw, cfg.Firewall.Config) b := blocker.GetBlocker(fw, cfg.Firewall.Config)
if ip == "" { if ip == "" {
fmt.Println("IP can't be empty") return fmt.Errorf("IP can't be empty")
os.Exit(1)
} }
if net.ParseIP(ip) == nil { if net.ParseIP(ip) == nil {
fmt.Println("Invalid IP") return fmt.Errorf("invalid IP")
os.Exit(1)
} }
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
err = b.Unban(ip) err = b.Unban(ip)
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
err = db.RemoveBan(ip) err = db.RemoveBan(ip)
if err != nil {
return err
}
fmt.Println("IP unblocked successfully!")
return nil
}()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
fmt.Println("IP unblocked successfully!")
}, },
} }
@@ -70,9 +70,9 @@ var BanCmd = &cobra.Command{
Use: "ban", Use: "ban",
Short: "Ban IP", Short: "Ban IP",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := func() error {
if len(args) == 0 { if len(args) == 0 {
fmt.Println("IP can't be empty") return fmt.Errorf("IP can't be empty")
os.Exit(1)
} }
if ttl_fw == "" { if ttl_fw == "" {
ttl_fw = "1y" ttl_fw = "1y"
@@ -80,39 +80,38 @@ var BanCmd = &cobra.Command{
ip := args[0] ip := args[0]
db, err := storage.NewBanWriter() db, err := storage.NewBanWriter()
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
cfg, err := config.LoadConfig() cfg, err := config.LoadConfig()
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
fw := cfg.Firewall.Name fw := cfg.Firewall.Name
b := blocker.GetBlocker(fw, cfg.Firewall.Config) b := blocker.GetBlocker(fw, cfg.Firewall.Config)
if ip == "" { if ip == "" {
fmt.Println("IP can't be empty") return fmt.Errorf("IP can't be empty")
os.Exit(1)
} }
if net.ParseIP(ip) == nil { if net.ParseIP(ip) == nil {
fmt.Println("Invalid IP") return fmt.Errorf("invalid IP")
os.Exit(1)
} }
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
err = b.Ban(ip) err = b.Ban(ip)
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
err = db.AddBan(ip, ttl_fw, "manual ban") err = db.AddBan(ip, ttl_fw, "manual ban")
if err != nil {
return err
}
fmt.Println("IP blocked successfully!")
return nil
}()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
fmt.Println("IP blocked successfully!")
}, },
} }

View File

@@ -94,7 +94,6 @@ func (f *Firewalld) PortClose(port int, protocol string) error {
// #nosec G204 - handle is extracted from nftables output and validated // #nosec G204 - handle is extracted from nftables output and validated
if port >= 0 && port <= 65535 { if port >= 0 && port <= 65535 {
if protocol != "tcp" && protocol != "udp" { if protocol != "tcp" && protocol != "udp" {
f.logger.Error("invalid protocol")
return fmt.Errorf("invalid protocol") return fmt.Errorf("invalid protocol")
} }
s := strconv.Itoa(port) s := strconv.Itoa(port)
@@ -106,13 +105,11 @@ func (f *Firewalld) PortClose(port int, protocol string) error {
) )
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
f.logger.Error(err.Error())
return err return err
} }
f.logger.Info("Remove port " + s + " " + string(output)) f.logger.Info("Remove port " + s + " " + string(output))
output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput() output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput()
if err != nil { if err != nil {
f.logger.Error(err.Error())
return err return err
} }
f.logger.Info("Reload " + string(output)) f.logger.Info("Reload " + string(output))

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
@@ -57,13 +58,9 @@ func NewRule(
return err return err
} }
defer func() { defer func() {
err = file.Close() err = errors.Join(err, file.Close())
if err != nil {
fmt.Println(err)
}
}() }()
cfg := Rules{Rules: r} cfg := Rules{Rules: r}
err = toml.NewEncoder(file).Encode(cfg) err = toml.NewEncoder(file).Encode(cfg)
if err != nil { if err != nil {
return err return err
@@ -126,24 +123,24 @@ func EditRule(Name string, ServiceName string, Path string, Status string, Metho
} }
func ParseDurationWithYears(s string) (time.Duration, error) { func ParseDurationWithYears(s string) (time.Duration, error) {
if strings.HasSuffix(s, "y") { if ss, ok := strings.CutSuffix(s, "y"); ok {
years, err := strconv.Atoi(strings.TrimSuffix(s, "y")) years, err := strconv.Atoi(ss)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return time.Duration(years) * 365 * 24 * time.Hour, nil return time.Duration(years) * 365 * 24 * time.Hour, nil
} }
if strings.HasSuffix(s, "M") { if ss, ok := strings.CutSuffix(s, "M"); ok {
months, err := strconv.Atoi(strings.TrimSuffix(s, "M")) months, err := strconv.Atoi(ss)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return time.Duration(months) * 30 * 24 * time.Hour, nil return time.Duration(months) * 30 * 24 * time.Hour, nil
} }
if strings.HasSuffix(s, "d") { if ss, ok := strings.CutSuffix(s, "d"); ok {
days, err := strconv.Atoi(strings.TrimSuffix(s, "d")) days, err := strconv.Atoi(ss)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@@ -21,7 +21,7 @@ type BanWriter struct {
func NewBanWriter() (*BanWriter, error) { func NewBanWriter() (*BanWriter, error) {
db, err := sql.Open( db, err := sql.Open(
"sqlite", "sqlite",
"/var/lib/banforge/bans.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)", buildSqliteDsn(banDBPath, pragmas),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -175,7 +175,6 @@ func (d *BanReader) IsBanned(ip string) (bool, error) {
} }
func (d *BanReader) BanList() error { func (d *BanReader) BanList() error {
var count int var count int
t := table.NewWriter() t := table.NewWriter()
t.SetOutputMirror(os.Stdout) t.SetOutputMirror(os.Stdout)

View File

@@ -2,55 +2,60 @@ package storage
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"strings"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
func CreateTables() error { const (
// Requests DB DBDir = "/var/lib/banforge/"
db_r, err := sql.Open("sqlite", ReqDBPath = DBDir + "requests.db"
"/var/lib/banforge/requests.db?"+ banDBPath = DBDir + "bans.db"
"mode=rwc&"+ )
"_pragma=journal_mode(WAL)&"+
"_pragma=busy_timeout(30000)&"+
"_pragma=synchronous(NORMAL)")
if err != nil {
return fmt.Errorf("failed to open requests db: %w", err)
}
defer func() {
err = db_r.Close()
if err != nil {
fmt.Println(err)
}
}()
_, err = db_r.Exec(CreateRequestsTable) var pragmas = map[string]string{
if err != nil { `journal_mode`: `wal`,
return fmt.Errorf("failed to create requests table: %w", err) `synchronous`: `normal`,
} `busy_timeout`: `30000`,
// also consider these
// Bans DB // `temp_store`: `memory`,
db_b, err := sql.Open("sqlite", // `cache_size`: `1000000000`,
"/var/lib/banforge/bans.db?"+ }
"mode=rwc&"+
"_pragma=journal_mode(WAL)&"+ func buildSqliteDsn(path string, pragmas map[string]string) string {
"_pragma=busy_timeout(30000)&"+ pragmastrs := make([]string, len(pragmas))
"_pragma=synchronous(FULL)") i := 0
if err != nil { for k, v := range pragmas {
return fmt.Errorf("failed to open bans db: %w", err) pragmastrs[i] = (fmt.Sprintf(`pragma=%s(%s)`, k, v))
} i++
defer func() { }
err = db_b.Close() return path + "?" + "mode=rwc&" + strings.Join(pragmastrs, "&")
if err != nil { }
fmt.Println(err)
} func initDB(dsn, sqlstr string) (err error) {
}() db, err := sql.Open("sqlite", dsn)
if err != nil {
_, err = db_b.Exec(CreateBansTable) return fmt.Errorf("failed to open %q: %w", dsn, err)
if err != nil { }
return fmt.Errorf("failed to create bans table: %w", err) defer func() {
} closeErr := db.Close()
fmt.Println("Tables created successfully!") if closeErr != nil {
return nil err = errors.Join(err, fmt.Errorf("failed to close %q: %w", dsn, closeErr))
}
}()
_, err = db.Exec(sqlstr)
if err != nil {
return fmt.Errorf("failed to create table: %w", err)
}
return err
}
func CreateTables() (err error) {
// Requests DB
err1 := initDB(buildSqliteDsn(ReqDBPath, pragmas), CreateRequestsTable)
err2 := initDB(buildSqliteDsn(banDBPath, pragmas), CreateBansTable)
return errors.Join(err1, err2)
} }

View File

@@ -7,15 +7,15 @@ import (
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
type Request_Writer struct { type RequestWriter struct {
logger *logger.Logger logger *logger.Logger
db *sql.DB db *sql.DB
} }
func NewRequestsWr() (*Request_Writer, error) { func NewRequestsWr() (*RequestWriter, error) {
db, err := sql.Open( db, err := sql.Open(
"sqlite", "sqlite",
"/var/lib/banforge/requests.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)", buildSqliteDsn(ReqDBPath, pragmas),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -23,7 +23,7 @@ func NewRequestsWr() (*Request_Writer, error) {
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1) db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(0) db.SetConnMaxLifetime(0)
return &Request_Writer{ return &RequestWriter{
logger: logger.New(false), logger: logger.New(false),
db: db, db: db,
}, nil }, nil

View File

@@ -1,10 +1,13 @@
package storage package storage
import ( import (
"database/sql"
"errors"
"fmt"
"time" "time"
) )
func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) { func WriteReq(db *RequestWriter, resultCh <-chan *LogEntry) {
db.logger.Info("Starting log writer") db.logger.Info("Starting log writer")
const batchSize = 100 const batchSize = 100
const flushInterval = 1 * time.Second const flushInterval = 1 * time.Second
@@ -14,29 +17,36 @@ func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
defer ticker.Stop() defer ticker.Stop()
flush := func() { flush := func() {
defer db.logger.Debug("Flushed batch", "count", len(batch))
err := func() (err error) {
if len(batch) == 0 { if len(batch) == 0 {
return return nil
} }
tx, err := db.db.Begin() tx, err := db.db.Begin()
if err != nil { if err != nil {
db.logger.Error("Failed to begin transaction", "error", err) return fmt.Errorf("failed to begin transaction: %w", err)
return
} }
defer func() {
if rollbackErr := tx.Rollback(); rollbackErr != nil &&
!errors.Is(rollbackErr, sql.ErrTxDone) {
err = errors.Join(
err,
fmt.Errorf("failed to rollback transaction: %w", rollbackErr),
)
}
}()
stmt, err := tx.Prepare( stmt, err := tx.Prepare(
"INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)", "INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)",
) )
if err != nil { if err != nil {
db.logger.Error("Failed to prepare statement", "error", err) err = fmt.Errorf("failed to prepare statement: %w", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { return err
db.logger.Error("Failed to rollback transaction", "error", rollbackErr)
}
return
} }
defer func() { defer func() {
if closeErr := stmt.Close(); closeErr != nil { if closeErr := stmt.Close(); closeErr != nil {
db.logger.Error("Failed to close statement", "error", closeErr) err = errors.Join(err, fmt.Errorf("failed to close statement: %w", closeErr))
} }
}() }()
@@ -50,17 +60,20 @@ func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339),
) )
if err != nil { if err != nil {
db.logger.Error("Failed to insert entry", "error", err) db.logger.Error(fmt.Errorf("failed to insert entry: %w", err).Error())
} }
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
db.logger.Error("Failed to commit transaction", "error", err) return fmt.Errorf("failed to commit transaction: %w", err)
return
} }
db.logger.Debug("Flushed batch", "count", len(batch))
batch = batch[:0] batch = batch[:0]
return err
}()
if err != nil {
db.logger.Error(err.Error())
}
} }
for { for {

View File

@@ -277,7 +277,7 @@ func TestWrite_ChannelClosed(t *testing.T) {
} }
} }
func NewRequestWriterWithDBPath(dbPath string) (*Request_Writer, error) { func NewRequestWriterWithDBPath(dbPath string) (*RequestWriter, error) {
db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)") db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -285,13 +285,13 @@ func NewRequestWriterWithDBPath(dbPath string) (*Request_Writer, error) {
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1) db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(0) db.SetConnMaxLifetime(0)
return &Request_Writer{ return &RequestWriter{
logger: logger.New(false), logger: logger.New(false),
db: db, db: db,
}, nil }, nil
} }
func (w *Request_Writer) CreateTable() error { func (w *RequestWriter) CreateTable() error {
_, err := w.db.Exec(CreateRequestsTable) _, err := w.db.Exec(CreateRequestsTable)
if err != nil { if err != nil {
return err return err
@@ -300,7 +300,7 @@ func (w *Request_Writer) CreateTable() error {
return nil return nil
} }
func (w *Request_Writer) Close() error { func (w *RequestWriter) Close() error {
w.logger.Info("Closing request database connection") w.logger.Info("Closing request database connection")
err := w.db.Close() err := w.db.Close()
if err != nil { if err != nil {
@@ -309,7 +309,7 @@ func (w *Request_Writer) Close() error {
return nil return nil
} }
func (w *Request_Writer) GetRequestCount() (int, error) { func (w *RequestWriter) GetRequestCount() (int, error) {
var count int var count int
err := w.db.QueryRow("SELECT COUNT(*) FROM requests").Scan(&count) err := w.db.QueryRow("SELECT COUNT(*) FROM requests").Scan(&count)
if err != nil { if err != nil {