2026-08-03 18:13:30 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
|
2026-08-04 14:57:40 +08:00
|
|
|
"cmbuyer/admin/internal/auth"
|
|
|
|
|
"cmbuyer/admin/internal/config"
|
2026-08-04 20:57:33 +08:00
|
|
|
"cmbuyer/admin/internal/deviceauth"
|
2026-08-03 18:13:30 +08:00
|
|
|
"cmbuyer/admin/internal/server"
|
2026-08-04 19:42:35 +08:00
|
|
|
evidencestorage "cmbuyer/admin/internal/storage/evidence"
|
2026-08-04 16:33:22 +08:00
|
|
|
"cmbuyer/admin/internal/storage/sqlite"
|
2026-08-04 22:33:16 +08:00
|
|
|
"cmbuyer/admin/internal/taskclaim"
|
2026-08-04 19:42:35 +08:00
|
|
|
"cmbuyer/admin/internal/taskdetail"
|
2026-08-04 16:33:22 +08:00
|
|
|
"cmbuyer/admin/internal/tasks"
|
2026-08-03 18:13:30 +08:00
|
|
|
)
|
|
|
|
|
|
2026-08-04 20:57:33 +08:00
|
|
|
// Device Bearer credentials must not cross a plaintext LAN. The MVP is a same-computer
|
|
|
|
|
// deployment, so widening this address requires a separately reviewed TLS boundary first.
|
|
|
|
|
const listenAddress = "127.0.0.1:8080"
|
2026-08-03 18:13:30 +08:00
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if err := run(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func run() error {
|
2026-08-04 14:57:40 +08:00
|
|
|
configuration, err := config.LoadFromEnv()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-04 16:33:22 +08:00
|
|
|
database, err := sqlite.Open(configuration.DatabaseSource)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer database.Close()
|
|
|
|
|
taskStore, err := tasks.NewSQLiteStore(database)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-04 18:24:39 +08:00
|
|
|
taskStore.SetStartPolicy(tasks.StartPolicy{AuthorizationTTL: configuration.AuthorizationTTL, MaxQuantity: configuration.MaxTaskQuantity, MaxTotalPrice: configuration.MaxTotalPrice})
|
2026-08-04 19:42:35 +08:00
|
|
|
detailStore, err := taskdetail.NewSQLiteStore(database)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
evidenceStore, err := evidencestorage.NewStore(database, configuration.EvidenceDirectory)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-04 20:57:33 +08:00
|
|
|
deviceAuthenticator, err := deviceauth.NewSQLiteAuthenticator(database)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-04 22:33:16 +08:00
|
|
|
claimStore, err := taskclaim.NewStore(database, configuration.ClaimTokenSecret, configuration.ClaimLeaseTTL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-04 14:57:40 +08:00
|
|
|
|
|
|
|
|
router, err := server.NewRouter(server.Options{
|
|
|
|
|
AdminUsername: configuration.AdminUsername,
|
|
|
|
|
AdminPasswordBcrypt: configuration.AdminPasswordBcrypt,
|
|
|
|
|
Sessions: auth.NewManager(configuration.SessionSecret, configuration.CookieSecure),
|
2026-08-04 16:33:22 +08:00
|
|
|
Tasks: taskStore,
|
2026-08-04 19:42:35 +08:00
|
|
|
TaskDetails: detailStore,
|
|
|
|
|
Evidence: evidenceStore,
|
2026-08-04 20:57:33 +08:00
|
|
|
DeviceAuthenticator: deviceAuthenticator,
|
2026-08-04 22:33:16 +08:00
|
|
|
TaskClaims: claimStore,
|
2026-08-04 14:57:40 +08:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = http.ListenAndServe(listenAddress, router)
|
2026-08-03 18:13:30 +08:00
|
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|