tangled
alpha
login
or
join now
atscan.net
/
atscand
1
fork
atom
wip
1
fork
atom
overview
issues
pulls
pipelines
rename
tree.fail
4 months ago
d131ad8b
513a289e
+65
-192
13 changed files
expand all
collapse all
unified
split
.gitignore
Makefile
cmd
atscanner.go
config.sample.yaml
go.mod
internal
api
handlers.go
server.go
pds
scanner.go
plc
manager.go
scanner.go
storage
postgres.go
worker
scheduler.go
utils
migrate-ipinfo.sh
+2
-1
.gitignore
···
5
.DS_Store
6
plc_cache\.tmp/*
7
plc_bundles*
8
-
config.yaml
0
···
5
.DS_Store
6
plc_cache\.tmp/*
7
plc_bundles*
8
+
config.yaml
9
+
atscand
+38
-7
Makefile
···
1
-
all: run
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
2
3
-
run:
4
-
go run cmd/atscanner.go -verbose
0
0
5
6
-
clean-db:
7
-
dropdb -U atscanner atscanner
8
-
createdb atscanner -O atscanner
9
10
update-plcbundle:
11
-
GOPROXY=direct go get -u github.com/atscan/plcbundle@latest
0
0
0
0
0
0
0
···
1
+
.PHONY: all build install test clean fmt lint help
2
+
3
+
# Binary name
4
+
BINARY_NAME=atscand
5
+
INSTALL_PATH=$(GOPATH)/bin
6
+
7
+
# Go commands
8
+
GOCMD=go
9
+
GOBUILD=$(GOCMD) build
10
+
GOINSTALL=$(GOCMD) install
11
+
GOCLEAN=$(GOCMD) clean
12
+
GOTEST=$(GOCMD) test
13
+
GOGET=$(GOCMD) get
14
+
GOFMT=$(GOCMD) fmt
15
+
GOMOD=$(GOCMD) mod
16
+
GORUN=$(GOCMD) run
17
+
18
+
# Default target
19
+
all: build
20
+
21
+
# Build the CLI tool
22
+
build:
23
+
@echo "Building $(BINARY_NAME)..."
24
+
$(GOBUILD) -o $(BINARY_NAME) ./cmd/atscand
25
26
+
# Install the CLI tool globally
27
+
install:
28
+
@echo "Installing $(BINARY_NAME)..."
29
+
$(GOINSTALL) ./cmd/atscand
30
31
+
run:
32
+
$(GORUN) cmd/atscand/main.go -verbose
0
33
34
update-plcbundle:
35
+
GOPROXY=direct go get -u github.com/atscan/plcbundle@latest
36
+
37
+
# Show help
38
+
help:
39
+
@echo "Available targets:"
40
+
@echo " make build - Build the binary"
41
+
@echo " make install - Install binary globally"
42
+
@echo " make run - Run app"
-159
cmd/atscanner.go
···
1
-
package main
2
-
3
-
import (
4
-
"context"
5
-
"flag"
6
-
"fmt"
7
-
"os"
8
-
"os/signal"
9
-
"syscall"
10
-
"time"
11
-
12
-
"github.com/atscan/atscanner/internal/api"
13
-
"github.com/atscan/atscanner/internal/config"
14
-
"github.com/atscan/atscanner/internal/log"
15
-
"github.com/atscan/atscanner/internal/pds"
16
-
"github.com/atscan/atscanner/internal/plc"
17
-
"github.com/atscan/atscanner/internal/storage"
18
-
"github.com/atscan/atscanner/internal/worker"
19
-
)
20
-
21
-
const VERSION = "1.0.0"
22
-
23
-
func main() {
24
-
configPath := flag.String("config", "config.yaml", "path to config file")
25
-
verbose := flag.Bool("verbose", false, "enable verbose logging")
26
-
flag.Parse()
27
-
28
-
// Load configuration
29
-
cfg, err := config.Load(*configPath)
30
-
if err != nil {
31
-
fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
32
-
os.Exit(1)
33
-
}
34
-
35
-
// Override verbose setting if flag is provided
36
-
if *verbose {
37
-
cfg.API.Verbose = true
38
-
}
39
-
40
-
// Initialize logger
41
-
log.Init(cfg.API.Verbose)
42
-
43
-
// Print banner
44
-
log.Banner(VERSION)
45
-
46
-
// Print configuration summary
47
-
log.PrintConfig(map[string]string{
48
-
"Database Type": cfg.Database.Type,
49
-
"Database Path": cfg.Database.Path, // Will be auto-redacted
50
-
"PLC Directory": cfg.PLC.DirectoryURL,
51
-
"PLC Scan Interval": cfg.PLC.ScanInterval.String(),
52
-
"PLC Bundle Dir": cfg.PLC.BundleDir,
53
-
"PLC Cache": fmt.Sprintf("%v", cfg.PLC.UseCache),
54
-
"PLC Index DIDs": fmt.Sprintf("%v", cfg.PLC.IndexDIDs),
55
-
"PDS Scan Interval": cfg.PDS.ScanInterval.String(),
56
-
"PDS Workers": fmt.Sprintf("%d", cfg.PDS.Workers),
57
-
"PDS Timeout": cfg.PDS.Timeout.String(),
58
-
"API Host": cfg.API.Host,
59
-
"API Port": fmt.Sprintf("%d", cfg.API.Port),
60
-
"Verbose Logging": fmt.Sprintf("%v", cfg.API.Verbose),
61
-
})
62
-
63
-
// Initialize database using factory pattern
64
-
db, err := storage.NewDatabase(cfg.Database.Type, cfg.Database.Path)
65
-
if err != nil {
66
-
log.Fatal("Failed to initialize database: %v", err)
67
-
}
68
-
defer func() {
69
-
log.Info("Closing database connection...")
70
-
db.Close()
71
-
}()
72
-
73
-
// Set scan retention from config
74
-
if cfg.PDS.ScanRetention > 0 {
75
-
db.SetScanRetention(cfg.PDS.ScanRetention)
76
-
log.Verbose("Scan retention set to %d scans per endpoint", cfg.PDS.ScanRetention)
77
-
}
78
-
79
-
// Run migrations
80
-
if err := db.Migrate(); err != nil {
81
-
log.Fatal("Failed to run migrations: %v", err)
82
-
}
83
-
84
-
ctx, cancel := context.WithCancel(context.Background())
85
-
defer cancel()
86
-
87
-
// Initialize workers
88
-
log.Info("Initializing scanners...")
89
-
90
-
bundleManager, err := plc.NewBundleManager(cfg.PLC.BundleDir, cfg.PLC.DirectoryURL, db, cfg.PLC.IndexDIDs)
91
-
if err != nil {
92
-
log.Fatal("Failed to create bundle manager: %v", err)
93
-
}
94
-
defer bundleManager.Close()
95
-
log.Verbose("✓ Bundle manager initialized (shared)")
96
-
97
-
plcScanner := plc.NewScanner(db, cfg.PLC, bundleManager)
98
-
defer plcScanner.Close()
99
-
log.Verbose("✓ PLC scanner initialized")
100
-
101
-
pdsScanner := pds.NewScanner(db, cfg.PDS)
102
-
log.Verbose("✓ PDS scanner initialized")
103
-
104
-
scheduler := worker.NewScheduler()
105
-
106
-
// Schedule PLC directory scan
107
-
scheduler.AddJob("plc_scan", cfg.PLC.ScanInterval, func() {
108
-
if err := plcScanner.Scan(ctx); err != nil {
109
-
log.Error("PLC scan error: %v", err)
110
-
}
111
-
})
112
-
log.Verbose("✓ PLC scan job scheduled (interval: %s)", cfg.PLC.ScanInterval)
113
-
114
-
// Schedule PDS availability checks
115
-
scheduler.AddJob("pds_scan", cfg.PDS.ScanInterval, func() {
116
-
if err := pdsScanner.ScanAll(ctx); err != nil {
117
-
log.Error("PDS scan error: %v", err)
118
-
}
119
-
})
120
-
log.Verbose("✓ PDS scan job scheduled (interval: %s)", cfg.PDS.ScanInterval)
121
-
122
-
// Start API server
123
-
log.Info("Starting API server on %s:%d...", cfg.API.Host, cfg.API.Port)
124
-
apiServer := api.NewServer(db, cfg.API, cfg.PLC, bundleManager)
125
-
go func() {
126
-
if err := apiServer.Start(); err != nil {
127
-
log.Fatal("API server error: %v", err)
128
-
}
129
-
}()
130
-
131
-
// Give the API server a moment to start
132
-
time.Sleep(100 * time.Millisecond)
133
-
log.Info("✓ API server started successfully")
134
-
log.Info("")
135
-
log.Info("🚀 ATScanner is running!")
136
-
log.Info(" API available at: http://%s:%d", cfg.API.Host, cfg.API.Port)
137
-
log.Info(" Press Ctrl+C to stop")
138
-
log.Info("")
139
-
140
-
// Start scheduler
141
-
scheduler.Start(ctx)
142
-
143
-
// Wait for interrupt
144
-
sigChan := make(chan os.Signal, 1)
145
-
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
146
-
<-sigChan
147
-
148
-
log.Info("")
149
-
log.Info("Shutting down gracefully...")
150
-
cancel()
151
-
152
-
log.Info("Stopping API server...")
153
-
apiServer.Shutdown(context.Background())
154
-
155
-
log.Info("Waiting for active tasks to complete...")
156
-
time.Sleep(2 * time.Second)
157
-
158
-
log.Info("✓ Shutdown complete. Goodbye!")
159
-
}
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+1
-1
config.sample.yaml
···
1
database:
2
type: "postgres" # or "sqlite"
3
-
path: "postgres://atscanner:YOUR_PASSWORD@localhost:5432/atscanner?sslmode=disable"
4
# For SQLite: path: "atscan.db"
5
6
plc:
···
1
database:
2
type: "postgres" # or "sqlite"
3
+
path: "postgres://atscand:YOUR_PASSWORD@localhost:5432/atscand?sslmode=disable"
4
# For SQLite: path: "atscan.db"
5
6
plc:
+1
-1
go.mod
···
1
-
module github.com/atscan/atscanner
2
3
go 1.23.0
4
···
1
+
module github.com/atscan/atscand
2
3
go 1.23.0
4
+4
-4
internal/api/handlers.go
···
11
"strings"
12
"time"
13
14
-
"github.com/atscan/atscanner/internal/log"
15
-
"github.com/atscan/atscanner/internal/monitor"
16
-
"github.com/atscan/atscanner/internal/plc"
17
-
"github.com/atscan/atscanner/internal/storage"
18
"github.com/atscan/plcbundle"
19
"github.com/gorilla/mux"
20
)
···
11
"strings"
12
"time"
13
14
+
"github.com/atscan/atscand/internal/log"
15
+
"github.com/atscan/atscand/internal/monitor"
16
+
"github.com/atscan/atscand/internal/plc"
17
+
"github.com/atscan/atscand/internal/storage"
18
"github.com/atscan/plcbundle"
19
"github.com/gorilla/mux"
20
)
+4
-4
internal/api/server.go
···
6
"net/http"
7
"time"
8
9
-
"github.com/atscan/atscanner/internal/config"
10
-
"github.com/atscan/atscanner/internal/log"
11
-
"github.com/atscan/atscanner/internal/plc"
12
-
"github.com/atscan/atscanner/internal/storage"
13
"github.com/gorilla/handlers"
14
"github.com/gorilla/mux"
15
)
···
6
"net/http"
7
"time"
8
9
+
"github.com/atscan/atscand/internal/config"
10
+
"github.com/atscan/atscand/internal/log"
11
+
"github.com/atscan/atscand/internal/plc"
12
+
"github.com/atscan/atscand/internal/storage"
13
"github.com/gorilla/handlers"
14
"github.com/gorilla/mux"
15
)
+5
-5
internal/pds/scanner.go
···
9
"time"
10
11
"github.com/acarl005/stripansi"
12
-
"github.com/atscan/atscanner/internal/config"
13
-
"github.com/atscan/atscanner/internal/ipinfo"
14
-
"github.com/atscan/atscanner/internal/log"
15
-
"github.com/atscan/atscanner/internal/monitor"
16
-
"github.com/atscan/atscanner/internal/storage"
17
)
18
19
type Scanner struct {
···
9
"time"
10
11
"github.com/acarl005/stripansi"
12
+
"github.com/atscan/atscand/internal/config"
13
+
"github.com/atscan/atscand/internal/ipinfo"
14
+
"github.com/atscan/atscand/internal/log"
15
+
"github.com/atscan/atscand/internal/monitor"
16
+
"github.com/atscan/atscand/internal/storage"
17
)
18
19
type Scanner struct {
+2
-2
internal/plc/manager.go
···
7
"sort"
8
"time"
9
10
-
"github.com/atscan/atscanner/internal/log"
11
-
"github.com/atscan/atscanner/internal/storage"
12
plcbundle "github.com/atscan/plcbundle"
13
)
14
···
7
"sort"
8
"time"
9
10
+
"github.com/atscan/atscand/internal/log"
11
+
"github.com/atscan/atscand/internal/storage"
12
plcbundle "github.com/atscan/plcbundle"
13
)
14
+3
-3
internal/plc/scanner.go
···
6
"strings"
7
"time"
8
9
-
"github.com/atscan/atscanner/internal/config"
10
-
"github.com/atscan/atscanner/internal/log"
11
-
"github.com/atscan/atscanner/internal/storage"
12
)
13
14
type Scanner struct {
···
6
"strings"
7
"time"
8
9
+
"github.com/atscan/atscand/internal/config"
10
+
"github.com/atscan/atscand/internal/log"
11
+
"github.com/atscan/atscand/internal/storage"
12
)
13
14
type Scanner struct {
+1
-1
internal/storage/postgres.go
···
7
"fmt"
8
"time"
9
10
-
"github.com/atscan/atscanner/internal/log"
11
"github.com/jackc/pgx/v5"
12
"github.com/jackc/pgx/v5/pgxpool"
13
_ "github.com/jackc/pgx/v5/stdlib"
···
7
"fmt"
8
"time"
9
10
+
"github.com/atscan/atscand/internal/log"
11
"github.com/jackc/pgx/v5"
12
"github.com/jackc/pgx/v5/pgxpool"
13
_ "github.com/jackc/pgx/v5/stdlib"
+2
-2
internal/worker/scheduler.go
···
5
"sync"
6
"time"
7
8
-
"github.com/atscan/atscanner/internal/log"
9
-
"github.com/atscan/atscanner/internal/monitor"
10
)
11
12
type Job struct {
···
5
"sync"
6
"time"
7
8
+
"github.com/atscan/atscand/internal/log"
9
+
"github.com/atscan/atscand/internal/monitor"
10
)
11
12
type Job struct {
+2
-2
utils/migrate-ipinfo.sh
···
4
# Configuration (edit these)
5
DB_HOST="localhost"
6
DB_PORT="5432"
7
-
DB_NAME="atscanner"
8
-
DB_USER="atscanner"
9
DB_PASSWORD="Noor1kooz5eeFai9leZagh5ua5eihai4"
10
11
# Colors for output
···
4
# Configuration (edit these)
5
DB_HOST="localhost"
6
DB_PORT="5432"
7
+
DB_NAME="atscand"
8
+
DB_USER="atscand"
9
DB_PASSWORD="Noor1kooz5eeFai9leZagh5ua5eihai4"
10
11
# Colors for output