74 Commits

Author SHA1 Message Date
eb9e152cbe fix references to codeberg 2025-11-03 10:35:59 +01:00
9cecf1c09e moved to codeberg 2025-11-03 09:33:05 +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
T.v.Dein
675d10d8fa Merge pull request #10 from TLINDEN/proto
Use protobuf for internal data storage instead of json
2024-12-30 12:33:03 +01:00
659e3472bb fixed copylocks linter warning 2024-12-30 12:23:13 +01:00
1eb5efae0c finalized conversion to protobuf:
- fixed import+export
- generalized file options
- always store keys as lowercase
- fixed+enhanced docs
- fixed tests
2024-12-30 12:12:02 +01:00
Thomas von Dein
bb5c268ca8 todo 2024-12-29 23:29:23 +01:00
a4b6a3cfdf restructured data storage, values now have their own sub bucket 2024-12-29 18:29:43 +01:00
c144e99b41 switch to use protobuf for internal data structure in DB 2024-12-29 13:01:16 +01:00
Thomas von Dein
2a6a651b91 fix pod bug 2024-12-25 23:53:34 +01:00
Thomas von Dein
33d638aff0 fix typos and formatting 2024-12-23 23:51:17 +01:00
8a8888a3cf +badges 2024-12-23 20:02:38 +01:00
a590eaf903 +todo 2024-12-23 20:00:48 +01:00
cb6abecaf9 +man page badge 2024-12-23 19:58:51 +01:00
a777c9cab4 added -i option to list to search case insensitive 2024-12-23 19:49:42 +01:00
2f652dc57d todo 2024-12-23 19:31:50 +01:00
T.v.Dein
f5f3760e67 Merge pull request #9 from TLINDEN/edit
Updated manual
2024-12-23 16:23:12 +01:00
T.v.Dein
2253acf19d Merge branch 'main' into edit 2024-12-23 16:21:26 +01:00
b7e3267695 updated manual 2024-12-23 16:19:27 +01:00
Thomas von Dein
6adeb618b7 add dbtool note 2024-12-22 21:12:16 +01:00
b262c73746 added edit command, fixed set -o - 2024-12-22 17:50:40 +01:00
66d6bd2a41 added edit command, fixed set -o - 2024-12-22 17:46:44 +01:00
34 changed files with 32 additions and 3167 deletions

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

View File

@@ -1,42 +0,0 @@
#
# 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/>.
#
FROM golang:1.22-alpine as builder
RUN apk update
RUN apk upgrade
RUN apk add --no-cache git make
RUN git --version
WORKDIR /work
COPY go.mod .
COPY . .
RUN go mod download
RUN make
FROM alpine:latest
LABEL maintainer="Thomas von Dein <git@daemon.de>"
WORKDIR /app
COPY --from=builder /work/anydb /app/anydb
ENV LANG C.UTF-8
USER 1001:1001
ENTRYPOINT ["/app/anydb"]
CMD ["-h"]

100
Makefile
View File

