55 Commits

Author SHA1 Message Date
53d69b5278 add dist makefile 2025-11-03 10:01:24 +01:00
T. von Dein
a16b9e796c move to codeberg (#22) 2025-11-03 09:15:27 +01:00
eb39f57199 fix copy/paste error 2025-10-14 16:51:22 +02:00
471d89eccd bump version 2025-10-02 22:26:47 +02:00
dependabot[bot]
b74bb79a45 Bump golang.org/x/crypto from 0.41.0 to 0.42.0 (#65) 2025-10-02 22:26:01 +02:00
dependabot[bot]
39436215d7 Bump github.com/spf13/cobra from 1.9.1 to 1.10.1 (#63) 2025-10-02 22:25:36 +02:00
dependabot[bot]
074547c356 Bump docker/login-action from 3.5.0 to 3.6.0 (#62) 2025-10-02 22:23:44 +02:00
dependabot[bot]
79d031d6a8 Bump actions/setup-go from 5 to 6 (#61) 2025-10-02 22:22:51 +02:00
dependabot[bot]
e42f664dfd Bump github.com/olekukonko/tablewriter from 1.0.9 to 1.1.0 (#64) 2025-10-02 22:22:18 +02:00
dependabot[bot]
e75062d2fd Bump google.golang.org/protobuf from 1.36.6 to 1.36.9 (#66) 2025-10-02 22:21:01 +02:00
6481e0cd15 push only ci 2025-10-02 22:20:41 +02:00
T.v.Dein
62c975206f Update golang (#60) 2025-09-18 20:13:02 +02:00
T.v.Dein
36a7d9f0c8 bump version (#59) 2025-09-18 19:55:15 +02:00
dependabot[bot]
c3eb61c6d2 Bump golang.org/x/term from 0.32.0 to 0.34.0 (#58) 2025-09-18 19:51:35 +02:00
dependabot[bot]
c8b3fd782e Bump actions/checkout from 4 to 5 (#57) 2025-09-18 19:51:15 +02:00
dependabot[bot]
d7c20ed245 Bump go.etcd.io/bbolt from 1.4.1 to 1.4.3 (#56) 2025-09-18 19:50:51 +02:00
dependabot[bot]
0e4eb60271 Bump golang.org/x/crypto from 0.38.0 to 0.41.0 (#55) 2025-09-18 19:47:19 +02:00
dependabot[bot]
9b25725f5f Bump docker/login-action from 3.4.0 to 3.5.0 (#54) 2025-09-18 19:46:47 +02:00
dependabot[bot]
e34c25c889 Bump github.com/gofiber/fiber/v2 from 2.52.8 to 2.52.9 (#53) 2025-09-18 19:45:54 +02:00
dependabot[bot]
b3a7064160 Bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.9 (#52) 2025-09-18 19:45:23 +02:00
T.v.Dein
1b00aeb23c update boltdb (#47) 2025-06-12 13:44:32 +02:00
T.v.Dein
aad9b31169 update dependencies (#46)
* update dependencies
2025-06-10 16:12:54 +02:00
dependabot[bot]
f22719a92b Bump golang.org/x/crypto from 0.31.0 to 0.37.0 (#35) 2025-04-25 14:27:49 +02:00
dependabot[bot]
b9f9655d7c Bump github.com/spf13/cobra from 1.8.1 to 1.9.1 (#28) 2025-04-25 14:18:06 +02:00
dependabot[bot]
edc08f12a7 Bump github.com/rogpeppe/go-internal from 1.13.1 to 1.14.1 (#29) 2025-04-25 14:13:34 +02:00
dependabot[bot]
e549ae8ed5 Bump docker/build-push-action from 6.13.0 to 6.15.0 (#32) 2025-04-25 13:58:35 +02:00
dependabot[bot]
2c0cc36551 Bump google.golang.org/protobuf from 1.36.5 to 1.36.6 (#33) 2025-04-25 13:58:17 +02:00
dependabot[bot]
7fe8004d41 Bump docker/login-action from 3.3.0 to 3.4.0 (#36) 2025-04-25 13:57:16 +02:00
T.v.Dein
12e3029145 refactored db.Set(): remove redundant View() call, use db.txGet() (#38)
* refactored db.Set(): remove redundant View() call, use db.txGet()
2025-04-25 13:56:46 +02:00
438248d267 build release bins w/o symbols and debug 2025-02-19 18:10:55 +01:00
05a812ebde add caution about deletion bug 2025-02-18 15:23:03 +01:00
T.v.Dein
ef85582488 fix #26: delete test now tests for correct value to not exist (#27)
Co-authored-by: Thomas von Dein <tom@vondein.org>
2025-02-18 15:19:05 +01:00
T.v.Dein
eb18e97c0d Add little UI script using fzf (#25)
* added Taglist template helper
* added simple fzf ui script
2025-02-17 13:47:20 +01:00
T.v.Dein
be13ec1111 fix #23: delete didn't work anymore (#24) 2025-02-17 13:46:59 +01:00
T.v.Dein
44bb6a0a26 add bolt logger interface with slog for debugging (#22) 2025-02-17 13:46:41 +01:00
T.v.Dein
6d2800c72e fix #5: fix ci testscript under windows by adding clean dep to test (#21)
Co-authored-by: Thomas von Dein <tom@vondein.org>
2025-02-12 13:00:58 +01:00
83de01b349 fix release builder 2025-02-10 15:49:22 +01:00
b4cf1e4d1c link to the bug 2025-02-10 15:41:30 +01:00
42dc598deb Merge branch 'main' of github.com:TLINDEN/anydb 2025-02-10 15:40:05 +01:00
ff169a7198 add warning about #19 2025-02-10 15:39:40 +01:00
T.v.Dein
6c4f119bfa Fixes and additions: (#20)
- fix encryption bug #19, which was a regression
- added encryption unit test
- added debug logging here and there

Co-authored-by: Thomas von Dein <tom@vondein.org>
2025-02-10 15:34:04 +01:00
16b6075329 ci tuning 2025-02-10 15:33:21 +01:00
07808187d5 enhance changelog generation, update release builder 2025-02-05 17:49:26 +01:00
T.v.Dein
bc1b08d6f9 fix #16: add documentation about how to use .Created.AsTime (#18)
Co-authored-by: Thomas von Dein <tom@vondein.org>
2025-02-03 17:50:28 +01:00
dependabot[bot]
bd5c1e3abb Bump docker/build-push-action from 6.10.0 to 6.13.0 (#13)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.10.0 to 6.13.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](48aba3b46d...ca877d9245)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 17:37:55 +01:00
dependabot[bot]
57026e11aa Bump actions/setup-go from 1 to 5 (#12)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 1 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v1...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 17:37:41 +01:00
dependabot[bot]
d0fb560bb2 Bump actions/checkout from 2 to 4 (#14)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 17:37:24 +01:00
T.v.Dein
f2bc21e03a Feat/releasenotes (#17)
* add release notes, start with PR mode
2025-02-03 17:36:49 +01:00
717510a2f9 fix gifs 2025-02-02 19:45:06 +01:00
T.v.Dein
d794cc8608 Feature/vhs demo (#15)
* add vhs made demo gif

* add support for ANYDB_DB env var

* left one section

* fixed data type bug, added demo gifs, upgraded dependencies

---------

Co-authored-by: Thomas von Dein <tom@vondein.org>
2025-02-02 19:42:12 +01:00
359d134d6e do not push binaries locally anymore 2025-01-18 10:50:33 +01:00
a674c9c602 build release binaries with ci workflow 2025-01-18 10:48:37 +01:00
e26c61e26f use correct tag 2025-01-18 10:36:31 +01:00
T.v.Dein
e054d1e530 Add release ci (#11)
* add release binary builder
2025-01-18 10:32:01 +01:00
9afca91159 fixed crash in api list, added filter support to api list 2025-01-01 18:08:43 +01:00
47 changed files with 1227 additions and 544 deletions

96
.gh-dash.yml Normal file
View File

@@ -0,0 +1,96 @@
prSections:
- title: Responsible PRs
filters: repo:tlinden/anydb is:open NOT dependabot
layout:
repoName:
hidden: true
- title: Responsible Dependabot PRs
filters: repo:tlinden/anydb is:open dependabot
layout:
repoName:
hidden: true
issuesSections:
- title: Responsible Issues
filters: is:open repo:tlinden/anydb -author:@me
layout:
repoName:
hidden: true
- title: Note-to-Self Issues
filters: is:open repo:tlinden/anydb author:@me
layout:
creator:
hidden: true
repoName:
hidden: true
defaults:
preview:
open: false
width: 100
keybindings:
universal:
- key: "shift+down"
builtin: pageDown
- key: "shift+up"
builtin: pageUp
prs:
- key: g
name: gitu
command: >
cd {{.RepoPath}} && /home/scip/bin/gitu
- key: M
name: squash-merge
command: gh pr merge --rebase --squash --admin --repo {{.RepoName}} {{.PrNumber}}
- key: i
name: show ci checks
command: gh pr checks --repo {{.RepoName}} {{.PrNumber}} | glow -p
- key: e
name: edit pr
command: ~/.config/gh-dash/edit-gh-pr {{.RepoName}} {{.PrNumber}}
- key: E
name: open repo in emacs
command: emacsclient {{.RepoPath}} &
issues:
- key: v
name: view
command: gh issue view --repo {{.RepoName}} {{.IssueNumber}} | glow -p
- key: l
name: add label
command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --add-label $(gum choose bug enhancement question dependencies wontfix)
- key: L
name: remove label
command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --remove-label $(gum choose bug enhancement question dependencies wontfix)
- key: E
name: open repo in emacs
command: emacsclient {{.RepoPath}} &
theme:
ui:
sectionsShowCount: true
table:
compact: false
showSeparator: true
colors:
text:
primary: "#E2E1ED"
secondary: "#6770cb"
inverted: "#242347"
faint: "#b0793b"
warning: "#E0AF68"
success: "#3DF294"
background:
selected: "#1B1B33"
border:
primary: "#383B5B"
secondary: "#39386B"
faint: "#8d3e0b"
repoPaths:
:owner/:repo: ~/dev/:repo
pager:
diff: delta

View File

@@ -1,47 +0,0 @@
name: build-and-test
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
version: [1.21]
os: [ubuntu-latest, macos-latest]
name: Build
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.version }}
id: go
- name: checkout
uses: actions/checkout@v4
- name: build
run: go build
- name: test
run: make test
- name: Update coverage report
uses: ncruces/go-coverage-report@main
with:
report: true
chart: true
amend: true
if: |
matrix.os == 'ubuntu-latest' &&
github.event_name == 'push'
continue-on-error: true
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.21
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6

View File

@@ -1,34 +0,0 @@
name: build-push-image
on:
push:
tags:
- 'v*'
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: https://ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
with:
push: true
tags: ghcr.io/tlinden/anydb:${{ github.ref_name}}
- name: Build and push latest Docker image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
with:
push: true
tags: ghcr.io/tlinden/anydb:latest

6
.golangci.bck.yaml Normal file
View File

@@ -0,0 +1,6 @@
linters:
exclusions:
rules:
- linters:
- staticcheck
text: "QF1008:"

7
.golangci.yaml Normal file
View File

@@ -0,0 +1,7 @@
version: "2"
linters:
exclusions:
rules:
- linters:
- staticcheck
text: "QF1008:"

69
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,69 @@
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
before:
hooks:
- go mod tidy
gitea_urls:
api: https://codeberg.org/api/v1
download: https://codeberg.org
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
- freebsd
archives:
- formats: [tar.gz]
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
- goos: linux
formats: [tar.gz,binary]
files:
- src: "*.md"
strip_parent: true
- src: "docs/*"
strip_parent: true
- src: Makefile.dist
dst: Makefile
wrap_in_directory: true
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
groups:
- title: Improved
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
order: 0
- title: Fixed
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Changed
order: 999
release:
header: "# Release Notes"
footer: >-
---
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/epuppy/compare/{{ .PreviousTag }}...{{ .Tag }})

27
.woodpecker/build.yaml Normal file
View File

@@ -0,0 +1,27 @@
matrix:
platform:
- linux/amd64
goversion:
- 1.24
labels:
platform: ${platform}
steps:
build:
when:
event: [push]
image: golang:${goversion}
commands:
- go get
- go build
- go test
linter:
when:
event: [push]
image: golang:${goversion}
commands:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
- golangci-lint --version
- golangci-lint run ./...

32
.woodpecker/image.yaml Normal file
View File

@@ -0,0 +1,32 @@
# https://woodpecker-ci.org/plugins/docker-buildx
# enable Package unit and go to /scip/-/packages after building to link to proj
variables:
- &repo codeberg.org/${CI_REPO_OWNER}/anydb
steps:
dryrun:
image: docker.io/woodpeckerci/plugin-docker-buildx:latest
settings:
dockerfile: Dockerfile
platforms: linux/amd64
dry_run: true
repo: *repo
tags: latest
when:
event: [pull_request]
publish:
image: docker.io/woodpeckerci/plugin-docker-buildx:latest
settings:
dockerfile: Dockerfile
platforms: linux/amd64
repo: *repo
registry: codeberg.org
tags: latest,${CI_COMMIT_SHA:0:8},${CI_COMMIT_TAG}
username: ${CI_REPO_OWNER}
password:
from_secret: REGISTRY_TOKEN
when:
event: [tag]
branch: main

15
.woodpecker/release.yaml Normal file
View File

@@ -0,0 +1,15 @@
# build release
labels:
platform: linux/amd64
steps:
goreleaser:
image: goreleaser/goreleaser
when:
event: [tag]
environment:
GITEA_TOKEN:
from_secret: DEPLOY_TOKEN
commands:
- goreleaser release --clean --verbose

View File

@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
FROM golang:1.22-alpine as builder
FROM golang:1.24-alpine as builder
RUN apk update
RUN apk upgrade

View File

@@ -51,15 +51,15 @@ endif
app/dbentry.pb.go: app/dbentry.proto
protoc -I=. --go_out=app app/dbentry.proto
mv app/github.com/tlinden/anydb/app/dbentry.pb.go app/dbentry.pb.go
mv app/codeberg.org/scip/anydb/app/dbentry.pb.go app/dbentry.pb.go
rm -rf app/github.com
buildlocal:
go build -ldflags "-X 'github.com/tlinden/anydb/cfg.VERSION=$(VERSION)'"
go build -ldflags "-X 'codeberg.org/scip/anydb/cfg.VERSION=$(VERSION)'"
# binaries are being built by ci workflow on tag creation
release:
./mkrel.sh $(tool) $(version)
gh release create $(version) --generate-notes releases/*
gh release create $(version) --generate-notes
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
@@ -70,12 +70,12 @@ install: buildlocal
clean:
rm -rf $(tool) releases coverage.out
test:
test: clean
ANYDB_PASSWORD=test go test -v ./...
singletest:
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
ANYDB_PASSWORD=test go test -run $(TEST) github.com/tlinden/anydb/$(MOD)
ANYDB_PASSWORD=test go test -run $(TEST) codeberg.org/scip/anydb/$(MOD)
cover-report:
go test ./... -cover -coverprofile=coverage.out
@@ -103,3 +103,9 @@ lint:
lint-full:
golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn,stylecheck,testpackage,mirror,nestif,revive,goerr113,gomnd
gocritic check -enableAll *.go
demo:
make -C demo demo
.PHONY: demo

20
Makefile.dist Normal file
View File

@@ -0,0 +1,20 @@
# -*-make-*-
.PHONY: install all
tool = rpn
PREFIX = /usr/local
UID = root
GID = 0
all:
@echo "Type 'sudo make install' to install the tool."
@echo "To change prefix, type 'sudo make install PREFIX=/opt'"
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/

172
README.md
View File

@@ -1,11 +1,23 @@
## A personal key value store
[![Actions](https://github.com/tlinden/anydb/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/anydb/actions)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/anydb/blob/master/LICENSE)
[![Go Coverage](https://github.com/tlinden/anydb/wiki/coverage.svg)](https://raw.githack.com/wiki/tlinden/anydb/coverage.html)
[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/anydb)](https://goreportcard.com/report/github.com/tlinden/anydb)
[![GitHub release](https://img.shields.io/github/v/release/tlinden/anydb?color=%2300a719)](https://github.com/TLINDEN/anydb/releases/latest)
[![Documentation](https://img.shields.io/badge/manpage-documentation-blue)](https://github.com/TLINDEN/anydb/blob/master/anydb.pod)
[![status-badge](https://ci.codeberg.org/api/badges/15517/status.svg)](https://ci.codeberg.org/repos/15517)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/anydb/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/anydb)](https://goreportcard.com/report/codeberg.org/scip/anydb)
[![GitHub release](https://img.shields.io/github/v/release/tlinden/anydb?color=%2300a719)](https://codeberg.org/scip/anydb/releases)
[![Documentation](https://img.shields.io/badge/manpage-documentation-blue)](https://codeberg.org/scip/anydb/raw/branch/main/anydb.pod)
> [!CAUTION]
> Between version 0.1.0 and version 0.2.1 deletion of keys did not work. There
> is a unit test to check for this, but this unit test had a bug as well and
> didn't catch it. The bug and the test have been fixed. You are advised to
> upgrade to 0.2.1 and above.
> [!CAUTION]
> Version 0.1.3 introduced a [regression](https://codeberg.org/scip/anydb/issues/19),
> which caused the encryption feature not to work correctly anymore.
> If you are using anydb 0.1.3, you are urgently advised to
> upgrade to 0.2.0
Anydb is a simple to use commandline tool to store anything you'd
like, even binary files etc. It is a re-implementation of
@@ -34,147 +46,17 @@ And I wrote a very similar [tool](https://www.daemon.de/projects/dbtool/) 24 yea
**anydb** can do all the things you can do with skate:
```shell
# Store something (and sync it to the network)
anydb set kitty meow
# Fetch something (from the local cache)
anydb get kitty
# Whats in the store?
anydb list
# Spaces are fine
anydb set "kitty litter" "smells great"
# You can store binary data, too
anydb set profile-pic < my-cute-pic.jpg
anydb get profile-pic > here-it-is.jpg
# Unicode also works, of course
anydb set 猫咪 喵
anydb get 猫咪
# For more info
anydb --help
# Do creative things with anydb list
anydb set penelope marmalade
anydb set christian tacos
anydb set muesli muesli
anydb list | xargs -n 2 printf '%s loves %s.\n'
```
![simple demo](https://codeberg.org/scip/anydb/raw/branch/demo/intro.gif)
However, there are more features than just that!
```shell
# you can assign tags
anydb set foo bar -t note,important
# and filter for them
anydb list -t important
# beside tags filtering you can also use regexps for searching
# note, by default the list command only searches through keys
anydb list '[a-z]+\d'
# do a full text search
anydb list '[a-z]+\d' -s
# anydb also supports a wide output
anydb list -m wide
KEY TAGS SIZE AGE VALUE
blah important 4 B 7 seconds ago haha
foo 3 B 15 seconds ago bar
猫咪 3 B 3 seconds ago 喵
# there are shortcuts as well
anydb ls -l
anydb /
# other outputs are possible as well
anydb list -m json
# you can backup your database
anydb export -o backup.json
# and import it somewhere else
anydb import -i 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. 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 }}"
# or, to simulate skate's -k or -v
anydb ls -m template -T "{{ .Key }}"
anydb ls -m template -T "{{ .Value }}"
# maybe you want to digest the item in a shell script? also
# note, that both the list and get commands support templates
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created}}'")
echo "$key: $value"
# run the restful api server
anydb serve
# post a new key
curl -X PUT localhost:8787/anydb/v1/ \
-H 'Content-Type: application/json' \
-d '{"key":"foo","val":"bar"}'
# retrieve it
curl localhost:8787/anydb/v1/foo
# list keys
curl localhost:8787/anydb/v1/
# as you might correctly suspect you can store multi-line values or
# the content of text files. but what to do if you want to change it?
# here's one way:
anydb get contract24 > file.txt && vi file.txt && anydb set contract24 -r file.txt
# annoying. better do this
anydb edit contract24
# sometimes you need to know some details about the current database
# add -d for more details
anydb info
# it comes with a manpage builtin
anydb man
```
![advanced demo](https://codeberg.org/scip/anydb/raw/branch/demo/advanced.gif)
## Installation
There are multiple ways to install **anydb**:
- Go to the [latest release page](https://github.com/tlinden/anydb/releases/latest),
- Go to the [latest release page](https://codeberg.org/scip/anydb/releases),
locate the binary for your operating system and platform.
Download it and put it into some directory within your `$PATH` variable.
@@ -187,7 +69,7 @@ There are multiple ways to install **anydb**:
- You can also install from source. Issue the following commands in your shell:
```shell
git clone https://github.com/TLINDEN/anydb.git
git clone https://codeberg.org/scip/anydb.git
cd anydb
make
sudo make install
@@ -195,7 +77,7 @@ There are multiple ways to install **anydb**:
- Or, if you have the GO toolkit installed, just install it like this:
```shell
go install github.com/tlinden/anydb@latest
go install codeberg.org/scip/anydb@latest
```
If you do not find a binary release for your platform, please don't
@@ -222,14 +104,14 @@ Here, we operate in a local directory `mydb`, which we'll use as HOME
inside the docker container. anydb will store its database in
`mydb/.config/anydb/default.db`.
A list of available images is [here](https://github.com/tlinden/anydb/pkgs/container/anydb/versions?filters%5Bversion_type%5D=tagged)
A list of available images is [here](https://codeberg.org/scip/anydb/pkgs/container/anydb/versions?filters%5Bversion_type%5D=tagged)
## Documentation
The documentation is provided as a unix man-page. It will be
automatically installed if you install from source. However, you can
[read the man-page online](https://github.com/TLINDEN/anydb/blob/master/anydb.pod)
[read the man-page online](https://codeberg.org/scip/anydb/blob/master/anydb.pod)
Or if you cloned the repository you can read it this way (perl needs
to be installed though): `perldoc anydb.pod`.
@@ -246,7 +128,7 @@ best way for me to forget to do something.
In order to report a bug, unexpected behavior, feature requests or to
submit a patch, please open an issue on github:
https://github.com/TLINDEN/anydb/issues.
https://codeberg.org/scip/anydb/issues.
## Copyright and license
@@ -258,7 +140,7 @@ T.v.Dein <tom AT vondein DOT org>
## Project homepage
https://github.com/TLINDEN/anydb
https://codeberg.org/scip/anydb
## Copyright and License

20
TODO.md
View File

@@ -4,36 +4,16 @@
- mime-type => exec app + value
- add waitgroup to db.go funcs
- RestList does not support any params?
- lc() incoming tags+keys
## DB Structure
- put tags into sub bucket see #1
- change structure to:
data bucket
key => {key,value[0:60],isbin:bool}
value bucket
key => value (maybe always use []byte here)
tags bucket
key/tag => tag/key
tag/key => tag
So, list just uses the data bucket, no large contents.
A tag search only looksup matching tags, see #1.
Only a full text search and get would need to dig into the value bucket.
A delete would just delete all keys from all values and then:
lookup in tags bucket for all key/*, then iterate over the values and
remove all tag/key's. Then deleting a key would not leave any residue
behind.
However, maybe change the list command to just list everything and add
an extra find command for fulltext or tag search. Maybe still provide
filter options in list command but only filter for keys.
DONE: most of the above, except the tag stuff. manpage needs update and tests.
maybe stitch the find command and just add -f (full text search) to list.

40
anydb.1
View File

@@ -1,4 +1,4 @@
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "ANYDB 1"
.TH ANYDB 1 "2024-12-30" "1" "User Commands"
.TH ANYDB 1 "2025-02-11" "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
@@ -608,19 +608,23 @@ required, the template provided applies to every matching entry
separatley.
.PP
The following template variables can be used:
.IP "\fBKey\fR \- string" 4
.IX Item "Key - string"
.IP "\fB.Key\fR \- string" 4
.IX Item ".Key - string"
.PD 0
.IP "\fBValue\fR \- string" 4
.IX Item "Value - string"
.IP "\fBBin\fR \- []byte" 4
.IX Item "Bin - []byte"
.IP "\fBCreated\fR \- time.Time" 4
.IX Item "Created - time.Time"
.IP "\fBTags\fR \- []string" 4
.IX Item "Tags - []string"
.IP "\fBEncrypted\fR bool" 4
.IX Item "Encrypted bool"
.IP "\fB.Value\fR \- string" 4
.IX Item ".Value - string"
.IP "\fB.Bin\fR \- []byte" 4
.IX Item ".Bin - []byte"
.IP "\fB.Created\fR \- timestamp.Time" 4
.IX Item ".Created - timestamp.Time"
.PD
To retrieve a string representation of the timestamp, use \f(CW\*(C`.Created.AsTime\*(C'\fR.
If you need a unix timestamp since epoch, use \f(CW\*(C`.Created.Unix\*(C'\fR.
.IP "\fB.Tags\fR \- []string" 4
.IX Item ".Tags - []string"
.PD 0
.IP "\fB.Encrypted\fR bool" 4
.IX Item ".Encrypted bool"
.PD
.PP
Prepend a single dot (\*(L".\*(R") before each variable name.
@@ -637,14 +641,14 @@ Format the list in a way so that is possible to evaluate it in a
shell:
.PP
.Vb 2
\& eval $(anydb get foo \-m template \-T "key=\*(Aq{{ .Key }}\*(Aq value=\*(Aq{{ .Value }}\*(Aq ts=\*(Aq{{ .Created}}\*(Aq")
\& echo "Key: $key, Value: $value"
\& eval $(anydb get foo \-m template \-T "key=\*(Aq{{ .Key }}\*(Aq value=\*(Aq{{ .Value }}\*(Aq ts=\*(Aq{{ .Created.AsTime}}\*(Aq")
\& echo "Key: $key, Value: $value, When: $ts"
.Ve
.PP
Print the values in \s-1CSV\s0 format \s-1ONLY\s0 if they have some tag:
.PP
.Vb 1
\& anydb list \-m template \-T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"
\& anydb list \-m template \-T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created.AsTime}}{{ end }}"
.Ve
.SH "CONFIGURATION"
.IX Header "CONFIGURATION"
@@ -742,7 +746,7 @@ List all keys:
.IX Header "BUGS"
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
<https://github.com/TLINDEN/anydb/issues>.
<https://codeberg.org/scip/anydb/issues>.
.PP
Please repeat the failing command with debugging enabled \f(CW\*(C`\-d\*(C'\fR and
include the output in the issue.

View File

@@ -448,17 +448,20 @@ The following template variables can be used:
=over
=item B<Key> - string
=item B<.Key> - string
=item B<Value> - string
=item B<.Value> - string
=item B<Bin> - []byte
=item B<.Bin> - []byte
=item B<Created> - time.Time
=item B<.Created> - timestamp.Time
=item B<Tags> - []string
To retrieve a string representation of the timestamp, use C<.Created.AsTime>.
If you need a unix timestamp since epoch, use C<.Created.Unix>.
=item B<Encrypted> bool
=item B<.Tags> - []string
=item B<.Encrypted> bool
=back
@@ -473,12 +476,12 @@ Only show the keys of all entries:
Format the list in a way so that is possible to evaluate it in a
shell:
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created}}'")
echo "Key: $key, Value: $value"
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created.AsTime}}'")
echo "Key: $key, Value: $value, When: $ts"
Print the values in CSV format ONLY if they have some tag:
anydb list -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"
anydb list -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created.AsTime}}{{ end }}"
=head1 CONFIGURATION
@@ -580,7 +583,7 @@ List all keys:
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
L<https://github.com/TLINDEN/anydb/issues>.
L<https://codeberg.org/scip/anydb/issues>.
Please repeat the failing command with debugging enabled C<-d> and
include the output in the issue.

View File

@@ -33,6 +33,9 @@ type DbAttr struct {
File string
Encrypted bool
Binary bool
// conf flags, needed for incoming rest requests
Fulltext bool
}
// check if value is to be read from a file or stdin, setup preview

View File

@@ -20,6 +20,7 @@ import (
"crypto/rand"
"errors"
"fmt"
"log/slog"
"os"
"syscall"
@@ -34,7 +35,7 @@ const (
ArgonParallel uint8 = 2
ArgonSaltLen int = 16
ArgonKeyLen uint32 = 32
B64SaltLen int = 22
B64SaltLen int = 16 //22
)
type Key struct {
@@ -84,7 +85,11 @@ func DeriveKey(password []byte, salt []byte) (*Key, error) {
ArgonKeyLen,
)
return &Key{Key: hash, Salt: salt}, nil
key := &Key{Key: hash, Salt: salt}
slog.Debug("derived key", "key", string(key.Key), "salt", string(key.Salt))
return key, nil
}
// Retrieve a random chunk of given size
@@ -124,10 +129,13 @@ func Encrypt(pass []byte, attr *DbAttr) error {
cipher := aead.Seal(nonce, nonce, attr.Val, nil)
attr.Val = append(attr.Val, key.Salt...)
attr.Val = key.Salt
attr.Val = append(attr.Val, cipher...)
attr.Encrypted = true
attr.Preview = "<encrypted-content>"
slog.Debug("encrypted attr", "salt", string(key.Salt), "cipher", string(attr.Val))
return nil
}
@@ -156,5 +164,12 @@ func Decrypt(pass []byte, cipherb []byte) ([]byte, error) {
nonce, ciphertext := cipher[:aead.NonceSize()], cipher[aead.NonceSize():]
return aead.Open(nil, nonce, ciphertext, nil)
clear, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
slog.Debug("decrypted attr", "salt", string(key.Salt), "clear", string(clear))
return clear, err
}

147
app/db.go
View File

@@ -20,12 +20,16 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"
"time"
common "codeberg.org/scip/anydb/common"
bolt "go.etcd.io/bbolt"
"google.golang.org/protobuf/proto"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
@@ -59,20 +63,51 @@ type DbTag struct {
Keys []string `json:"key"`
}
func (entry *DbEntry) Taglist() string {
return strings.Join(entry.Tags, ",")
}
const BucketData string = "data"
func GetDbFile(file string) string {
if file != "" {
return file
}
file = os.Getenv("ANYDB_DB")
if file != "" {
return file
}
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
return filepath.Join(home, ".config", "anydb", "default.db")
}
func New(file string, bucket string, debug bool) (*DB, error) {
return &DB{Debug: debug, Dbfile: file, Bucket: bucket}, nil
}
func (db *DB) Open() error {
slog.Debug("opening DB", "dbfile", db.Dbfile)
if _, err := os.Stat(filepath.Dir(db.Dbfile)); os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(db.Dbfile), 0700); err != nil {
return err
}
}
b, err := bolt.Open(db.Dbfile, 0600, nil)
var opts *bolt.Options
if db.Debug {
log := common.Slogger{Logger: slog.Default()}
opts = &bolt.Options{Logger: log}
}
b, err := bolt.Open(db.Dbfile, 0600, opts)
if err != nil {
return fmt.Errorf("failed to open DB %s: %w", db.Dbfile, err)
}
@@ -81,8 +116,10 @@ func (db *DB) Open() error {
return nil
}
func (db *DB) Close() error {
return db.DB.Close()
func (db *DB) Close() {
if err := db.DB.Close(); err != nil {
log.Fatal(err)
}
}
func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
@@ -95,20 +132,30 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
var filter *regexp.Regexp
if len(attr.Args) > 0 {
// via cli
filter = regexp.MustCompile(attr.Args[0])
}
if len(attr.Key) > 0 {
// via api
filter = regexp.MustCompile(attr.Key)
}
err := db.DB.View(func(tx *bolt.Tx) error {
root := tx.Bucket([]byte(db.Bucket))
if root == nil {
return nil
}
slog.Debug("opened root bucket", "root", root)
bucket := root.Bucket([]byte("meta"))
if bucket == nil {
return nil
}
slog.Debug("opened buckets", "root", root, "data", bucket)
databucket := root.Bucket([]byte("data"))
if databucket == nil {
return fmt.Errorf("failed to retrieve data sub bucket")
@@ -120,7 +167,13 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
return fmt.Errorf("failed to unmarshal from protobuf: %w", err)
}
entry.Value = databucket.Get([]byte(entry.Key)) // empty is ok
if fulltext {
// avoid crash due to access fault
value := databucket.Get([]byte(entry.Key)) // empty is ok
vc := make([]byte, len(value))
copy(vc, value)
entry.Value = string(vc)
}
var include bool
@@ -185,45 +238,29 @@ func (db *DB) Set(attr *DbAttr) error {
// check if the entry already exists and if yes, check if it has
// any tags. if so, we initialize our update struct with these
// tags unless it has new tags configured.
err := db.DB.View(func(tx *bolt.Tx) error {
root := tx.Bucket([]byte(db.Bucket))
if root == nil {
return nil
}
bucket := root.Bucket([]byte("meta"))
if bucket == nil {
return nil
}
pbentry := bucket.Get([]byte(entry.Key))
if pbentry == nil {
return nil
}
var oldentry DbEntry
if err := proto.Unmarshal(pbentry, &oldentry); err != nil {
return fmt.Errorf("failed to unmarshal from protobuf: %w", err)
slog.Debug("+++ GET")
oldentry, err := db.txGet(attr)
if err != nil {
if !strings.Contains(err.Error(), "no such key") {
return err
}
}
if oldentry != nil {
if len(oldentry.Tags) > 0 && len(entry.Tags) == 0 {
// initialize update entry with tags from old entry
entry.Tags = oldentry.Tags
}
return nil
})
if err != nil {
return err
}
slog.Debug("+++ MARSHAL")
// marshall our data
pbentry, err := proto.Marshal(&entry)
if err != nil {
return fmt.Errorf("failed to marshall protobuf: %w", err)
}
slog.Debug("+++ UPDATE")
err = db.DB.Update(func(tx *bolt.Tx) error {
// create root bucket
root, err := tx.CreateBucketIfNotExists([]byte(db.Bucket))
@@ -237,6 +274,8 @@ func (db *DB) Set(attr *DbAttr) error {
return fmt.Errorf("failed to create DB meta sub bucket: %w", err)
}
slog.Debug("opened/created buckets", "root", root, "data", bucket)
// write meta data
err = bucket.Put([]byte(entry.Key), []byte(pbentry))
if err != nil {
@@ -265,12 +304,10 @@ func (db *DB) Set(attr *DbAttr) error {
return nil
}
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
if err := db.Open(); err != nil {
return nil, err
}
defer db.Close()
// internal DB getter, assumes db.DB has already been
// opened successfully. Do NOT call this w/o valid
// DB handle!
func (db *DB) txGet(attr *DbAttr) (*DbEntry, error) {
entry := DbEntry{}
err := db.DB.View(func(tx *bolt.Tx) error {
@@ -286,6 +323,8 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
return nil
}
slog.Debug("opened buckets", "root", root, "data", bucket)
// retrieve meta data
pbentry := bucket.Get([]byte(attr.Key))
if pbentry == nil {
@@ -314,32 +353,54 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
vc := make([]byte, len(value))
copy(vc, value)
entry.Value = vc
entry.Value = string(vc)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to read from DB: %w", err)
return nil, err
}
return &entry, nil
}
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
if err := db.Open(); err != nil {
return nil, err
}
defer db.Close()
entry, err := db.txGet(attr)
if err != nil {
return nil, fmt.Errorf("failed to read from DB: %w", err)
}
return entry, nil
}
func (db *DB) Del(attr *DbAttr) error {
// FIXME: check if it exists prior to just call bucket.Delete()?
if err := db.Open(); err != nil {
return err
}
defer db.Close()
err := db.DB.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(db.Bucket))
// root bucket
root := tx.Bucket([]byte(db.Bucket))
if root == nil {
return nil
}
// get data sub bucket
bucket := root.Bucket([]byte("meta"))
if bucket == nil {
return nil
}
slog.Debug("opened buckets", "data", bucket)
return bucket.Delete([]byte(attr.Key))
})
@@ -392,6 +453,8 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
return fmt.Errorf("failed to create DB meta sub bucket: %w", err)
}
slog.Debug("opened buckets", "root", root, "data", bucket)
for _, entry := range entries {
pbentry, err := proto.Marshal(entry)
if err != nil {
@@ -411,7 +474,7 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
}
// write value
err = databucket.Put([]byte(entry.Key), entry.Value)
err = databucket.Put([]byte(entry.Key), []byte(entry.Value))
if err != nil {
return fmt.Errorf("failed to insert data: %w", err)
}
@@ -499,6 +562,8 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) {
return fmt.Errorf("failed to retrieve data sub bucket")
}
slog.Debug("opened buckets", "root", root, "data", bucket)
// iterate over all db entries in meta sub bucket
err := bucket.ForEach(func(key, pbentry []byte) error {
var entry DbEntry
@@ -514,7 +579,7 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) {
vc := make([]byte, len(value))
copy(vc, value)
entry.Value = vc
entry.Value = string(vc)
entries = append(entries, &entry)
return nil

View File

@@ -2,8 +2,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.1
// protoc v3.21.12
// protoc-gen-go v1.36.5
// protoc v4.24.4
// source: app/dbentry.proto
package app
@@ -14,6 +14,7 @@ import (
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -33,7 +34,7 @@ type DbEntry struct {
Size uint64 `protobuf:"varint,6,opt,name=Size,proto3" json:"Size,omitempty"`
Encrypted bool `protobuf:"varint,7,opt,name=Encrypted,proto3" json:"Encrypted,omitempty"`
Binary bool `protobuf:"varint,8,opt,name=Binary,proto3" json:"Binary,omitempty"`
Value []byte `protobuf:"bytes,9,opt,name=Value,proto3" json:"Value,omitempty"`
Value string `protobuf:"bytes,9,opt,name=Value,proto3" json:"Value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -124,16 +125,16 @@ func (x *DbEntry) GetBinary() bool {
return false
}
func (x *DbEntry) GetValue() []byte {
func (x *DbEntry) GetValue() string {
if x != nil {
return x.Value
}
return nil
return ""
}
var File_app_dbentry_proto protoreflect.FileDescriptor
var file_app_dbentry_proto_rawDesc = []byte{
var file_app_dbentry_proto_rawDesc = string([]byte{
0x0a, 0x11, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x62, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x70, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
@@ -152,20 +153,20 @@ var file_app_dbentry_proto_rawDesc = []byte{
0x28, 0x08, 0x52, 0x09, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a,
0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x42,
0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1e, 0x5a, 0x1c, 0x67,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1e, 0x5a, 0x1c, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6c, 0x69, 0x6e, 0x64, 0x65,
0x6e, 0x2f, 0x61, 0x6e, 0x79, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
})
var (
file_app_dbentry_proto_rawDescOnce sync.Once
file_app_dbentry_proto_rawDescData = file_app_dbentry_proto_rawDesc
file_app_dbentry_proto_rawDescData []byte
)
func file_app_dbentry_proto_rawDescGZIP() []byte {
file_app_dbentry_proto_rawDescOnce.Do(func() {
file_app_dbentry_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dbentry_proto_rawDescData)
file_app_dbentry_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dbentry_proto_rawDesc), len(file_app_dbentry_proto_rawDesc)))
})
return file_app_dbentry_proto_rawDescData
}
@@ -193,7 +194,7 @@ func file_app_dbentry_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_dbentry_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dbentry_proto_rawDesc), len(file_app_dbentry_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
@@ -204,7 +205,6 @@ func file_app_dbentry_proto_init() {
MessageInfos: file_app_dbentry_proto_msgTypes,
}.Build()
File_app_dbentry_proto = out.File
file_app_dbentry_proto_rawDesc = nil
file_app_dbentry_proto_goTypes = nil
file_app_dbentry_proto_depIdxs = nil
}

View File

@@ -5,7 +5,7 @@ package app;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/tlinden/anydb/app";
option go_package = "codeberg.org/scip/anydb/app";
message DbEntry {
string Id = 1;
@@ -16,5 +16,5 @@ message DbEntry {
uint64 Size = 6;
bool Encrypted = 7;
bool Binary = 8;
bytes Value = 9;
string Value = 9;
}

View File

@@ -20,8 +20,7 @@ import "os"
func cleanError(file string, err error) error {
// remove given [backup] file and forward the given error
os.Remove(file)
return err
return os.Remove(file)
}
func fileExists(filename string) bool {

View File

@@ -1,5 +1,5 @@
/*
Copyright © 2024 Thomas von Dein
Copyright © 2025 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
@@ -22,11 +22,11 @@ import (
"os"
"github.com/pelletier/go-toml"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/common"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/common"
)
var Version string = "v0.1.0"
var Version string = "v0.2.6"
type BucketConfig struct {
Encrypt bool

View File

@@ -411,12 +411,16 @@ TEMPLATES
The following template variables can be used:
Key - string
Value - string
Bin - []byte
Created - time.Time
Tags - []string
Encrypted bool
.Key - string
.Value - string
.Bin - []byte
.Created - timestamp.Time
To retrieve a string representation of the timestamp, use
".Created.AsTime". If you need a unix timestamp since epoch, use
".Created.Unix".
.Tags - []string
.Encrypted bool
Prepend a single dot (".") before each variable name.
@@ -428,12 +432,12 @@ TEMPLATES
Format the list in a way so that is possible to evaluate it in a shell:
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created}}'")
echo "Key: $key, Value: $value"
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created.AsTime}}'")
echo "Key: $key, Value: $value, When: $ts"
Print the values in CSV format ONLY if they have some tag:
anydb list -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"
anydb list -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created.AsTime}}{{ end }}"
CONFIGURATION
Anydb looks at the following locations for a configuration file, in that
@@ -513,7 +517,7 @@ REST API
BUGS
In order to report a bug, unexpected behavior, feature requests or to
submit a patch, please open an issue on github:
<https://github.com/TLINDEN/anydb/issues>.
<https://codeberg.org/scip/anydb/issues>.
Please repeat the failing command with debugging enabled "-d" and
include the output in the issue.

View File

@@ -22,9 +22,9 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"github.com/tlinden/anydb/output"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
"codeberg.org/scip/anydb/output"
)
func Set(conf *cfg.Config) *cobra.Command {
@@ -118,12 +118,13 @@ func Get(conf *cfg.Config) *cobra.Command {
return err
}
clear, err := app.Decrypt(pass, entry.Value)
clear, err := app.Decrypt(pass, []byte(entry.Value))
if err != nil {
return err
}
entry.Value = clear
entry.Value = string(clear)
entry.Size = uint64(len(entry.Value))
entry.Encrypted = false
}
@@ -155,7 +156,7 @@ func Del(conf *cfg.Config) *cobra.Command {
Long: `Delete key and value matching key`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("No key specified")
return errors.New("no key specified")
}
// errors at this stage do not cause the usage to be shown
@@ -182,7 +183,7 @@ func List(conf *cfg.Config) *cobra.Command {
)
var cmd = &cobra.Command{
Use: "list [<filter-regex> | -t <tag> ] [-m <mode>] [-nNif] [-T <tpl>]",
Use: "list [<filter-regex> | -t <tag> ] [-m <mode>] [-nNis] [-T <tpl>]",
Short: "List database contents",
Long: `List database contents`,
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -21,14 +21,15 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"github.com/tlinden/anydb/output"
"github.com/tlinden/anydb/rest"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
"codeberg.org/scip/anydb/output"
"codeberg.org/scip/anydb/rest"
)
func Export(conf *cfg.Config) *cobra.Command {
@@ -216,12 +217,12 @@ func Edit(conf *cfg.Config) *cobra.Command {
}
password = pass
clear, err := app.Decrypt(pass, entry.Value)
clear, err := app.Decrypt(pass, []byte(entry.Value))
if err != nil {
return err
}
entry.Value = clear
entry.Value = string(clear)
entry.Encrypted = false
}
@@ -285,7 +286,11 @@ func editContent(editor string, content string) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to create templ file: %w", err)
}
defer os.Remove(tmp.Name())
defer func() {
if err := os.Remove(tmp.Name()); err != nil {
log.Fatal(err)
}
}()
// put the content into a tmp file
_, err = tmp.WriteString(content)
@@ -310,7 +315,11 @@ func editContent(editor string, content string) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to open temp file: %w", err)
}
defer modified.Close()
defer func() {
if err := modified.Close(); err != nil {
log.Fatal(err)
}
}()
newcontent, err := io.ReadAll(modified)
if err != nil {

View File

@@ -19,13 +19,15 @@ package cmd
import (
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime/debug"
"github.com/alecthomas/repr"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
"github.com/tlinden/yadu"
)
func completion(cmd *cobra.Command, mode string) error {
@@ -67,12 +69,6 @@ func Execute() {
Short: "anydb",
Long: `A personal key value store`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
db, err := app.New(conf.Dbfile, conf.Dbbucket, conf.Debug)
if err != nil {
return err
}
conf.DB = db
var configs []string
if configfile != "" {
@@ -86,9 +82,34 @@ func Execute() {
}
if conf.Debug {
repr.Println(conf)
buildInfo, _ := debug.ReadBuildInfo()
opts := &yadu.Options{
Level: slog.LevelDebug,
AddSource: true,
}
slog.SetLogLoggerLevel(slog.LevelDebug)
handler := yadu.NewHandler(os.Stdout, opts)
debuglogger := slog.New(handler).With(
slog.Group("program_info",
slog.Int("pid", os.Getpid()),
slog.String("go_version", buildInfo.GoVersion),
),
)
slog.SetDefault(debuglogger)
slog.Debug("parsed config", "conf", conf)
}
dbfile := app.GetDbFile(conf.Dbfile)
db, err := app.New(dbfile, conf.Dbbucket, conf.Debug)
if err != nil {
return err
}
conf.DB = db
return nil
},
@@ -114,7 +135,7 @@ func Execute() {
rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "v", false, "Print program version")
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging")
rootCmd.PersistentFlags().StringVarP(&conf.Dbfile, "dbfile", "f",
filepath.Join(home, ".config", "anydb", "default.db"), "DB file to use")
"", "DB file to use (default: ~/.config/anydb/default.db)")
rootCmd.PersistentFlags().StringVarP(&conf.Dbbucket, "bucket", "b",
app.BucketData, "use other bucket (default: "+app.BucketData+")")
rootCmd.PersistentFlags().StringVarP(&configfile, "config", "c", "", "toml config file")

View File

@@ -20,8 +20,7 @@ import "os"
func CleanError(file string, err error) error {
// remove given [backup] file and forward the given error
os.Remove(file)
return err
return os.Remove(file)
}
func FileExists(filename string) bool {

23
common/logger.go Normal file
View File

@@ -0,0 +1,23 @@
package common
import (
"fmt"
"log/slog"
)
type Slogger struct {
*slog.Logger
}
func (l Slogger) Debug(v ...interface{}) {}
func (l Slogger) Debugf(format string, v ...interface{}) { l.Logger.Debug(fmt.Sprintf(format, v...)) }
func (l Slogger) Error(v ...interface{}) {}
func (l Slogger) Errorf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }
func (l Slogger) Info(v ...interface{}) {}
func (l Slogger) Infof(format string, v ...interface{}) { l.Logger.Info(fmt.Sprintf(format, v...)) }
func (l Slogger) Warning(v ...interface{}) {}
func (l Slogger) Warningf(format string, v ...interface{}) { l.Logger.Warn(fmt.Sprintf(format, v...)) }
func (l Slogger) Fatal(v ...interface{}) {}
func (l Slogger) Fatalf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }
func (l Slogger) Panic(v ...interface{}) {}
func (l Slogger) Panicf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }

95
contrib/anydbui Executable file
View File

@@ -0,0 +1,95 @@
#!/bin/sh
template="{{.Key}} {{.Created.AsTime.Year}}-{{.Created.AsTime.Month}}-{{.Created.AsTime.Day}} {{.Taglist}} {{.Preview}}"
header="TITLE DATE TAGS PREVIEW"
# its possible to use another version of anydb for testing purposes
anydb="${ANYDB:-anydb}"
# list command
command="( echo '$header'; $anydb ls -m template --template '$template' ) | column -t -l4"
for binary in fzf $anydb column diff awk less; do
if ! type $binary > /dev/null 2>&1; then
echo "$binary is not installed!"
exit 1
fi
done
if type gum > /dev/null 2>&1; then
GUM=1
fi
_rand() {
awk 'BEGIN {srand(); printf( "%d\n", 1024 * rand() )}'
}
_list() {
(
echo "$header"
$anydb ls -m template --template "$template"
) | column -t -l4
}
_updater() {
_port="$1"
_cache="/tmp/anydbcache.$port"
_current="/tmp/anydbcache.$port.current"
touch $_current $_cache
while :; do
sleep 10
_list > $_current
if ! diff -q $_current $_cache > /dev/null 2>&1; then
curl -d "reload($command)+clear-screen" "http://127.0.0.1:$_port"
fi
cp $_current $_cache
done
}
_cleanup() {
# get rid of the update child
kill $pid
# clean up reloader cache
rm -f /tmp/anydbcache.${port}*
}
# fork background updater
port=$((8000 + $(_rand) % 1000))
_updater $port &
pid=$!
db=$($anydb info | grep Database | cut -d: -f2)
shorthelp="$db - [enter]read [c-e]edit [c-k]kill [c-c]exit [c-k]delete"
if test -n "$GUM"; then
color="Color \"#ff\" \"#0000ff\"" # white on blue
db="{{ $color \"Database:\"}}$db"
enter="{{ $color \"[Enter]\"}} Read"
edit="{{ $color \"[ctrl-c]\"}} Edit"
delete="{{ $color \"[ctrl-k]\"}} Delete"
template="$db $enter $edit $delete"
shorthelp=$(echo "$template" | gum format -t template)
fi
trap '_cleanup; (exit $?); exit' INT TERM EXIT
: | command="$command" fzf \
--bind "start:reload:$command" \
--header-lines 1 --layout=reverse --info=inline \
--height 100% --pointer="→" --separator="─" \
--scrollbar="│" --preview-window "right:50%" \
--border-label="$shorthelp" \
--border=bottom --layout=reverse --info=inline \
--height 100% --header-first --cycle --header-lines=1 --listen=$port \
--bind "enter:execute: clear; $anydb get {1} | less" \
--bind "ctrl-e:execute: $anydb edit {1}" \
--bind "ctrl-k:execute:$anydb del {1}"

21
demo/Makefile Normal file
View File

@@ -0,0 +1,21 @@
.PHONY: demo clean check clean-demo
VHS = vhs
clean-demo:
rm -f local.db*
%.gif: %.tape
@echo "vhs $<"
env PATH=..:$(PATH) ANYDB_DB=local.db vhs $<
clean:
rm -vf *.db* *.json
check:
ls -l ../anydb
demo: check clean-demo intro.gif advanced.gif

BIN
demo/advanced.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

182
demo/advanced.tape Normal file
View File

@@ -0,0 +1,182 @@
# -*-sh-*-
Output advanced.gif
Set FontSize 20
Set Width 1000
Set Height 800
Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "magenta": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightMagenta": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selection": "#3d3c58", "cursor": "#b3b0d6" }
Set WindowBar Colorful
Set BorderRadius 10
Set Shell zsh
Set FontFamily "IBM Plex Mono"
Set CursorBlink false
Set PlaybackSpeed 1
Set TypingSpeed .05
Hide
Type `PROMPT=''`
Enter
Type "setopt interactivecomments"
Enter
Type "autoload -U colors && colors"
Enter
Type `PS1="%{$fg[magenta]%}demo> %{$reset_color%}"`
Enter
Type "clear"
Enter
Show
Type "# you can assign tags"
Enter
Sleep 1s
Type "anydb set foo bar -t note,important"
Enter
Sleep 3s
Enter
Type "# and filter for them"
Enter
Sleep 1s
Type "anydb list -t important"
Enter
Sleep 3s
Enter
Type "# beside tags filtering you can also use regexps for searching"
Enter
Type "# note, by default the list command only searches through keys"
Enter
Sleep 1s
Type "anydb list '[a-z]+'"
Enter
Sleep 3s
Enter
Type "# do a full text search"
Enter
Sleep 1s
Type "anydb list '[a-z]+' -s"
Enter
Sleep 3s
Enter
Type "# anydb also supports a wide output"
Enter
Sleep 1s
Type "anydb list -m wide"
Enter
Sleep 3s
Enter
Type "# there are shortcuts as well"
Enter
Sleep 1s
Type "anydb ls -l"
Enter
Sleep 2s
Type "anydb /"
Enter
Sleep 3s
Enter
Type "# other outputs are possible as well"
Enter
Sleep 1s
Type "anydb list -m json"
Enter
Sleep 3s
Enter
Type "# you can backup your database"
Enter
Sleep 1s
Type "anydb export -o backup.json"
Enter
Sleep 3s
Enter
Type "# and import it somewhere else"
Enter
Sleep 1s
Type "rm local.db"
Enter
Sleep 1s
Type "anydb ls -l"
Enter
Sleep 1s
Type "anydb import -i backup.json"
Enter
Sleep 1s
Type "anydb ls -l"
Enter
Sleep 3s
Enter
Type "# you can encrypt entries. anydb asks for a passphrase"
Enter
Type "# and will do the same when you retrieve the key using the"
Enter
Type "# get command. anydb will ask you interactively for a password"
Enter
Sleep 1s
Type "anydb set address 'Beatstreet 42' -e"
Enter
Type "pass"
Enter
Sleep 3s
Enter
Type "# but you can provide it via an environment variable too"
Enter
Sleep 1s
Type "ANYDB_PASSWORD=foo anydb set -e secretkey blahblah"
Enter
Sleep 3s
Enter
Type "# using template output mode you can freely design how to print stuff"
Enter
Type "# here, we print the values in CSV format ONLY if they have some tag"
Enter
Type "# also note, that we're printing the creation timestamp as epoch"
Sleep 1s
Type `anydb ls -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created.AsTime.Unix}}{{ end }}"`
Enter
Sleep 3s
Enter
Type "# or, to simulate skate's -k or -v"
Enter
Sleep 1s
Type `anydb ls -m template -T "{{ .Key }}"`
Enter
Sleep 1s
Type `anydb ls -m template -T "{{ .Value }}"`
Enter
Sleep 3s
Enter
Type "# maybe you want to digest the item in a shell script? also"
Enter
Type "# note, that both the list and get commands support templates"
Enter
Sleep 1s
Type `eval $(anydb get kitty -m template -T "value='{{ .Value }}'"); echo "value: $value"`
Enter
Sleep 3s
Enter
Type "# sometimes you need to know some details about the current database"
Enter
Type "# add -d for more details"
Enter
Sleep 1
Type "anydb info"
Enter
Sleep 3s
Enter
Type "# Try it out yourself: codeberg.org/scip/anydb!"
Enter
Sleep 4s

BIN
demo/intro.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

76
demo/intro.tape Normal file
View File

@@ -0,0 +1,76 @@
# -*-sh-*-
Output intro.gif
Set FontSize 20
Set Width 1000
Set Height 800
Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "magenta": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightMagenta": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selection": "#3d3c58", "cursor": "#b3b0d6" }
Set WindowBar Colorful
Set BorderRadius 10
Set Shell zsh
Set FontFamily "IBM Plex Mono"
Set CursorBlink false
Set PlaybackSpeed 1
Set TypingSpeed .05
Hide
Type `PROMPT=''`
Enter
Type "setopt interactivecomments"
Enter
Type "autoload -U colors && colors"
Enter
Type `PS1="%{$fg[magenta]%}demo> %{$reset_color%}"`
Enter
Type "clear"
Enter
Show
Type "# Store something"
Enter
Sleep 1s
Type "anydb set kitty meow"
Enter
Sleep 3s
Enter
Type `# What's in the store?`
Enter
Sleep 1s
Type "anydb ls"
Enter
Sleep 3s
Enter
Type "# Fetch something"
Enter
Sleep 1s
Type "anydb get kitty"
Enter
Sleep 3s
Enter
Type "# Unicode also works, of course"
Enter
Sleep 1s
Type "anydb set 猫咪 喵"
Enter
Sleep 2s
Type "anydb get 猫咪"
Enter
Sleep 3s
Enter
Type "# Do creative things with anydb list"
Enter
Sleep 1s
Type "anydb set penelope marmalade"
Enter
Type "anydb set christian tacos"
Enter
Type "anydb set muesli muesli"
Enter
Type "anydb list | xargs -n 2 printf '%s loves %s.\n'"
Enter
Sleep 3s

53
go.mod
View File

@@ -1,33 +1,40 @@
module github.com/tlinden/anydb
module codeberg.org/scip/anydb
go 1.22.1
go 1.24.0
toolchain go1.24.1
require (
github.com/alecthomas/repr v0.4.0 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gofiber/fiber/v2 v2.52.5 // indirect
github.com/gofiber/fiber/v3 v3.0.0-beta.3 // indirect
github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect
github.com/dustin/go-humanize v1.0.1
github.com/gofiber/fiber/v2 v2.52.9
github.com/inconshreveable/mousetrap v1.1.0
github.com/olekukonko/tablewriter v1.1.0
github.com/pelletier/go-toml v1.9.5
github.com/rogpeppe/go-internal v1.14.1
github.com/spf13/cobra v1.10.1
github.com/tlinden/yadu v0.1.3
go.etcd.io/bbolt v1.4.3
golang.org/x/crypto v0.42.0
golang.org/x/term v0.35.0
google.golang.org/protobuf v1.36.9
)
require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
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/mattn/go-runewidth v0.0.16 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // 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
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.55.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
go.etcd.io/bbolt v1.3.11 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

123
go.sum
View File

@@ -1,96 +1,75 @@
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/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/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/fiber/v3 v3.0.0-beta.3 h1:7Q2I+HsIqnIEEDB+9oe7Gadpakh6ZLhXpTYz/L20vrg=
github.com/gofiber/fiber/v3 v3.0.0-beta.3/go.mod h1:kcMur0Dxqk91R7p4vxEpJfDWZ9u5IfvrtQc8Bvv/JmY=
github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co=
github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/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-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

25
main.go
View File

@@ -19,17 +19,40 @@ package main
import (
"bufio"
"fmt"
"log/slog"
"os"
"runtime"
"github.com/inconshreveable/mousetrap"
"github.com/tlinden/anydb/cmd"
"codeberg.org/scip/anydb/cmd"
)
func main() {
const NoLogsLevel = 100
slog.SetLogLoggerLevel(NoLogsLevel)
Main()
}
func init() {
// if we're running on Windows AND if the user double clicked the
// exe file from explorer, we tell them and then wait until any
// key has been hit, which will make the cmd window disappear and
// thus give the user time to read it.
if runtime.GOOS == "windows" {
if mousetrap.StartedByExplorer() {
fmt.Println("Do no double click anydb.exe!")
fmt.Println("Please open a command shell and run it from there.")
fmt.Println()
fmt.Print("Press any key to quit: ")
_, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
panic(err)
}
}
}
}
func Main() int {
cmd.Execute()
return 0

View File

@@ -17,16 +17,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"os"
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"anydb": Main,
}))
testscript.Main(m, map[string]func(){
"anydb": main,
})
}
func TestAnydb(t *testing.T) {

View File

@@ -52,8 +52,15 @@ for D in $DIST; do
tardir="${tool}-${os}-${arch}-${version}"
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
if test "$D" = "linux/amd64"; then
pie="-buildmode=pie"
fi
set -x
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o ${binfile}
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static -w" --trimpath $pie -o ${binfile}
strip --strip-all ${binfile}
mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = anydb

View File

@@ -21,8 +21,8 @@ import (
"fmt"
"os"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
)
func WriteJSON(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {

View File

@@ -1,5 +1,5 @@
/*
Copyright © 2024 Thomas von Dein
Copyright © 2025 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
@@ -28,12 +28,13 @@ import (
"github.com/dustin/go-humanize"
"github.com/olekukonko/tablewriter"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
)
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
// FIXME: call sort here
switch conf.Mode {
case "wide", "", "table":
return ListTable(writer, conf, entries)
@@ -72,7 +73,9 @@ func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) err
}
if buf.Len() > 0 {
fmt.Fprintln(writer, buf.String())
if _, err := fmt.Fprintln(writer, buf.String()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
}
@@ -81,13 +84,46 @@ func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) err
func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
styleTSV := tw.NewSymbolCustom("space").WithColumn("\t")
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(tw.Rendition{
Borders: tw.BorderNone,
Symbols: styleTSV,
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.On},
Lines: tw.Lines{ShowFooterLine: tw.Off, ShowHeaderLine: tw.Off},
},
})),
tablewriter.WithConfig(tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoFormat: tw.Off,
},
Padding: tw.CellPadding{
Global: tw.Padding{Left: "", Right: ""},
},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoWrap: tw.WrapNone,
Alignment: tw.AlignLeft,
},
Padding: tw.CellPadding{
Global: tw.Padding{Left: "", Right: ""},
},
},
}),
tablewriter.WithPadding(tw.PaddingNone),
)
if !conf.NoHeaders {
if conf.Mode == "wide" {
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "UPDATED", "VALUE"})
table.Header([]string{"KEY", "TAGS", "SIZE", "UPDATED", "VALUE"})
} else {
table.SetHeader([]string{"KEY", "VALUE"})
table.Header([]string{"KEY", "VALUE"})
}
}
@@ -95,44 +131,42 @@ func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error
if conf.Mode == "wide" {
switch conf.NoHumanize {
case true:
if err :=
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
strconv.FormatUint(row.Size, 10),
row.Created.AsTime().Format("02.01.2006T03:04.05"),
row.Preview,
})
}); err != nil {
return fmt.Errorf("failed to add data to table: %w", err)
}
default:
table.Append([]string{
if err := table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
humanize.Bytes(uint64(row.Size)),
humanize.Time(row.Created.AsTime()),
row.Preview,
})
}); err != nil {
return fmt.Errorf("failed to add data to table: %w", err)
}
}
} else {
table.Append([]string{row.Key, row.Preview})
if err := table.Append([]string{row.Key, row.Preview}); err != nil {
return fmt.Errorf("failed to add data to table: %w", err)
}
}
}
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetNoWhiteSpace(true)
if err := table.Render(); err != nil {
return fmt.Errorf("failed to render table: %w", err)
}
table.SetTablePadding("\t") // pad with tabs
table.Render()
fmt.Fprint(writer, tableString.String())
if _, err := fmt.Fprint(writer, tableString.String()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}

View File

@@ -20,12 +20,13 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"reflect"
"github.com/dustin/go-humanize"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
"golang.org/x/term"
//"github.com/alecthomas/repr"
)
@@ -43,7 +44,9 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn
if isatty {
fmt.Println("binary data omitted")
} else {
os.Stdout.Write(entry.Value)
if _, err := os.Stdout.WriteString(entry.Value); err != nil {
return err
}
}
} else {
fmt.Print(string(entry.Value))
@@ -79,13 +82,17 @@ func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.
if err != nil {
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
}
defer fd.Close()
defer func() {
if err := fd.Close(); err != nil {
log.Fatal(err)
}
}()
fileHandle = fd
}
// actually write file content
_, err = fileHandle.Write(entry.Value)
_, err = fileHandle.WriteString(entry.Value)
if !entry.Binary {
if entry.Value[entry.Size-1] != '\n' {
@@ -102,34 +109,45 @@ 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 {
fmt.Fprintf(writer, "Database: %s\n", info.Path)
if _, err := fmt.Fprintf(writer, "Database: %s\n", info.Path); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
for _, bucket := range info.Buckets {
if conf.NoHumanize {
fmt.Fprintf(
if _, err := fmt.Fprintf(
writer,
"%19s: %s\n%19s: %d\n%19s: %d\n%19s: %t\n",
"Bucket", bucket.Name,
"Size", bucket.Size,
"Keys", bucket.Keys,
"Encrypted", conf.Encrypt)
"Encrypted", conf.Encrypt); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
} else {
fmt.Fprintf(
if _, err := fmt.Fprintf(
writer,
"%19s: %s\n%19s: %s\n%19s: %d\n",
"Bucket", bucket.Name,
"Size", humanize.Bytes(uint64(bucket.Size)),
"Keys", bucket.Keys)
"Keys", bucket.Keys); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
if conf.Debug {
val := reflect.ValueOf(&bucket.Stats).Elem()
for i := 0; i < val.NumField(); i++ {
fmt.Fprintf(writer, "%19s: %v\n", val.Type().Field(i).Name, val.Field(i))
if _, err := fmt.Fprintf(writer, "%19s: %v\n", val.Type().Field(i).Name, val.Field(i)); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
}
fmt.Fprintln(writer)
if _, err := fmt.Fprintln(writer); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
return nil
}

View File

@@ -20,8 +20,8 @@ import (
//"github.com/alecthomas/repr"
"github.com/gofiber/fiber/v2"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg"
)
type SetContext struct {
@@ -44,7 +44,6 @@ func RestList(c *fiber.Ctx, conf *cfg.Config) error {
attr := new(app.DbAttr)
if len(c.Body()) > 0 {
if err := c.BodyParser(attr); err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
"errors": err.Error(),
@@ -54,7 +53,7 @@ func RestList(c *fiber.Ctx, conf *cfg.Config) error {
}
// get list
entries, err := conf.DB.List(attr, false)
entries, err := conf.DB.List(attr, attr.Fulltext)
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Unable to list keys: "+err.Error())

View File

@@ -21,7 +21,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/tlinden/anydb/cfg"
"codeberg.org/scip/anydb/cfg"
)
// used to return to the api client
@@ -42,6 +42,11 @@ func Runserver(conf *cfg.Config, args []string) error {
return RestList(c, conf)
})
api.Post("/", func(c *fiber.Ctx) error {
// same thing as above but allows to supply parameters, see app.Dbattr{}
return RestList(c, conf)
})
api.Get("/:key", func(c *fiber.Ctx) error {
return RestGet(c, conf)
})
@@ -99,11 +104,7 @@ Wrapper to respond with proper json status, message and code,
shall be prepared and called by the handlers directly.
*/
func JsonStatus(c *fiber.Ctx, code int, msg string) error {
success := true
if code != fiber.StatusOK {
success = false
}
success := code == fiber.StatusOK
return c.Status(code).JSON(Result{
Code: code,

37
t/crypt.txtar Normal file
View File

@@ -0,0 +1,37 @@
#
# Copyright © 2025 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/>.
#
# encrypt something
exec env ANYDB_PASSWORD=12345 anydb -f test.db set -e secret eshishinusan
# retrieve it
exec env ANYDB_PASSWORD=12345 anydb -f test.db get secret
stdout eshishinusan
# but has it really been encrypted?
! exec env ANYDB_PASSWORD=8d8d8 anydb -f test.db get secret
! stdout eshishinusan
stderr 'message authentication failed'
# what about the listing
exec anydb -f test.db ls -l
stdout 'encrypted-content'
! stdout eshishinusan
# and the export?
exec anydb -f test.db export -o -
! stdout eshishinusan

View File

@@ -78,4 +78,4 @@ exec anydb -f test.db del foo
# check deleted
exec anydb -f test.db list
! stdout bar
! stdout blah