9 Commits

Author SHA1 Message Date
brent saner
236165bec8 v1.8.0
API CHANGES:
* Technically should be a v2.x, but cryptparse has been moved to its own
  module: r00t2.io/cryptparse
2024-10-17 15:34:26 -04:00
brent saner
4cb0403e08 v1.7.1
FIX:
* cryptparse/ParseCipher* funcs were not properly building cipher name
  comparison map
2024-09-09 13:42:56 -04:00
brent saner
0318a9759b v1.7.0
ADDED:
* cryptparse/ParseTlsCipherStrict()
* cryptparse/ParseTlsCipherSuiteStrict()
2024-09-09 13:06:07 -04:00
brent saner
1a93d5d9f3 v1.6.0
ADDED:
- env/DefEnv(), env/DefEnvBlank()
2024-09-06 12:50:23 -04:00
brent saner
5dc944cf21 v1.5.1
FIXES:
* cryptparse.TlsUri.ToConn and cryptparse.TlsUri.ToTlsConn would
  previously use incorrect "host" parameter during dial for UDS/IPC
  sockets.
2024-08-12 15:59:38 -04:00
brent saner
77a85a4f84 v1.5.0
ADDED:
* cryptpartse.TlsUri now has methods to returned dialed net.Conn and
  tls.Conn, or can use WithConn to add TLS to an already-dialed net.Conn.
2024-08-08 19:40:11 -04:00
brent saner
43d1ddfeb8 v1.4.1
FIX:
* missing dep for windows env
2024-06-27 16:36:45 -04:00
brent saner
db20c70d86 v1.4.0
ADDED:
* cryptparse, which makes managing certs/keys MUCH much easier (and
  better) than the stdlib utilities.
2024-06-21 17:18:19 -04:00
brent saner
c0c924b75a v1.3.0
ADDED:
* auger, some convenience funcs around Augeas.
2024-06-20 05:26:56 -04:00
11 changed files with 549 additions and 15 deletions

2
TODO
View File

@@ -4,6 +4,8 @@
--- https://github.com/hlandau/passlib --- https://github.com/hlandau/passlib
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen) -- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
- auger needs to be build-constrained to linux.
- unit tests - unit tests
- constants/vars for errors - constants/vars for errors

9
auger/consts.go Normal file
View File

@@ -0,0 +1,9 @@
package auger
const (
augLensTpl string = "/augeas/load/%v" // A fmt.Sprintf string (single placeholder only) for Lens module roots.
augFsTree string = "/files"
augFsTpl string = augFsTree + "%v//%v" // A fmt.Sprintf string (first placeholder fspath, second placeholder includeDirective) for files to search for the includeDirective.
augInclTfm string = "incl" // The transformer keyword for Augeas includes.
augAppendSuffix string = "[last()+1]"
)

63
auger/funcs.go Normal file
View File

@@ -0,0 +1,63 @@
package auger
import (
`io/fs`
`os`
`strings`
)
/*
AugpathToFspath returns the filesystem path from an Augeas path.
It is *required* and expected that the Augeas standard /files prefix be removed first;
if not, it is assumed to be part of the filesystem path.
If a valid path cannot be determined, fsPath will be empty.
*/
func AugpathToFspath(augPath string) (fsPath string, err error) {
var path string
var num int
var augSplit []string = strings.Split(augPath, "/")
num = len(augSplit)
for i := num - 1; i >= 0; i-- {
path = strings.Join(augSplit[:i], "/")
if !fs.ValidPath(path) {
continue
}
if _, err = os.Stat(path); err != nil {
if os.IsNotExist(err) {
err = nil
continue
} else {
return
}
}
fsPath = path
return
}
return
}
// dedupePaths is used to reduce new to only unique entries that do not exist in existing.
func dedupePaths(new, existing []string) (missing []string) {
var ok bool
var m map[string]bool = make(map[string]bool)
for _, path := range existing {
m[path] = true
}
for _, path := range new {
if _, ok = m[path]; !ok {
missing = append(missing, path)
}
}
return
}

