mirror of
https://github.com/safing/portmaster
synced 2025-04-22 20:09:09 +00:00
Compare commits
51 commits
Author | SHA1 | Date | |
---|---|---|---|
|
eccda20802 | ||
|
7b05ed82b2 | ||
|
b9a9129e81 | ||
|
be133b8856 | ||
|
32c7f6e7d1 | ||
|
4f3ebc7156 | ||
|
9b12dfffc2 | ||
|
2c8ab54104 | ||
|
c0d8d0c2f0 | ||
|
67cfefde9b | ||
|
b8ab348095 | ||
|
782c07d867 | ||
|
a13d52b68f | ||
|
71f6f09384 | ||
|
c04213219b | ||
|
7373b8868b | ||
|
b68646c689 | ||
|
130c4a427c | ||
|
76c352da5a | ||
|
83292d761c | ||
|
4721e58727 | ||
|
90ead7d5e5 | ||
|
c742c7dfd1 | ||
|
fe8a560f9e | ||
|
fdca991166 | ||
|
5e7ad95a44 | ||
|
40b443282f | ||
|
d8108bff0e | ||
|
98137ca4b6 | ||
|
857df4086f | ||
|
32d6e1cb04 | ||
|
0f28af66cd | ||
|
726159427b | ||
|
88b92dcc93 | ||
|
f021ec2444 | ||
|
3478622eb8 | ||
|
b4fda1bdce | ||
|
0937bedd6c | ||
|
241bf20c7a | ||
|
3ee214abaf | ||
|
96209c28cf | ||
|
ef7b129ced | ||
|
1e9e6263d4 | ||
|
c7f3475382 | ||
|
6c014d227c | ||
|
df70c70ab5 | ||
|
692838b696 | ||
|
475d69f8a2 | ||
|
9d874daed2 | ||
|
05a5d5e350 | ||
|
22253c4e9e |
89 changed files with 3162 additions and 287 deletions
.github/workflows
.gitignore.golangci.ymlEarthfilebase
cmds
desktop/angular/src
go.modgo.sumservice
core/base
firewall/interception
dnsmonitor
windowskext2
intel
mgr
netquery
network
profile
resolver
ui
updates
spn
crew
testing
windows_core_dll
windows_kext
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
|
@ -38,9 +38,9 @@ jobs:
|
|||
cache: false
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.60.3
|
||||
version: v1.64.6
|
||||
only-new-issues: true
|
||||
args: -c ./.golangci.yml --timeout 15m
|
||||
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,8 +12,7 @@ go.mod.*
|
|||
vendor
|
||||
|
||||
# testing
|
||||
testing
|
||||
spn/testing/simple/testdata
|
||||
testdata
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.a
|
||||
|
|
|
@ -8,7 +8,6 @@ linters:
|
|||
- contextcheck
|
||||
- cyclop
|
||||
- depguard
|
||||
- exhaustivestruct
|
||||
- exhaustruct
|
||||
- forbidigo
|
||||
- funlen
|
||||
|
@ -16,12 +15,8 @@ linters:
|
|||
- gochecknoinits
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- goerr113
|
||||
- gomnd
|
||||
- gomoddirectives
|
||||
- ifshort
|
||||
- interfacebloat
|
||||
- interfacer
|
||||
- ireturn
|
||||
- lll
|
||||
- mnd
|
||||
|
@ -32,7 +27,6 @@ linters:
|
|||
- noctx
|
||||
- nolintlint
|
||||
- nonamedreturns
|
||||
- nosnakecase
|
||||
- perfsprint # TODO(ppacher): we should re-enanble this one to avoid costly fmt.* calls in the hot-path
|
||||
- revive
|
||||
- tagliatelle
|
||||
|
@ -42,15 +36,14 @@ linters:
|
|||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
- gci
|
||||
- tenv # Deprecated
|
||||
|
||||
linters-settings:
|
||||
revive:
|
||||
# See https://github.com/mgechev/revive#available-rules for details.
|
||||
enable-all-rules: true
|
||||
gci:
|
||||
# put imports beginning with prefix after 3rd-party packages;
|
||||
# only support one prefix
|
||||
# if not set, use goimports.local-prefixes
|
||||
goimports:
|
||||
local-prefixes: github.com/safing
|
||||
godox:
|
||||
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
|
||||
|
|
11
Earthfile
11
Earthfile
|
@ -1,9 +1,9 @@
|
|||
VERSION --arg-scope-and-set --global-cache 0.8
|
||||
|
||||
ARG --global go_version = 1.22
|
||||
ARG --global go_version = 1.24
|
||||
ARG --global node_version = 18
|
||||
ARG --global rust_version = 1.79
|
||||
ARG --global golangci_lint_version = 1.57.1
|
||||
ARG --global golangci_lint_version = 1.64.6
|
||||
|
||||
ARG --global go_builder_image = "golang:${go_version}-alpine"
|
||||
ARG --global node_builder_image = "node:${node_version}"
|
||||
|
@ -70,6 +70,11 @@ build:
|
|||
# ./dist/all/assets.zip
|
||||
BUILD +assets
|
||||
|
||||
build-spn:
|
||||
BUILD +go-build --CMDS="hub" --GOOS="linux" --GOARCH="amd64"
|
||||
BUILD +go-build --CMDS="hub" --GOOS="linux" --GOARCH="arm64"
|
||||
# TODO: Add other platforms
|
||||
|
||||
go-ci:
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="amd64"
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="arm64"
|
||||
|
@ -420,7 +425,7 @@ rust-base:
|
|||
DO rust+INIT --keep_fingerprints=true
|
||||
|
||||
# For now we need tauri-cli 2.0.0 for bulding
|
||||
DO rust+CARGO --args="install tauri-cli --version 2.1.0"
|
||||
DO rust+CARGO --args="install tauri-cli --version 2.1.0 --locked"
|
||||
|
||||
# Explicitly cache here.
|
||||
SAVE IMAGE --cache-hint
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/safing/portmaster/base/database/dbmodule"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/base/utils/debug"
|
||||
|
@ -144,12 +145,13 @@ func InitializeUnitTestDataroot(testName string) (string, error) {
|
|||
return "", fmt.Errorf("failed to make tmp dir: %w", err)
|
||||
}
|
||||
|
||||
ds := utils.NewDirStructure(basePath, 0o0755)
|
||||
ds := utils.NewDirStructure(basePath, utils.PublicReadPermission)
|
||||
SetDataRoot(ds)
|
||||
err = dataroot.Initialize(basePath, 0o0755)
|
||||
err = dataroot.Initialize(basePath, utils.PublicReadPermission)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to initialize dataroot: %w", err)
|
||||
}
|
||||
dbmodule.SetDatabaseLocation(dataroot.Root())
|
||||
|
||||
return basePath, nil
|
||||
}
|
||||
|
|
|
@ -264,6 +264,20 @@ func (c *Controller) Purge(ctx context.Context, q *query.Query, local, internal
|
|||
return 0, ErrNotImplemented
|
||||
}
|
||||
|
||||
// PurgeOlderThan deletes all records last updated before the given time.
|
||||
// It returns the number of successful deletes and an error.
|
||||
func (c *Controller) PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time, local, internal bool) (int, error) {
|
||||
if shuttingDown.IsSet() {
|
||||
return 0, ErrShuttingDown
|
||||
}
|
||||
|
||||
if purger, ok := c.storage.(storage.PurgeOlderThan); ok {
|
||||
return purger.PurgeOlderThan(ctx, prefix, purgeBefore, local, internal, c.shadowDelete)
|
||||
}
|
||||
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Shutdown shuts down the storage.
|
||||
func (c *Controller) Shutdown() error {
|
||||
return c.storage.Shutdown()
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
@ -37,7 +36,6 @@ func SetDatabaseLocation(dirStructureRoot *utils.DirStructure) {
|
|||
}
|
||||
|
||||
func prep() error {
|
||||
SetDatabaseLocation(dataroot.Root())
|
||||
if databaseStructureRoot == nil {
|
||||
return errors.New("database location not specified")
|
||||
}
|
||||
|
|
|
@ -562,6 +562,27 @@ func (i *Interface) Purge(ctx context.Context, q *query.Query) (int, error) {
|
|||
return db.Purge(ctx, q, i.options.Local, i.options.Internal)
|
||||
}
|
||||
|
||||
// PurgeOlderThan deletes all records last updated before the given time.
|
||||
// It returns the number of successful deletes and an error.
|
||||
func (i *Interface) PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time) (int, error) {
|
||||
dbName, dbKeyPrefix := record.ParseKey(prefix)
|
||||
if dbName == "" {
|
||||
return 0, errors.New("unknown database")
|
||||
}
|
||||
|
||||
db, err := getController(dbName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Check if database is read only before we add to the cache.
|
||||
if db.ReadOnly() {
|
||||
return 0, ErrReadOnly
|
||||
}
|
||||
|
||||
return db.PurgeOlderThan(ctx, dbKeyPrefix, purgeBefore, i.options.Local, i.options.Internal)
|
||||
}
|
||||
|
||||
// Subscribe subscribes to updates matching the given query.
|
||||
func (i *Interface) Subscribe(q *query.Query) (*Subscription, error) {
|
||||
_, err := q.Check()
|
||||
|
|
|
@ -9,9 +9,8 @@ import (
|
|||
"github.com/safing/portmaster/base/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
databasesSubDir = "databases"
|
||||
)
|
||||
// DatabasesSubDir defines the sub directory where the databases are stored.
|
||||
const DatabasesSubDir = "databases"
|
||||
|
||||
var (
|
||||
initialized = abool.NewBool(false)
|
||||
|
@ -25,7 +24,7 @@ var (
|
|||
|
||||
// InitializeWithPath initializes the database at the specified location using a path.
|
||||
func InitializeWithPath(dirPath string) error {
|
||||
return Initialize(utils.NewDirStructure(dirPath, 0o0755))
|
||||
return Initialize(utils.NewDirStructure(dirPath, utils.PublicReadPermission))
|
||||
}
|
||||
|
||||
// Initialize initializes the database at the specified location using a dir structure.
|
||||
|
@ -34,7 +33,7 @@ func Initialize(dirStructureRoot *utils.DirStructure) error {
|
|||
rootStructure = dirStructureRoot
|
||||
|
||||
// ensure root and databases dirs
|
||||
databasesStructure = rootStructure.ChildDir(databasesSubDir, 0o0700)
|
||||
databasesStructure = rootStructure.ChildDir(DatabasesSubDir, utils.AdminOnlyPermission)
|
||||
err := databasesStructure.Ensure()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create/open database directory (%s): %w", rootStructure.Path, err)
|
||||
|
@ -67,7 +66,7 @@ func Shutdown() (err error) {
|
|||
|
||||
// getLocation returns the storage location for the given name and type.
|
||||
func getLocation(name, storageType string) (string, error) {
|
||||
location := databasesStructure.ChildDir(name, 0o0700).ChildDir(storageType, 0o0700)
|
||||
location := databasesStructure.ChildDir(name, utils.AdminOnlyPermission).ChildDir(storageType, utils.AdminOnlyPermission)
|
||||
// check location
|
||||
err := location.Ensure()
|
||||
if err != nil {
|
||||
|
|
|
@ -99,6 +99,11 @@ func (q *Query) MatchesKey(dbKey string) bool {
|
|||
return strings.HasPrefix(dbKey, q.dbKeyPrefix)
|
||||
}
|
||||
|
||||
// HasWhereCondition returns whether the query has a "where" condition set.
|
||||
func (q *Query) HasWhereCondition() bool {
|
||||
return q.where != nil
|
||||
}
|
||||
|
||||
// MatchesRecord checks whether the query matches the supplied database record (value only).
|
||||
func (q *Query) MatchesRecord(r record.Record) bool {
|
||||
if q.where == nil {
|
||||
|
|
|
@ -102,7 +102,7 @@ func (b *Base) SetMeta(meta *Meta) {
|
|||
b.meta = meta
|
||||
}
|
||||
|
||||
// Marshal marshals the object, without the database key or metadata. It returns nil if the record is deleted.
|
||||
// Marshal marshals the format and data.
|
||||
func (b *Base) Marshal(self Record, format uint8) ([]byte, error) {
|
||||
if b.Meta() == nil {
|
||||
return nil, errors.New("missing meta")
|
||||
|
@ -119,7 +119,20 @@ func (b *Base) Marshal(self Record, format uint8) ([]byte, error) {
|
|||
return dumped, nil
|
||||
}
|
||||
|
||||
// MarshalRecord packs the object, including metadata, into a byte array for saving in a database.
|
||||
// MarshalDataOnly marshals the data only.
|
||||
func (b *Base) MarshalDataOnly(self Record, format uint8) ([]byte, error) {
|
||||
if b.Meta() == nil {
|
||||
return nil, errors.New("missing meta")
|
||||
}
|
||||
|
||||
if b.Meta().Deleted > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return dsd.DumpWithoutIdentifier(self, format, "")
|
||||
}
|
||||
|
||||
// MarshalRecord marshals the data, format and metadata.
|
||||
func (b *Base) MarshalRecord(self Record) ([]byte, error) {
|
||||
if b.Meta() == nil {
|
||||
return nil, errors.New("missing meta")
|
||||
|
|
|
@ -49,11 +49,21 @@ func (m *Meta) MakeCrownJewel() {
|
|||
m.cronjewel = true
|
||||
}
|
||||
|
||||
// IsCrownJewel returns whether the database record is marked as a crownjewel.
|
||||
func (m *Meta) IsCrownJewel() bool {
|
||||
return m.cronjewel
|
||||
}
|
||||
|
||||
// MakeSecret sets the database record as secret, meaning that it may only be used internally, and not by interfacing processes, such as the UI.
|
||||
func (m *Meta) MakeSecret() {
|
||||
m.secret = true
|
||||
}
|
||||
|
||||
// IsSecret returns whether the database record is marked as a secret.
|
||||
func (m *Meta) IsSecret() bool {
|
||||
return m.secret
|
||||
}
|
||||
|
||||
// Update updates the internal meta states and should be called before writing the record to the database.
|
||||
func (m *Meta) Update() {
|
||||
now := time.Now().Unix()
|
||||
|
|
|
@ -20,6 +20,7 @@ type Record interface {
|
|||
|
||||
// Serialization.
|
||||
Marshal(self Record, format uint8) ([]byte, error)
|
||||
MarshalDataOnly(self Record, format uint8) ([]byte, error)
|
||||
MarshalRecord(self Record) ([]byte, error)
|
||||
GetAccessor(self Record) accessor.Accessor
|
||||
|
||||
|
|
|
@ -79,7 +79,21 @@ func NewWrapper(key string, meta *Meta, format uint8, data []byte) (*Wrapper, er
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Marshal marshals the object, without the database key or metadata.
|
||||
// NewWrapperFromDatabase returns a new record wrapper for the given data.
|
||||
func NewWrapperFromDatabase(dbName, dbKey string, meta *Meta, format uint8, data []byte) (*Wrapper, error) {
|
||||
return &Wrapper{
|
||||
Base{
|
||||
dbName: dbName,
|
||||
dbKey: dbKey,
|
||||
meta: meta,
|
||||
},
|
||||
sync.Mutex{},
|
||||
format,
|
||||
data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Marshal marshals the format and data.
|
||||
func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) {
|
||||
if w.Meta() == nil {
|
||||
return nil, errors.New("missing meta")
|
||||
|
@ -100,7 +114,24 @@ func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
// MarshalRecord packs the object, including metadata, into a byte array for saving in a database.
|
||||
// MarshalDataOnly marshals the data only.
|
||||
func (w *Wrapper) MarshalDataOnly(self Record, format uint8) ([]byte, error) {
|
||||
if w.Meta() == nil {
|
||||
return nil, errors.New("missing meta")
|
||||
}
|
||||
|
||||
if w.Meta().Deleted > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if format != dsd.AUTO && format != w.Format {
|
||||
return nil, errors.New("could not dump model, wrapped object format mismatch")
|
||||
}
|
||||
|
||||
return w.Data, nil
|
||||
}
|
||||
|
||||
// MarshalRecord marshals the data, format and metadata.
|
||||
func (w *Wrapper) MarshalRecord(r Record) ([]byte, error) {
|
||||
// Duplication necessary, as the version from Base would call Base.Marshal instead of Wrapper.Marshal
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hectane/go-acl"
|
||||
"github.com/safing/portmaster/base/database/iterator"
|
||||
"github.com/safing/portmaster/base/database/query"
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
|
@ -289,11 +288,8 @@ func writeFile(filename string, data []byte, perm os.FileMode) error {
|
|||
defer t.Cleanup() //nolint:errcheck
|
||||
|
||||
// Set permissions before writing data, in case the data is sensitive.
|
||||
if onWindows {
|
||||
err = acl.Chmod(filename, perm)
|
||||
} else {
|
||||
err = t.Chmod(perm)
|
||||
}
|
||||
// TODO(vladimir): to set permissions on windows we need the full path of the file.
|
||||
err = t.Chmod(perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -46,3 +46,8 @@ type Batcher interface {
|
|||
type Purger interface {
|
||||
Purge(ctx context.Context, q *query.Query, local, internal, shadowDelete bool) (int, error)
|
||||
}
|
||||
|
||||
// PurgeOlderThan defines the database storage API for backends that support the PurgeOlderThan operation.
|
||||
type PurgeOlderThan interface {
|
||||
PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time, local, internal, shadowDelete bool) (int, error)
|
||||
}
|
||||
|
|
6
base/database/storage/sqlite/bobgen.yaml
Normal file
6
base/database/storage/sqlite/bobgen.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
sqlite:
|
||||
dsn: "testdata/schema.db"
|
||||
except:
|
||||
migrations:
|
||||
|
||||
no_factory: true
|
7
base/database/storage/sqlite/migrations/0_settings.sql
Normal file
7
base/database/storage/sqlite/migrations/0_settings.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
PRAGMA auto_vacuum = INCREMENTAL; -- https://sqlite.org/pragma.html#pragma_auto_vacuum
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
PRAGMA auto_vacuum = NONE; -- https://sqlite.org/pragma.html#pragma_auto_vacuum
|
19
base/database/storage/sqlite/migrations/1_initial.sql
Normal file
19
base/database/storage/sqlite/migrations/1_initial.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
CREATE TABLE records (
|
||||
key TEXT PRIMARY KEY,
|
||||
|
||||
format SMALLINT,
|
||||
value BLOB,
|
||||
|
||||
created BIGINT NOT NULL,
|
||||
modified BIGINT NOT NULL,
|
||||
expires BIGINT DEFAULT 0 NOT NULL,
|
||||
deleted BIGINT DEFAULT 0 NOT NULL,
|
||||
secret BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
crownjewel BOOLEAN DEFAULT FALSE NOT NULL
|
||||
);
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
DROP TABLE records;
|
5
base/database/storage/sqlite/migrations_config.yml
Normal file
5
base/database/storage/sqlite/migrations_config.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
development:
|
||||
dialect: sqlite3
|
||||
datasource: testdata/schema.db
|
||||
dir: migrations
|
||||
table: migrations
|
131
base/database/storage/sqlite/models/bob_main.bob.go
Normal file
131
base/database/storage/sqlite/models/bob_main.bob.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"strings"
|
||||
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/clause"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
|
||||
sqliteDriver "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
var TableNames = struct {
|
||||
Records string
|
||||
}{
|
||||
Records: "records",
|
||||
}
|
||||
|
||||
var ColumnNames = struct {
|
||||
Records recordColumnNames
|
||||
}{
|
||||
Records: recordColumnNames{
|
||||
Key: "key",
|
||||
Format: "format",
|
||||
Value: "value",
|
||||
Created: "created",
|
||||
Modified: "modified",
|
||||
Expires: "expires",
|
||||
Deleted: "deleted",
|
||||
Secret: "secret",
|
||||
Crownjewel: "crownjewel",
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
SelectWhere = Where[*dialect.SelectQuery]()
|
||||
InsertWhere = Where[*dialect.InsertQuery]()
|
||||
UpdateWhere = Where[*dialect.UpdateQuery]()
|
||||
DeleteWhere = Where[*dialect.DeleteQuery]()
|
||||
)
|
||||
|
||||
func Where[Q sqlite.Filterable]() struct {
|
||||
Records recordWhere[Q]
|
||||
} {
|
||||
return struct {
|
||||
Records recordWhere[Q]
|
||||
}{
|
||||
Records: buildRecordWhere[Q](RecordColumns),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
SelectJoins = getJoins[*dialect.SelectQuery]
|
||||
UpdateJoins = getJoins[*dialect.UpdateQuery]
|
||||
)
|
||||
|
||||
type joinSet[Q interface{ aliasedAs(string) Q }] struct {
|
||||
InnerJoin Q
|
||||
LeftJoin Q
|
||||
RightJoin Q
|
||||
}
|
||||
|
||||
func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] {
|
||||
return joinSet[Q]{
|
||||
InnerJoin: j.InnerJoin.aliasedAs(alias),
|
||||
LeftJoin: j.LeftJoin.aliasedAs(alias),
|
||||
RightJoin: j.RightJoin.aliasedAs(alias),
|
||||
}
|
||||
}
|
||||
|
||||
type joins[Q dialect.Joinable] struct{}
|
||||
|
||||
func buildJoinSet[Q interface{ aliasedAs(string) Q }, C any, F func(C, string) Q](c C, f F) joinSet[Q] {
|
||||
return joinSet[Q]{
|
||||
InnerJoin: f(c, clause.InnerJoin),
|
||||
LeftJoin: f(c, clause.LeftJoin),
|
||||
RightJoin: f(c, clause.RightJoin),
|
||||
}
|
||||
}
|
||||
|
||||
func getJoins[Q dialect.Joinable]() joins[Q] {
|
||||
return joins[Q]{}
|
||||
}
|
||||
|
||||
type modAs[Q any, C interface{ AliasedAs(string) C }] struct {
|
||||
c C
|
||||
f func(C) bob.Mod[Q]
|
||||
}
|
||||
|
||||
func (m modAs[Q, C]) Apply(q Q) {
|
||||
m.f(m.c).Apply(q)
|
||||
}
|
||||
|
||||
func (m modAs[Q, C]) AliasedAs(alias string) bob.Mod[Q] {
|
||||
m.c = m.c.AliasedAs(alias)
|
||||
return m
|
||||
}
|
||||
|
||||
func randInt() int64 {
|
||||
out := int64(new(maphash.Hash).Sum64())
|
||||
|
||||
if out < 0 {
|
||||
return -out % 10000
|
||||
}
|
||||
|
||||
return out % 10000
|
||||
}
|
||||
|
||||
// ErrUniqueConstraint captures all unique constraint errors by explicitly leaving `s` empty.
|
||||
var ErrUniqueConstraint = &UniqueConstraintError{s: ""}
|
||||
|
||||
type UniqueConstraintError struct {
|
||||
// s is a string uniquely identifying the constraint in the raw error message returned from the database.
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *UniqueConstraintError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func (e *UniqueConstraintError) Is(target error) bool {
|
||||
err, ok := target.(*sqliteDriver.Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return err.Code() == 2067 && strings.Contains(err.Error(), e.s)
|
||||
}
|
9
base/database/storage/sqlite/models/bob_main_test.bob.go
Normal file
9
base/database/storage/sqlite/models/bob_main_test.bob.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import "github.com/stephenafamo/bob"
|
||||
|
||||
// Make sure the type Record runs hooks after queries
|
||||
var _ bob.HookableType = &Record{}
|
555
base/database/storage/sqlite/models/records.bob.go
Normal file
555
base/database/storage/sqlite/models/records.bob.go
Normal file
|
@ -0,0 +1,555 @@
|
|||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/aarondl/opt/null"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dm"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/sm"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/um"
|
||||
"github.com/stephenafamo/bob/expr"
|
||||
)
|
||||
|
||||
// Record is an object representing the database table.
|
||||
type Record struct {
|
||||
Key string `db:"key,pk" `
|
||||
Format null.Val[int16] `db:"format" `
|
||||
Value null.Val[[]byte] `db:"value" `
|
||||
Created int64 `db:"created" `
|
||||
Modified int64 `db:"modified" `
|
||||
Expires int64 `db:"expires" `
|
||||
Deleted int64 `db:"deleted" `
|
||||
Secret bool `db:"secret" `
|
||||
Crownjewel bool `db:"crownjewel" `
|
||||
}
|
||||
|
||||
// RecordSlice is an alias for a slice of pointers to Record.
|
||||
// This should almost always be used instead of []*Record.
|
||||
type RecordSlice []*Record
|
||||
|
||||
// Records contains methods to work with the records table
|
||||
var Records = sqlite.NewTablex[*Record, RecordSlice, *RecordSetter]("", "records")
|
||||
|
||||
// RecordsQuery is a query on the records table
|
||||
type RecordsQuery = *sqlite.ViewQuery[*Record, RecordSlice]
|
||||
|
||||
type recordColumnNames struct {
|
||||
Key string
|
||||
Format string
|
||||
Value string
|
||||
Created string
|
||||
Modified string
|
||||
Expires string
|
||||
Deleted string
|
||||
Secret string
|
||||
Crownjewel string
|
||||
}
|
||||
|
||||
var RecordColumns = buildRecordColumns("records")
|
||||
|
||||
type recordColumns struct {
|
||||
tableAlias string
|
||||
Key sqlite.Expression
|
||||
Format sqlite.Expression
|
||||
Value sqlite.Expression
|
||||
Created sqlite.Expression
|
||||
Modified sqlite.Expression
|
||||
Expires sqlite.Expression
|
||||
Deleted sqlite.Expression
|
||||
Secret sqlite.Expression
|
||||
Crownjewel sqlite.Expression
|
||||
}
|
||||
|
||||
func (c recordColumns) Alias() string {
|
||||
return c.tableAlias
|
||||
}
|
||||
|
||||
func (recordColumns) AliasedAs(alias string) recordColumns {
|
||||
return buildRecordColumns(alias)
|
||||
}
|
||||
|
||||
func buildRecordColumns(alias string) recordColumns {
|
||||
return recordColumns{
|
||||
tableAlias: alias,
|
||||
Key: sqlite.Quote(alias, "key"),
|
||||
Format: sqlite.Quote(alias, "format"),
|
||||
Value: sqlite.Quote(alias, "value"),
|
||||
Created: sqlite.Quote(alias, "created"),
|
||||
Modified: sqlite.Quote(alias, "modified"),
|
||||
Expires: sqlite.Quote(alias, "expires"),
|
||||
Deleted: sqlite.Quote(alias, "deleted"),
|
||||
Secret: sqlite.Quote(alias, "secret"),
|
||||
Crownjewel: sqlite.Quote(alias, "crownjewel"),
|
||||
}
|
||||
}
|
||||
|
||||
type recordWhere[Q sqlite.Filterable] struct {
|
||||
Key sqlite.WhereMod[Q, string]
|
||||
Format sqlite.WhereNullMod[Q, int16]
|
||||
Value sqlite.WhereNullMod[Q, []byte]
|
||||
Created sqlite.WhereMod[Q, int64]
|
||||
Modified sqlite.WhereMod[Q, int64]
|
||||
Expires sqlite.WhereMod[Q, int64]
|
||||
Deleted sqlite.WhereMod[Q, int64]
|
||||
Secret sqlite.WhereMod[Q, bool]
|
||||
Crownjewel sqlite.WhereMod[Q, bool]
|
||||
}
|
||||
|
||||
func (recordWhere[Q]) AliasedAs(alias string) recordWhere[Q] {
|
||||
return buildRecordWhere[Q](buildRecordColumns(alias))
|
||||
}
|
||||
|
||||
func buildRecordWhere[Q sqlite.Filterable](cols recordColumns) recordWhere[Q] {
|
||||
return recordWhere[Q]{
|
||||
Key: sqlite.Where[Q, string](cols.Key),
|
||||
Format: sqlite.WhereNull[Q, int16](cols.Format),
|
||||
Value: sqlite.WhereNull[Q, []byte](cols.Value),
|
||||
Created: sqlite.Where[Q, int64](cols.Created),
|
||||
Modified: sqlite.Where[Q, int64](cols.Modified),
|
||||
Expires: sqlite.Where[Q, int64](cols.Expires),
|
||||
Deleted: sqlite.Where[Q, int64](cols.Deleted),
|
||||
Secret: sqlite.Where[Q, bool](cols.Secret),
|
||||
Crownjewel: sqlite.Where[Q, bool](cols.Crownjewel),
|
||||
}
|
||||
}
|
||||
|
||||
// RecordSetter is used for insert/upsert/update operations
|
||||
// All values are optional, and do not have to be set
|
||||
// Generated columns are not included
|
||||
type RecordSetter struct {
|
||||
Key omit.Val[string] `db:"key,pk" `
|
||||
Format omitnull.Val[int16] `db:"format" `
|
||||
Value omitnull.Val[[]byte] `db:"value" `
|
||||
Created omit.Val[int64] `db:"created" `
|
||||
Modified omit.Val[int64] `db:"modified" `
|
||||
Expires omit.Val[int64] `db:"expires" `
|
||||
Deleted omit.Val[int64] `db:"deleted" `
|
||||
Secret omit.Val[bool] `db:"secret" `
|
||||
Crownjewel omit.Val[bool] `db:"crownjewel" `
|
||||
}
|
||||
|
||||
func (s RecordSetter) SetColumns() []string {
|
||||
vals := make([]string, 0, 9)
|
||||
if !s.Key.IsUnset() {
|
||||
vals = append(vals, "key")
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
vals = append(vals, "format")
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
vals = append(vals, "value")
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
vals = append(vals, "created")
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
vals = append(vals, "modified")
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
vals = append(vals, "expires")
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
vals = append(vals, "deleted")
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
vals = append(vals, "secret")
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
vals = append(vals, "crownjewel")
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (s RecordSetter) Overwrite(t *Record) {
|
||||
if !s.Key.IsUnset() {
|
||||
t.Key, _ = s.Key.Get()
|
||||
}
|
||||
if !s.Format.IsUnset() {
|
||||
t.Format, _ = s.Format.GetNull()
|
||||
}
|
||||
if !s.Value.IsUnset() {
|
||||
t.Value, _ = s.Value.GetNull()
|
||||
}
|
||||
if !s.Created.IsUnset() {
|
||||
t.Created, _ = s.Created.Get()
|
||||
}
|
||||
if !s.Modified.IsUnset() {
|
||||
t.Modified, _ = s.Modified.Get()
|
||||
}
|
||||
if !s.Expires.IsUnset() {
|
||||
t.Expires, _ = s.Expires.Get()
|
||||
}
|
||||
if !s.Deleted.IsUnset() {
|
||||
t.Deleted, _ = s.Deleted.Get()
|
||||
}
|
||||
if !s.Secret.IsUnset() {
|
||||
t.Secret, _ = s.Secret.Get()
|
||||
}
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
t.Crownjewel, _ = s.Crownjewel.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RecordSetter) Apply(q *dialect.InsertQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeInsertHooks.RunHooks(ctx, exec, s)
|
||||
})
|
||||
|
||||
if len(q.Table.Columns) == 0 {
|
||||
q.Table.Columns = s.SetColumns()
|
||||
}
|
||||
|
||||
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
vals := make([]bob.Expression, 0, 9)
|
||||
if !s.Key.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Key))
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Format))
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Value))
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Created))
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Modified))
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Expires))
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Deleted))
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Secret))
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Crownjewel))
|
||||
}
|
||||
|
||||
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
func (s RecordSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return um.Set(s.Expressions()...)
|
||||
}
|
||||
|
||||
func (s RecordSetter) Expressions(prefix ...string) []bob.Expression {
|
||||
exprs := make([]bob.Expression, 0, 9)
|
||||
|
||||
if !s.Key.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "key")...),
|
||||
sqlite.Arg(s.Key),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "format")...),
|
||||
sqlite.Arg(s.Format),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "value")...),
|
||||
sqlite.Arg(s.Value),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "created")...),
|
||||
sqlite.Arg(s.Created),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "modified")...),
|
||||
sqlite.Arg(s.Modified),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "expires")...),
|
||||
sqlite.Arg(s.Expires),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "deleted")...),
|
||||
sqlite.Arg(s.Deleted),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "secret")...),
|
||||
sqlite.Arg(s.Secret),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "crownjewel")...),
|
||||
sqlite.Arg(s.Crownjewel),
|
||||
}})
|
||||
}
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
// FindRecord retrieves a single record by primary key
|
||||
// If cols is empty Find will return all columns.
|
||||
func FindRecord(ctx context.Context, exec bob.Executor, KeyPK string, cols ...string) (*Record, error) {
|
||||
if len(cols) == 0 {
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
sm.Columns(Records.Columns().Only(cols...)),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
// RecordExists checks the presence of a single record by primary key
|
||||
func RecordExists(ctx context.Context, exec bob.Executor, KeyPK string) (bool, error) {
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
).Exists(ctx, exec)
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after Record is retrieved from the database
|
||||
func (o *Record) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Records.AfterSelectHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Records.AfterInsertHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// PrimaryKeyVals returns the primary key values of the Record
|
||||
func (o *Record) PrimaryKeyVals() bob.Expression {
|
||||
return sqlite.Arg(o.Key)
|
||||
}
|
||||
|
||||
func (o *Record) pkEQ() dialect.Expression {
|
||||
return sqlite.Quote("records", "key").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
return o.PrimaryKeyVals().WriteSQL(ctx, w, d, start)
|
||||
}))
|
||||
}
|
||||
|
||||
// Update uses an executor to update the Record
|
||||
func (o *Record) Update(ctx context.Context, exec bob.Executor, s *RecordSetter) error {
|
||||
v, err := Records.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *v
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a single Record record with an executor
|
||||
func (o *Record) Delete(ctx context.Context, exec bob.Executor) error {
|
||||
_, err := Records.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload refreshes the Record using the executor
|
||||
func (o *Record) Reload(ctx context.Context, exec bob.Executor) error {
|
||||
o2, err := Records.Query(
|
||||
SelectWhere.Records.Key.EQ(o.Key),
|
||||
).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *o2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after RecordSlice is retrieved from the database
|
||||
func (o RecordSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Records.AfterSelectHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Records.AfterInsertHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) pkIN() dialect.Expression {
|
||||
if len(o) == 0 {
|
||||
return sqlite.Raw("NULL")
|
||||
}
|
||||
|
||||
return sqlite.Quote("records", "key").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
pkPairs := make([]bob.Expression, len(o))
|
||||
for i, row := range o {
|
||||
pkPairs[i] = row.PrimaryKeyVals()
|
||||
}
|
||||
return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
// copyMatchingRows finds models in the given slice that have the same primary key
|
||||
// then it first copies the existing relationships from the old model to the new model
|
||||
// and then replaces the old model in the slice with the new model
|
||||
func (o RecordSlice) copyMatchingRows(from ...*Record) {
|
||||
for i, old := range o {
|
||||
for _, new := range from {
|
||||
if new.Key != old.Key {
|
||||
continue
|
||||
}
|
||||
|
||||
o[i] = new
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMod modifies an update query with "WHERE primary_key IN (o...)"
|
||||
func (o RecordSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeUpdateHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Record:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Record:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case RecordSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Record or a slice of Record
|
||||
// then run the AfterUpdateHooks on the slice
|
||||
_, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)"
|
||||
func (o RecordSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] {
|
||||
return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeDeleteHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Record:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Record:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case RecordSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Record or a slice of Record
|
||||
// then run the AfterDeleteHooks on the slice
|
||||
_, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
func (o RecordSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals RecordSetter) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Records.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) DeleteAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Records.Delete(o.DeleteMod()).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) ReloadAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o2, err := Records.Query(sm.Where(o.pkIN())).All(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.copyMatchingRows(o2...)
|
||||
|
||||
return nil
|
||||
}
|
125
base/database/storage/sqlite/prepared.go
Normal file
125
base/database/storage/sqlite/prepared.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/im"
|
||||
"github.com/stephenafamo/bob/expr"
|
||||
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/database/storage/sqlite/models"
|
||||
"github.com/safing/structures/dsd"
|
||||
)
|
||||
|
||||
var UsePreparedStatements bool = true
|
||||
|
||||
// PutMany stores many records in the database.
|
||||
func (db *SQLite) putManyWithPreparedStmts(shadowDelete bool) (chan<- record.Record, <-chan error) {
|
||||
batch := make(chan record.Record, 100)
|
||||
errs := make(chan error, 1)
|
||||
|
||||
// Simulate upsert with custom selection on conflict.
|
||||
rawQuery, _, err := models.Records.Insert(
|
||||
im.Into("records", "key", "format", "value", "created", "modified", "expires", "deleted", "secret", "crownjewel"),
|
||||
im.Values(expr.Arg("key"), expr.Arg("format"), expr.Arg("value"), expr.Arg("created"), expr.Arg("modified"), expr.Arg("expires"), expr.Arg("deleted"), expr.Arg("secret"), expr.Arg("crownjewel")),
|
||||
im.OnConflict("key").DoUpdate(
|
||||
im.SetExcluded("format", "value", "created", "modified", "expires", "deleted", "secret", "crownjewel"),
|
||||
),
|
||||
).Build(db.ctx)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
// Start transaction.
|
||||
tx, err := db.bob.BeginTx(db.ctx, nil)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
// Create prepared statement WITHIN TRANSACTION.
|
||||
preparedStmt, err := tx.PrepareContext(db.ctx, rawQuery)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
// start handler
|
||||
go func() {
|
||||
// Read all put records.
|
||||
writeBatch:
|
||||
for {
|
||||
select {
|
||||
case r := <-batch:
|
||||
if r != nil {
|
||||
// Write record.
|
||||
err := writeWithPreparedStatement(db.ctx, &preparedStmt, r)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
break writeBatch
|
||||
}
|
||||
} else {
|
||||
// Finalize transcation.
|
||||
errs <- tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
case <-db.ctx.Done():
|
||||
break writeBatch
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback transaction.
|
||||
errs <- tx.Rollback()
|
||||
}()
|
||||
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
func writeWithPreparedStatement(ctx context.Context, pStmt *bob.StdPrepared, r record.Record) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// Serialize to JSON.
|
||||
data, err := r.MarshalDataOnly(r, dsd.JSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get Meta.
|
||||
m := r.Meta()
|
||||
|
||||
// Insert.
|
||||
if len(data) > 0 {
|
||||
format := strconv.Itoa(dsd.JSON)
|
||||
_, err = pStmt.ExecContext(
|
||||
ctx,
|
||||
r.DatabaseKey(),
|
||||
format,
|
||||
data,
|
||||
m.Created,
|
||||
m.Modified,
|
||||
m.Expires,
|
||||
m.Deleted,
|
||||
m.IsSecret(),
|
||||
m.IsCrownJewel(),
|
||||
)
|
||||
} else {
|
||||
_, err = pStmt.ExecContext(
|
||||
ctx,
|
||||
r.DatabaseKey(),
|
||||
nil,
|
||||
nil,
|
||||
m.Created,
|
||||
m.Modified,
|
||||
m.Expires,
|
||||
m.Deleted,
|
||||
m.IsSecret(),
|
||||
m.IsCrownJewel(),
|
||||
)
|
||||
}
|
||||
return err
|
||||
}
|
86
base/database/storage/sqlite/prepared_test.go
Normal file
86
base/database/storage/sqlite/prepared_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkPutMany(b *testing.B) {
|
||||
// Configure prepared statement usage.
|
||||
origSetting := UsePreparedStatements
|
||||
UsePreparedStatements = false
|
||||
defer func() {
|
||||
UsePreparedStatements = origSetting
|
||||
}()
|
||||
|
||||
// Run benchmark.
|
||||
benchPutMany(b)
|
||||
}
|
||||
|
||||
func BenchmarkPutManyPreparedStmt(b *testing.B) {
|
||||
// Configure prepared statement usage.
|
||||
origSetting := UsePreparedStatements
|
||||
UsePreparedStatements = true
|
||||
defer func() {
|
||||
UsePreparedStatements = origSetting
|
||||
}()
|
||||
|
||||
// Run benchmark.
|
||||
benchPutMany(b)
|
||||
}
|
||||
|
||||
func benchPutMany(b *testing.B) { //nolint:thelper
|
||||
// Start database.
|
||||
testDir := b.TempDir()
|
||||
db, err := openSQLite("test", testDir, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
// shutdown
|
||||
err = db.Shutdown()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start benchmarking.
|
||||
b.ResetTimer()
|
||||
|
||||
// Benchmark PutMany.
|
||||
records, errs := db.PutMany(false)
|
||||
for i := range b.N {
|
||||
// Create test record.
|
||||
newTestRecord := &TestRecord{
|
||||
S: "banana",
|
||||
I: 42,
|
||||
I8: 42,
|
||||
I16: 42,
|
||||
I32: 42,
|
||||
I64: 42,
|
||||
UI: 42,
|
||||
UI8: 42,
|
||||
UI16: 42,
|
||||
UI32: 42,
|
||||
UI64: 42,
|
||||
F32: 42.42,
|
||||
F64: 42.42,
|
||||
B: true,
|
||||
}
|
||||
newTestRecord.UpdateMeta()
|
||||
newTestRecord.SetKey("test:" + strconv.Itoa(i))
|
||||
|
||||
select {
|
||||
case records <- newTestRecord:
|
||||
case err := <-errs:
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize.
|
||||
close(records)
|
||||
err = <-errs
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
51
base/database/storage/sqlite/schema.go
Normal file
51
base/database/storage/sqlite/schema.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package sqlite
|
||||
|
||||
// Base command for sql-migrate:
|
||||
//go:generate -command migrate go tool github.com/rubenv/sql-migrate/sql-migrate
|
||||
|
||||
// Run missing migrations:
|
||||
//go:generate migrate up --config=migrations_config.yml
|
||||
|
||||
// Redo last migration:
|
||||
// x go:generate migrate redo --config=migrations_config.yml
|
||||
|
||||
// Undo all migrations:
|
||||
// x go:generate migrate down --config=migrations_config.yml
|
||||
|
||||
// Generate models with bob:
|
||||
//go:generate go tool github.com/stephenafamo/bob/gen/bobgen-sqlite
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/database/storage/sqlite/models"
|
||||
)
|
||||
|
||||
//go:embed migrations/*
|
||||
var dbMigrations embed.FS
|
||||
|
||||
func getMigrations() migrate.EmbedFileSystemMigrationSource {
|
||||
return migrate.EmbedFileSystemMigrationSource{
|
||||
FileSystem: dbMigrations,
|
||||
Root: "migrations",
|
||||
}
|
||||
}
|
||||
|
||||
func getMeta(r *models.Record) *record.Meta {
|
||||
meta := &record.Meta{
|
||||
Created: r.Created,
|
||||
Modified: r.Modified,
|
||||
Expires: r.Expires,
|
||||
Deleted: r.Deleted,
|
||||
}
|
||||
if r.Secret {
|
||||
meta.MakeSecret()
|
||||
}
|
||||
if r.Crownjewel {
|
||||
meta.MakeCrownJewel()
|
||||
}
|
||||
return meta
|
||||
}
|
566
base/database/storage/sqlite/sqlite.go
Normal file
566
base/database/storage/sqlite/sqlite.go
Normal file
|
@ -0,0 +1,566 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
sqldblogger "github.com/simukti/sqldb-logger"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/im"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/um"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/safing/portmaster/base/database/accessor"
|
||||
"github.com/safing/portmaster/base/database/iterator"
|
||||
"github.com/safing/portmaster/base/database/query"
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/database/storage"
|
||||
"github.com/safing/portmaster/base/database/storage/sqlite/models"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/structures/dsd"
|
||||
)
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
ErrQueryTimeout = errors.New("query timeout")
|
||||
)
|
||||
|
||||
// SQLite storage.
|
||||
type SQLite struct {
|
||||
name string
|
||||
|
||||
db *sql.DB
|
||||
bob bob.DB
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
}
|
||||
|
||||
func init() {
|
||||
_ = storage.Register("sqlite", func(name, location string) (storage.Interface, error) {
|
||||
return NewSQLite(name, location)
|
||||
})
|
||||
}
|
||||
|
||||
// NewSQLite creates a sqlite database.
|
||||
func NewSQLite(name, location string) (*SQLite, error) {
|
||||
return openSQLite(name, location, false)
|
||||
}
|
||||
|
||||
// openSQLite creates a sqlite database.
|
||||
func openSQLite(name, location string, printStmts bool) (*SQLite, error) {
|
||||
dbFile := filepath.Join(location, "db.sqlite")
|
||||
|
||||
// Open database file.
|
||||
// Default settings:
|
||||
// _time_format = YYYY-MM-DDTHH:MM:SS.SSS
|
||||
// _txlock = deferred
|
||||
db, err := sql.Open("sqlite", dbFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open sqlite: %w", err)
|
||||
}
|
||||
|
||||
// Enable statement printing.
|
||||
if printStmts {
|
||||
db = sqldblogger.OpenDriver(dbFile, db.Driver(), &statementLogger{})
|
||||
}
|
||||
|
||||
// Set other settings.
|
||||
pragmas := []string{
|
||||
"PRAGMA journal_mode=WAL;", // Corruption safe write ahead log for txs.
|
||||
"PRAGMA synchronous=NORMAL;", // Best for WAL.
|
||||
"PRAGMA cache_size=-10000;", // 10MB Cache.
|
||||
"PRAGMA busy_timeout=3000;", // 3s (3000ms) timeout for locked tables.
|
||||
}
|
||||
for _, pragma := range pragmas {
|
||||
_, err := db.Exec(pragma)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init sqlite with %s: %w", pragma, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Run migrations on database.
|
||||
n, err := migrate.Exec(db, "sqlite3", getMigrations(), migrate.Up)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("migrate sqlite: %w", err)
|
||||
}
|
||||
log.Debugf("database/sqlite: ran %d migrations on %s database", n, name)
|
||||
|
||||
// Return as bob database.
|
||||
ctx, cancelCtx := context.WithCancel(context.Background())
|
||||
return &SQLite{
|
||||
name: name,
|
||||
db: db,
|
||||
bob: bob.NewDB(db),
|
||||
ctx: ctx,
|
||||
cancelCtx: cancelCtx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get returns a database record.
|
||||
func (db *SQLite) Get(key string) (record.Record, error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Get record from database.
|
||||
r, err := models.FindRecord(db.ctx, db.bob, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", storage.ErrNotFound, err)
|
||||
}
|
||||
|
||||
// Return data in wrapper.
|
||||
return record.NewWrapperFromDatabase(
|
||||
db.name,
|
||||
key,
|
||||
getMeta(r),
|
||||
uint8(r.Format.GetOrZero()), //nolint:gosec // Values are within uint8.
|
||||
r.Value.GetOrZero(),
|
||||
)
|
||||
}
|
||||
|
||||
// GetMeta returns the metadata of a database record.
|
||||
func (db *SQLite) GetMeta(key string) (*record.Meta, error) {
|
||||
r, err := db.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.Meta(), nil
|
||||
}
|
||||
|
||||
// Put stores a record in the database.
|
||||
func (db *SQLite) Put(r record.Record) (record.Record, error) {
|
||||
return db.putRecord(r, nil)
|
||||
}
|
||||
|
||||
func (db *SQLite) putRecord(r record.Record, tx *bob.Tx) (record.Record, error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Lock record if in a transaction.
|
||||
if tx != nil {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
}
|
||||
|
||||
// Serialize to JSON.
|
||||
data, err := r.MarshalDataOnly(r, dsd.JSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Prepare for setter.
|
||||
setFormat := omitnull.From(int16(dsd.JSON))
|
||||
setData := omitnull.From(data)
|
||||
if len(data) == 0 {
|
||||
setFormat.Null()
|
||||
setData.Null()
|
||||
}
|
||||
|
||||
// Create structure for insert.
|
||||
m := r.Meta()
|
||||
setter := models.RecordSetter{
|
||||
Key: omit.From(r.DatabaseKey()),
|
||||
Format: setFormat,
|
||||
Value: setData,
|
||||
Created: omit.From(m.Created),
|
||||
Modified: omit.From(m.Modified),
|
||||
Expires: omit.From(m.Expires),
|
||||
Deleted: omit.From(m.Deleted),
|
||||
Secret: omit.From(m.IsSecret()),
|
||||
Crownjewel: omit.From(m.IsCrownJewel()),
|
||||
}
|
||||
|
||||
// Simulate upsert with custom selection on conflict.
|
||||
dbQuery := models.Records.Insert(
|
||||
&setter,
|
||||
im.OnConflict("key").DoUpdate(
|
||||
im.SetExcluded("format", "value", "created", "modified", "expires", "deleted", "secret", "crownjewel"),
|
||||
),
|
||||
)
|
||||
|
||||
// Execute in transaction or directly.
|
||||
if tx != nil {
|
||||
_, err = dbQuery.Exec(db.ctx, tx)
|
||||
} else {
|
||||
_, err = dbQuery.Exec(db.ctx, db.bob)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// PutMany stores many records in the database.
|
||||
func (db *SQLite) PutMany(shadowDelete bool) (chan<- record.Record, <-chan error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Check if we should use prepared statement optimized inserting.
|
||||
if UsePreparedStatements {
|
||||
return db.putManyWithPreparedStmts(shadowDelete)
|
||||
}
|
||||
|
||||
batch := make(chan record.Record, 100)
|
||||
errs := make(chan error, 1)
|
||||
|
||||
tx, err := db.bob.BeginTx(db.ctx, nil)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
// start handler
|
||||
go func() {
|
||||
// Read all put records.
|
||||
writeBatch:
|
||||
for {
|
||||
select {
|
||||
case r := <-batch:
|
||||
if r != nil {
|
||||
// Write record.
|
||||
_, err := db.putRecord(r, &tx)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
break writeBatch
|
||||
}
|
||||
} else {
|
||||
// Finalize transcation.
|
||||
errs <- tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
case <-db.ctx.Done():
|
||||
break writeBatch
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback transaction.
|
||||
errs <- tx.Rollback()
|
||||
}()
|
||||
|
||||
return batch, errs
|
||||
}
|
||||
|
||||
// Delete deletes a record from the database.
|
||||
func (db *SQLite) Delete(key string) error {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
toDelete := &models.Record{Key: key}
|
||||
return toDelete.Delete(db.ctx, db.bob)
|
||||
}
|
||||
|
||||
// Query returns a an iterator for the supplied query.
|
||||
func (db *SQLite) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
_, err := q.Check()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid query: %w", err)
|
||||
}
|
||||
|
||||
queryIter := iterator.New()
|
||||
|
||||
go db.queryExecutor(queryIter, q, local, internal)
|
||||
return queryIter, nil
|
||||
}
|
||||
|
||||
func (db *SQLite) queryExecutor(queryIter *iterator.Iterator, q *query.Query, local, internal bool) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Build query.
|
||||
var recordQuery *sqlite.ViewQuery[*models.Record, models.RecordSlice]
|
||||
if q.DatabaseKeyPrefix() != "" {
|
||||
recordQuery = models.Records.View.Query(
|
||||
models.SelectWhere.Records.Key.Like(q.DatabaseKeyPrefix() + "%"),
|
||||
)
|
||||
} else {
|
||||
recordQuery = models.Records.View.Query()
|
||||
}
|
||||
|
||||
// Get cursor to go over all records in the query.
|
||||
cursor, err := models.RecordsQuery.Cursor(recordQuery, db.ctx, db.bob)
|
||||
if err != nil {
|
||||
queryIter.Finish(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = cursor.Close()
|
||||
}()
|
||||
|
||||
recordsLoop:
|
||||
for cursor.Next() {
|
||||
// Get next record
|
||||
r, cErr := cursor.Get()
|
||||
if cErr != nil {
|
||||
err = fmt.Errorf("cursor error: %w", cErr)
|
||||
break recordsLoop
|
||||
}
|
||||
|
||||
// Check if key matches.
|
||||
if !q.MatchesKey(r.Key) {
|
||||
continue recordsLoop
|
||||
}
|
||||
|
||||
// Check Meta.
|
||||
m := getMeta(r)
|
||||
if !m.CheckValidity() ||
|
||||
!m.CheckPermission(local, internal) {
|
||||
continue recordsLoop
|
||||
}
|
||||
|
||||
// Check Data.
|
||||
if q.HasWhereCondition() {
|
||||
if r.Format.IsNull() || r.Value.IsNull() {
|
||||
continue recordsLoop
|
||||
}
|
||||
|
||||
jsonData := string(r.Value.GetOrZero())
|
||||
jsonAccess := accessor.NewJSONAccessor(&jsonData)
|
||||
if !q.MatchesAccessor(jsonAccess) {
|
||||
continue recordsLoop
|
||||
}
|
||||
}
|
||||
|
||||
// Build database record.
|
||||
matched, _ := record.NewWrapperFromDatabase(
|
||||
db.name,
|
||||
r.Key,
|
||||
m,
|
||||
uint8(r.Format.GetOrZero()), //nolint:gosec // Values are within uint8.
|
||||
r.Value.GetOrZero(),
|
||||
)
|
||||
|
||||
select {
|
||||
case <-queryIter.Done:
|
||||
break recordsLoop
|
||||
case queryIter.Next <- matched:
|
||||
default:
|
||||
select {
|
||||
case <-queryIter.Done:
|
||||
break recordsLoop
|
||||
case queryIter.Next <- matched:
|
||||
case <-time.After(1 * time.Second):
|
||||
err = ErrQueryTimeout
|
||||
break recordsLoop
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
queryIter.Finish(err)
|
||||
}
|
||||
|
||||
// Purge deletes all records that match the given query. It returns the number of successful deletes and an error.
|
||||
func (db *SQLite) Purge(ctx context.Context, q *query.Query, local, internal, shadowDelete bool) (int, error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Optimize for local and internal queries without where clause and without shadow delete.
|
||||
if local && internal && !shadowDelete && !q.HasWhereCondition() {
|
||||
// First count entries (SQLite does not support affected rows)
|
||||
n, err := models.Records.Query(
|
||||
models.SelectWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
|
||||
).Count(db.ctx, db.bob)
|
||||
if err != nil || n == 0 {
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Delete entries.
|
||||
_, err = models.Records.Delete(
|
||||
models.DeleteWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
|
||||
).Exec(db.ctx, db.bob)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Optimize for local and internal queries without where clause, but with shadow delete.
|
||||
if local && internal && shadowDelete && !q.HasWhereCondition() {
|
||||
// First count entries (SQLite does not support affected rows)
|
||||
n, err := models.Records.Query(
|
||||
models.SelectWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
|
||||
).Count(db.ctx, db.bob)
|
||||
if err != nil || n == 0 {
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Mark purged records as deleted.
|
||||
now := time.Now().Unix()
|
||||
_, err = models.Records.Update(
|
||||
um.SetCol("format").ToArg(nil),
|
||||
um.SetCol("value").ToArg(nil),
|
||||
um.SetCol("deleted").ToArg(now),
|
||||
models.UpdateWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
|
||||
).Exec(db.ctx, db.bob)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Otherwise, iterate over all entries and delete matching ones.
|
||||
|
||||
// TODO: Non-local, non-internal or content matching queries are not supported at the moment.
|
||||
return 0, storage.ErrNotImplemented
|
||||
}
|
||||
|
||||
// PurgeOlderThan deletes all records last updated before the given time. It returns the number of successful deletes and an error.
|
||||
func (db *SQLite) PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time, local, internal, shadowDelete bool) (int, error) {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
purgeBeforeInt := purgeBefore.Unix()
|
||||
|
||||
// Optimize for local and internal queries without where clause and without shadow delete.
|
||||
if local && internal && !shadowDelete {
|
||||
// First count entries (SQLite does not support affected rows)
|
||||
n, err := models.Records.Query(
|
||||
models.SelectWhere.Records.Key.Like(prefix+"%"),
|
||||
models.SelectWhere.Records.Modified.LT(purgeBeforeInt),
|
||||
).Count(db.ctx, db.bob)
|
||||
if err != nil || n == 0 {
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Delete entries.
|
||||
_, err = models.Records.Delete(
|
||||
models.DeleteWhere.Records.Key.Like(prefix+"%"),
|
||||
models.DeleteWhere.Records.Modified.LT(purgeBeforeInt),
|
||||
).Exec(db.ctx, db.bob)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Optimize for local and internal queries without where clause, but with shadow delete.
|
||||
if local && internal && shadowDelete {
|
||||
// First count entries (SQLite does not support affected rows)
|
||||
n, err := models.Records.Query(
|
||||
models.SelectWhere.Records.Key.Like(prefix+"%"),
|
||||
models.SelectWhere.Records.Modified.LT(purgeBeforeInt),
|
||||
).Count(db.ctx, db.bob)
|
||||
if err != nil || n == 0 {
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// Mark purged records as deleted.
|
||||
now := time.Now().Unix()
|
||||
_, err = models.Records.Update(
|
||||
um.SetCol("format").ToArg(nil),
|
||||
um.SetCol("value").ToArg(nil),
|
||||
um.SetCol("deleted").ToArg(now),
|
||||
models.UpdateWhere.Records.Key.Like(prefix+"%"),
|
||||
models.UpdateWhere.Records.Modified.LT(purgeBeforeInt),
|
||||
).Exec(db.ctx, db.bob)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// TODO: Non-local or non-internal queries are not supported at the moment.
|
||||
return 0, storage.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ReadOnly returns whether the database is read only.
|
||||
func (db *SQLite) ReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Injected returns whether the database is injected.
|
||||
func (db *SQLite) Injected() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MaintainRecordStates maintains records states in the database.
|
||||
func (db *SQLite) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time, shadowDelete bool) error {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
now := time.Now().Unix()
|
||||
purgeThreshold := purgeDeletedBefore.Unix()
|
||||
|
||||
// Option 1: Using shadow delete.
|
||||
if shadowDelete {
|
||||
// Mark expired records as deleted.
|
||||
_, err := models.Records.Update(
|
||||
um.SetCol("format").ToArg(nil),
|
||||
um.SetCol("value").ToArg(nil),
|
||||
um.SetCol("deleted").ToArg(now),
|
||||
models.UpdateWhere.Records.Deleted.EQ(0),
|
||||
models.UpdateWhere.Records.Expires.GT(0),
|
||||
models.UpdateWhere.Records.Expires.LT(now),
|
||||
).Exec(db.ctx, db.bob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to shadow delete expired records: %w", err)
|
||||
}
|
||||
|
||||
// Purge deleted records before threshold.
|
||||
_, err = models.Records.Delete(
|
||||
models.DeleteWhere.Records.Deleted.GT(0),
|
||||
models.DeleteWhere.Records.Deleted.LT(purgeThreshold),
|
||||
).Exec(db.ctx, db.bob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to purge deleted records (before threshold): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option 2: Immediate delete.
|
||||
|
||||
// Delete expired record.
|
||||
_, err := models.Records.Delete(
|
||||
models.DeleteWhere.Records.Expires.GT(0),
|
||||
models.DeleteWhere.Records.Expires.LT(now),
|
||||
).Exec(db.ctx, db.bob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete expired records: %w", err)
|
||||
}
|
||||
|
||||
// Delete shadow deleted records.
|
||||
_, err = models.Records.Delete(
|
||||
models.DeleteWhere.Records.Deleted.GT(0),
|
||||
).Exec(db.ctx, db.bob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to purge deleted records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLite) Maintain(ctx context.Context) error {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Remove up to about 100KB of SQLite pages from the freelist on every run.
|
||||
// (Assuming 4KB page size.)
|
||||
_, err := db.db.ExecContext(ctx, "PRAGMA incremental_vacuum(25);")
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *SQLite) MaintainThorough(ctx context.Context) error {
|
||||
db.wg.Add(1)
|
||||
defer db.wg.Done()
|
||||
|
||||
// Remove all pages from the freelist.
|
||||
_, err := db.db.ExecContext(ctx, "PRAGMA incremental_vacuum;")
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown shuts down the database.
|
||||
func (db *SQLite) Shutdown() error {
|
||||
db.wg.Wait()
|
||||
db.cancelCtx()
|
||||
|
||||
return db.bob.Close()
|
||||
}
|
||||
|
||||
type statementLogger struct{}
|
||||
|
||||
func (sl statementLogger) Log(ctx context.Context, level sqldblogger.Level, msg string, data map[string]interface{}) {
|
||||
fmt.Printf("SQL: %s --- %+v\n", msg, data)
|
||||
}
|
216
base/database/storage/sqlite/sqlite_test.go
Normal file
216
base/database/storage/sqlite/sqlite_test.go
Normal file
|
@ -0,0 +1,216 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/safing/portmaster/base/database/query"
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/database/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
// Compile time interface checks.
|
||||
_ storage.Interface = &SQLite{}
|
||||
_ storage.Batcher = &SQLite{}
|
||||
_ storage.Purger = &SQLite{}
|
||||
)
|
||||
|
||||
type TestRecord struct { //nolint:maligned
|
||||
record.Base
|
||||
sync.Mutex
|
||||
S string
|
||||
I int
|
||||
I8 int8
|
||||
I16 int16
|
||||
I32 int32
|
||||
I64 int64
|
||||
UI uint
|
||||
UI8 uint8
|
||||
UI16 uint16
|
||||
UI32 uint32
|
||||
UI64 uint64
|
||||
F32 float32
|
||||
F64 float64
|
||||
B bool
|
||||
}
|
||||
|
||||
func TestSQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// start
|
||||
testDir := t.TempDir()
|
||||
db, err := openSQLite("test", testDir, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
// shutdown
|
||||
err = db.Shutdown()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
a := &TestRecord{
|
||||
S: "banana",
|
||||
I: 42,
|
||||
I8: 42,
|
||||
I16: 42,
|
||||
I32: 42,
|
||||
I64: 42,
|
||||
UI: 42,
|
||||
UI8: 42,
|
||||
UI16: 42,
|
||||
UI32: 42,
|
||||
UI64: 42,
|
||||
F32: 42.42,
|
||||
F64: 42.42,
|
||||
B: true,
|
||||
}
|
||||
a.SetMeta(&record.Meta{})
|
||||
a.Meta().Update()
|
||||
a.SetKey("test:A")
|
||||
|
||||
// put record
|
||||
_, err = db.Put(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get and compare
|
||||
r1, err := db.Get("A")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a1 := &TestRecord{}
|
||||
err = record.Unwrap(r1, a1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, a, a1, "struct must match")
|
||||
|
||||
// setup query test records
|
||||
qA := &TestRecord{}
|
||||
qA.SetKey("test:path/to/A")
|
||||
qA.UpdateMeta()
|
||||
|
||||
qB := &TestRecord{}
|
||||
qB.SetKey("test:path/to/B")
|
||||
qB.UpdateMeta()
|
||||
// Set creation/modification in the past.
|
||||
qB.Meta().Created = time.Now().Add(-time.Hour).Unix()
|
||||
qB.Meta().Modified = time.Now().Add(-time.Hour).Unix()
|
||||
|
||||
qC := &TestRecord{}
|
||||
qC.SetKey("test:path/to/C")
|
||||
qC.UpdateMeta()
|
||||
// Set expiry in the past.
|
||||
qC.Meta().Expires = time.Now().Add(-time.Hour).Unix()
|
||||
|
||||
qZ := &TestRecord{}
|
||||
qZ.SetKey("test:z")
|
||||
qZ.UpdateMeta()
|
||||
|
||||
put, errs := db.PutMany(false)
|
||||
put <- qA
|
||||
put <- qB
|
||||
put <- qC
|
||||
put <- qZ
|
||||
close(put)
|
||||
err = <-errs
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test query
|
||||
q := query.New("test:path/to/").MustBeValid()
|
||||
it, err := db.Query(q, true, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cnt := 0
|
||||
for range it.Next {
|
||||
cnt++
|
||||
}
|
||||
if it.Err() != nil {
|
||||
t.Fatal(it.Err())
|
||||
}
|
||||
if cnt != 2 {
|
||||
// Note: One is expired.
|
||||
t.Fatalf("unexpected query result count: %d", cnt)
|
||||
}
|
||||
|
||||
// delete
|
||||
err = db.Delete("A")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check if its gone
|
||||
_, err = db.Get("A")
|
||||
if err == nil {
|
||||
t.Fatal("should fail")
|
||||
}
|
||||
|
||||
// purge older than
|
||||
n, err := db.PurgeOlderThan(t.Context(), "path/to/", time.Now().Add(-30*time.Minute), true, true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("unexpected purge older than delete count: %d", n)
|
||||
}
|
||||
|
||||
// maintenance
|
||||
err = db.MaintainRecordStates(t.Context(), time.Now().Add(-time.Minute), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// maintenance
|
||||
err = db.MaintainRecordStates(t.Context(), time.Now(), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// purge
|
||||
n, err = db.Purge(t.Context(), query.New("test:path/to/").MustBeValid(), true, true, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("unexpected purge delete count: %d", n)
|
||||
}
|
||||
|
||||
// Maintenance
|
||||
err = db.Maintain(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("Maintain: %s", err)
|
||||
}
|
||||
err = db.MaintainThorough(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("MaintainThorough: %s", err)
|
||||
}
|
||||
|
||||
// test query
|
||||
q = query.New("test").MustBeValid()
|
||||
it, err = db.Query(q, true, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cnt = 0
|
||||
for range it.Next {
|
||||
cnt++
|
||||
}
|
||||
if it.Err() != nil {
|
||||
t.Fatal(it.Err())
|
||||
}
|
||||
if cnt != 1 {
|
||||
t.Fatalf("unexpected query result count: %d", cnt)
|
||||
}
|
||||
}
|
0
base/database/storage/sqlite/testdata/.gitkeep
vendored
Normal file
0
base/database/storage/sqlite/testdata/.gitkeep
vendored
Normal file
|
@ -2,7 +2,6 @@ package dataroot
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
)
|
||||
|
@ -10,7 +9,7 @@ import (
|
|||
var root *utils.DirStructure
|
||||
|
||||
// Initialize initializes the data root directory.
|
||||
func Initialize(rootDir string, perm os.FileMode) error {
|
||||
func Initialize(rootDir string, perm utils.FSPermission) error {
|
||||
if root != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ func Set(setName string, setVersion string, setLicenseName string) {
|
|||
|
||||
if setVersion != "" {
|
||||
version = setVersion
|
||||
versionNumber = setVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,10 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/hectane/go-acl"
|
||||
"github.com/safing/jess/filesig"
|
||||
"github.com/safing/jess/lhash"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/base/utils/renameio"
|
||||
)
|
||||
|
||||
|
@ -137,17 +137,10 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
|
|||
return fmt.Errorf("%s: failed to finalize file %s: %w", reg.Name, rv.storagePath(), err)
|
||||
}
|
||||
// set permissions
|
||||
if onWindows {
|
||||
err = acl.Chmod(rv.storagePath(), 0o0755)
|
||||
if err != nil {
|
||||
log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
|
||||
}
|
||||
} else {
|
||||
// TODO: only set executable files to 0755, set other to 0644
|
||||
err = os.Chmod(rv.storagePath(), 0o0755) //nolint:gosec // See TODO above.
|
||||
if err != nil {
|
||||
log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
|
||||
}
|
||||
// TODO: distinguish between executable and non executable files.
|
||||
err = utils.SetExecPermission(rv.storagePath(), utils.PublicReadPermission)
|
||||
if err != nil {
|
||||
log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
|
||||
}
|
||||
|
||||
log.Debugf("%s: fetched %s and stored to %s", reg.Name, downloadURL, rv.storagePath())
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -13,10 +12,6 @@ import (
|
|||
"github.com/safing/portmaster/base/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
onWindows = runtime.GOOS == "windows"
|
||||
)
|
||||
|
||||
// ResourceRegistry is a registry for managing update resources.
|
||||
type ResourceRegistry struct {
|
||||
sync.RWMutex
|
||||
|
@ -98,7 +93,7 @@ func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error {
|
|||
|
||||
// initialize private attributes
|
||||
reg.storageDir = storageDir
|
||||
reg.tmpDir = storageDir.ChildDir("tmp", 0o0700)
|
||||
reg.tmpDir = storageDir.ChildDir("tmp", utils.AdminOnlyPermission)
|
||||
reg.resources = make(map[string]*Resource)
|
||||
if reg.state == nil {
|
||||
reg.state = &RegistryState{}
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestMain(m *testing.M) {
|
|||
DevMode: true,
|
||||
Online: true,
|
||||
}
|
||||
err = registry.Initialize(utils.NewDirStructure(tmpDir, 0o0777))
|
||||
err = registry.Initialize(utils.NewDirStructure(tmpDir, utils.PublicWritePermission))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -6,15 +6,13 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/hectane/go-acl"
|
||||
)
|
||||
|
||||
const isWindows = runtime.GOOS == "windows"
|
||||
|
||||
// EnsureDirectory ensures that the given directory exists and that is has the given permissions set.
|
||||
// If path is a file, it is deleted and a directory created.
|
||||
func EnsureDirectory(path string, perm os.FileMode) error {
|
||||
func EnsureDirectory(path string, perm FSPermission) error {
|
||||
// open path
|
||||
f, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
@ -23,10 +21,10 @@ func EnsureDirectory(path string, perm os.FileMode) error {
|
|||
// directory exists, check permissions
|
||||
if isWindows {
|
||||
// Ignore windows permission error. For none admin users it will always fail.
|
||||
acl.Chmod(path, perm)
|
||||
_ = SetDirPermission(path, perm)
|
||||
return nil
|
||||
} else if f.Mode().Perm() != perm {
|
||||
return os.Chmod(path, perm)
|
||||
} else if f.Mode().Perm() != perm.AsUnixDirExecPermission() {
|
||||
return SetDirPermission(path, perm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -37,17 +35,17 @@ func EnsureDirectory(path string, perm os.FileMode) error {
|
|||
}
|
||||
// file does not exist (or has been deleted)
|
||||
if err == nil || errors.Is(err, fs.ErrNotExist) {
|
||||
err = os.Mkdir(path, perm)
|
||||
err = os.Mkdir(path, perm.AsUnixDirExecPermission())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create dir %s: %w", path, err)
|
||||
}
|
||||
if isWindows {
|
||||
// Ignore windows permission error. For none admin users it will always fail.
|
||||
acl.Chmod(path, perm)
|
||||
return nil
|
||||
} else {
|
||||
return os.Chmod(path, perm)
|
||||
// Set permissions.
|
||||
err = SetDirPermission(path, perm)
|
||||
// Ignore windows permission error. For none admin users it will always fail.
|
||||
if !isWindows {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// other error opening path
|
||||
return fmt.Errorf("failed to access %s: %w", path, err)
|
||||
|
|
20
base/utils/permissions.go
Normal file
20
base/utils/permissions.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
//go:build !windows
|
||||
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
// SetDirPermission sets the permission of a directory.
|
||||
func SetDirPermission(path string, perm FSPermission) error {
|
||||
return os.Chmod(path, perm.AsUnixDirExecPermission())
|
||||
}
|
||||
|
||||
// SetExecPermission sets the permission of an executable file.
|
||||
func SetExecPermission(path string, perm FSPermission) error {
|
||||
return SetDirPermission(path, perm)
|
||||
}
|
||||
|
||||
// SetFilePermission sets the permission of a non executable file.
|
||||
func SetFilePermission(path string, perm FSPermission) error {
|
||||
return os.Chmod(path, perm.AsUnixFilePermission())
|
||||
}
|
79
base/utils/permissions_windows.go
Normal file
79
base/utils/permissions_windows.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/hectane/go-acl"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
systemSID *windows.SID
|
||||
adminsSID *windows.SID
|
||||
usersSID *windows.SID
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize Security ID for all need groups.
|
||||
// Reference: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
|
||||
var err error
|
||||
systemSID, err = windows.StringToSid("S-1-5-18") // SYSTEM (Local System)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
adminsSID, err = windows.StringToSid("S-1-5-32-544") // Administrators
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
usersSID, err = windows.StringToSid("S-1-5-32-545") // Users
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDirPermission sets the permission of a directory.
|
||||
func SetDirPermission(path string, perm FSPermission) error {
|
||||
SetFilePermission(path, perm)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExecPermission sets the permission of an executable file.
|
||||
func SetExecPermission(path string, perm FSPermission) error {
|
||||
SetFilePermission(path, perm)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFilePermission sets the permission of a non executable file.
|
||||
func SetFilePermission(path string, perm FSPermission) {
|
||||
switch perm {
|
||||
case AdminOnlyPermission:
|
||||
// Set only admin rights, remove all others.
|
||||
acl.Apply(
|
||||
path,
|
||||
true,
|
||||
false,
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID),
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID),
|
||||
)
|
||||
case PublicReadPermission:
|
||||
// Set admin rights and read/execute rights for users, remove all others.
|
||||
acl.Apply(
|
||||
path,
|
||||
true,
|
||||
false,
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID),
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID),
|
||||
acl.GrantSid(windows.GENERIC_READ|windows.GENERIC_EXECUTE, usersSID),
|
||||
)
|
||||
case PublicWritePermission:
|
||||
// Set full control to admin and regular users. Guest users will not have access.
|
||||
acl.Apply(
|
||||
path,
|
||||
true,
|
||||
false,
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID),
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID),
|
||||
acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, usersSID),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -2,25 +2,61 @@ package utils
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type FSPermission uint8
|
||||
|
||||
const (
|
||||
AdminOnlyPermission FSPermission = iota
|
||||
PublicReadPermission
|
||||
PublicWritePermission
|
||||
)
|
||||
|
||||
// AsUnixDirExecPermission return the corresponding unix permission for a directory or executable.
|
||||
func (perm FSPermission) AsUnixDirExecPermission() fs.FileMode {
|
||||
switch perm {
|
||||
case AdminOnlyPermission:
|
||||
return 0o700
|
||||
case PublicReadPermission:
|
||||
return 0o755
|
||||
case PublicWritePermission:
|
||||
return 0o777
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// AsUnixFilePermission return the corresponding unix permission for a regular file.
|
||||
func (perm FSPermission) AsUnixFilePermission() fs.FileMode {
|
||||
switch perm {
|
||||
case AdminOnlyPermission:
|
||||
return 0o600
|
||||
case PublicReadPermission:
|
||||
return 0o644
|
||||
case PublicWritePermission:
|
||||
return 0o666
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// DirStructure represents a directory structure with permissions that should be enforced.
|
||||
type DirStructure struct {
|
||||
sync.Mutex
|
||||
|
||||
Path string
|
||||
Dir string
|
||||
Perm os.FileMode
|
||||
Perm FSPermission
|
||||
Parent *DirStructure
|
||||
Children map[string]*DirStructure
|
||||
}
|
||||
|
||||
// NewDirStructure returns a new DirStructure.
|
||||
func NewDirStructure(path string, perm os.FileMode) *DirStructure {
|
||||
func NewDirStructure(path string, perm FSPermission) *DirStructure {
|
||||
return &DirStructure{
|
||||
Path: path,
|
||||
Perm: perm,
|
||||
|
@ -29,7 +65,7 @@ func NewDirStructure(path string, perm os.FileMode) *DirStructure {
|
|||
}
|
||||
|
||||
// ChildDir adds a new child DirStructure and returns it. Should the child already exist, the existing child is returned and the permissions are updated.
|
||||
func (ds *DirStructure) ChildDir(dirName string, perm os.FileMode) (child *DirStructure) {
|
||||
func (ds *DirStructure) ChildDir(dirName string, perm FSPermission) (child *DirStructure) {
|
||||
ds.Lock()
|
||||
defer ds.Unlock()
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ func ExampleDirStructure() {
|
|||
// output:
|
||||
// / [755]
|
||||
// /repo [777]
|
||||
// /repo/b [707]
|
||||
// /repo/b/c [750]
|
||||
// /repo/b/d [707]
|
||||
// /repo/b/d/e [707]
|
||||
// /repo/b/d/f [707]
|
||||
// /repo/b/d/f/g [707]
|
||||
// /repo/b/d/f/g/h [707]
|
||||
// /repo/b [755]
|
||||
// /repo/b/c [777]
|
||||
// /repo/b/d [755]
|
||||
// /repo/b/d/e [755]
|
||||
// /repo/b/d/f [755]
|
||||
// /repo/b/d/f/g [755]
|
||||
// /repo/b/d/f/g/h [755]
|
||||
// /secret [700]
|
||||
|
||||
basePath, err := os.MkdirTemp("", "")
|
||||
|
@ -28,12 +28,12 @@ func ExampleDirStructure() {
|
|||
return
|
||||
}
|
||||
|
||||
ds := NewDirStructure(basePath, 0o0755)
|
||||
secret := ds.ChildDir("secret", 0o0700)
|
||||
repo := ds.ChildDir("repo", 0o0777)
|
||||
_ = repo.ChildDir("a", 0o0700)
|
||||
b := repo.ChildDir("b", 0o0707)
|
||||
c := b.ChildDir("c", 0o0750)
|
||||
ds := NewDirStructure(basePath, PublicReadPermission)
|
||||
secret := ds.ChildDir("secret", AdminOnlyPermission)
|
||||
repo := ds.ChildDir("repo", PublicWritePermission)
|
||||
_ = repo.ChildDir("a", AdminOnlyPermission)
|
||||
b := repo.ChildDir("b", PublicReadPermission)
|
||||
c := b.ChildDir("c", PublicWritePermission)
|
||||
|
||||
err = ds.Ensure()
|
||||
if err != nil {
|
||||
|
|
|
@ -33,7 +33,7 @@ func main() {
|
|||
flag.Parse()
|
||||
|
||||
// Set name and license.
|
||||
info.Set("SPN Hub", "", "GPLv3")
|
||||
info.Set("SPN Hub", "0.7.8", "GPLv3")
|
||||
|
||||
// Configure metrics.
|
||||
_ = metrics.SetNamespace("hub")
|
||||
|
@ -45,10 +45,6 @@ func main() {
|
|||
// Set SPN public hub mode.
|
||||
conf.EnablePublicHub(true)
|
||||
|
||||
// Set default log level.
|
||||
log.SetLogLevel(log.WarningLevel)
|
||||
_ = log.Start()
|
||||
|
||||
// Create instance.
|
||||
var execCmdLine bool
|
||||
instance, err := spn.New()
|
||||
|
@ -79,6 +75,10 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Set default log level.
|
||||
log.SetLogLevel(log.WarningLevel)
|
||||
_ = log.Start()
|
||||
|
||||
// Start
|
||||
go func() {
|
||||
err = instance.Start()
|
||||
|
|
|
@ -225,14 +225,14 @@ func configureRegistry(mustLoadIndex bool) error {
|
|||
// Remove left over quotes.
|
||||
dataDir = strings.Trim(dataDir, `\"`)
|
||||
// Initialize data root.
|
||||
err := dataroot.Initialize(dataDir, 0o0755)
|
||||
err := dataroot.Initialize(dataDir, utils.PublicReadPermission)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize data root: %w", err)
|
||||
}
|
||||
dataRoot = dataroot.Root()
|
||||
|
||||
// Initialize registry.
|
||||
err = registry.Initialize(dataRoot.ChildDir("updates", 0o0755))
|
||||
err = registry.Initialize(dataRoot.ChildDir("updates", utils.PublicReadPermission))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -24,7 +25,7 @@ var cleanStructureCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func cleanAndEnsureExecDir() error {
|
||||
execDir := dataRoot.ChildDir("exec", 0o777)
|
||||
execDir := dataRoot.ChildDir("exec", utils.PublicWritePermission)
|
||||
|
||||
// Clean up and remove exec dir.
|
||||
err := os.RemoveAll(execDir.Path)
|
||||
|
|
|
@ -179,14 +179,14 @@ func configureRegistry(mustLoadIndex bool) error {
|
|||
// Remove left over quotes.
|
||||
dataDir = strings.Trim(dataDir, `\"`)
|
||||
// Initialize data root.
|
||||
err := dataroot.Initialize(dataDir, 0o0755)
|
||||
err := dataroot.Initialize(dataDir, utils.PublicReadPermission)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize data root: %w", err)
|
||||
}
|
||||
dataRoot = dataroot.Root()
|
||||
|
||||
// Initialize registry.
|
||||
err = registry.Initialize(dataRoot.ChildDir("updates", 0o0755))
|
||||
err = registry.Initialize(dataRoot.ChildDir("updates", utils.PublicReadPermission))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ func configureRegistry(mustLoadIndex bool) error {
|
|||
|
||||
func ensureLoggingDir() error {
|
||||
// set up logs root
|
||||
logsRoot = dataRoot.ChildDir("logs", 0o0777)
|
||||
logsRoot = dataRoot.ChildDir("logs", utils.PublicWritePermission)
|
||||
err := logsRoot.Ensure()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize logs root (%q): %w", logsRoot.Path, err)
|
||||
|
|
|
@ -31,7 +31,7 @@ var rootCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
registry = &updater.ResourceRegistry{}
|
||||
err = registry.Initialize(utils.NewDirStructure(absDistPath, 0o0755))
|
||||
err = registry.Initialize(utils.NewDirStructure(absDistPath, utils.PublicReadPermission))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -63,7 +64,7 @@ func release(cmd *cobra.Command, args []string) error {
|
|||
fmt.Println("aborted...")
|
||||
return nil
|
||||
}
|
||||
symlinksDir := registry.StorageDir().ChildDir("latest", 0o755)
|
||||
symlinksDir := registry.StorageDir().ChildDir("latest", utils.PublicReadPermission)
|
||||
err = registry.CreateSymlinks(symlinksDir)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="flex justify-between items-center p-4 px-12 text-xxs">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="flex items-center">
|
||||
<a class="text-secondary hover:text-primary" [routerLink]="['/app/overview']">Apps</a>
|
||||
<div class="cursor-pointer text-secondary hover:text-primary" [routerLink]="['/app/overview']">Apps</div>
|
||||
<svg viewBox="0 0 24 24" class="inline-block w-4 h-4 text-secondary">
|
||||
<g fill="none" class="inner" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2" d="M10 16l4-4-4-4" />
|
||||
|
|
|
@ -27,9 +27,15 @@ if (typeof (CSS as any)['registerProperty'] === 'function') {
|
|||
}
|
||||
|
||||
function handleExternalResources(e: Event) {
|
||||
// TODO:
|
||||
// This code executes "openExternal()" when any "<a />" element in the app is clicked.
|
||||
// This could potentially be a security issue.
|
||||
// We should consider restricting this to only external links that belong to a certain domain (e.g., https://safing.io).
|
||||
|
||||
// get click target
|
||||
let target: HTMLElement | null = e.target as HTMLElement;
|
||||
// traverse until we reach an a tag
|
||||
|
||||
// traverse until we reach element "<a />"
|
||||
while (!!target && target.tagName !== "A") {
|
||||
target = target.parentElement;
|
||||
}
|
||||
|
|
109
go.mod
109
go.mod
|
@ -1,6 +1,8 @@
|
|||
module github.com/safing/portmaster
|
||||
|
||||
go 1.22.0
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
// TODO: Remove when https://github.com/tc-hib/winres/pull/4 is released.
|
||||
replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2
|
||||
|
@ -8,14 +10,15 @@ replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2
|
|||
require (
|
||||
fyne.io/systray v1.11.0
|
||||
github.com/VictoriaMetrics/metrics v1.35.1
|
||||
github.com/Xuanwo/go-locale v1.1.2
|
||||
github.com/Xuanwo/go-locale v1.1.1
|
||||
github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6
|
||||
github.com/agext/levenshtein v1.2.3
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/awalterschulze/gographviz v2.0.3+incompatible
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible
|
||||
github.com/cilium/ebpf v0.16.0
|
||||
github.com/cilium/ebpf v0.17.1
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgraph-io/badger v1.6.2
|
||||
|
@ -34,10 +37,10 @@ require (
|
|||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb
|
||||
github.com/jackc/puddle/v2 v2.2.2
|
||||
github.com/lmittmann/tint v1.0.5
|
||||
github.com/maruel/panicparse/v2 v2.3.1
|
||||
github.com/lmittmann/tint v1.0.6
|
||||
github.com/maruel/panicparse/v2 v2.4.0
|
||||
github.com/mat/besticon v3.12.0+incompatible
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
|
@ -46,12 +49,15 @@ require (
|
|||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/r3labs/diff/v3 v3.0.1
|
||||
github.com/rot256/pblind v0.0.0-20240730113005-f3275049ead5
|
||||
github.com/rubenv/sql-migrate v1.7.1
|
||||
github.com/safing/jess v0.3.5
|
||||
github.com/safing/structures v1.1.0
|
||||
github.com/safing/structures v1.2.0
|
||||
github.com/seehuhn/fortuna v1.0.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spkg/zipfs v0.7.1
|
||||
github.com/stephenafamo/bob v0.30.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tannerryan/ring v1.1.2
|
||||
github.com/tc-hib/winres v0.3.1
|
||||
|
@ -61,69 +67,120 @@ require (
|
|||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
|
||||
github.com/varlink/go v0.4.0
|
||||
github.com/vincent-petithory/dataurl v1.0.0
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
||||
golang.org/x/image v0.22.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/sync v0.9.0
|
||||
golang.org/x/sys v0.27.0
|
||||
go.etcd.io/bbolt v1.3.10
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
||||
golang.org/x/image v0.19.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.29.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
zombiezen.com/go/sqlite v1.4.0
|
||||
modernc.org/sqlite v1.32.0
|
||||
zombiezen.com/go/sqlite v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect
|
||||
github.com/aead/ecdh v0.2.0 // indirect
|
||||
github.com/alessio/shellescape v1.4.2 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/danieljoos/wincred v1.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.9.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/godbus/dbus v4.1.0+incompatible // indirect
|
||||
github.com/godror/godror v0.40.4 // indirect
|
||||
github.com/godror/knownpb v0.1.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
|
||||
github.com/golang/glog v1.2.1 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect
|
||||
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
|
||||
github.com/knadh/koanf/providers/env v0.1.0 // indirect
|
||||
github.com/knadh/koanf/providers/file v0.1.0 // indirect
|
||||
github.com/knadh/koanf/v2 v2.1.0 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/mattn/go-oci8 v0.1.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.19 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/cli v1.1.5 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/posener/complete v1.2.3 // indirect
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/seehuhn/sha256d v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stephenafamo/scan v0.6.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.23.7 // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/volatiletech/inflect v0.0.1 // indirect
|
||||
github.com/volatiletech/strmangle v0.0.6 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zalando/go-keyring v0.2.5 // indirect
|
||||
github.com/zalando/go-keyring v0.2.6 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/tools v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.61.2 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.59.9 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.34.1 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/rubenv/sql-migrate/sql-migrate
|
||||
github.com/stephenafamo/bob/gen/bobgen-sqlite
|
||||
)
|
||||
|
|
278
go.sum
278
go.sum
|
@ -1,28 +1,49 @@
|
|||
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4=
|
||||
github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc=
|
||||
github.com/VictoriaMetrics/metrics v1.35.1 h1:o84wtBKQbzLdDy14XeskkCZih6anG+veZ1SwJHFGwrU=
|
||||
github.com/VictoriaMetrics/metrics v1.35.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
|
||||
github.com/Xuanwo/go-locale v1.1.2 h1:6H+olvrQcyVOZ+GAC2rXu4armacTT4ZrFCA0mB24XVo=
|
||||
github.com/Xuanwo/go-locale v1.1.2/go.mod h1:1JBER4QV7Ji39GJ4AvVlfvqmTUqopzxQxdg2mXYOw94=
|
||||
github.com/Xuanwo/go-locale v1.1.1 h1:nhvzo1phY4LRwdrwVwKWXn5iZ0pMwwsa3o29yiDRuZc=
|
||||
github.com/Xuanwo/go-locale v1.1.1/go.mod h1:ldC3FzZeMYALkL3YYpwhr4iVYdOIUx42kORcnAHdKUo=
|
||||
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf h1:+edM69bH/X6JpYPmJYBRLanAMe1V5yRXYU3hHUovGcE=
|
||||
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf/go.mod h1:FZqLhJSj2tg0ZN48GB1zvj00+ZYcHPqgsC7yzcgCq6k=
|
||||
github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0 h1:vLrhbOWVPxtHao/QthU8pcpI4DbtSGnWgH7qIJf8F6k=
|
||||
github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0/go.mod h1:l4/5NZtYd/SIohsFhaJQQe+sPOTG22furpZ5FvcYOzk=
|
||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs=
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
|
||||
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E=
|
||||
github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
|
@ -34,20 +55,23 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
|||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||
github.com/cilium/ebpf v0.17.1 h1:G8mzU81R2JA1nE5/8SRubzqvBMmAmri2VL8BIZPWvV0=
|
||||
github.com/cilium/ebpf v0.17.1/go.mod h1:vay2FaYSmIlv3r8dNACd4mW/OCaZLJKJOo+IHBvCIO8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
|
||||
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
|
||||
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
|
||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
|
||||
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
|
@ -62,6 +86,9 @@ github.com/dhaavi/winres v0.2.2/go.mod h1:1NTs+/DtKP1BplIL1+XQSoq4X1PUfLczexS7gf
|
|||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
|
@ -72,33 +99,54 @@ github.com/florianl/go-nfqueue v1.3.2/go.mod h1:eSnAor2YCfMCVYrVNEhkLGN/r1L+J4uD
|
|||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk=
|
||||
github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI=
|
||||
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d h1:QQP1nE4qh5aHTGvI1LgOFxZYVxYoGeMfbNHikogPyoA=
|
||||
github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godror/godror v0.40.4 h1:X1e7hUd02GDaLWKZj40Z7L0CP0W9TrGgmPQZw6+anBg=
|
||||
github.com/godror/godror v0.40.4/go.mod h1:i8YtVTHUJKfFT3wTat4A9UoqScUtZXiYB9Rf3SVARgc=
|
||||
github.com/godror/knownpb v0.1.1 h1:A4J7jdx7jWBhJm18NntafzSC//iZDHkDi1+juwQ5pTI=
|
||||
github.com/godror/knownpb v0.1.1/go.mod h1:4nRFbQo1dDuwKnblRXDxrfCFYeT4hjg3GjMqef58eRE=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
|
||||
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
|
@ -118,8 +166,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
|
@ -131,14 +183,25 @@ github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:Fecb
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM=
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
@ -161,8 +224,20 @@ github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa79
|
|||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
|
||||
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
|
||||
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
|
||||
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
|
||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||
github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8=
|
||||
github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
@ -171,23 +246,33 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
|
||||
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc=
|
||||
github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/maruel/panicparse/v2 v2.3.1 h1:NtJavmbMn0DyzmmSStE8yUsmPZrZmudPH7kplxBinOA=
|
||||
github.com/maruel/panicparse/v2 v2.3.1/go.mod h1:s3UmQB9Fm/n7n/prcD2xBGDkwXD6y2LeZnhbEXvs9Dg=
|
||||
github.com/maruel/panicparse/v2 v2.4.0 h1:yQKMIbQ0DKfinzVkTkcUzQyQ60UCiNnYfR7PWwTs2VI=
|
||||
github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4=
|
||||
github.com/mat/besticon v3.12.0+incompatible h1:1KTD6wisfjfnX+fk9Kx/6VEZL+MAW1LhCkL9Q47H9Bg=
|
||||
github.com/mat/besticon v3.12.0+incompatible/go.mod h1:mA1auQYHt6CW5e7L9HJLmqVQC8SzNk2gVwouO0AbiEU=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-oci8 v0.1.1 h1:aEUDxNAyDG0tv8CA3TArnDQNyc4EhnWlsfxRgDHABHM=
|
||||
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
|
@ -211,9 +296,11 @@ github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5A
|
|||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
|
||||
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
@ -221,6 +308,7 @@ github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5
|
|||
github.com/mitchellh/go-server-timing v1.0.1/go.mod h1:Mo6GKi9FSLwWFAMn3bqVPWe20y5ri5QGQuO9D9MCOxk=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
|
@ -229,6 +317,12 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
|||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE=
|
||||
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
|
||||
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
|
||||
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
|
@ -238,6 +332,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc=
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
|
||||
github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg=
|
||||
github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
@ -246,12 +347,15 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
|
|||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rot256/pblind v0.0.0-20240730113005-f3275049ead5 h1:R/qQ2Hw5/BgVQS87pg/mRTep8RxqDY0rcj4pbpBvNF8=
|
||||
github.com/rot256/pblind v0.0.0-20240730113005-f3275049ead5/go.mod h1:NTdpGnZ/E2cKXTiAz824w1p6OIm0mBbXcyuiYPCi/Ps=
|
||||
github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4=
|
||||
github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safing/jess v0.3.5 h1:KS5elTKfWcDUow8SUoCj5QdyyGJNoExJNySerNkbxUU=
|
||||
github.com/safing/jess v0.3.5/go.mod h1:+B6UJnXVxi406Wk08SDnoC5NNBL7t3N0vZGokEbkVQI=
|
||||
github.com/safing/structures v1.1.0 h1:QzHBQBjaZSLzw2f6PM4ibSmPcfBHAOB5CKJ+k4FYkhQ=
|
||||
github.com/safing/structures v1.1.0/go.mod h1:QUrB74FcU41ahQ5oy3YNFCoSq+twE/n3+vNZc2K35II=
|
||||
github.com/safing/structures v1.2.0 h1:S6EzKxxGYTO6P9P3Dkab9gisLOrfAyvy7JzFOUSkOUk=
|
||||
github.com/safing/structures v1.2.0/go.mod h1:zIun7mz3xV0dJ3vXRZuU71ATzT8D/0hGJO+u+bk5Kvs=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0=
|
||||
|
@ -260,12 +364,20 @@ github.com/seehuhn/sha256d v1.0.0 h1:TXTsAuEWr02QjRm153Fnvvb6fXXDo7Bmy1FizxarGYw
|
|||
github.com/seehuhn/sha256d v1.0.0/go.mod h1:PEuxg9faClSveVuFXacQmi+NtDI/PX8bpKjtNzf2+s4=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551 h1:+EXKKt7RC4HyE/iE8zSeFL+7YBL8Z7vpBaEE3c7lCnk=
|
||||
github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551/go.mod h1:ztTX0ctjRZ1wn9OXrzhonvNmv43yjFUXJYJR95JQAJE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
|
@ -279,12 +391,21 @@ github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr
|
|||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spkg/zipfs v0.7.1 h1:+2X5lvNHTybnDMQZAIHgedRXZK1WXdc+94R/P5v2XWE=
|
||||
github.com/spkg/zipfs v0.7.1/go.mod h1:48LW+/Rh1G7aAav1ew1PdlYn52T+LM+ARmSHfDNJvg8=
|
||||
github.com/stephenafamo/bob v0.30.0 h1:pGCS3iMh1xr2xlAP20eBV6gmqun6CCH/05l7uJJfvJI=
|
||||
github.com/stephenafamo/bob v0.30.0/go.mod h1:0z9AeWTOTJmGsokEtQReTEJry4iI9J+SCyKMcr40mOo=
|
||||
github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97 h1:XItoZNmhOih06TC02jK7l3wlpZ0XT/sPQYutDcGOQjg=
|
||||
github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97/go.mod h1:bM3Vmw1IakoaXocHmMIGgJFYob0vuK+CFWiJHQvz0jQ=
|
||||
github.com/stephenafamo/scan v0.6.1 h1:nXokGCQwYazMuyvdNAoK0T8Z76FWcpMvDdtengpz6PU=
|
||||
github.com/stephenafamo/scan v0.6.1/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg=
|
||||
github.com/stephenafamo/sqlparser v0.0.0-20241111104950-b04fa8a26c9c h1:JFga++XBnZG2xlnvQyHJkeBWZ9G9mGdtgvLeSRbp/BA=
|
||||
github.com/stephenafamo/sqlparser v0.0.0-20241111104950-b04fa8a26c9c/go.mod h1:4iveRk8mkzQZxDuK/W0MGLrGmu/igyDYWNDD4a6v0r0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
|
@ -304,11 +425,13 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
|
||||
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
|
||||
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
|
@ -322,41 +445,54 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
|
|||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU=
|
||||
github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA=
|
||||
github.com/volatiletech/strmangle v0.0.6 h1:AdOYE3B2ygRDq4rXDij/MMwq6KVK/pWAYxpC7CLrkKQ=
|
||||
github.com/volatiletech/strmangle v0.0.6/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8=
|
||||
github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
|
||||
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
|
@ -378,15 +514,18 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -397,7 +536,9 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -420,34 +561,44 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
|
@ -455,26 +606,31 @@ google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7ya
|
|||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8=
|
||||
modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.22.3 h1:C7AW89Zw3kygesTQWBzApwIn9ldM+cb/plrTIKq41Os=
|
||||
modernc.org/ccgo/v4 v4.22.3/go.mod h1:Dz7n0/UkBbH3pnYaxgi1mFSfF4REqUOZNziphZASx6k=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.20.7 h1:skrinQsjxWfvj6nbC3ztZPJy+NuwmB3hV9zX/pthNYQ=
|
||||
modernc.org/ccgo/v4 v4.20.7/go.mod h1:UOkI3JSG2zT4E2ioHlncSOZsXbuDCZLvPi3uMlZT5GY=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.61.2 h1:dkO4DlowfClcJYsvf/RiK6fUwvzCQTmB34bJLt0CAGQ=
|
||||
modernc.org/libc v1.61.2/go.mod h1:4QGjNyX3h+rn7V5oHpJY2yH0QN6frt1X+5BkXzwLPCo=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.59.9 h1:k+nNDDakwipimgmJ1D9H466LhFeSkaPPycAs1OZiDmY=
|
||||
modernc.org/libc v1.59.9/go.mod h1:EY/egGEU7Ju66eU6SBqCNYaFUDuc4npICkMWnU5EE3A=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
|
@ -483,11 +639,13 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk=
|
||||
modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
||||
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
||||
zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
|
||||
zombiezen.com/go/sqlite v1.3.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=
|
||||
|
|
|
@ -1,43 +1,52 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/safing/portmaster/base/database"
|
||||
_ "github.com/safing/portmaster/base/database/dbmodule"
|
||||
_ "github.com/safing/portmaster/base/database/storage/bbolt"
|
||||
_ "github.com/safing/portmaster/base/database/storage/sqlite"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
)
|
||||
|
||||
// Default Values (changeable for testing).
|
||||
var (
|
||||
DefaultDatabaseStorageType = "bbolt"
|
||||
DefaultDatabaseStorageType = "sqlite"
|
||||
)
|
||||
|
||||
func registerDatabases() error {
|
||||
// If there is an existing bbolt core database, use it instead.
|
||||
coreStorageType := DefaultDatabaseStorageType
|
||||
if utils.PathExists(filepath.Join(dataroot.Root().Path, database.DatabasesSubDir, "core", "bbolt")) {
|
||||
coreStorageType = "bbolt"
|
||||
}
|
||||
|
||||
// Register core database.
|
||||
_, err := database.Register(&database.Database{
|
||||
Name: "core",
|
||||
Description: "Holds core data, such as settings and profiles",
|
||||
StorageType: DefaultDatabaseStorageType,
|
||||
StorageType: coreStorageType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there is an existing cache bbolt database, use it instead.
|
||||
cacheStorageType := DefaultDatabaseStorageType
|
||||
if utils.PathExists(filepath.Join(dataroot.Root().Path, database.DatabasesSubDir, "cache", "bbolt")) {
|
||||
cacheStorageType = "bbolt"
|
||||
}
|
||||
|
||||
// Register cache database.
|
||||
_, err = database.Register(&database.Database{
|
||||
Name: "cache",
|
||||
Description: "Cached data, such as Intelligence and DNS Records",
|
||||
StorageType: DefaultDatabaseStorageType,
|
||||
StorageType: cacheStorageType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// _, err = database.Register(&database.Database{
|
||||
// Name: "history",
|
||||
// Description: "Historic event data",
|
||||
// StorageType: DefaultDatabaseStorageType,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/database/dbmodule"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
||||
|
@ -54,10 +56,13 @@ func prep(instance instance) error {
|
|||
}
|
||||
|
||||
// initialize structure
|
||||
err := dataroot.Initialize(dataDir, 0o0755)
|
||||
err := dataroot.Initialize(dataDir, utils.PublicReadPermission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set root dir for the databases.
|
||||
dbmodule.SetDatabaseLocation(dataroot.Root())
|
||||
}
|
||||
|
||||
// set api listen address
|
||||
|
|
|
@ -22,8 +22,8 @@ type ETWSession struct {
|
|||
state uintptr
|
||||
}
|
||||
|
||||
// NewSession creates new ETW event listener and initilizes it. This is a low level interface, make sure to call DestorySession when you are done using it.
|
||||
func NewSession(etwInterface *integration.ETWFunctions, callback func(domain string, result string)) (*ETWSession, error) {
|
||||
// NewSession creates new ETW event listener and initializes it. This is a low level interface, make sure to call DestroySession when you are done using it.
|
||||
func NewSession(etwInterface *integration.ETWFunctions, callback func(domain string, pid uint32, result string)) (*ETWSession, error) {
|
||||
if etwInterface == nil {
|
||||
return nil, fmt.Errorf("etw interface was nil")
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ func NewSession(etwInterface *integration.ETWFunctions, callback func(domain str
|
|||
_ = etwSession.i.StopOldSession()
|
||||
|
||||
// Initialize notification activated callback
|
||||
win32Callback := windows.NewCallback(func(domain *uint16, result *uint16) uintptr {
|
||||
callback(windows.UTF16PtrToString(domain), windows.UTF16PtrToString(result))
|
||||
win32Callback := windows.NewCallback(func(domain *uint16, pid uint32, result *uint16) uintptr {
|
||||
callback(windows.UTF16PtrToString(domain), pid, windows.UTF16PtrToString(result))
|
||||
return 0
|
||||
})
|
||||
// The function only allocates memory it will not fail.
|
||||
|
@ -83,7 +83,7 @@ func (l *ETWSession) FlushTrace() error {
|
|||
return l.i.FlushTrace(l.state)
|
||||
}
|
||||
|
||||
// StopTrace stopes the trace. This will cause StartTrace to return.
|
||||
// StopTrace stops the trace. This will cause StartTrace to return.
|
||||
func (l *ETWSession) StopTrace() error {
|
||||
return l.i.StopTrace(l.state)
|
||||
}
|
||||
|
|
|
@ -141,5 +141,5 @@ func (l *Listener) processAnswer(domain string, queryResult *QueryResult) {
|
|||
}
|
||||
}
|
||||
|
||||
saveDomain(domain, ips, cnames)
|
||||
saveDomain(domain, ips, cnames, resolver.IPInfoProfileScopeGlobal)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package dnsmonitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/process"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
)
|
||||
|
||||
|
@ -79,7 +81,7 @@ func (l *Listener) stop() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) processEvent(domain string, result string) {
|
||||
func (l *Listener) processEvent(domain string, pid uint32, result string) {
|
||||
if processIfSelfCheckDomain(dns.Fqdn(domain)) {
|
||||
// Not need to process result.
|
||||
return
|
||||
|
@ -90,6 +92,15 @@ func (l *Listener) processEvent(domain string, result string) {
|
|||
return
|
||||
}
|
||||
|
||||
profileScope := resolver.IPInfoProfileScopeGlobal
|
||||
// Get the profile ID if the process can be found
|
||||
if proc, err := process.GetOrFindProcess(context.Background(), int(pid)); err == nil {
|
||||
if profile := proc.Profile(); profile != nil {
|
||||
if localProfile := profile.LocalProfile(); localProfile != nil {
|
||||
profileScope = localProfile.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
cnames := make(map[string]string)
|
||||
ips := []net.IP{}
|
||||
|
||||
|
@ -115,5 +126,5 @@ func (l *Listener) processEvent(domain string, result string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
saveDomain(domain, ips, cnames)
|
||||
saveDomain(domain, ips, cnames, profileScope)
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func (dl *DNSMonitor) Flush() error {
|
|||
return dl.listener.flush()
|
||||
}
|
||||
|
||||
func saveDomain(domain string, ips []net.IP, cnames map[string]string) {
|
||||
func saveDomain(domain string, ips []net.IP, cnames map[string]string, profileScope string) {
|
||||
fqdn := dns.Fqdn(domain)
|
||||
// Create new record for this IP.
|
||||
record := resolver.ResolvedDomain{
|
||||
|
@ -75,7 +75,7 @@ func saveDomain(domain string, ips []net.IP, cnames map[string]string) {
|
|||
record.AddCNAMEs(cnames)
|
||||
|
||||
// Add to cache
|
||||
saveIPsInCache(ips, resolver.IPInfoProfileScopeGlobal, record)
|
||||
saveIPsInCache(ips, profileScope, record)
|
||||
}
|
||||
|
||||
func New(instance instance) (*DNSMonitor, error) {
|
||||
|
|
|
@ -107,6 +107,7 @@ func Handler(ctx context.Context, packets chan packet.Packet, bandwidthUpdate ch
|
|||
newPacket := &Packet{
|
||||
verdictRequest: conn.ID,
|
||||
payload: conn.Payload,
|
||||
payloadLayer: conn.PayloadLayer,
|
||||
verdictSet: abool.NewBool(false),
|
||||
}
|
||||
info := newPacket.Info()
|
||||
|
|
|
@ -397,28 +397,32 @@ func (e *Entity) getDomainLists(ctx context.Context) {
|
|||
}
|
||||
|
||||
func splitDomain(domain string) []string {
|
||||
domain = strings.Trim(domain, ".")
|
||||
suffix, _ := publicsuffix.PublicSuffix(domain)
|
||||
if suffix == domain {
|
||||
// Get suffix.
|
||||
d := strings.TrimSuffix(domain, ".")
|
||||
suffix, icann := publicsuffix.PublicSuffix(d)
|
||||
if suffix == d {
|
||||
return []string{domain}
|
||||
}
|
||||
|
||||
domainWithoutSuffix := domain[:len(domain)-len(suffix)]
|
||||
domainWithoutSuffix = strings.Trim(domainWithoutSuffix, ".")
|
||||
|
||||
splitted := strings.FieldsFunc(domainWithoutSuffix, func(r rune) bool {
|
||||
// Split all subdomain into labels.
|
||||
labels := strings.FieldsFunc(d[:len(d)-len(suffix)], func(r rune) bool {
|
||||
return r == '.'
|
||||
})
|
||||
|
||||
domains := make([]string, 0, len(splitted))
|
||||
for idx := range splitted {
|
||||
|
||||
d := strings.Join(splitted[idx:], ".") + "." + suffix
|
||||
if d[len(d)-1] != '.' {
|
||||
d += "."
|
||||
}
|
||||
domains = append(domains, d)
|
||||
// Build list of all domains up to the public suffix.
|
||||
domains := make([]string, 0, len(labels)+1)
|
||||
for idx := range labels {
|
||||
domains = append(
|
||||
domains,
|
||||
strings.Join(labels[idx:], ".")+"."+suffix+".",
|
||||
)
|
||||
}
|
||||
|
||||
// If the suffix is not a real TLD, but a public suffix, add it to the list.
|
||||
if !icann {
|
||||
domains = append(domains, suffix+".")
|
||||
}
|
||||
|
||||
return domains
|
||||
}
|
||||
|
||||
|
|
33
service/intel/entity_test.go
Normal file
33
service/intel/entity_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package intel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var splitDomainTestCases = [][]string{
|
||||
// Regular registered domains and subdomains.
|
||||
{"example.com."},
|
||||
{"www.example.com.", "example.com."},
|
||||
{"sub.domain.example.com.", "domain.example.com.", "example.com."},
|
||||
{"example.co.uk."},
|
||||
{"www.example.co.uk.", "example.co.uk."},
|
||||
|
||||
// TLD or public suffix: Return as is.
|
||||
{"com."},
|
||||
{"googleapis.com."},
|
||||
|
||||
// Public suffix domains: Return including public suffix.
|
||||
{"test.googleapis.com.", "googleapis.com."},
|
||||
{"sub.domain.googleapis.com.", "domain.googleapis.com.", "googleapis.com."},
|
||||
}
|
||||
|
||||
func TestSplitDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testCase := range splitDomainTestCases {
|
||||
splitted := splitDomain(testCase[0])
|
||||
assert.Equal(t, testCase, splitted, "result must match")
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ var (
|
|||
var cache = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
CacheSize: 2 ^ 8,
|
||||
CacheSize: 256,
|
||||
})
|
||||
|
||||
// getFileFunc is the function used to get a file from
|
||||
|
@ -146,7 +146,7 @@ func persistRecords(startJob func(func() error), records <-chan record.Record) {
|
|||
|
||||
timePerEntity := time.Since(start) / time.Duration(cnt)
|
||||
speed := float64(time.Second) / float64(timePerEntity)
|
||||
log.Debugf("processed %d entities in %s with %s / entity (%.2f entities/second)", cnt, time.Since(start), timePerEntity, speed)
|
||||
log.Debugf("intel/filterlists: processed %d entities in %s with %s / entity (%.2f entities/second)", cnt, time.Since(start), timePerEntity, speed)
|
||||
}
|
||||
|
||||
batch := database.NewInterface(&database.Options{Local: true, Internal: true})
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/database/query"
|
||||
"github.com/safing/portmaster/base/database/storage"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
|
@ -158,9 +159,25 @@ func performUpdate(ctx context.Context) error {
|
|||
|
||||
func removeAllObsoleteFilterEntries(wc *mgr.WorkerCtx) error {
|
||||
log.Debugf("intel/filterlists: cleanup task started, removing obsolete filter list entries ...")
|
||||
n, err := cache.Purge(wc.Ctx(), query.New(filterListKeyPrefix).Where(
|
||||
// TODO(ppacher): remember the timestamp we started the last update
|
||||
// and use that rather than "one hour ago"
|
||||
|
||||
// TODO: Remember the timestamp we started the last update and use that rather than "one hour ago".
|
||||
|
||||
// First try to purge with PurgeOlderThan.
|
||||
n, err := cache.PurgeOlderThan(wc.Ctx(), filterListKeyPrefix, time.Now().Add(-time.Hour))
|
||||
switch {
|
||||
case err == nil:
|
||||
// Success!
|
||||
log.Debugf("intel/filterlists: successfully removed %d obsolete entries", n)
|
||||
return nil
|
||||
case errors.Is(err, database.ErrNotImplemented) || errors.Is(err, storage.ErrNotImplemented):
|
||||
// Try next method.
|
||||
default:
|
||||
// Return error.
|
||||
return err
|
||||
}
|
||||
|
||||
// Try with regular purge.
|
||||
n, err = cache.Purge(wc.Ctx(), query.New(filterListKeyPrefix).Where(
|
||||
query.Where("UpdatedAt", query.LessThan, time.Now().Add(-time.Hour).Unix()),
|
||||
))
|
||||
if err != nil {
|
||||
|
|
|
@ -4,7 +4,7 @@ import "time"
|
|||
|
||||
// SleepyTicker is wrapper over time.Ticker that respects the sleep mode of the module.
|
||||
type SleepyTicker struct {
|
||||
ticker time.Ticker
|
||||
ticker *time.Ticker
|
||||
normalDuration time.Duration
|
||||
sleepDuration time.Duration
|
||||
sleepMode bool
|
||||
|
@ -16,7 +16,7 @@ type SleepyTicker struct {
|
|||
// If sleepDuration is set to 0 ticker will not tick during sleep.
|
||||
func NewSleepyTicker(normalDuration time.Duration, sleepDuration time.Duration) *SleepyTicker {
|
||||
st := &SleepyTicker{
|
||||
ticker: *time.NewTicker(normalDuration),
|
||||
ticker: time.NewTicker(normalDuration),
|
||||
normalDuration: normalDuration,
|
||||
sleepDuration: sleepDuration,
|
||||
sleepMode: false,
|
||||
|
|
57
service/mgr/sleepyticker_test.go
Normal file
57
service/mgr/sleepyticker_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package mgr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSleepyTickerStop(t *testing.T) {
|
||||
normalDuration := 100 * time.Millisecond
|
||||
sleepDuration := 200 * time.Millisecond
|
||||
|
||||
st := NewSleepyTicker(normalDuration, sleepDuration)
|
||||
st.Stop() // no panic expected here
|
||||
}
|
||||
|
||||
func TestSleepyTicker(t *testing.T) {
|
||||
normalDuration := 100 * time.Millisecond
|
||||
sleepDuration := 200 * time.Millisecond
|
||||
|
||||
st := NewSleepyTicker(normalDuration, sleepDuration)
|
||||
|
||||
// Test normal mode
|
||||
select {
|
||||
case <-st.Wait():
|
||||
// Expected tick
|
||||
case <-time.After(normalDuration + 50*time.Millisecond):
|
||||
t.Error("expected tick in normal mode")
|
||||
}
|
||||
|
||||
// Test sleep mode
|
||||
st.SetSleep(true)
|
||||
select {
|
||||
case <-st.Wait():
|
||||
// Expected tick
|
||||
case <-time.After(sleepDuration + 50*time.Millisecond):
|
||||
t.Error("expected tick in sleep mode")
|
||||
}
|
||||
|
||||
// Test sleep mode with sleepDuration == 0
|
||||
st = NewSleepyTicker(normalDuration, 0)
|
||||
st.SetSleep(true)
|
||||
select {
|
||||
case <-st.Wait():
|
||||
t.Error("did not expect tick when sleepDuration is 0")
|
||||
case <-time.After(normalDuration):
|
||||
// Expected no tick
|
||||
}
|
||||
|
||||
// Test stopping the ticker
|
||||
st.Stop()
|
||||
select {
|
||||
case <-st.Wait():
|
||||
t.Error("did not expect tick after stopping the ticker")
|
||||
case <-time.After(normalDuration):
|
||||
// Expected no tick
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/safing/portmaster/base/config"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/netquery/orm"
|
||||
"github.com/safing/portmaster/service/network"
|
||||
"github.com/safing/portmaster/service/network/netutils"
|
||||
|
@ -127,7 +128,7 @@ type (
|
|||
// Note that write connections are serialized by the Database object before being
|
||||
// handed over to SQLite.
|
||||
func New(dbPath string) (*Database, error) {
|
||||
historyParentDir := dataroot.Root().ChildDir("databases", 0o700)
|
||||
historyParentDir := dataroot.Root().ChildDir("databases", utils.AdminOnlyPermission)
|
||||
if err := historyParentDir.Ensure(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure database directory exists: %w", err)
|
||||
}
|
||||
|
@ -225,7 +226,7 @@ func (db *Database) Close() error {
|
|||
|
||||
// VacuumHistory rewrites the history database in order to purge deleted records.
|
||||
func VacuumHistory(ctx context.Context) (err error) {
|
||||
historyParentDir := dataroot.Root().ChildDir("databases", 0o700)
|
||||
historyParentDir := dataroot.Root().ChildDir("databases", utils.AdminOnlyPermission)
|
||||
if err := historyParentDir.Ensure(); err != nil {
|
||||
return fmt.Errorf("failed to ensure database directory exists: %w", err)
|
||||
}
|
||||
|
|
|
@ -538,8 +538,9 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) {
|
|||
|
||||
// Find domain and DNS context of entity.
|
||||
if conn.Entity.Domain == "" && conn.process.Profile() != nil {
|
||||
profileScope := conn.process.Profile().LocalProfile().ID
|
||||
// check if we can find a domain for that IP
|
||||
ipinfo, err := resolver.GetIPInfo(conn.process.Profile().LocalProfile().ID, pkt.Info().RemoteIP().String())
|
||||
ipinfo, err := resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String())
|
||||
if err != nil {
|
||||
// Try again with the global scope, in case DNS went through the system resolver.
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
|
||||
|
@ -555,6 +556,13 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) {
|
|||
// Error flushing, dont try again.
|
||||
break
|
||||
}
|
||||
// Try with profile scope
|
||||
ipinfo, err = resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String())
|
||||
if err == nil {
|
||||
log.Tracer(pkt.Ctx()).Debugf("network: found domain with scope (%s) from dnsmonitor after %d tries", profileScope, +1)
|
||||
break
|
||||
}
|
||||
// Try again with the global scope
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
|
||||
if err == nil {
|
||||
log.Tracer(pkt.Ctx()).Debugf("network: found domain from dnsmonitor after %d tries", i+1)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/safing/portmaster/base/database/migration"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
_ "github.com/safing/portmaster/service/core/base"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/profile/binmeta"
|
||||
|
@ -70,7 +71,7 @@ func prep() error {
|
|||
}
|
||||
|
||||
// Setup icon storage location.
|
||||
iconsDir := dataroot.Root().ChildDir("databases", 0o0700).ChildDir("icons", 0o0700)
|
||||
iconsDir := dataroot.Root().ChildDir("databases", utils.AdminOnlyPermission).ChildDir("icons", utils.AdminOnlyPermission)
|
||||
if err := iconsDir.Ensure(); err != nil {
|
||||
return fmt.Errorf("failed to create/check icons directory: %w", err)
|
||||
}
|
||||
|
|
|
@ -510,7 +510,7 @@ func setScopedResolvers(resolvers []*Resolver) {
|
|||
for _, resolver := range resolvers {
|
||||
if resolver.Info.IPScope.IsLAN() {
|
||||
localResolvers = append(localResolvers, resolver)
|
||||
} else if _, err := netenv.GetLocalNetwork(resolver.Info.IP); err != nil {
|
||||
} else if net, _ := netenv.GetLocalNetwork(resolver.Info.IP); net != nil {
|
||||
localResolvers = append(localResolvers, resolver)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
||||
|
@ -27,7 +28,7 @@ func start() error {
|
|||
// may seem dangerous, but proper permission on the parent directory provide
|
||||
// (some) protection.
|
||||
// Processes must _never_ read from this directory.
|
||||
err := dataroot.Root().ChildDir("exec", 0o0777).Ensure()
|
||||
err := dataroot.Root().ChildDir("exec", utils.PublicWritePermission).Ensure()
|
||||
if err != nil {
|
||||
log.Warningf("ui: failed to create safe exec dir: %s", err)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/safing/portmaster/base/dataroot"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/helper"
|
||||
)
|
||||
|
@ -138,7 +139,7 @@ func start() error {
|
|||
}
|
||||
|
||||
// initialize
|
||||
err = registry.Initialize(dataroot.Root().ChildDir(updatesDirName, 0o0755))
|
||||
err = registry.Initialize(dataroot.Root().ChildDir(updatesDirName, utils.PublicReadPermission))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hectane/go-acl"
|
||||
processInfo "github.com/shirou/gopsutil/process"
|
||||
"github.com/tevino/abool"
|
||||
|
||||
|
@ -21,6 +20,7 @@ import (
|
|||
"github.com/safing/portmaster/base/notifications"
|
||||
"github.com/safing/portmaster/base/rng"
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/base/utils/renameio"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/helper"
|
||||
|
@ -351,17 +351,15 @@ func upgradeBinary(fileToUpgrade string, file *updater.File) error {
|
|||
|
||||
// check permissions
|
||||
if onWindows {
|
||||
err = acl.Chmod(fileToUpgrade, 0o0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set permissions on %s: %w", fileToUpgrade, err)
|
||||
}
|
||||
_ = utils.SetExecPermission(fileToUpgrade, utils.PublicReadPermission)
|
||||
} else {
|
||||
perm := utils.PublicReadPermission
|
||||
info, err := os.Stat(fileToUpgrade)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file info on %s: %w", fileToUpgrade, err)
|
||||
}
|
||||
if info.Mode() != 0o0755 {
|
||||
err := os.Chmod(fileToUpgrade, 0o0755) //nolint:gosec // Set execute permissions.
|
||||
if info.Mode() != perm.AsUnixDirExecPermission() {
|
||||
err = utils.SetExecPermission(fileToUpgrade, perm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set permissions on %s: %w", fileToUpgrade, err)
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ func getStickiedHub(conn *network.Connection) (sticksTo *stickyHub) {
|
|||
|
||||
// If the IP did not stick and we have a domain, check if that sticks.
|
||||
if sticksTo == nil && conn.Entity.Domain != "" {
|
||||
sticksTo, ok := stickyDomains[makeStickyDomainKey(conn)]
|
||||
var ok bool
|
||||
sticksTo, ok = stickyDomains[makeStickyDomainKey(conn)]
|
||||
if ok && !sticksTo.isExpired() {
|
||||
sticksTo.LastSeen = time.Now()
|
||||
}
|
||||
|
@ -146,6 +147,8 @@ func (t *Tunnel) avoidDestinationHub() {
|
|||
Avoid: true,
|
||||
}
|
||||
log.Warningf("spn/crew: avoiding %s for %s", t.dstPin.Hub, ipKey)
|
||||
|
||||
// TODO: Question: Should we avoid the domain as well? (stickyDomains)
|
||||
}
|
||||
|
||||
func cleanStickyHubs(ctx *mgr.WorkerCtx) error {
|
||||
|
|
25
spn/testing/README.md
Normal file
25
spn/testing/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Testing Port17
|
||||
|
||||
## Simple Docker Setup
|
||||
|
||||
Run `run.sh` to start the docker compose test network.
|
||||
Then, connect to the test network, by starting the core with the "test" spn map and the correct bootstrap file.
|
||||
|
||||
Run `stop.sh` to remove all docker resources again.
|
||||
|
||||
Setup Guide can be found in the directory.
|
||||
|
||||
## Advanced Setup with Shadow
|
||||
|
||||
For advanced testing we use [shadow](https://github.com/shadow/shadow).
|
||||
The following section will help you set up shadow and will guide you how to test Port17 in a local Shadow environment.
|
||||
|
||||
### Setting up
|
||||
|
||||
Download the docker version from here: [https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz](https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz)
|
||||
|
||||
Then import the image into docker with `gunzip -c shadow-standalone.tar.gz | sudo docker load`.
|
||||
|
||||
### Running
|
||||
|
||||
Execute `sudo docker run -t -i -u shadow shadow-standalone /bin/bash` to start an interactive container with shadow.
|
50
spn/testing/simple/README.md
Normal file
50
spn/testing/simple/README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Setup Guide
|
||||
|
||||
1. Build SPN Hub
|
||||
|
||||
```
|
||||
cd ../../../cmds/hub/
|
||||
./build
|
||||
```
|
||||
|
||||
2. Reset any previous state (for a fresh test)
|
||||
|
||||
```
|
||||
./reset-databases.sh
|
||||
```
|
||||
|
||||
3. Change compose file and config template as required
|
||||
|
||||
Files:
|
||||
- `docker-compose.yml`
|
||||
- `config-template.json`
|
||||
|
||||
4. Start test network
|
||||
|
||||
```
|
||||
./run.sh
|
||||
```
|
||||
|
||||
5. Option 1: Join as Hub
|
||||
|
||||
For testing just one Hub with a different build or config, you can simply use `./join.sh` to join the network with the most recently build hub binary.
|
||||
|
||||
6. Option 2: Join as Portmaster
|
||||
|
||||
For connecting to the SPN test network with Portmaster, execute portmaster like this:
|
||||
|
||||
```
|
||||
sudo ../../../cmds/portmaster-core/portmaster-core --disable-shutdown-event --devmode --log debug --data /opt/safing/portmaster --spn-map test --bootstrap-file ./testdata/shared/bootstrap.dsd
|
||||
```
|
||||
|
||||
Note:
|
||||
This uses the same portmaster data and config as your installed version.
|
||||
As the SPN Test net operates under a different ID ("test" instead of "main"), this will not pollute the SPN state of your installed Portmaster.
|
||||
|
||||
7. Stop the test net
|
||||
|
||||
This is important, as just stopping the `./run.sh` script will leave you with interfaces with public IPs!
|
||||
|
||||
```
|
||||
./stop.sh
|
||||
```
|
41
spn/testing/simple/clientsim.sh
Executable file
41
spn/testing/simple/clientsim.sh
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
realpath() {
|
||||
path=`eval echo "$1"`
|
||||
folder=$(dirname "$path")
|
||||
echo $(cd "$folder"; pwd)/$(basename "$path");
|
||||
}
|
||||
|
||||
if [[ ! -f "../../client" ]]; then
|
||||
echo "please compile client.go in main directory (output: client)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bin_path="$(realpath ../../client)"
|
||||
data_path="$(realpath ./testdata)"
|
||||
if [[ ! -d "$data_path" ]]; then
|
||||
mkdir "$data_path"
|
||||
fi
|
||||
shared_path="$(realpath ./testdata/shared)"
|
||||
if [[ ! -d "$shared_path" ]]; then
|
||||
mkdir "$shared_path"
|
||||
fi
|
||||
|
||||
docker network ls | grep spn-simpletest-network >/dev/null 2>&1
|
||||
if [[ $? -ne 0 ]]; then
|
||||
docker network create spn-simpletest-network --subnet 6.0.0.0/24
|
||||
fi
|
||||
|
||||
docker run -ti --rm \
|
||||
--name spn-simpletest-clientsim \
|
||||
--network spn-simpletest-network \
|
||||
-v $bin_path:/opt/client:ro \
|
||||
-v $data_path/clientsim:/opt/data \
|
||||
-v $shared_path:/opt/shared \
|
||||
--entrypoint /opt/client \
|
||||
toolset.safing.network/dev \
|
||||
--data /opt/data \
|
||||
--bootstrap-file /opt/shared/bootstrap.dsd \
|
||||
--log trace $*
|
19
spn/testing/simple/config-template.json
Normal file
19
spn/testing/simple/config-template.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"core": {
|
||||
"log": {
|
||||
"level": "trace"
|
||||
},
|
||||
"metrics": {
|
||||
"instance": "test_$HUBNAME",
|
||||
"push": ""
|
||||
}
|
||||
},
|
||||
"spn": {
|
||||
"publicHub": {
|
||||
"name": "test-$HUBNAME",
|
||||
"transports": ["http:80", "http:8080", "tcp:17"],
|
||||
"allowUnencrypted": true,
|
||||
"bindToAdvertised": true
|
||||
}
|
||||
}
|
||||
}
|
139
spn/testing/simple/docker-compose.yml
Normal file
139
spn/testing/simple/docker-compose.yml
Normal file
|
@ -0,0 +1,139 @@
|
|||
version: "2.4"
|
||||
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 6.0.0.0/24
|
||||
|
||||
services:
|
||||
hub1:
|
||||
container_name: spn-test-simple-hub1
|
||||
hostname: hub1
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_BIN}:/opt/hub1:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub1:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.11
|
||||
|
||||
hub2:
|
||||
container_name: spn-test-simple-hub2
|
||||
hostname: hub2
|
||||
image: alpine
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_BIN}:/opt/hub2:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub2:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.12
|
||||
|
||||
hub3:
|
||||
container_name: spn-test-simple-hub3
|
||||
hostname: hub3
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_BIN}:/opt/hub3:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub3:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.13
|
||||
|
||||
hub4:
|
||||
container_name: spn-test-simple-hub4
|
||||
hostname: hub4
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_BIN}:/opt/hub4:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub4:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.14
|
||||
|
||||
hub5:
|
||||
container_name: spn-test-simple-hub5
|
||||
hostname: hub5
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_BIN}:/opt/hub5:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub5:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.15
|
||||
|
||||
hub6:
|
||||
container_name: spn-test-simple-hub6
|
||||
hostname: hub6
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_OLD_BIN}:/opt/hub6:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub6:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.16
|
||||
|
||||
hub7:
|
||||
container_name: spn-test-simple-hub7
|
||||
hostname: hub7
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_OLD_BIN}:/opt/hub7:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub7:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.17
|
||||
|
||||
hub8:
|
||||
container_name: spn-test-simple-hub8
|
||||
hostname: hub8
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_OLD_BIN}:/opt/hub8:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub8:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.18
|
||||
|
||||
hub9:
|
||||
container_name: spn-test-simple-hub9
|
||||
hostname: hub9
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_OLD_BIN}:/opt/hub9:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub9:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.19
|
||||
|
||||
hub10:
|
||||
container_name: spn-test-simple-hub10
|
||||
hostname: hub10
|
||||
image: toolset.safing.network/dev
|
||||
entrypoint: "/opt/shared/entrypoint.sh"
|
||||
volumes:
|
||||
- ${SPN_TEST_OLD_BIN}:/opt/hub10:ro
|
||||
- ${SPN_TEST_DATA_DIR}/hub10:/opt/data
|
||||
- ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 6.0.0.20
|
17
spn/testing/simple/entrypoint.sh
Executable file
17
spn/testing/simple/entrypoint.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Get hostname.
|
||||
HUBNAME=$HOSTNAME
|
||||
if [ "$HUBNAME" = "" ]; then
|
||||
HUBNAME=$(cat /etc/hostname)
|
||||
fi
|
||||
export HUBNAME
|
||||
|
||||
# Read, process and write config.
|
||||
cat /opt/shared/config-template.json | sed "s/\$HUBNAME/$HUBNAME/g" > /opt/data/config.json
|
||||
|
||||
# Get binary to start.
|
||||
BIN=$(ls /opt/ | grep hub)
|
||||
|
||||
# Start Hub.
|
||||
/opt/$BIN --data /opt/data --log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd --api-address 0.0.0.0:817 --devmode
|
35
spn/testing/simple/inject-intel.sh
Executable file
35
spn/testing/simple/inject-intel.sh
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
MAIN_INTEL_FILE="intel-testnet.json"
|
||||
|
||||
if [[ ! -f $MAIN_INTEL_FILE ]]; then
|
||||
echo "missing $MAIN_INTEL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "if the containing directory cannot be created, you might need to adjust permissions, as nodes are run with root in test containers..."
|
||||
echo "$ sudo chmod -R 777 data/hub*/updates"
|
||||
echo "starting to update..."
|
||||
|
||||
for hubDir in data/hub*; do
|
||||
# Build destination path
|
||||
hubIntelFile="${hubDir}/updates/all/intel/spn/main-intel_v0-0-0.dsd"
|
||||
|
||||
# Copy file
|
||||
mkdir -p "${hubDir}/updates/all/intel/spn"
|
||||
echo -n "J" > "$hubIntelFile"
|
||||
cat $MAIN_INTEL_FILE >> "$hubIntelFile"
|
||||
|
||||
echo "updated $hubIntelFile"
|
||||
done
|
||||
|
||||
if [[ -d /var/lib/portmaster ]]; then
|
||||
echo "updating intel for local portmaster installation..."
|
||||
|
||||
portmasterSPNIntelFile="/var/lib/portmaster/updates/all/intel/spn/main-intel_v0-0-0.dsd"
|
||||
echo -n "J" > "$portmasterSPNIntelFile"
|
||||
cat $MAIN_INTEL_FILE >> "$portmasterSPNIntelFile"
|
||||
echo "updated $portmasterSPNIntelFile"
|
||||
fi
|
25
spn/testing/simple/intel-client.yaml
Normal file
25
spn/testing/simple/intel-client.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Get current list of IDs from test net:
|
||||
# curl http://127.0.0.1:817/api/v1/spn/map/test/pins | jq ".[] | .ID"
|
||||
# Then import into test client with:
|
||||
# curl -X POST --upload-file intel-client.yaml http://127.0.0.1:817/api/v1/spn/map/test/intel/update
|
||||
Hubs:
|
||||
Zwm48YWWFGdwXjhE1MyEkWfqxPr9DiUBoXpusTZ1FMQnuK:
|
||||
Trusted: true
|
||||
Zwu5LkkbfCbAcYxWG3vtWF1VvWjgWpc1GJfkwRdLFNtytV:
|
||||
Trusted: true
|
||||
ZwuQpz5CqYmYoLnt9KXQ8oxnmosBzfrCYwCGhxT4fsG1Dz:
|
||||
Trusted: true
|
||||
ZwwmC3dHzr7J6XW9mc2KD6FDNuXwPVJUFi9dLnDSNMyjLk:
|
||||
Trusted: true
|
||||
ZwxSBdvqtJyz8zRWKZe6QyK51KH9av6VFay2GQvpFrWKHq:
|
||||
Trusted: true
|
||||
ZwxnuL6zMLj4AxJX8Bj369w2tNrVtYxzffVcXZuMxdxbGj:
|
||||
Trusted: true
|
||||
ZwyXdnC8JkC7m796skGD7QWGoYycByR3KVntkXMY8CxRZQ:
|
||||
Trusted: true
|
||||
Zwz7AHiH1EevD9eYFqvQQPbVWyBBcksTRxxafbRx5Cvc4F:
|
||||
Trusted: true
|
||||
ZwzMtc65t9XBMwmLm2xNSL69FvqHGPLiqeNBZ3eEN5a9sS:
|
||||
Trusted: true
|
||||
ZwzjnCUNGsuWnkYmN3QEj8JPLxU6V1QQFk9b47AigmPKiH:
|
||||
Trusted: true
|
17
spn/testing/simple/intel-testnet.json
Normal file
17
spn/testing/simple/intel-testnet.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"BootstrapHubs": [
|
||||
],
|
||||
"TrustedHubs": [
|
||||
"ZwrY9G9HDo1J3qQrrQs8VF2KD99bj7KyWesJ5kWFUDBU6r",
|
||||
"Zwj56ZFXrsud8gc1Rw3zuxRwMLhGkwvtvnTxCVtJ8EWLhQ",
|
||||
"ZwpdW87ityD9i3N9x8oweCJnbZEqo346VBg4mCsCvTr1Zo",
|
||||
"ZwpJ6ebddk1sccUVpo92JUqicBfKzBN2w4pEGoEY7UsNhX",
|
||||
"Zwte3Jffp9PWmeWfrn8RyGuvZZFCg3v7XR3tpQjdo9TpVt",
|
||||
"ZwrTcdiPF5zR5h9q9EdjHCrrXzYVBdQe5HmEYUWXdLkke3",
|
||||
"Zwv7tSn5iU6bYKn53NaGCxPtL8vSxSK7F9VdQezDaDCLBt",
|
||||
"Zwvtdq3K9knP9iNaRS1Ju8CETWTqy7oRwbScjBtJGBpqhB"
|
||||
],
|
||||
"AdviseOnlyTrustedHubs": true,
|
||||
"AdviseOnlyTrustedHomeHubs": true,
|
||||
"AdviseOnlyTrustedDestinationHubs": true
|
||||
}
|
42
spn/testing/simple/join.sh
Executable file
42
spn/testing/simple/join.sh
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
realpath() {
|
||||
path=`eval echo "$1"`
|
||||
folder=$(dirname "$path")
|
||||
echo $(cd "$folder"; pwd)/$(basename "$path");
|
||||
}
|
||||
|
||||
leftover=$(docker ps -a | grep spn-test-simple-me | cut -d" " -f1)
|
||||
if [[ $leftover != "" ]]; then
|
||||
docker stop $leftover
|
||||
docker rm $leftover
|
||||
fi
|
||||
|
||||
if [[ ! -f "../../../cmds/hub/hub" ]]; then
|
||||
echo "please build the hub cmd using cmds/hub/build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)"
|
||||
SPN_TEST_DATA_DIR="$(realpath ./testdata)"
|
||||
if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then
|
||||
mkdir "$SPN_TEST_DATA_DIR"
|
||||
fi
|
||||
SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)"
|
||||
if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then
|
||||
mkdir "$SPN_TEST_SHARED_DATA_DIR"
|
||||
fi
|
||||
|
||||
docker run -ti \
|
||||
--name spn-test-simple-me \
|
||||
--hostname me \
|
||||
--network spn-test-simple_default \
|
||||
-v $SPN_TEST_BIN:/opt/hub_me:ro \
|
||||
-v $SPN_TEST_DATA_DIR/me:/opt/data \
|
||||
-v $SPN_TEST_SHARED_DATA_DIR:/opt/shared \
|
||||
--entrypoint /opt/hub_me \
|
||||
toolset.safing.network/dev \
|
||||
--devmode --api-address 0.0.0.0:8081 \
|
||||
--data /opt/data -log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd
|
7
spn/testing/simple/reset-databases.sh
Executable file
7
spn/testing/simple/reset-databases.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
rm -rf testdata/me/*
|
||||
rm -rf testdata/shared/*
|
||||
rm -rf testdata/hub*/databases
|
52
spn/testing/simple/run.sh
Executable file
52
spn/testing/simple/run.sh
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
realpath() {
|
||||
path=`eval echo "$1"`
|
||||
folder=$(dirname "$path")
|
||||
echo $(cd "$folder"; pwd)/$(basename "$path");
|
||||
}
|
||||
|
||||
leftovers=$(docker ps -a | grep spn-test-simple | cut -d" " -f1)
|
||||
if [[ $leftovers != "" ]]; then
|
||||
docker stop $leftovers
|
||||
docker rm $leftovers
|
||||
fi
|
||||
|
||||
if [[ ! -f "../../../cmds/hub/hub" ]]; then
|
||||
echo "please build the hub cmd using cmds/hub/build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create variables.
|
||||
SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)"
|
||||
SPN_TEST_DATA_DIR="$(realpath ./testdata)"
|
||||
if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then
|
||||
mkdir "$SPN_TEST_DATA_DIR"
|
||||
fi
|
||||
SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)"
|
||||
if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then
|
||||
mkdir "$SPN_TEST_SHARED_DATA_DIR"
|
||||
fi
|
||||
|
||||
# Check if there is an old binary for testing.
|
||||
SPN_TEST_OLD_BIN=$SPN_TEST_BIN
|
||||
if [[ -f "./testdata/old-hub" ]]; then
|
||||
SPN_TEST_OLD_BIN="$(realpath ./testdata/old-hub)"
|
||||
echo "WARNING: running in hybrid mode with old version at $SPN_TEST_OLD_BIN"
|
||||
fi
|
||||
|
||||
# Export variables
|
||||
export SPN_TEST_BIN
|
||||
export SPN_TEST_OLD_BIN
|
||||
export SPN_TEST_DATA_DIR
|
||||
export SPN_TEST_SHARED_DATA_DIR
|
||||
|
||||
# Copy files.
|
||||
cp config-template.json ./testdata/shared/config-template.json
|
||||
cp entrypoint.sh ./testdata/shared/entrypoint.sh
|
||||
chmod 555 ./testdata/shared/entrypoint.sh
|
||||
|
||||
# Run!
|
||||
docker compose -p spn-test-simple up --remove-orphans
|
15
spn/testing/simple/stop.sh
Executable file
15
spn/testing/simple/stop.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
|
||||
docker compose -p spn-test-simple stop
|
||||
docker compose -p spn-test-simple rm
|
||||
|
||||
oldnet=$(docker network ls | grep spn-test-simple | cut -d" " -f1)
|
||||
if [[ $oldnet != "" ]]; then
|
||||
docker network rm $oldnet
|
||||
fi
|
||||
|
||||
if [[ -d "data/shared" ]]; then
|
||||
rm -r "data/shared"
|
||||
fi
|
|
@ -22,7 +22,7 @@ static const GUID PORTMASTER_ETW_SESSION_GUID = {
|
|||
#define LOGSESSION_NAME L"PortmasterDNSEventListener"
|
||||
|
||||
// Fuction type of the callback that will be called on each event.
|
||||
typedef uint64_t(*GoEventRecordCallback)(wchar_t* domain, wchar_t* result);
|
||||
typedef uint64_t(*GoEventRecordCallback)(wchar_t* domain, uint32_t pid, wchar_t* result);
|
||||
|
||||
// Holds the state of the ETW Session.
|
||||
struct ETWSessionState {
|
||||
|
@ -41,7 +41,7 @@ static bool getPropertyValue(PEVENT_RECORD evt, LPWSTR prop, PBYTE* pData) {
|
|||
DataDescriptor.ArrayIndex = 0;
|
||||
|
||||
DWORD PropertySize = 0;
|
||||
// Check if the data is avaliable and what is the size of it.
|
||||
// Check if the data is available and what is the size of it.
|
||||
DWORD status =
|
||||
TdhGetPropertySize(evt, 0, NULL, 1, &DataDescriptor, &PropertySize);
|
||||
if (ERROR_SUCCESS != status) {
|
||||
|
@ -79,7 +79,7 @@ static void WINAPI EventRecordCallback(PEVENT_RECORD eventRecord) {
|
|||
ETWSessionState* state = (ETWSessionState*)eventRecord->UserContext;
|
||||
|
||||
if (resultValue != NULL && domainValue != NULL) {
|
||||
state->callback((wchar_t*)domainValue, (wchar_t*)resultValue);
|
||||
state->callback((wchar_t*)domainValue, eventRecord->EventHeader.ProcessId, (wchar_t*)resultValue);
|
||||
}
|
||||
|
||||
free(resultValue);
|
||||
|
@ -160,7 +160,7 @@ extern "C" {
|
|||
EVENT_TRACE_CONTROL_STOP);
|
||||
}
|
||||
|
||||
// PM_ETWFlushTrace Closes the session and frees resourses.
|
||||
// PM_ETWFlushTrace Closes the session and frees recourses.
|
||||
__declspec(dllexport) uint32_t PM_ETWDestroySession(ETWSessionState* state) {
|
||||
if (state == NULL) {
|
||||
return 1;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Portmaster Windows kext
|
||||
Implementation of Safing's Portmaster Windows kernel extension in Rust.
|
||||
|
||||
### Documentation
|
||||
### Documentation
|
||||
|
||||
- [Driver](driver/README.md) -> entry point.
|
||||
- [WDK](wdk/README.md) -> Windows Driver Kit interface.
|
||||
|
@ -9,8 +9,11 @@ Implementation of Safing's Portmaster Windows kernel extension in Rust.
|
|||
- [Release](release/README.md) -> Guide how to do a release build.
|
||||
- [Windows Filtering Platform - MS](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/roadmap-for-developing-wfp-callout-drivers) -> The driver is build on top of WFP.
|
||||
|
||||
### Building (For release)
|
||||
|
||||
### Building
|
||||
Please refer to [release/README.md](release/README.md) for details about the release procedure.
|
||||
|
||||
### Building (For testing and development)
|
||||
|
||||
The Windows Portmaster Kernel Extension is currently only developed and tested for the amd64 (64-bit) architecture.
|
||||
|
||||
|
@ -53,23 +56,18 @@ __Build driver:__
|
|||
|
||||
```sh
|
||||
cd driver
|
||||
cargo build
|
||||
cargo build --release
|
||||
```
|
||||
> Build also works on linux
|
||||
|
||||
__Link and sign:__
|
||||
On a windows machine copy `driver.lib` form the project target directory (`driver/target/x86_64-pc-windows-msvc/debug/driver.lib`) in the same folder as `link.bat`.
|
||||
Run `link.bat`.
|
||||
On a windows machine copy `driver.lib` from the project target directory (`driver/target/x86_64-pc-windows-msvc/release/driver.lib`) in the same folder as `link-dev.ps1`.
|
||||
Run `link-dev.ps1`.
|
||||
|
||||
`driver.sys` should appear in the folder. Load and use the driver.
|
||||
`driver.sys` should appear in the folder.
|
||||
|
||||
### Test
|
||||
- Install go
|
||||
- https://go.dev/dl/
|
||||
|
||||
```sh
|
||||
cd kext_tester
|
||||
go run .
|
||||
Sign the driver with the test certificate:
|
||||
```
|
||||
|
||||
> make sure the hardcoded path in main.go is pointing to the correct `.sys` file
|
||||
SignTool sign /v /s TestCertStoreName /n TestCertName driver.sys
|
||||
```
|
||||
Load and use the driver.
|
||||
|
|
21
windows_kext/link-dev.ps1
Normal file
21
windows_kext/link-dev.ps1
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Example script for creating debug builds. Libraries may change depending on the version of the WDK that is installed.
|
||||
|
||||
$SDK_Version = "10.0.26100.0"
|
||||
|
||||
link.exe /OUT:driver.sys `
|
||||
/MANIFEST:NO /PROFILE /Driver `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\wdmsec.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\ndis.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\fwpkclnt.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\BufferOverflowK.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\ntoskrnl.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\hal.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\wmilib.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfLdr.lib" `
|
||||
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfDriverEntry.lib" `
|
||||
"driver.lib" `
|
||||
/RELEASE /VERSION:"10.0" /DEBUG /MACHINE:X64 /ENTRY:"FxDriverEntry" /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:NATIVE",6.01" /OPT:ICF /ERRORREPORT:PROMPT /MERGE:"_TEXT=.text;_PAGE=PAGE" /NOLOGO /NODEFAULTLIB /SECTION:"INIT,d"
|
||||
|
||||
if(!$?) {
|
||||
Exit $LASTEXITCODE
|
||||
}
|
|
@ -1,25 +1,31 @@
|
|||
# Kext release tool
|
||||
|
||||
### Generate the zip file
|
||||
## Generate the zip file
|
||||
|
||||
- Make sure the deriver version in `kextinterface/version.txt` is up to date
|
||||
|
||||
- Make sure `kextinterface/version.txt` is up to date
|
||||
- Execute: `cargo run`
|
||||
* This will generate release `kext_release_vX-X-X.zip` file. Which contains all the necessary files to make the release.
|
||||
_This will generate release `portmaster-kext-release-bundle-vX-X-X-X.zip` file. Which contains all the necessary files to make the release._
|
||||
|
||||
### Generate the cab file
|
||||
## Generate the cab file
|
||||
|
||||
- Copy the zip and extract it on a windows machine.
|
||||
* Visual Studio 2022 and WDK need to be installed.
|
||||
- From VS Command Prompt / PowerShell run:
|
||||
```
|
||||
cd kext_release_v.../
|
||||
./build_cab.bat
|
||||
```
|
||||
> Script is written for VS `$SDK_Version = "10.0.22621.0"`. If different version is used update the script.
|
||||
**Precondition:** Visual Studio 2022 and WDK need to be installed.
|
||||
|
||||
- Sing the cab file
|
||||
- copy the zip and extract it on a windows machine.
|
||||
|
||||
### Let Microsoft Sign
|
||||
- update `.\build_cab.ps1`: set correct SDK version you use.
|
||||
_e.g.: $SDK_Version = "10.0.26100.0" (see in `C:\Program Files (x86)\Windows Kits\10\Lib`)_
|
||||
|
||||
- Use "Developer PowerShell for VS":
|
||||
|
||||
```powershell
|
||||
cd portmaster-kext-release-bundle-v...
|
||||
.\build_cab.ps1
|
||||
```
|
||||
|
||||
- Sing the the output cab file: `portmaster-kext-release-bundle-v...\PortmasterKext_v....cab`
|
||||
|
||||
## Let Microsoft Sign
|
||||
|
||||
- Go to https://partner.microsoft.com/en-us/dashboard/hardware/driver/New
|
||||
- Enter "PortmasterKext vX.X.X #1" as the product name
|
||||
|
|
|
@ -10,13 +10,17 @@ static LIB_PATH: &'static str = "./build/x86_64-pc-windows-msvc/release/driver.l
|
|||
|
||||
fn main() {
|
||||
build_driver();
|
||||
println!(
|
||||
"Building kext v{}-{}-{} #{}",
|
||||
|
||||
let filename = format!(
|
||||
"portmaster-kext-release-bundle-v{}-{}-{}-{}.zip",
|
||||
VERSION[0], VERSION[1], VERSION[2], VERSION[3]
|
||||
);
|
||||
|
||||
println!("Building KEXT: {}", filename);
|
||||
|
||||
// Create Zip that will hold all the release files and scripts.
|
||||
let file = File::create("portmaster-kext-release-bundle.zip").unwrap();
|
||||
let file = File::create(&filename).unwrap();
|
||||
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
|
||||
// Write files to zip
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Remove previous cab build
|
||||
Remove-Item -Path "PortmasterKext_v2-0-0.cab" -ErrorAction SilentlyContinue
|
||||
|
||||
$SDK_Version = "10.0.22621.0"
|
||||
$SDK_Version = "10.0.26100.0"
|
||||
|
||||
# Build metadata file
|
||||
rc -I "C:\Program Files (x86)\Windows Kits\10\Include\$SDK_Version\um" `
|
||||
|
|
Loading…
Add table
Reference in a new issue