@@ -1,100 +0,0 @@
# 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/>.
#
# no need to modify anything below
tool = anydb
version = $(shell egrep "= .v" cfg/config.go | cut -d'=' -f2 | cut -d'"' -f 2)
archs = android darwin freebsd linux netbsd openbsd windows
PREFIX = /usr/local
UID = root
GID = 0
BRANCH = $(shell git branch --show-current)
COMMIT = $(shell git rev-parse --short=8 HEAD)
BUILD = $(shell date +%Y.%m.%d.%H%M%S)
VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
HAVE_POD := $(shell pod2text -h 2>/dev/null)
all: $(tool).1 cmd/$(tool).go buildlocal
%.1: %.pod
ifdef HAVE_POD
pod2man -c "User Commands" -r 1 -s 1 $*.pod > $*.1
endif
cmd/%.go: %.pod
ifdef HAVE_POD
echo "package cmd" > cmd/$*.go
echo >> cmd/$*.go
echo "var manpage = \`" >> cmd/$*.go
pod2text $*.pod >> cmd/$*.go
echo "\`" >> cmd/$*.go
endif
# echo "var usage = \`" >> cmd/$*.go
# awk '/SYNOPS/{f=1;next} /DESCR/{f=0} f' $*.pod | sed 's/^ //' >> cmd/$*.go
# echo "\`" >> cmd/$*.go
buildlocal:
go build -ldflags "-X 'github.com/tlinden/anydb/cfg.VERSION=$(VERSION)'"
release:
./mkrel.sh $(tool) $(version)
gh release create $(version) --generate-notes releases/*
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
clean:
rm -rf $(tool) releases coverage.out
test:
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)
cover-report:
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
show-versions: buildlocal
@echo "### anydb version:"
@./anydb --version
@echo
@echo "### go module versions:"
@go list -m all
@echo
@echo "### go version used for building:"
@grep -m 1 go go.mod
goupdate:
go get -t -u=patch ./...
lint:
golangci-lint run
# keep til ireturn
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

162
README.md
View File

@@ -1,8 +1,26 @@
## 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 Report Card](https://goreportcard.com/badge/github.com/tlinden/anydb)](https://goreportcard.com/report/github.com/tlinden/anydb)
> [!IMPORTANT]
> This software is now being maintained on [Codeberg](https://codeberg.org/scip/anydb/).
[![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://github.com/TLINDEN/anydb/blob/master/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
@@ -27,137 +45,21 @@ reasons:
- templates for custom output for maximum flexibility
- includes a tiny web server, which serves a restful API
And I wrote a very similar [tool](https://www.daemon.de/projects/dbtool/) 24 years ago and wanted to do it again wit go.
**anydb** can do all the things you can do with skate:
```shell
# Store something (and sync it to the network)
anydb set kitty meow
![simple demo](https://codeberg.org/scip/anydb/raw/branch/demo/intro.gif)
# 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'
```
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
anydb list '[a-z]+\d'
# anydb also supports a wide output
anydb list -o 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 -o json
# you can backup your database
anydb export -o backup.json
# and import it somewhere else
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. 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/
# 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.
@@ -170,7 +72,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
@@ -178,7 +80,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
@@ -205,14 +107,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`.
@@ -229,7 +131,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
@@ -241,7 +143,7 @@ T.v.Dein <tom AT vondein DOT org>
## Project homepage
https://github.com/TLINDEN/anydb
https://codeberg.org/scip/anydb
## Copyright and License

View File

@@ -1,3 +0,0 @@
- repl
- mime-type => exec app + value
- [edit command](https://github.com/TLINDEN/rpnc/blob/master/command.go#L249)

181
anydb.1
View File

@@ -1,181 +0,0 @@
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" Set up some character translations and predefined strings. \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
. ds -- \(*W-
. ds PI pi
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
. ds L" ""
. ds R" ""
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds -- \|\(em\|
. ds PI \(*p
. ds L" ``
. ds R" ''
. ds C`
. ds C'
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is >0, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.\"
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{\
. if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{\
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
. \" fudge factors for nroff and troff
.if n \{\
. ds #H 0
. ds #V .8m
. ds #F .3m
. ds #[ \f1
. ds #] \fP
.\}
.if t \{\
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
. ds #V .6m
. ds #F 0
. ds #[ \&
. ds #] \&
.\}
. \" simple accents for nroff and troff
.if n \{\
. ds ' \&
. ds ` \&
. ds ^ \&
. ds , \&
. ds ~ ~
. ds /
.\}
.if t \{\
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.\}
. \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
. \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
. \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
\{\
. ds : e
. ds 8 ss
. ds o a
. ds d- d\h'-1'\(ga
. ds D- D\h'-1'\(hy
. ds th \o'bp'
. ds Th \o'LP'
. ds ae ae
. ds Ae AE
.\}
.rm #[ #] #H #V #F C
.\" ========================================================================
.\"
.IX Title "ANYDB 1"
.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
.nh
.SH "anydb"
.IX Header "anydb"
anydb \- a personal key value store
.SH "SYNOPSIS"
.IX Header "SYNOPSIS"
.Vb 3
\& Usage:
\& anydb <command> [options] [flags]
\& anydb [command]
\&
\& Available Commands:
\& completion Generate the autocompletion script for the specified shell
\& del Delete key
\& export Export database to json
\& get Retrieve value for a key
\& help Help about any command
\& import Import database dump
\& list List database contents
\& set Insert key/value pair
\&
\& Flags:
\& \-f, \-\-dbfile string DB file to use (default "/home/scip/.config/anydb/default.db")
\& \-d, \-\-debug Enable debugging
\& \-h, \-\-help help for anydb
\& \-v, \-\-version Print program version
\&
\& Use "anydb [command] \-\-help" for more information about a command.
.Ve
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
Anydb is a simple to use commandline tool to store anything you'd
like, even binary files etc. It uses a key/value store (bbolt) in your
home directory.
.SH "LICENSE"
.IX Header "LICENSE"
This software is licensed under the \s-1GNU GENERAL PUBLIC LICENSE\s0 version 3.
.PP
Copyright (c) 2024 by Thomas von Dein
.SH "AUTHORS"
.IX Header "AUTHORS"
Thomas von Dein \fBtom \s-1AT\s0 vondein \s-1DOT\s0 org\fR

View File

@@ -1,46 +0,0 @@
=head1 anydb
anydb - a personal key value store
=head1 SYNOPSIS
Usage:
anydb <command> [options] [flags]
anydb [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
del Delete key
export Export database to json
get Retrieve value for a key
help Help about any command
import Import database dump
list List database contents
set Insert key/value pair
Flags:
-f, --dbfile string DB file to use (default "/home/scip/.config/anydb/default.db")
-d, --debug Enable debugging
-h, --help help for anydb
-v, --version Print program version
Use "anydb [command] --help" for more information about a command.
=head1 DESCRIPTION
Anydb is a simple to use commandline tool to store anything you'd
like, even binary files etc. It uses a key/value store (bbolt) in your
home directory.
=head1 LICENSE
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3.
Copyright (c) 2024 by Thomas von Dein
=head1 AUTHORS
Thomas von Dein B<tom AT vondein DOT org>
=cut

View File

@@ -1,108 +0,0 @@
/*
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 app
import (
"fmt"
"io"
"os"
"unicode/utf8"
)
type DbAttr struct {
Key string
Val string
Bin []byte
Args []string
Tags []string
File string
Encrypted bool
}
func (attr *DbAttr) ParseKV() error {
switch len(attr.Args) {
case 1:
// 1 arg = key + read from file or stdin
attr.Key = attr.Args[0]
if attr.File == "" {
attr.File = "-"
}
case 2:
attr.Key = attr.Args[0]
attr.Val = attr.Args[1]
if attr.Args[1] == "-" {
attr.File = "-"
}
}
if attr.File != "" {
return attr.GetFileValue()
}
return nil
}
func (attr *DbAttr) GetFileValue() error {
var fd io.Reader
if attr.File == "-" {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
fd = os.Stdin
}
} else {
filehandle, err := os.OpenFile(attr.File, os.O_RDONLY, 0600)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", attr.File, err)
}
fd = filehandle
}
if fd != nil {
// read from file or stdin pipe
data, err := io.ReadAll(fd)
if err != nil {
return fmt.Errorf("failed to read from pipe: %w", err)
}
// poor man's text file test
sdata := string(data)
if utf8.ValidString(sdata) {
attr.Val = sdata
} else {
attr.Bin = data
}
} else {
// read from console stdin
var input string
var data string
for {
_, err := fmt.Scanln(&input)
if err != nil {
break
}
data += input + "\n"
}
attr.Val = data
}
return nil
}

View File

@@ -1,173 +0,0 @@
/*
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 app
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"os"
"syscall"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/term"
)
const (
ArgonMem uint32 = 64 * 1024
ArgonIter uint32 = 5
ArgonParallel uint8 = 2
ArgonSaltLen int = 16
ArgonKeyLen uint32 = 32
B64SaltLen int = 22
)
type Key struct {
Salt []byte
Key []byte
}
// called from interactive thread, hides input and returns clear text
// password
func AskForPassword() ([]byte, error) {
fmt.Fprint(os.Stderr, "Password: ")
pass, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return nil, fmt.Errorf("failed to read password: %w", err)
}
fmt.Fprintln(os.Stderr)
return pass, nil
}
// We're using the Argon2id key derivation algorithm to derive a
// secure key from the given password. This is important, because
// users might use unsecure passwords. The resulting encrypted data
// might of course easily be decrypted using brute force methods if a
// weak password was used, but that would cost, because of the key
// derivation. It does several rounds of hash calculations which take
// a considerable amount of cpu time. For our legal user that's no
// problem because it's being executed only once, but an attacker has
// to do it in a forever loop, which will take a lot of time.
func DeriveKey(password []byte, salt []byte) (*Key, error) {
if salt == nil {
// none given, new password
newsalt, err := GetRandom(ArgonSaltLen, ArgonSaltLen)
if err != nil {
return nil, err
}
salt = newsalt
}
hash := argon2.IDKey(
[]byte(password), salt,
ArgonIter,
ArgonMem,
ArgonParallel,
ArgonKeyLen,
)
return &Key{Key: hash, Salt: salt}, nil
}
// Retrieve a random chunk of given size
func GetRandom(size int, capacity int) ([]byte, error) {
buf := make([]byte, size, capacity)
_, err := rand.Read(buf)
if err != nil {
return nil, fmt.Errorf("failed to retrieve random bytes: %w", err)
}
return buf, nil
}
// Encrypt clear text given in attr using ChaCha20 and auhtenticate
// using the mac Poly1305. The cipher text will be put into attr, thus
// modifying it.
//
// The cipher text consists of:
// base64(password-salt) + base64(12 byte nonce + ciphertext + 16 byte mac)
func Encrypt(pass []byte, attr *DbAttr) error {
key, err := DeriveKey(pass, nil)
if err != nil {
return err
}
aead, err := chacha20poly1305.New(key.Key)
if err != nil {
return fmt.Errorf("failed to create AEAD cipher: %w", err)
}
var plain []byte
if attr.Val != "" {
plain = []byte(attr.Val)
} else {
plain = attr.Bin
}
total := aead.NonceSize() + len(plain) + aead.Overhead()
nonce, err := GetRandom(aead.NonceSize(), total)
if err != nil {
return err
}
cipher := aead.Seal(nonce, nonce, plain, nil)
attr.Bin = nil
attr.Val = base64.RawStdEncoding.EncodeToString(key.Salt) +
base64.RawStdEncoding.EncodeToString(cipher)
attr.Encrypted = true
return nil
}
// Do the reverse
func Decrypt(pass []byte, cipherb64 string) ([]byte, error) {
salt, err := base64.RawStdEncoding.Strict().DecodeString(cipherb64[0:B64SaltLen])
if err != nil {
return nil, fmt.Errorf("failed to encode to base64: %w", err)
}
key, err := DeriveKey(pass, salt)
if err != nil {
return nil, err
}
cipher, err := base64.RawStdEncoding.Strict().DecodeString(cipherb64[B64SaltLen:])
if err != nil {
return nil, fmt.Errorf("failed to encode to base64: %w", err)
}
aead, err := chacha20poly1305.New(key.Key)
if err != nil {
return nil, fmt.Errorf("failed to create AEAD cipher: %w", err)
}
if len(cipher) < aead.NonceSize() {
return nil, errors.New("ciphertext too short")
}
nonce, ciphertext := cipher[:aead.NonceSize()], cipher[aead.NonceSize():]
return aead.Open(nil, nonce, ciphertext, nil)
}

412
app/db.go
View File

@@ -1,412 +0,0 @@
/*
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 app
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
bolt "go.etcd.io/bbolt"
)
const MaxValueWidth int = 60
type DB struct {
Debug bool
Dbfile string
Bucket string
DB *bolt.DB
}
type DbEntry struct {
Id string `json:"id"`
Key string `json:"key"`
Value string `json:"value"`
Encrypted bool `json:"encrypted"`
Bin []byte `json:"bin"`
Tags []string `json:"tags"`
Created time.Time `json:"created"`
Size int
}
type BucketInfo struct {
Name string
Keys int
Size int
Sequence uint64
Stats bolt.BucketStats
}
type DbInfo struct {
Buckets []BucketInfo
Path string
}
// Post process an entry for list output.
// Do NOT call it during write processing!
func (entry *DbEntry) Normalize() {
entry.Size = len(entry.Value)
if entry.Encrypted {
entry.Value = "<encrypted-content>"
}
if len(entry.Bin) > 0 {
entry.Value = "<binary-content>"
entry.Size = len(entry.Bin)
}
if len(entry.Value) > MaxValueWidth {
entry.Value = entry.Value[0:MaxValueWidth] + "..."
}
}
type DbEntries []DbEntry
type DbTag struct {
Keys []string `json:"key"`
}
const BucketData string = "data"
func New(file string, bucket string, debug bool) (*DB, error) {
return &DB{Debug: debug, Dbfile: file, Bucket: bucket}, nil
}
func (db *DB) Open() error {
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)
if err != nil {
return fmt.Errorf("failed to open DB %s: %w", db.Dbfile, err)
}
db.DB = b
return nil
}
func (db *DB) Close() error {
return db.DB.Close()
}
func (db *DB) List(attr *DbAttr) (DbEntries, error) {
if err := db.Open(); err != nil {
return nil, err
}
defer db.Close()
var entries DbEntries
var filter *regexp.Regexp
if len(attr.Args) > 0 {
filter = regexp.MustCompile(attr.Args[0])
}
err := db.DB.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(db.Bucket))
if bucket == nil {
return nil
}
err := bucket.ForEach(func(key, jsonentry []byte) error {
var entry DbEntry
if err := json.Unmarshal(jsonentry, &entry); err != nil {
return fmt.Errorf("failed to unmarshal from json: %w", err)
}
var include bool
switch {
case filter != nil:
if filter.MatchString(entry.Value) ||
filter.MatchString(entry.Key) ||
filter.MatchString(strings.Join(entry.Tags, " ")) {
include = true
}
case len(attr.Tags) > 0:
for _, search := range attr.Tags {
for _, tag := range entry.Tags {
if tag == search {
include = true
break
}
}
if include {
break
}
}
default:
include = true
}
if include {
entries = append(entries, entry)
}
return nil
})
return err
})
return entries, err
}
func (db *DB) Set(attr *DbAttr) error {
if err := db.Open(); err != nil {
return err
}
defer db.Close()
entry := DbEntry{
Key: attr.Key,
Value: attr.Val,
Bin: attr.Bin,
Tags: attr.Tags,
Encrypted: attr.Encrypted,
Created: time.Now(),
}
// 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 {
bucket := tx.Bucket([]byte(db.Bucket))
if bucket == nil {
return nil
}
jsonentry := bucket.Get([]byte(entry.Key))
if jsonentry == nil {
return nil
}
var oldentry DbEntry
if err := json.Unmarshal(jsonentry, &oldentry); err != nil {
return fmt.Errorf("failed to unmarshal from json: %w", err)
}
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
}
err = db.DB.Update(func(tx *bolt.Tx) error {
// insert data
bucket, err := tx.CreateBucketIfNotExists([]byte(db.Bucket))
if err != nil {
return fmt.Errorf("failed to create DB bucket: %w", err)
}
jsonentry, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("failed to marshall json: %w", err)
}
err = bucket.Put([]byte(entry.Key), []byte(jsonentry))
if err != nil {
return fmt.Errorf("failed to insert data: %w", err)
}
return nil
})
if err != nil {
return err
}
return nil
}
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
if err := db.Open(); err != nil {
return nil, err
}
defer db.Close()
entry := DbEntry{}
err := db.DB.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(db.Bucket))
if bucket == nil {
return nil
}
jsonentry := bucket.Get([]byte(attr.Key))
if jsonentry == nil {
// FIXME: shall we return a key not found error?
return nil
}
if err := json.Unmarshal(jsonentry, &entry); err != nil {
return fmt.Errorf("failed to unmarshal from json: %w", err)
}
return nil
})
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))
if bucket == nil {
return nil
}
return bucket.Delete([]byte(attr.Key))
})
return err
}
func (db *DB) Import(attr *DbAttr) (string, error) {
// open json file into attr.Val
if err := attr.GetFileValue(); err != nil {
return "", err
}
if attr.Val == "" {
return "", errors.New("empty json file")
}
var entries DbEntries
now := time.Now()
newfile := db.Dbfile + now.Format("-02.01.2006T03:04.05")
if err := json.Unmarshal([]byte(attr.Val), &entries); err != nil {
return "", cleanError(newfile, fmt.Errorf("failed to unmarshal json: %w", err))
}
if fileExists(db.Dbfile) {
// backup the old file
err := os.Rename(db.Dbfile, newfile)
if err != nil {
return "", fmt.Errorf("failed to rename file %s to %s: %w", db.Dbfile, newfile, err)
}
}
// should now be a new db file
if err := db.Open(); err != nil {
return "", cleanError(newfile, err)
}
defer db.Close()
err := db.DB.Update(func(tx *bolt.Tx) error {
// insert data
bucket, err := tx.CreateBucketIfNotExists([]byte(db.Bucket))
if err != nil {
return fmt.Errorf("failed to create bucket: %w", err)
}
for _, entry := range entries {
jsonentry, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("failed to marshall json: %w", err)
}
err = bucket.Put([]byte(entry.Key), []byte(jsonentry))
if err != nil {
return fmt.Errorf("failed to insert data into DB: %w", err)
}
}
return nil
})
if err != nil {
return "", cleanError(newfile, err)
}
return fmt.Sprintf("backed up database file to %s\nimported %d database entries\n",
newfile, len(entries)), nil
}
func (db *DB) Info() (*DbInfo, error) {
if err := db.Open(); err != nil {
return nil, err
}
defer db.Close()
info := &DbInfo{Path: db.Dbfile}
err := db.DB.View(func(tx *bolt.Tx) error {
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) + len(key)
return nil
})
if err != nil {
return fmt.Errorf("failed to read keys: %w", err)
}
info.Buckets = append(info.Buckets, binfo)
return nil
})
if err != nil {
return fmt.Errorf("failed to read from DB: %w", err)
}
return nil
})
return info, err
}

View File

@@ -1,26 +0,0 @@
/*
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 app
// look if a key in a map exists, generic variant
func Exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}

View File

@@ -1,36 +0,0 @@
/*
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 app
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()
}

View File

@@ -1,114 +0,0 @@
/*
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 (
"fmt"
"io"
"os"
"github.com/pelletier/go-toml"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/common"
)
var Version string = "v0.0.5"
type BucketConfig struct {
Encrypt bool
}
type Config struct {
Debug bool
Dbfile string
Dbbucket string
Template string
Mode string // wide, table, yaml, json
NoHeaders bool
NoHumanize bool
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
}

View File

@@ -1,60 +0,0 @@
/*
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 cmd
var manpage = `
anydb
anydb - a personal key value store
SYNOPSIS
Usage:
anydb <command> [options] [flags]
anydb [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
del Delete key
export Export database to json
get Retrieve value for a key
help Help about any command
import Import database dump
list List database contents
set Insert key/value pair
Flags:
-f, --dbfile string DB file to use (default "/home/scip/.config/anydb/default.db")
-d, --debug Enable debugging
-h, --help help for anydb
-v, --version Print program version
Use "anydb [command] --help" for more information about a command.
DESCRIPTION
Anydb is a simple to use commandline tool to store anything you'd like,
even binary files etc. It uses a key/value store (bbolt) in your home
directory.
LICENSE
This software is licensed under the GNU GENERAL PUBLIC LICENSE version
3.
Copyright (c) 2024 by Thomas von Dein
AUTHORS
Thomas von Dein tom AT vondein DOT org
`

View File

@@ -1,396 +0,0 @@
/*
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 cmd
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"unicode/utf8"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"github.com/tlinden/anydb/output"
"github.com/tlinden/anydb/rest"
)
func Set(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
)
var cmd = &cobra.Command{
Use: "set <key> [<value> | -r <file>] [-t <tag>]",
Short: "Insert key/value pair",
Long: `Insert key/value pair`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("no key/value pair specified")
}
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
if len(args) > 0 {
attr.Args = args
}
// turn comma list into slice, if needed
if len(attr.Tags) == 1 && strings.Contains(attr.Tags[0], ",") {
attr.Tags = strings.Split(attr.Tags[0], ",")
}
// check if value given as file or via stdin and fill attr accordingly
if err := attr.ParseKV(); err != nil {
return err
}
// encrypt?
if conf.Encrypt {
pass, err := getPassword()
if err != nil {
return err
}
err = app.Encrypt(pass, &attr)
if err != nil {
return err
}
}
return conf.DB.Set(&attr)
},
}
cmd.PersistentFlags().BoolVarP(&conf.Encrypt, "encrypt", "e", false, "encrypt value")
cmd.PersistentFlags().StringVarP(&attr.File, "file", "r", "", "Filename or - for STDIN")
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
cmd.Aliases = append(cmd.Aliases, "add")
cmd.Aliases = append(cmd.Aliases, "s")
cmd.Aliases = append(cmd.Aliases, "+")
return cmd
}
func Get(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
)
var cmd = &cobra.Command{
Use: "get <key> [-o <file>] [-m <mode>] [-n -N] [-T <tpl>]",
Short: "Retrieve value for a key",
Long: `Retrieve value for a key`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("no key specified")
}
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
if len(args) > 0 {
attr.Key = args[0]
}
entry, err := conf.DB.Get(&attr)
if err != nil {
return err
}
if entry.Encrypted {
pass, err := getPassword()
if err != nil {
return err
}
clear, err := app.Decrypt(pass, entry.Value)
if err != nil {
return err
}
if utf8.ValidString(string(clear)) {
entry.Value = string(clear)
} else {
entry.Bin = clear
}
entry.Encrypted = false
}
return output.Print(os.Stdout, conf, &attr, entry)
},
}
cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output value to file (ignores -m)")
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (simple|wide|json) (default 'simple')")
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
cmd.Aliases = append(cmd.Aliases, "show")
cmd.Aliases = append(cmd.Aliases, "g")
cmd.Aliases = append(cmd.Aliases, ".")
return cmd
}
func Del(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
)
var cmd = &cobra.Command{
Use: "del <key>",
Short: "Delete key",
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")
}
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
if len(args) > 0 {
attr.Key = args[0]
}
return conf.DB.Del(&attr)
},
}
cmd.Aliases = append(cmd.Aliases, "d")
cmd.Aliases = append(cmd.Aliases, "rm")
return cmd
}
func Export(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
)
var cmd = &cobra.Command{
Use: "export [-o <json filename>]",
Short: "Export database to json",
Long: `Export database to json`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
conf.Mode = "json"
entries, err := conf.DB.List(&attr)
if err != nil {
return err
}
return output.WriteJSON(&attr, conf, entries)
},
}
cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output to file")
cmd.Aliases = append(cmd.Aliases, "dump")
cmd.Aliases = append(cmd.Aliases, "backup")
return cmd
}
func List(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
wide bool
)
var cmd = &cobra.Command{
Use: "list [<filter-regex>] [-t <tag>] [-m <mode>] [-n -N] [-T <tpl>]",
Short: "List database contents",
Long: `List database contents`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
if len(args) > 0 {
attr.Args = args
}
// turn comma list into slice, if needed
if len(attr.Tags) == 1 && strings.Contains(attr.Tags[0], ",") {
attr.Tags = strings.Split(attr.Tags[0], ",")
}
if wide {
conf.Mode = "wide"
}
entries, err := conf.DB.List(&attr)
if err != nil {
return err
}
return output.List(os.Stdout, conf, entries)
},
}
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (table|wide|json), wide is a verbose table. (default 'table')")
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
cmd.PersistentFlags().BoolVarP(&wide, "wide-output", "l", false, "output mode: wide")
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
cmd.Aliases = append(cmd.Aliases, "/")
cmd.Aliases = append(cmd.Aliases, "ls")
return cmd
}
func Import(conf *cfg.Config) *cobra.Command {
var (
attr app.DbAttr
)
var cmd = &cobra.Command{
Use: "import [<json file>]",
Short: "Import database dump",
Long: `Import database dump`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
out, err := conf.DB.Import(&attr)
if err != nil {
return err
}
fmt.Print(out)
return nil
},
}
cmd.PersistentFlags().StringVarP(&attr.File, "file", "r", "", "Filename or - for STDIN")
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
cmd.Aliases = append(cmd.Aliases, "add")
cmd.Aliases = append(cmd.Aliases, "s")
cmd.Aliases = append(cmd.Aliases, "+")
return cmd
}
func Help(conf *cfg.Config) *cobra.Command {
return nil
}
func Man(conf *cfg.Config) *cobra.Command {
var cmd = &cobra.Command{
Use: "man",
Short: "show manual page",
Long: `show manual page`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
man := exec.Command("less", "-")
var b bytes.Buffer
b.WriteString(manpage)
man.Stdout = os.Stdout
man.Stdin = &b
man.Stderr = os.Stderr
err := man.Run()
if err != nil {
return fmt.Errorf("failed to execute 'less': %w", err)
}
return nil
},
}
return cmd
}
func Serve(conf *cfg.Config) *cobra.Command {
var cmd = &cobra.Command{
Use: "serve [-l host:port]",
Short: "run REST API listener",
Long: `run REST API listener`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
return rest.Runserver(conf, nil)
},
}
cmd.PersistentFlags().StringVarP(&conf.Listen, "listen", "l", "localhost:8787", "host:port")
return cmd
}
func Info(conf *cfg.Config) *cobra.Command {
var cmd = &cobra.Command{
Use: "info",
Short: "info",
Long: `show info about database`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
info, err := conf.DB.Info()
if err != nil {
return err
}
return output.Info(os.Stdout, conf, info)
},
}
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
}

View File

@@ -1,136 +0,0 @@
/*
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 cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/alecthomas/repr"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
)
func completion(cmd *cobra.Command, mode string) error {
switch mode {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return errors.New("invalid shell parameter! Valid ones: bash|zsh|fish|powershell")
}
}
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",
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 != "" {
configs = []string{configfile}
} else {
configs = SearchConfigs
}
if err := conf.GetConfig(configs); err != nil {
return err
}
if conf.Debug {
repr.Println(conf)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if ShowVersion {
fmt.Printf("This is anydb version %s\n", cfg.Version)
return nil
}
if len(ShowCompletion) > 0 {
return completion(cmd, ShowCompletion)
}
if len(args) == 0 {
return errors.New("no command specified")
}
return nil
},
}
// options
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")
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))
rootCmd.AddCommand(Get(&conf))
rootCmd.AddCommand(Del(&conf))
rootCmd.AddCommand(Export(&conf))
rootCmd.AddCommand(Import(&conf))
rootCmd.AddCommand(Serve(&conf))
rootCmd.AddCommand(Man(&conf))
rootCmd.AddCommand(Info(&conf))
err = rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

View File

@@ -1,36 +0,0 @@
/*
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()
}

View File

@@ -1,38 +0,0 @@
#
# 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/>.
#
version: "3.9"
services:
init:
image: alpine:latest
user: "root"
group_add:
- '${GROUP_ID}'
volumes:
- ${OUTDIR}:/backup
command: chown -R ${USER_ID}:${USER_ID} /backup
anydb:
container_name: anydb
user: "${USER_ID}:${USER_ID}"
volumes:
- ${OUTDIR}:/backup
working_dir: /backup
build: .
image: anydb:latest
depends_on:
init:
condition: service_completed_successfully

View File

@@ -1,14 +0,0 @@
# defaults
dbfile = "~/.config/anydb/default.db"
dbbucket = "data"
noheaders = false
nohumanize = false
encrypt = false
listen = "localhost:8787"
# different setups for different buckets
[buckets.data]
encrypt = true
[buckets.test]
encrypt = false

32
go.mod
View File

@@ -1,32 +0,0 @@
module github.com/tlinden/anydb
go 1.22.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/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/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
github.com/spf13/pflag v1.0.5 // 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
)

94
go.sum
View File

@@ -1,94 +0,0 @@
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/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/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/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/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=
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/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/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=
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=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

55
main.go
View File

@@ -1,55 +0,0 @@
/*
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 main
import (
"bufio"
"fmt"
"os"
"runtime"
"github.com/inconshreveable/mousetrap"
"github.com/tlinden/anydb/cmd"
)
func main() {
Main()
}
func Main() int {
cmd.Execute()
return 0
}
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("Please 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)
}
}
}
}

View File

@@ -1,36 +0,0 @@
/*
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 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,
}))
}
func TestAnydb(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "t",
})
}

View File

@@ -1,75 +0,0 @@
#!/bin/bash
# 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/>.
# get list with: go tool dist list
DIST="darwin/amd64
freebsd/amd64
linux/amd64
netbsd/amd64
openbsd/amd64
windows/amd64
freebsd/arm64
linux/arm64
netbsd/arm64
openbsd/arm64
windows/arm64"
tool="$1"
version="$2"
if test -z "$version"; then
echo "Usage: $0 <tool name> <release version>"
exit 1
fi
rm -rf releases
mkdir -p releases
for D in $DIST; do
os=${D/\/*/}
arch=${D/*\//}
binfile="releases/${tool}-${os}-${arch}-${version}"
if test "$os" = "windows"; then
binfile="${binfile}.exe"
fi
tardir="${tool}-${os}-${arch}-${version}"
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
set -x
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o ${binfile}
mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = anydb
PREFIX = /usr/local
UID = root
GID = 0
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
tar cpzf ${tarfile} ${tardir}
sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
rm -rf ${tardir}
set +x
done

View File

@@ -1,50 +0,0 @@
/*
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 output
import (
"encoding/json"
"fmt"
"os"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
)
func WriteJSON(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {
jsonentries, err := json.Marshal(entries)
if err != nil {
return fmt.Errorf("failed to marshall json: %w", err)
}
if attr.File == "-" {
fmt.Println(string(jsonentries))
} else {
fd, err := os.OpenFile(attr.File, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
}
if _, err := fd.Write(jsonentries); err != nil {
return fmt.Errorf("failed writing to file %s: %w", attr.File, err)
}
fmt.Printf("database contents exported to %s\n", attr.File)
}
return nil
}

View File

@@ -1,143 +0,0 @@
/*
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 output
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
tpl "text/template"
"github.com/dustin/go-humanize"
"github.com/olekukonko/tablewriter"
"github.com/tlinden/anydb/app"
"github.com/tlinden/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)
case "json":
return ListJson(writer, conf, entries)
case "template":
return ListTemplate(writer, conf, entries)
default:
return errors.New("unsupported mode")
}
}
func ListJson(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
jsonentries, err := json.Marshal(entries)
if err != nil {
return fmt.Errorf("failed marshall json: %s", err)
}
fmt.Println(string(jsonentries))
return nil
}
func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
tmpl, err := tpl.New("list").Parse(conf.Template)
if err != nil {
return fmt.Errorf("failed to parse output template: %w", err)
}
buf := bytes.Buffer{}
for _, row := range entries {
row.Normalize()
buf.Reset()
err = tmpl.Execute(&buf, row)
if err != nil {
return fmt.Errorf("failed to execute output template: %w", err)
}
if buf.Len() > 0 {
fmt.Fprintln(writer, buf.String())
}
}
return nil
}
func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
if !conf.NoHeaders {
if conf.Mode == "wide" {
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "UPDATED", "VALUE"})
} else {
table.SetHeader([]string{"KEY", "VALUE"})
}
}
for _, row := range entries {
row.Normalize()
if conf.Mode == "wide" {
switch conf.NoHumanize {
case true:
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
strconv.Itoa(row.Size),
row.Created.Format("02.01.2006T03:04.05"),
row.Value,
})
default:
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
humanize.Bytes(uint64(row.Size)),
//row.Created.Format("02.01.2006T03:04.05"),
humanize.Time(row.Created),
row.Value,
})
}
} else {
table.Append([]string{row.Key, row.Value})
}
}
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)
table.SetTablePadding("\t") // pad with tabs
table.Render()
fmt.Fprint(writer, tableString.String())
return nil
}

View File

@@ -1,131 +0,0 @@
/*
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 output
import (
"encoding/json"
"fmt"
"io"
"os"
"reflect"
"strings"
"github.com/dustin/go-humanize"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"golang.org/x/term"
//"github.com/alecthomas/repr"
)
func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
if attr.File != "" {
return WriteFile(writer, conf, attr, entry)
}
isatty := term.IsTerminal(int(os.Stdout.Fd()))
switch conf.Mode {
case "simple", "":
if len(entry.Bin) > 0 {
if isatty {
fmt.Println("binary data omitted")
} else {
os.Stdout.Write(entry.Bin)
}
} else {
fmt.Print(entry.Value)
if !strings.HasSuffix(entry.Value, "\n") {
// always add a terminal newline
fmt.Println()
}
}
case "json":
jsonentry, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("failed to marshall json: %s", err)
}
fmt.Println(string(jsonentry))
case "wide":
return ListTable(writer, conf, app.DbEntries{*entry})
case "template":
return ListTemplate(writer, conf, app.DbEntries{*entry})
}
return nil
}
func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
fd, err := os.OpenFile(attr.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
}
defer fd.Close()
if len(entry.Bin) > 0 {
// binary file content
_, err = fd.Write(entry.Bin)
} else {
val := entry.Value
if !strings.HasSuffix(val, "\n") {
// always add a terminal newline
val += "\n"
}
_, err = fd.Write([]byte(val))
}
if err != nil {
return fmt.Errorf("failed to write to file %s: %w", attr.File, err)
}
return nil
}
func Info(writer io.Writer, conf *cfg.Config, info *app.DbInfo) error {
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%19s: %t\n",
"Bucket", bucket.Name,
"Size", bucket.Size,
"Keys", bucket.Keys,
"Encrypted", conf.Encrypt)
} 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)
}
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))
}
}
fmt.Fprintln(writer)
}
return nil
}

View File

@@ -1,141 +0,0 @@
/*
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 rest
import (
//"github.com/alecthomas/repr"
"github.com/gofiber/fiber/v2"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
)
type SetContext struct {
Query string `json:"query" form:"query"`
}
type ListResponse struct {
Success bool
Code int
Entries app.DbEntries
}
type SingleResponse struct {
Success bool
Code int
Entry *app.DbEntry
}
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(),
})
}
}
// get list
entries, err := conf.DB.List(attr)
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Unable to list keys: "+err.Error())
}
return c.Status(fiber.StatusOK).JSON(
ListResponse{
Success: true,
Code: fiber.StatusOK,
Entries: entries,
},
)
}
func RestGet(c *fiber.Ctx, conf *cfg.Config) error {
if c.Params("key") == "" {
return JsonStatus(c, fiber.StatusForbidden,
"key not provided")
}
// get list
entry, err := conf.DB.Get(&app.DbAttr{Key: c.Params("key")})
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Unable to get key: "+err.Error())
}
if entry.Key == "" {
return JsonStatus(c, fiber.StatusForbidden,
"Key does not exist")
}
return c.Status(fiber.StatusOK).JSON(
SingleResponse{
Success: true,
Code: fiber.StatusOK,
Entry: entry,
},
)
}
func RestDelete(c *fiber.Ctx, conf *cfg.Config) error {
if c.Params("key") == "" {
return JsonStatus(c, fiber.StatusForbidden,
"key not provided")
}
// get list
err := conf.DB.Del(&app.DbAttr{Key: c.Params("key")})
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Unable to delete key: "+err.Error())
}
return c.Status(fiber.StatusOK).JSON(
Result{
Success: true,
Code: fiber.StatusOK,
Message: "key deleted",
},
)
}
func RestSet(c *fiber.Ctx, conf *cfg.Config) error {
attr := new(app.DbAttr)
if err := c.BodyParser(attr); err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
"errors": err.Error(),
})
}
err := conf.DB.Set(attr)
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Unable to set key: "+err.Error())
}
return c.Status(fiber.StatusOK).JSON(
Result{
Success: true,
Code: fiber.StatusOK,
},
)
}

View File

@@ -1,113 +0,0 @@
/*
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 rest
import (
"github.com/gofiber/fiber/v2"
"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"
)
// used to return to the api client
type Result struct {
Success bool `json:"success"`
Message string `json:"message"`
Code int `json:"code"`
}
func Runserver(conf *cfg.Config, args []string) error {
// setup api server
router := SetupServer(conf)
// public rest api routes
api := router.Group("/anydb/v1")
{
api.Get("/", func(c *fiber.Ctx) error {
return RestList(c, conf)
})
api.Get("/:key", func(c *fiber.Ctx) error {
return RestGet(c, conf)
})
api.Delete("/:key", func(c *fiber.Ctx) error {
return RestDelete(c, conf)
})
api.Put("/", func(c *fiber.Ctx) error {
return RestSet(c, conf)
})
}
// public routes
{
router.Get("/", func(c *fiber.Ctx) error {
return c.Send([]byte("Use the REST API"))
})
}
return router.Listen(conf.Listen)
}
func SetupServer(conf *cfg.Config) *fiber.App {
// disable colors
fiber.DefaultColors = fiber.Colors{}
router := fiber.New(fiber.Config{
CaseSensitive: true,
StrictRouting: true,
Immutable: true,
ServerHeader: "anydb serve",
AppName: "anydb",
})
router.Use(logger.New(logger.Config{
Format: "${pid} ${ip}:${port} ${status} - ${method} ${path}\n",
DisableColors: true,
}))
router.Use(cors.New(cors.Config{
AllowMethods: "GET,PUT,POST,DELETE",
ExposeHeaders: "Content-Type,Accept",
}))
router.Use(compress.New(compress.Config{
Level: compress.LevelBestSpeed,
}))
return router
}
/*
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
}
return c.Status(code).JSON(Result{
Code: code,
Message: msg,
Success: success,
})
}

View File

@@ -1,34 +0,0 @@
#
# 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/>.
#
# simple file, we cannot use redirection here, so dd is our friend
exec dd if=/dev/random of=file.txt count=5 bs=10
# add file to db
exec anydb -f test.db set datum -r file.txt
# check for existence
exec anydb -f test.db get datum -o out.txt
exists out.txt
# check if its filled (50 bytes == count=5 x bs=10)
exec ls -l out.txt
stdout 50
# look if it's inside the db
exec anydb -f test.db ls
stdout datum.*binary-content

View File

@@ -1,25 +0,0 @@
#
# 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/>.
#
# check default outputs
exec anydb -v
stdout 'This is anydb version'
! exec anydb
stderr 'Available Commands:'

View File

@@ -1,32 +0,0 @@
#
# 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/>.
#
# setup simple db
exec anydb -f test.db set foo bar
# create backup
exec anydb -f test.db export -o backup.json
stdout 'database contents exported to backup.json'
# import into new db
exec anydb -f new.db import -r backup.json
stdout 'imported.*entries'
# check contents
exec anydb -f new.db list
stdout foo.*bar

View File

@@ -1,74 +0,0 @@
#
# 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/>.
#
# simple entry
exec anydb -f test.db set foo bar
# entry with tags
exec anydb -f test.db set color grey -t flower,plant
# simple list
exec anydb -f test.db list
stdout foo.*bar
# wide list
exec anydb -f test.db list -m wide
stdout 'plant.*now.*grey'
# list tagged
exec anydb -f test.db list -t flower
! stdout bar
# list with filter
exec anydb -f test.db list b.r
stdout bar
# get single entry
exec anydb -f test.db get color
stdout grey
# modify
exec anydb -f test.db set foo blah
# check modified
exec anydb -f test.db get foo
stdout blah
# modify tagged
exec anydb -f test.db set color grey -t butterfly
# check modified tagged
exec anydb -f test.db list -t butterfly
stdout grey
# check modified tagged, make sure
exec anydb -f test.db list -t flower
! stdout grey
# check json output
exec anydb -f test.db list -m json
stdout '^\[\{'
exec anydb -f test.db get color -m json
stdout '^\{'
# delete entry
exec anydb -f test.db del foo
# check deleted
exec anydb -f test.db list
! stdout bar