294
auger/funcs_aug.go Normal file
View File

@@ -0,0 +1,294 @@
package auger
import (
`bufio`
`errors`
`fmt`
`io`
`os`
`path/filepath`
`strings`
`github.com/davecgh/go-spew/spew`
`github.com/google/shlex`
`honnef.co/go/augeas`
`r00t2.io/sysutils/paths`
)
// Close cleanly closes the underlying Augeas connection.
func (a *Aug) Close() {
a.aug.Close()
}
// Interact provides an interactive shell-like interface for debugging purposes to explore the loaded Augeas tree.
func (a *Aug) Interact() (err error) {
var input string
var lexed []string
var cmd string
var arg string
var val string
var augVal string
var augVals []string
var buf *bufio.Reader = bufio.NewReader(os.Stdin)
fmt.Fprint(os.Stderr, "INTERACTIVE MODE\nCmds: get, getall, match, set, quit\n")
breakCmd:
for {
cmd, arg, val = "", "", ""
fmt.Fprint(os.Stderr, "> ")
if input, err = buf.ReadString('\n'); err != nil {
if errors.Is(err, io.EOF) {
err = nil
break breakCmd
}
return
}
if strings.TrimSpace(input) == "" {
continue
}
if lexed, err = shlex.Split(input); err != nil {
return
}
if lexed == nil || len(lexed) == 0 || len(lexed) > 3 {
fmt.Fprintf(os.Stderr, "Bad command: %#v\n", lexed)
continue
}
cmd = lexed[0]
switch len(lexed) {
case 2:
arg = lexed[1]
case 3:
arg = lexed[1]
val = lexed[2]
}
switch cmd {
case "get":
if strings.TrimSpace(arg) == "" {
fmt.Fprintln(os.Stderr, "Missing argument")
continue
}
if augVal, err = a.aug.Get(arg); err != nil {
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
err = nil
continue
}
fmt.Fprintln(os.Stderr, augVal)
case "getall":
if strings.TrimSpace(arg) == "" {
fmt.Fprintln(os.Stderr, "Missing argument")
continue
}
if augVals, err = a.aug.GetAll(arg); err != nil {
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
err = nil
continue
}
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
case "match":
if strings.TrimSpace(arg) == "" {
fmt.Fprintln(os.Stderr, "Missing argument")
continue
}
if augVals, err = a.aug.Match(arg); err != nil {
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
err = nil
continue
}
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
case "set":
if strings.TrimSpace(arg) == "" {
fmt.Fprintln(os.Stderr, "Missing argument")
continue
}
if strings.TrimSpace(val) == "" {
fmt.Fprintln(os.Stderr, "Missing value")
continue
}
if err = a.aug.Set(arg, val); err != nil {
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
err = nil
continue
}
fmt.Fprintln(os.Stderr, "Success!")
case "quit":
break breakCmd
default:
fmt.Fprintf(os.Stderr, "Unknown command: %v\n", cmd)
continue
}
}
return
}
/*
RecursiveInclude parses the configuration files belonging to Augeas lens name augLens,
searching for all occurrences of includeDirective, loading those files (if they exist),
and continuing so forth recursively, loading them into the Augeas file tree.
If any relative paths are found, they will be assumed to be relative to fsRoot ("/" if empty).
For e.g. Nginx, you almost absolutely want to set this to "/etc/nginx", but you really should
use absolute paths for every include in your configs if supported by the application; it will
lead to much less guesswork and much more accurate recursing/walking.
Some lens recursively load depending on their respective include directive(s) automatically;
some (such as the Nginx lens) do not.
For example for Nginx, augLens should be "Nginx". RecursiveInclude will then iterate over
/augeas/load/Nginx/incl (/augeas/load/<augLens>/incl), parsing each file for includeDirective
(the "include" keyword, in Nginx's case), check if it is already loaded in /augeas/load/<augLens>/incl,
adding it and reloading if not, and then scanning *that* file for includeDirective, etc.
An error will be returned if augLens is a nonexistent or not-loaded Augeas lens module.
Depending on how many files there are and whether globs vs. explicit filepaths are included, this may take a while.
*/
func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string) (err error) {
if err = a.addIncl(includeDirective, augLens, fsRoot, nil); err != nil {
return
}
return
}
/*
addIncl is used by RecursiveInclude.
includeDirective, augLens, and fsRoot have the same meaning as in RecursiveInclude.
newInclPaths are new filesystem paths/Augeas-compatible glob patterns to load into the filetree and recurse into.
They may be nil, especially if the first run.
*/
func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPaths []string) (err error) {
var matches []string // Passed around set of Augeas matches.
var includes []string // Filepath(s)/glob(s) from fetching includeDirective in lensInclPath. These are internal to the application but are recursed.
var lensInclPath string // The path of the included paths in the tree. These are internal to Augeas, not the application.
var appendPath string // The path for new Augeas includes.
var match []string // A placeholder for iterating when populating includes.
var fpath string // A placeholder for finding the path of a conf file that contains an includeDirective.
var lensPath string = fmt.Sprintf(augLensTpl, augLens) // The path of the lens (augLens) itself.
var augErr *augeas.Error = new(augeas.Error) // We use this to skip "nonexistent" lens.
if fsRoot == "" {
fsRoot = "/"
}
if err = paths.RealPath(&fsRoot); err != nil {
return
}
for strings.HasSuffix(lensPath, "/") {
lensPath = lensPath[:len(lensPath)-1]
}
if !strings.HasSuffix(lensPath, "/"+augInclTfm) {
lensPath = strings.TrimSuffix(lensPath, "/"+augInclTfm)
}
lensInclPath = fmt.Sprintf("%v/%v", lensPath, augInclTfm)
appendPath = fmt.Sprintf("%v/%v", lensInclPath, augAppendSuffix)
// First canonize paths.
if newInclPaths != nil && len(newInclPaths) > 0 {
// Existing includes. We don't return on an empty lensInclPath because
if matches, err = a.aug.Match(lensInclPath); err != nil {
if errors.As(err, augErr) && augErr.Code == augeas.NoMatch {
err = nil
} else {
return
}
} else {
for idx, m := range matches {
if matches[idx], err = a.aug.Get(m); err != nil {
return
}
}
}
// Normalize new include(s).
for idx, i := range newInclPaths {
if !filepath.IsAbs(i) {
newInclPaths[idx] = filepath.Join(fsRoot, i)
}
if err = paths.RealPath(&newInclPaths[idx]); err != nil {
return
}
}
// We don't want to bother adding multiple incl's for the same path(s); it can negatively affect Augeas loads.
newInclPaths = dedupePaths(newInclPaths, matches)
// Add the new path(s) as Augeas include entries.
if newInclPaths != nil {
for _, fsPath := range newInclPaths {
if err = a.aug.Set(appendPath, fsPath); err != nil {
return
}
}
/*
And then load the new files into the tree.
This is done at the end as it takes WAYYY less time to just reload the tree
as a whole once you have more than, say, 30 files added at a time.
*/
if err = a.aug.Load(); err != nil {
return
}
}
}
// We now fetch all values (filepath/globs) that match the includeDirective and recurse with those as new include files.
matches = nil
if includes, err = a.aug.GetAll(lensInclPath); err != nil {
return
}
for _, fsPath := range includes {
// This gets the Augeas filetree path, NOT the FS path...
if match, err = a.aug.Match(fmt.Sprintf(augFsTpl, fsPath, includeDirective)); err != nil {
return
}
if match == nil || len(match) == 0 {
continue
}
/*
For each directive match, we need to:
1.) normalize to an FS *file* path (strip augFsTree from the beginning
2.) walk backwards (via AugpathToFspath) until we find an actual file
3.) get the *dirname* of that file
4.) join the value (included file) to #3
IF
fsRoot == "/"
This very obviously breaks for applications with arbitrary roots (like Nginx including relative to /etc/nginx).
That's why we warn about it in Aug.RecursiveInclude. Caveat emptor.
*/
for idx, ftreePath := range match {
if fpath, err = a.aug.Get(ftreePath); err != nil {
return
}
if fsRoot == "/" {
if ftreePath, err = AugpathToFspath(
strings.TrimSuffix(ftreePath, augFsTree),
); err != nil {
return
}
if ftreePath != "" {
fpath = filepath.Join(filepath.Dir(ftreePath), fpath)
}
}
match[idx] = fpath
}
matches = append(matches, match...)
}
if matches != nil && len(matches) != 0 {
if err = a.addIncl(includeDirective, augLens, fsRoot, matches); err != nil {
return
}
}
return
}

