mirror of
https://codeberg.org/scip/anydb.git
synced 2025-12-16 20:10:59 +01:00
fixes and additions:
- add ANYDB_PASSWORD env var - add config file support, including buckets dict - finalized custom bucket support - fine tuned info support
This commit is contained in:
22
README.md
22
README.md
@@ -95,9 +95,29 @@ anydb import -r backup.json
|
||||
|
||||
# you can encrypt entries. anydb asks for a passphrase
|
||||
# and will do the same when you retrieve the key using the
|
||||
# get command.
|
||||
# get command. anydb will ask you interactively for a password
|
||||
anydb set mypassword -e
|
||||
|
||||
# but you can provide it via an environment variable too
|
||||
ANYDB_PASSWORD=foo anydb set -e secretkey blahblah
|
||||
|
||||
# too tiresome to add -e every time you add an entry?
|
||||
# use a per bucket config
|
||||
cat ~/.config/anydb/anydb.toml
|
||||
[buckets.data]
|
||||
encrypt = true
|
||||
anydb set foo bar # will be encrypted
|
||||
|
||||
# speaking of buckets, you can use different buckets
|
||||
anydb -b test set foo bar
|
||||
|
||||
# and speaking of configs, you can place a config file at these places:
|
||||
# ~/.config/anydb/anydb.toml
|
||||
# ~/.anydb.toml
|
||||
# anydb.toml (current directory)
|
||||
# or specify one using -c <filename>
|
||||
# look at example.toml
|
||||
|
||||
# using template output mode you can freely design how to print stuff
|
||||
# here, we print the values in CSV format ONLY if they have some tag
|
||||
anydb ls -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"
|
||||
|
||||
4
TODO.md
4
TODO.md
@@ -1,7 +1,3 @@
|
||||
- repl
|
||||
- mime-type => exec app + value
|
||||
- custom buckets (like skate: key@bucket or key+bucket)
|
||||
- encryption per bucket, one key for all entries (in that bucket)
|
||||
- `-b bucket`, use B for encrypted bucke?
|
||||
- [edit command](https://github.com/TLINDEN/rpnc/blob/master/command.go#L249)
|
||||
- env var for password
|
||||
|
||||
4
anydb.1
4
anydb.1
@@ -1,4 +1,4 @@
|
||||
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
|
||||
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
|
||||
.\"
|
||||
.\" Standard preamble:
|
||||
.\" ========================================================================
|
||||
@@ -133,7 +133,7 @@
|
||||
.\" ========================================================================
|
||||
.\"
|
||||
.IX Title "ANYDB 1"
|
||||
.TH ANYDB 1 "2024-12-18" "1" "User Commands"
|
||||
.TH ANYDB 1 "2024-12-22" "1" "User Commands"
|
||||
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||
.\" way too many mistakes in technical documents.
|
||||
.if n .ad l
|
||||
|
||||
29
app/db.go
29
app/db.go
@@ -50,9 +50,11 @@ type DbEntry struct {
|
||||
}
|
||||
|
||||
type BucketInfo struct {
|
||||
Name string
|
||||
Keys int
|
||||
Size int
|
||||
Name string
|
||||
Keys int
|
||||
Size int
|
||||
Sequence uint64
|
||||
Stats bolt.BucketStats
|
||||
}
|
||||
|
||||
type DbInfo struct {
|
||||
@@ -264,6 +266,7 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
|
||||
|
||||
jsonentry := bucket.Get([]byte(attr.Key))
|
||||
if jsonentry == nil {
|
||||
// FIXME: shall we return a key not found error?
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -373,16 +376,23 @@ func (db *DB) Info() (*DbInfo, error) {
|
||||
info := &DbInfo{Path: db.Dbfile}
|
||||
|
||||
err := db.DB.View(func(tx *bolt.Tx) error {
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
binfo := BucketInfo{Name: string(name)}
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
stats := bucket.Stats()
|
||||
|
||||
binfo := BucketInfo{
|
||||
Name: string(name),
|
||||
Sequence: bucket.Sequence(),
|
||||
Keys: stats.KeyN,
|
||||
Stats: bucket.Stats(),
|
||||
}
|
||||
|
||||
err := bucket.ForEach(func(key, entry []byte) error {
|
||||
binfo.Size += len(entry)
|
||||
binfo.Keys++
|
||||
binfo.Size += len(entry) + len(key)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to read keys: %w", err)
|
||||
}
|
||||
|
||||
info.Buckets = append(info.Buckets, binfo)
|
||||
@@ -391,11 +401,12 @@ func (db *DB) Info() (*DbInfo, error) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to read from DB: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
return info, err
|
||||
}
|
||||
|
||||
106
cfg/config.go
106
cfg/config.go
@@ -1,20 +1,114 @@
|
||||
/*
|
||||
Copyright © 2024 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cfg
|
||||
|
||||
import "github.com/tlinden/anydb/app"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/tlinden/anydb/app"
|
||||
"github.com/tlinden/anydb/common"
|
||||
)
|
||||
|
||||
var Version string = "v0.0.4"
|
||||
|
||||
type BucketConfig struct {
|
||||
Encrypt bool
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Debug bool
|
||||
Dbfile string
|
||||
Dbbucket string
|
||||
Dbbucket string
|
||||
Template string
|
||||
Mode string // wide, table, yaml, json
|
||||
NoHeaders bool
|
||||
NoHumanize bool
|
||||
Encrypt bool
|
||||
DB *app.DB
|
||||
File string
|
||||
Tags []string
|
||||
Encrypt bool // one entry
|
||||
Listen string
|
||||
Buckets map[string]BucketConfig // config file only
|
||||
|
||||
Tags []string // internal
|
||||
DB *app.DB // internal
|
||||
File string // internal
|
||||
}
|
||||
|
||||
func (conf *Config) GetConfig(files []string) error {
|
||||
for _, file := range files {
|
||||
if err := conf.ParseConfigFile(file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conf *Config) ParseConfigFile(file string) error {
|
||||
if !common.FileExists(file) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := os.OpenFile(file, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open config file %s: %w", file, err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from config file: %w", err)
|
||||
}
|
||||
|
||||
add := Config{}
|
||||
err = toml.Unmarshal(data, &add)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshall toml: %w", err)
|
||||
}
|
||||
|
||||
// merge new values into existing config
|
||||
switch {
|
||||
case add.Debug != conf.Debug:
|
||||
conf.Debug = add.Debug
|
||||
case add.Dbfile != "":
|
||||
conf.Dbfile = add.Dbfile
|
||||
case add.Dbbucket != "":
|
||||
conf.Dbbucket = add.Dbbucket
|
||||
case add.Template != "":
|
||||
conf.Template = add.Template
|
||||
case add.NoHeaders != conf.NoHeaders:
|
||||
conf.NoHeaders = add.NoHeaders
|
||||
case add.NoHumanize != conf.NoHumanize:
|
||||
conf.NoHumanize = add.NoHumanize
|
||||
case add.Encrypt != conf.Encrypt:
|
||||
conf.Encrypt = add.Encrypt
|
||||
case add.Listen != "":
|
||||
conf.Listen = add.Listen
|
||||
}
|
||||
|
||||
// only supported in config files
|
||||
conf.Buckets = add.Buckets
|
||||
|
||||
// determine bucket encryption mode
|
||||
for name, bucket := range conf.Buckets {
|
||||
if name == conf.Dbbucket {
|
||||
conf.Encrypt = bucket.Encrypt
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
25
cmd/crud.go
25
cmd/crud.go
@@ -65,7 +65,7 @@ func Set(conf *cfg.Config) *cobra.Command {
|
||||
|
||||
// encrypt?
|
||||
if conf.Encrypt {
|
||||
pass, err := app.AskForPassword()
|
||||
pass, err := getPassword()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func Get(conf *cfg.Config) *cobra.Command {
|
||||
}
|
||||
|
||||
if entry.Encrypted {
|
||||
pass, err := app.AskForPassword()
|
||||
pass, err := getPassword()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -371,7 +371,26 @@ func Info(conf *cfg.Config) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&conf.Listen, "listen", "l", "localhost:8787", "host:port")
|
||||
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getPassword() ([]byte, error) {
|
||||
var pass []byte
|
||||
|
||||
envpass := os.Getenv("ANYDB_PASSWORD")
|
||||
|
||||
if envpass == "" {
|
||||
readpass, err := app.AskForPassword()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pass = readpass
|
||||
} else {
|
||||
pass = []byte(envpass)
|
||||
}
|
||||
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
34
cmd/root.go
34
cmd/root.go
@@ -22,6 +22,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tlinden/anydb/app"
|
||||
"github.com/tlinden/anydb/cfg"
|
||||
@@ -45,10 +46,22 @@ func completion(cmd *cobra.Command, mode string) error {
|
||||
func Execute() {
|
||||
var (
|
||||
conf cfg.Config
|
||||
configfile string
|
||||
ShowVersion bool
|
||||
ShowCompletion string
|
||||
)
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
SearchConfigs := []string{
|
||||
filepath.Join(home, ".config", "anydb", "anydb.toml"),
|
||||
filepath.Join(home, ".anydb.toml"),
|
||||
"anydb.toml",
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "anydb <command> [options]",
|
||||
Short: "anydb",
|
||||
@@ -61,6 +74,21 @@ func Execute() {
|
||||
|
||||
conf.DB = db
|
||||
|
||||
var configs []string
|
||||
if configfile != "" {
|
||||
configs = []string{configfile}
|
||||
} else {
|
||||
configs = SearchConfigs
|
||||
}
|
||||
|
||||
if err := conf.GetConfig(configs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.Debug {
|
||||
repr.Println(conf)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -82,11 +110,6 @@ func Execute() {
|
||||
},
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// options
|
||||
rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "v", false, "Print program version")
|
||||
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging")
|
||||
@@ -94,6 +117,7 @@ func Execute() {
|
||||
filepath.Join(home, ".config", "anydb", "default.db"), "DB file to use")
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Dbbucket, "bucket", "b",
|
||||
app.BucketData, "use other bucket (default: "+app.BucketData+")")
|
||||
rootCmd.PersistentFlags().StringVarP(&configfile, "config", "c", "", "toml config file")
|
||||
|
||||
rootCmd.AddCommand(Set(&conf))
|
||||
rootCmd.AddCommand(List(&conf))
|
||||
|
||||
36
common/io.go
Normal file
36
common/io.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright © 2024 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package common
|
||||
|
||||
import "os"
|
||||
|
||||
func CleanError(file string, err error) error {
|
||||
// remove given [backup] file and forward the given error
|
||||
os.Remove(file)
|
||||
return err
|
||||
}
|
||||
|
||||
func FileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
|
||||
if err != nil {
|
||||
// return false on any error
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -16,6 +16,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -45,6 +45,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
|
||||
@@ -21,8 +21,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/tlinden/anydb/app"
|
||||
"github.com/tlinden/anydb/cfg"
|
||||
"golang.org/x/term"
|
||||
@@ -96,12 +98,33 @@ func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.
|
||||
}
|
||||
|
||||
func Info(writer io.Writer, conf *cfg.Config, info *app.DbInfo) error {
|
||||
// repr.Println(info)
|
||||
fmt.Fprintf(writer, "Database: %s\n", info.Path)
|
||||
|
||||
for _, bucket := range info.Buckets {
|
||||
if conf.NoHumanize {
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
"%19s: %s\n%19s: %d\n%19s: %d\n",
|
||||
"Bucket", bucket.Name,
|
||||
"Size", bucket.Size,
|
||||
"Keys", bucket.Keys)
|
||||
} else {
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
"%19s: %s\n%19s: %s\n%19s: %d\n",
|
||||
"Bucket", bucket.Name,
|
||||
"Size", humanize.Bytes(uint64(bucket.Size)),
|
||||
"Keys", bucket.Keys)
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "Bucket: %s\n Size: %d\n Keys: %d\n\n", bucket.Name, bucket.Size, bucket.Keys)
|
||||
if conf.Debug {
|
||||
val := reflect.ValueOf(&bucket.Stats).Elem()
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
fmt.Fprintf(writer, "%19s: %d\n", val.Type().Field(i).Name, val.Field(i))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user