version 0.5 of spiller

This commit is contained in:
q
2025-09-19 10:08:13 +03:00
parent c39bdc9959
commit cc06827767
4 changed files with 128 additions and 119 deletions

View File

@@ -26,7 +26,7 @@ classifiers = [
dependencies = [] dependencies = []
[project.scripts] [project.scripts]
spill="spiller.spiller:main" spill="spiller:main"
[tool.hatch.version] [tool.hatch.version]
path = "spiller/__init__.py" path = "spiller/__init__.py"

View File

@@ -15,11 +15,11 @@ setup(
packages=["spiller"], packages=["spiller"],
version=version, version=version,
description="Very simple password storage, that encrypts with GPG cmdline tool.", description="Very simple password storage, that encrypts with GPG cmdline tool.",
author="Ville Rantanen", author="Q",
author_email="q@six9.net", author_email="q@six9.net",
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"spill = spiller.spiller:main", "spill = spiller:main",
] ]
}, },
) )

View File

@@ -1,2 +1,117 @@
__version__ = "0.4" import argparse
from spiller.spiller import Spiller, decrypt, encrypt import os
from spiller.spiller import DEFAULT_CONFIG, DEFAULT_STORAGE, Spiller, decrypt, encrypt
__version__ = "0.5"
def get_opts():
parser = argparse.ArgumentParser(
prog="spill",
description=f"""Key/value storage that uses JSON and GPG as backend.
Values are encrypted symmetrically with the key provided, or a random string is generated.
Encryption key can be passed from a variable SPILLER_KEY instead of a switch.
Storage file can be changed with SPILLER_STORAGE env variable, in a
"SPILLER_STORAGE": key in a JSON file read at {DEFAULT_STORAGE}
""",
)
parser.add_argument("--version", action="version", version=__version__)
parser.add_argument("-q", "--quiet", action="store_true", default=False)
subparsers = parser.add_subparsers(dest="command", help="Command")
set_parser = subparsers.add_parser("set")
get_parser = subparsers.add_parser("get")
del_parser = subparsers.add_parser("list")
del_parser = subparsers.add_parser("del")
set_parser.add_argument(
"name",
action="store",
help="Name of secret",
)
set_parser.add_argument(
"data",
action="store",
nargs="?",
help="Data to store. Must use this or --data-file.",
)
set_parser.add_argument(
"--data-file",
action="store",
type=argparse.FileType("r"),
help="Read the data to store from a file. Must use this or [data]. Will strip newlines at the end.",
)
set_parser.add_argument("--plain", action="store_true", default=False, help="Do not encrypt")
set_parser.add_argument(
"--key",
action="store",
default=os.getenv("SPILLER_KEY", None),
help="Encryption key (or use SPILLER_KEY)",
)
set_parser.add_argument(
"--key-file",
action="store",
default=None,
type=argparse.FileType("r"),
help="Read encryption key stored in a file",
)
get_parser.add_argument(
"name",
action="store",
help="Name of secret",
)
get_parser.add_argument(
"--key",
action="store",
default=os.getenv("SPILLER_KEY", None),
help="Decryption key (or use SPILLER_KEY)",
)
get_parser.add_argument(
"--key-file",
action="store",
default=None,
type=argparse.FileType("r"),
help="Read encryption key stored in a file. Will strip newlines at the end.",
)
del_parser.add_argument(
"name",
action="store",
help="Name of secret to delete",
)
args = parser.parse_args()
if args.command is None:
raise parser.error("Command missing")
try:
if args.key_file:
with args.key_file as fp:
args.key = fp.read().rstrip("\n")
except AttributeError:
pass
if args.command == "set":
if args.data and args.data_file:
raise parser.error("Can not use both [data] and --data-file")
if args.data is None and args.data_file is None:
raise parser.error("Must use either [data] or --data-file")
if args.data_file:
with args.data_file as fp:
args.data = fp.read().rstrip("\n")
return args
def main():
opts = get_opts()
spill = Spiller()
spill.verbose = not opts.quiet
if opts.command == "set":
spill.store(opts.name, opts.data, opts.key, opts.plain)
if opts.command == "get":
print(spill.retrieve(opts.name, opts.key), end="")
if opts.command == "list":
print(spill.format_storage())
if opts.command == "del":
spill.del_storage(opts.name)

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse
import json import json
import os import os
import random import random
@@ -9,98 +8,8 @@ import string
import subprocess import subprocess
import sys import sys
DEFAULT_CONFIG = os.path.expanduser("~/.config/spiller/config.json")
def get_opts(): DEFAULT_STORAGE = os.path.expanduser("~/.config/spiller/storage.json")
parser = argparse.ArgumentParser(
prog="spill",
description="""Key/value storage that uses JSON and GPG as backend.
Values are encrypted symmetrically with the key provided, or a random string is generated.
Encryption key can be passed from a variable SPILLER_KEY instead of a switch.
Storage file can be changed with SPILLER_STORAGE env variable, in a
"SPILLER_STORAGE": key in a JSON file read at ~/.config/spill/config.json
""",
)
subparsers = parser.add_subparsers(dest="command", help="Command")
set_parser = subparsers.add_parser("set")
get_parser = subparsers.add_parser("get")
del_parser = subparsers.add_parser("list")
del_parser = subparsers.add_parser("del")
set_parser.add_argument(
"name",
action="store",
help="Name of secret",
)
set_parser.add_argument(
"data",
action="store",
nargs="?",
help="Data to store. Must use this or --data-file.",
)
set_parser.add_argument(
"--data-file",
action="store",
type=argparse.FileType("r"),
help="Read the data to store from a file. Must use this or [data]. Will strip newlines at the end.",
)
set_parser.add_argument("--plain", action="store_true", default=False, help="Do not encrypt")
set_parser.add_argument(
"--key",
action="store",
default=os.getenv("SPILLER_KEY", None),
help="Encryption key (or use SPILLER_KEY)",
)
set_parser.add_argument(
"--key-file",
action="store",
default=None,
type=argparse.FileType("r"),
help="Read encryption key stored in a file",
)
get_parser.add_argument(
"name",
action="store",
help="Name of secret",
)
get_parser.add_argument(
"--key",
action="store",
default=os.getenv("SPILLER_KEY", None),
help="Decryption key (or use SPILLER_KEY)",
)
get_parser.add_argument(
"--key-file",
action="store",
default=None,
type=argparse.FileType("r"),
help="Read encryption key stored in a file. Will strip newlines at the end.",
)
del_parser.add_argument(
"name",
action="store",
help="Name of secret to delete",
)
args = parser.parse_args()
if args.command is None:
raise parser.error("Command missing")
try:
if args.key_file:
with args.key_file as fp:
args.key = fp.read().rstrip("\n")
except AttributeError:
pass
if args.command == "set":
if args.data and args.data_file:
raise parser.error("Can not use both [data] and --data-file")
if args.data is None and args.data_file is None:
raise parser.error("Must use either [data] or --data-file")
if args.data_file:
with args.data_file as fp:
args.data = fp.read().rstrip("\n")
return args
class Spiller: class Spiller:
@@ -114,10 +23,10 @@ class Spiller:
self.verbose = False self.verbose = False
def get_config(self): def get_config(self):
default_config = {"SPILLER_STORAGE": os.path.expanduser("~/.config/spiller/storage.json")} default_config = {"SPILLER_STORAGE": DEFAULT_STORAGE}
try: try:
with open(os.path.expanduser("~/.config/spiller/config.json"), "rt") as fp: with open(DEFAULT_CONFIG, "rt") as fp:
default_config.update(json.load(fp)) default_config.update(json.load(fp))
except Exception: except Exception:
pass pass
@@ -165,13 +74,13 @@ class Spiller:
except Exception: except Exception:
pass pass
def del_storage(name): def del_storage(self, name):
"""writes directly !""" """writes directly !"""
del self.storage[name] del self.storage[name]
self.save_storage() self.save_storage()
if self.verbose: if self.verbose:
print("Deleted " + name) print("Deleted " + name, file=sys.stderr)
def store(self, name, data, key, plain): def store(self, name, data, key, plain):
""" """
@@ -193,7 +102,7 @@ class Spiller:
if key == None: if key == None:
key = get_random_key() key = get_random_key()
if self.verbose: if self.verbose:
print("Random key: " + key) print("Random key: " + key, file=sys.stderr)
entry["data"], ec = self.encrypt(data, key) entry["data"], ec = self.encrypt(data, key)
if ec != 0: if ec != 0:
raise ValueError("Encryption Failed") raise ValueError("Encryption Failed")
@@ -234,7 +143,7 @@ class Spiller:
if encrypted == "": if encrypted == "":
if self.verbose: if self.verbose:
print("Encrypt failed!", file=sys.stderr) print("Encrypt failed!", file=sys.stderr)
None, 1 return None, 1
return encrypted, 0 return encrypted, 0
def decrypt(self, encrypted, key): def decrypt(self, encrypted, key):
@@ -271,18 +180,3 @@ def get_random_key():
return "-".join( return "-".join(
["".join([random.choice(string.ascii_letters + string.digits) for x in range(8)]) for x in range(5)] ["".join([random.choice(string.ascii_letters + string.digits) for x in range(8)]) for x in range(5)]
) )
def main():
opts = get_opts()
spill = Spiller()
spill.verbose = True
if opts.command == "set":
spill.store(opts.name, opts.data, opts.key, opts.plain)
if opts.command == "get":
print(spill.retrieve(opts.name, opts.key))
if opts.command == "list":
print(spill.format_storage())
if opts.command == "del":
spill.del_storage(opts.name)