41
auger/funcs_augflags.go Normal file
View File

@@ -0,0 +1,41 @@
package auger
import (
`honnef.co/go/augeas`
)
// Eval returns an evaluated set of flags.
func (a *AugFlags) Eval() (augFlags augeas.Flag) {
augFlags = augeas.None
if a.Backup != nil && *a.Backup {
augFlags |= augeas.SaveBackup
}
if a.NewFile != nil && *a.NewFile {
augFlags |= augeas.SaveNewFile
}
if a.TypeCheck != nil && *a.TypeCheck {
augFlags |= augeas.TypeCheck
}
if a.NoDfltModLoad != nil && *a.NoDfltModLoad {
augFlags |= augeas.NoModlAutoload
}
if a.DryRun != nil && *a.DryRun {
augFlags |= augeas.SaveNoop
}
if a.NoTree != nil && *a.NoTree {
augFlags |= augeas.NoLoad
}
if a.NoAutoModLoad != nil && *a.NoAutoModLoad {
augFlags |= augeas.NoModlAutoload
}
if a.EnableSpan != nil && *a.EnableSpan {
augFlags |= augeas.EnableSpan
}
if a.NoErrClose != nil && *a.NoErrClose {
augFlags |= augeas.NoErrClose
}
return
}

62
auger/types.go Normal file
View File

@@ -0,0 +1,62 @@
package auger
import (
`honnef.co/go/augeas`
)
// Aug is a wrapper around (honnef.co/go/)augeas.Augeas. Remember to call Aug.Close().
type Aug struct {
aug augeas.Augeas
}
// AugFlags contains flags to pass to the Augeas instance.
type AugFlags struct {
/*
Backup, if true, will enable Augeas backup mode (original files are saved with a .augsave suffix).
const: augeas.SaveBackup
*/
Backup *bool `toml:"Backup,omitempty"`
/*
NewFile, if true, will create new files (.augnew suffix) instead of overwriting the original file.
const: augeas.SaveNewFile
*/
NewFile *bool `toml:"NewFile,omitempty"`
/*
TypeCheck, if true, will perform a Lens typecheck.
HIGHLY UNRECOMMENDED; WILL INDUCE A HUGE FRONTLOAD.
const: augeas.TypeCheck
*/
TypeCheck *bool `toml:"TypeCheck,omitempty"`
/*
NoDfltModLoad, if true, will suppress loading the built-in/default modules.
Highly unrecommended, as we do not have a current way in the config to define load paths (yet).
const: augeas.NoStdinc
*/
NoDfltModLoad *bool `toml:"NoDfltModLoad,omitempty"`
/*
DryRun, if true, will make all saves NO-OPs.
const: augeas.SaveNoop
*/
DryRun *bool `toml:"DryRun,omitempty"`
/*
NoTree, if true, will not load the filetree automatically. Doesn't really affect this program.
const: augeas.NoLoad
*/
NoTree *bool `toml:"NoTree,omitempty"`
/*
NoAutoModLoad, if true, will supress automatically loading modules.
const: augeas.NoModlAutoload
*/
NoAutoModLoad *bool `toml:"NoAutoModLoad,omitempty"`
/*
EnableSpan, if true, will track span in input nodes (location information, essentially).
See https://augeas.net/docs/api.html#getting-the-span-of-a-node-related-to-a-file
const: augeas.EnableSpan
*/
EnableSpan *bool `toml:"EnableSpan,omitempty"`
/*
NoErrClose, if true, will suppress automatically closing on error.
const: augeas.NoErrClose
*/
NoErrClose *bool `toml:"NoErrClose,omitempty"`
}

5
cryptparse/doc.go Normal file
View File

@@ -0,0 +1,5 @@
/*
CRYPTPARSE HAS MOVED.
It is now its own module: r00t2.io/cryptparse
*/

View File

@@ -17,6 +17,34 @@ import (
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )
/*
DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified,
then the value specified by fallback will be returned instead
otherwise key's value is returned.
*/
func DefEnv(key, fallback string) (value string) {
var exists bool
if value, exists = os.LookupEnv(key); !exists {
value = fallback
}
return
}
// DefEnvBlank is like DefEnv but will ADDITIONALLY/ALSO apply fallback if key is *defined/exists but is an empty string*.
func DefEnvBlank(key, fallback string) (value string) {
value = DefEnv(key, fallback)
if value == "" {
value = fallback
}
return
}
// GetEnvMap returns a map of all environment variables. All values are strings. // GetEnvMap returns a map of all environment variables. All values are strings.
func GetEnvMap() (envVars map[string]string) { func GetEnvMap() (envVars map[string]string) {

View File

@@ -3,6 +3,8 @@
package envs package envs
import ( import (
"os"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )

18
go.mod
View File

@@ -1,18 +1,26 @@
module r00t2.io/sysutils module r00t2.io/sysutils/v2
go 1.21 go 1.23.2
require ( require (
github.com/Luzifer/go-dhparam v1.2.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/g0rbe/go-chattr v1.0.1 github.com/g0rbe/go-chattr v1.0.1
github.com/google/uuid v1.6.0 github.com/go-playground/validator/v10 v10.22.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
golang.org/x/sys v0.19.0 golang.org/x/sys v0.19.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.6.0 r00t2.io/goutils v1.6.0
) )
require ( require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/godbus/dbus v4.1.0+incompatible // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect
) )
// Pending https://github.com/g0rbe/go-chattr/pull/3 // Pending https://github.com/g0rbe/go-chattr/pull/3

40
go.sum
View File

@@ -1,22 +1,42 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/Luzifer/go-dhparam v1.2.0 h1:YwDf15FTsVriTynCv1qF+1Inh6E8Dg1+28tPEA3pvFo=
github.com/Luzifer/go-dhparam v1.2.0/go.mod h1:hnazoxBTsXnRvGXAosio70Tb1lWowquyhVdvsXdlIPc=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/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/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg= github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg=
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs= github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
r00t2.io/goutils v1.4.0 h1:/x/etLpMFv3+j1aPtT7KK2G0uOk+gQkGvXIYBCdjn3E= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
r00t2.io/goutils v1.4.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
r00t2.io/goutils v1.5.0 h1:haVk+wUK1BAk8f4UFGjy3ov3DwGMauZAOv/XYdb9isQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
r00t2.io/goutils v1.5.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.6.0 h1:oBC6PgBv0y/fdHeCmWgORHpBiU8uWw7IfFQJX5rIuzY= r00t2.io/goutils v1.6.0 h1:oBC6PgBv0y/fdHeCmWgORHpBiU8uWw7IfFQJX5rIuzY=
r00t2.io/goutils v1.6.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= r00t2.io/goutils v1.6.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=