usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

commit e3badb6790fe44c9ed978a71e6417f95e6ed9c1a
parent b92ff308c387aa91f6ff404de97122b3fd552855
Author: lash <dev@holbrook.no>
Date:   Tue, 31 Mar 2026 15:55:00 -0600

Consolidate arg parsing and context handling in cli

Diffstat:
Mdummy/setup.cfg | 9++++++++-
Mdummy/usawa/gui/__init__.py | 2+-
Mdummy/usawa/runnable/add.py | 129++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mdummy/usawa/runnable/create.py | 165+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mdummy/usawa/runnable/entry.py | 151++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mdummy/usawa/runnable/export.py | 93++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mdummy/usawa/runnable/import.py | 43++++++++++++++++++++++++-------------------
Mdummy/usawa/runnable/server.py | 2+-
Ddummy/usawa/runnable/setup_wallet.py | 111-------------------------------------------------------------------------------
Mdummy/usawa/runnable/view.py | 107+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Adummy/usawa/runnable/wallet.py | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 488 insertions(+), 439 deletions(-)

diff --git a/dummy/setup.cfg b/dummy/setup.cfg @@ -38,4 +38,11 @@ packages = [options.entry_points] console_scripts = usawad = usawa.runnable.server:main - usawa = usawa.runnable.gui:main + usawa-gui = usawa.runnable.gui:main + usawa-create = usawa.runnable.create:main + usawa-add = usawa.runnable.add:main + usawa-entry = usawa.runnable.entry:main + usawa-wallet = usawa.runnable.wallet:main + usawa-view = usawa.runnable.view:main + usawa-import = usawa.runnable.import:main + usawa-export = usawa.runnable.export:main diff --git a/dummy/usawa/gui/__init__.py b/dummy/usawa/gui/__init__.py @@ -2,7 +2,7 @@ import logging import signal import gi -from usawa.runnable.setup_wallet import setup_wallet +from usawa.runnable.wallet import setup_wallet gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") diff --git a/dummy/usawa/runnable/add.py b/dummy/usawa/runnable/add.py @@ -120,46 +120,6 @@ def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): return v -argp = argparse.ArgumentParser() -argp.add_argument('-i', action='store_true', help='interactive edit') -argp.add_argument('-r', type=str, help='external reference') -argp.add_argument('-s', type=str, dest='src_account', default='general', help='source account') -argp.add_argument('-t', type=str, dest='dst_account', default='general', help='destination account') -argp.add_argument('-a', type=str, dest='amount', help='source and destination amount') -argp.add_argument('-x', type=str, dest='attachment', default=[], action='append', help='add file attachment') -argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document') -argp.add_argument('--src-type', dest='src_type', type=str, choices=CATEGORIES, default='expense', help='source type') -argp.add_argument('--dst-type', dest='dst_type', type=str, choices=CATEGORIES, default='asset', help='dest type') -argp.add_argument('-d', '--description', dest='description', type=str, help='interactive edit') -# TODO: read default from xml if not defined -argp.add_argument('-u', '--unit', type=str, default=UnitIndex.default_unit, help='Unit to use for transaction') -argp.add_argument('-c', type=str, help='override config dir') -argp.add_argument('--unit-precision', dest='unit_precision', type=int, default=UnitIndex.default_precision, help='Unit precision') -argp.add_argument('--unit-rate', dest='unit_precision', type=float, default=1.0, help='Unit exchange rate') -argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') -argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') - -argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') -arg = argp.parse_args() -ctx = Context.from_args(arg) - -ledger = None -logg.warning('hardcoding unit index default sym, need unitindex xml parser') -logg.warning('using default sym for all entries for now') -ledger_tree = load(arg.ledger_xml_file) -uidx = UnitIndex.from_tree(ledger_tree) -ledger = Ledger.from_tree(ledger_tree) - -cfg = usawa.config.load_config(config_dir=arg.c) -db = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) -store = LedgerStore(db, ledger) -#pk = store.get_key() -#wallet = DemoWallet(privatekey=pk) -wallet = store.get_key(DemoWallet, passphrase=cfg.get('WALLET_KEY_PASSPHRASE')) -ledger.set_wallet(wallet) -dt = datetime.datetime.now() - - def do_interactive(ctx): v = input_or_default('Entry description', ctx.description) ctx.description = v @@ -186,24 +146,71 @@ def do_interactive(ctx): output = input_or_default('Output file', ctx.output) logg.debug('output {}'.format(output)) return ctx.open(output) - - -if arg.i: - ctx = do_interactive(ctx) - -ctx.validate() -entry = Entry(ledger.next_serial(), dt, parent=ledger.current(), description=ctx.description, ref=ctx.ref, unitindex=ctx.uidx) -entry.add_part(ctx.part[0], debit=True) -entry.add_part(ctx.part[1]) -for o in ctx.attach: - entry.attach(o) - try: - store.add_asset(o) - except FileExistsError: - logg.info('asset already in store: {}'.format(o)) -entry.sign(wallet) -store.add_entry(entry, update_ledger=True) -ledger.truncate() -ledger.sign() -ctx.f.write(ledger.to_string()) -ctx.close() + + +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-i', action='store_true', help='interactive edit') + argp.add_argument('-r', type=str, help='external reference') + argp.add_argument('-s', type=str, dest='src_account', default='general', help='source account') + argp.add_argument('-t', type=str, dest='dst_account', default='general', help='destination account') + argp.add_argument('-a', type=str, dest='amount', help='source and destination amount') + argp.add_argument('-x', type=str, dest='attachment', default=[], action='append', help='add file attachment') + argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document') + argp.add_argument('--src-type', dest='src_type', type=str, choices=CATEGORIES, default='expense', help='source type') + argp.add_argument('--dst-type', dest='dst_type', type=str, choices=CATEGORIES, default='asset', help='dest type') + argp.add_argument('-d', '--description', dest='description', type=str, help='interactive edit') + # TODO: read default from xml if not defined + argp.add_argument('-u', '--unit', type=str, default=UnitIndex.default_unit, help='Unit to use for transaction') + argp.add_argument('-c', type=str, help='override config dir') + argp.add_argument('--unit-precision', dest='unit_precision', type=int, default=UnitIndex.default_precision, help='Unit precision') + argp.add_argument('--unit-rate', dest='unit_precision', type=float, default=1.0, help='Unit exchange rate') + argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') + argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') + + argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') + arg = argp.parse_args() + ctx = Context.from_args(arg) + + ledger = None + logg.warning('hardcoding unit index default sym, need unitindex xml parser') + logg.warning('using default sym for all entries for now') + ledger_tree = load(arg.ledger_xml_file) + uidx = UnitIndex.from_tree(ledger_tree) + ledger = Ledger.from_tree(ledger_tree) + + cfg = usawa.config.load_config(config_dir=arg.c) + db = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) + store = LedgerStore(db, ledger) + #pk = store.get_key() + #wallet = DemoWallet(privatekey=pk) + wallet = store.get_key(DemoWallet, passphrase=cfg.get('WALLET_KEY_PASSPHRASE')) + ledger.set_wallet(wallet) + dt = datetime.datetime.now() + + + + + if arg.i: + ctx = do_interactive(ctx) + + ctx.validate() + entry = Entry(ledger.next_serial(), dt, parent=ledger.current(), description=ctx.description, ref=ctx.ref, unitindex=ctx.uidx) + entry.add_part(ctx.part[0], debit=True) + entry.add_part(ctx.part[1]) + for o in ctx.attach: + entry.attach(o) + try: + store.add_asset(o) + except FileExistsError: + logg.info('asset already in store: {}'.format(o)) + entry.sign(wallet) + store.add_entry(entry, update_ledger=True) + ledger.truncate() + ledger.sign() + ctx.f.write(ledger.to_string()) + ctx.close() + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/create.py b/dummy/usawa/runnable/create.py @@ -133,83 +133,88 @@ def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): return v -argp = argparse.ArgumentParser() -argp.add_argument('-i', action='store_true', help='interactive edit') -argp.add_argument('-t', dest='topic', type=str, help='ledger topic') -argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') -argp.add_argument('-u', '--unit', type=str, action='append', default=[], help='Units to use for transaction') -argp.add_argument('-p', action='store_true', help='Use passphrase to open wallet') -argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document') -argp.add_argument('-l', type=str, dest='src_uri', help='URI for data source') -argp.add_argument('-c', type=str, help='override config dir') -arg = argp.parse_args() - -if arg.v: - logg.setLevel(getattr(logging, arg.v.upper())) - -ctx = Context.from_args(arg) -cfg = usawa.config.load_config(config_dir=arg.c) - - -def do_interactive(ctx): - logg.debug("Creating new ledger") - v = input_or_default("Topic", ctx.topic) - - ctx.unit = input_or_default("Default unit", ctx.unit) - ctx.unit_precision = input_or_default("Unit decimals", ctx.unit_precision) - ctx.uri = input_or_default("Source URI", ctx.uri) - - input_or_default("XML output filename", ctx.output) - return ctx - - -if arg.i: - ctx = do_interactive(ctx) -ctx = ctx.open(ctx.output) -ctx.validate() - -ledger = Ledger(ctx.uidx, topic=ctx.topic, src=ctx.uri) -store_type = cfg.get('STORE_TYPE') -db = None -if store_type == 'valkey': - db = ValkeyStore( - "", - host=cfg.get("VALKEY_HOST"), - port=cfg.get("VALKEY_PORT"), - ) -elif store_type == 'fs': - db = FsStore( - base=cfg.get('FSSTORE_BASE', xdg_data_home()), - dbname='usawa', - ) -else: - raise ValueError('invalid store type: ' + store_type) -store = LedgerStore(db, ledger) -pk = None -wallet = None -dt = datetime.datetime.now() - -cfg = usawa.config.load_config(config_dir=arg.c) - -ops = int(cfg.get('WALLET_OPSLIMIT', 0)) -mem = int(cfg.get('WALLET_MEMLIMIT', 0)) -pw = cfg.get('WALLET_KEY_PASSPHRASE') -if arg.p and pw == None: - pw = getpass.getpass("passphrase: ") -logg.debug('ops {}'.format(ops)) -try: - wallet = store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) -except FileNotFoundError: - logg.info('no default key found') - wallet = DemoWallet() - store.add_key(wallet, passphrase=pw, opslimit=ops, memlimit=mem) -if wallet == None: - wallet = DemoWallet(privatekey=pk) - logg.info('loaded existing key. {}'.format(wallet.pubkey().hex())) - -acl = ACL.from_wallet(wallet) -#ledger.reset(topic=ctx.topic, src=ctx.uri, acl=acl, wallet=wallet) -ledger = Ledger(ctx.uidx, topic=ctx.topic, src=ctx.uri, acl=acl, wallet=wallet) -ledger.sign() -ctx.f.write(ledger.to_string()) -ctx.close() +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-i', action='store_true', help='interactive edit') + argp.add_argument('-t', dest='topic', type=str, help='ledger topic') + argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') + argp.add_argument('-u', '--unit', type=str, action='append', default=[], help='Units to use for transaction') + argp.add_argument('-p', action='store_true', help='Use passphrase to open wallet') + argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document') + argp.add_argument('-l', type=str, dest='src_uri', help='URI for data source') + argp.add_argument('-c', type=str, help='override config dir') + arg = argp.parse_args() + + if arg.v: + logg.setLevel(getattr(logging, arg.v.upper())) + + ctx = Context.from_args(arg) + cfg = usawa.config.load_config(config_dir=arg.c) + + + def do_interactive(ctx): + logg.debug("Creating new ledger") + v = input_or_default("Topic", ctx.topic) + + ctx.unit = input_or_default("Default unit", ctx.unit) + ctx.unit_precision = input_or_default("Unit decimals", ctx.unit_precision) + ctx.uri = input_or_default("Source URI", ctx.uri) + + input_or_default("XML output filename", ctx.output) + return ctx + + + if arg.i: + ctx = do_interactive(ctx) + ctx = ctx.open(ctx.output) + ctx.validate() + + ledger = Ledger(ctx.uidx, topic=ctx.topic, src=ctx.uri) + store_type = cfg.get('STORE_TYPE') + db = None + if store_type == 'valkey': + db = ValkeyStore( + "", + host=cfg.get("VALKEY_HOST"), + port=cfg.get("VALKEY_PORT"), + ) + elif store_type == 'fs': + db = FsStore( + base=cfg.get('FSSTORE_BASE', xdg_data_home()), + dbname='usawa', + ) + else: + raise ValueError('invalid store type: ' + store_type) + store = LedgerStore(db, ledger) + pk = None + wallet = None + dt = datetime.datetime.now() + + cfg = usawa.config.load_config(config_dir=arg.c) + + ops = int(cfg.get('WALLET_OPSLIMIT', 0)) + mem = int(cfg.get('WALLET_MEMLIMIT', 0)) + pw = cfg.get('WALLET_KEY_PASSPHRASE') + if arg.p and pw == None: + pw = getpass.getpass("passphrase: ") + logg.debug('ops {}'.format(ops)) + try: + wallet = store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) + except FileNotFoundError: + logg.info('no default key found') + wallet = DemoWallet() + store.add_key(wallet, passphrase=pw, opslimit=ops, memlimit=mem) + if wallet == None: + wallet = DemoWallet(privatekey=pk) + logg.info('loaded existing key. {}'.format(wallet.pubkey().hex())) + + acl = ACL.from_wallet(wallet) + #ledger.reset(topic=ctx.topic, src=ctx.uri, acl=acl, wallet=wallet) + ledger = Ledger(ctx.uidx, topic=ctx.topic, src=ctx.uri, acl=acl, wallet=wallet) + ledger.sign() + ctx.f.write(ledger.to_string()) + ctx.close() + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/entry.py b/dummy/usawa/runnable/entry.py @@ -5,6 +5,7 @@ import datetime import signal import sys import shutil +import getpass from whee.valkey import ValkeyStore from whee.fs import FsStore @@ -19,6 +20,7 @@ logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() + class Context: def __init__(self, args): @@ -79,7 +81,10 @@ class Context: self.store = LedgerStore(self.db, self.ledger) ops = int(self.cfg.get('WALLET_OPSLIMIT', 0)) mem = int(self.cfg.get('WALLET_MEMLIMIT', 0)) - self.wallet = self.store.get_key(DemoWallet, passphrase=self.cfg.get('WALLET_KEY_PASSPHRASE'), opslimit=ops, memlimit=mem) + pw = self.cfg.get('WALLET_KEY_PASSPHRASE') + if args.p and pw == None: + pw = getpass.getpass("passphrase: ") + self.wallet = self.store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) self.ledger.set_wallet(self.wallet) if args.e: self.entry = self.store.get_draft(self.entry) @@ -151,29 +156,6 @@ class Context: return datetime.date.fromisoformat(v) -argp = argparse.ArgumentParser() -argp.add_argument('-e', type=str, help='unique reference of entry') -argp.add_argument('-x', type=str, action='append', default=[], help='unique reference of attachment') -argp.add_argument('-z', type=str, action='append', default=[], help='sum of attachment') -argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') -argp.add_argument('-c', type=str, help='override config dir') -argp.add_argument('-d', type=str, help='transaction date or datetime') -argp.add_argument('-o', type=str, help='output ledger state') -argp.add_argument('--commit', action='store_true', dest='commit', help='commit to ledger') -argp.add_argument('ledger_file', type=str, help='ledger file') -args = argp.parse_args() - -if args.v: - logg.setLevel(getattr(logging, args.v.upper())) - -ctx = Context(args) - -def croak(*args, **kwargs): - sys.exit(1) - -signal.signal(signal.SIGINT, croak) -signal.signal(signal.SIGTERM, croak) - def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): if default != None: postfix = ' [{}]'.format(default) + postfix @@ -243,49 +225,78 @@ def enter_part(ctx, entry): return True -entry = None -if ctx.state == 0: - do_interactive_one(ctx) - entry = Entry.empty(ref=ctx.ref, unitindex=ctx.uidx, tx_date=ctx.txdate) - entry = ctx.store.get_draft(entry) - entry.description = ctx.description - entry.dt = ctx.txdate - do_interactive_two(ctx, entry) -else: - entry = ctx.entry - - -for v in args.x: - k = uuid.UUID(v) - asset = Asset(ref=str(k)) - asset = ctx.store.get_asset_indexed(asset) - entry.attach(asset) -for v in args.z: - k = bytes.fromhex(v) - asset = Asset(digest=k) - asset = ctx.store.get_asset(asset) - entry.attach(asset) - -if ctx.commit: - if entry.serial != -1: - raise AttributeError('entry draft already marked as committed') - entry.serial = ctx.ledger.next_serial() - - -v = input_or_default('Commit? (type YES, any other input is no)', '') -if v == 'YES': - entry.parent = ctx.ledger.cur - entry.sign(ctx.wallet) - entry.serial = ctx.ledger.next_serial() - ctx.store.add_entry(entry, update_ledger=True) - ctx.ledger.truncate() - ctx.ledger.sign() - if ctx.fp_bak != None: - shutil.copy(ctx.fp, ctx.fp_bak) - f = open(ctx.fp, 'w') - f.write(ctx.ledger.to_string()) - f.close() -else: - ctx.store.put_draft(entry) - -print(entry) +def main(): + def croak(*args, **kwargs): + sys.exit(1) + + signal.signal(signal.SIGINT, croak) + signal.signal(signal.SIGTERM, croak) + + argp = argparse.ArgumentParser() + argp.add_argument('-e', type=str, help='unique reference of entry') + argp.add_argument('-x', type=str, action='append', default=[], help='unique reference of attachment') + argp.add_argument('-z', type=str, action='append', default=[], help='sum of attachment') + argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') + argp.add_argument('-c', type=str, help='override config dir') + argp.add_argument('-d', type=str, help='transaction date or datetime') + argp.add_argument('-o', type=str, help='output ledger state') + argp.add_argument('-p', action='store_true', help='unlock wallet with password') + argp.add_argument('--commit', action='store_true', dest='commit', help='commit to ledger') + argp.add_argument('ledger_file', type=str, help='ledger file') + args = argp.parse_args() + + if args.v: + logg.setLevel(getattr(logging, args.v.upper())) + + ctx = Context(args) + + entry = None + if ctx.state == 0: + do_interactive_one(ctx) + entry = Entry.empty(ref=ctx.ref, unitindex=ctx.uidx, tx_date=ctx.txdate) + entry = ctx.store.get_draft(entry) + entry.description = ctx.description + entry.dt = ctx.txdate + do_interactive_two(ctx, entry) + else: + entry = ctx.entry + + + for v in args.x: + k = uuid.UUID(v) + asset = Asset(ref=str(k)) + asset = ctx.store.get_asset_indexed(asset) + entry.attach(asset) + for v in args.z: + k = bytes.fromhex(v) + asset = Asset(digest=k) + asset = ctx.store.get_asset(asset) + entry.attach(asset) + + if ctx.commit: + if entry.serial != -1: + raise AttributeError('entry draft already marked as committed') + entry.serial = ctx.ledger.next_serial() + + + v = input_or_default('Commit? (type YES, any other input is no)', '') + if v == 'YES': + entry.parent = ctx.ledger.cur + entry.sign(ctx.wallet) + entry.serial = ctx.ledger.next_serial() + ctx.store.add_entry(entry, update_ledger=True) + ctx.ledger.truncate() + ctx.ledger.sign() + if ctx.fp_bak != None: + shutil.copy(ctx.fp, ctx.fp_bak) + f = open(ctx.fp, 'w') + f.write(ctx.ledger.to_string()) + f.close() + else: + ctx.store.put_draft(entry) + + print(entry) + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/export.py b/dummy/usawa/runnable/export.py @@ -57,47 +57,52 @@ class Context: return ctx -argp = argparse.ArgumentParser() -argp.add_argument('-o', type=str, dest='output', help='output dir for resulting XML entry documents') -argp.add_argument('-c', type=str, help='override config dir') -argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') -argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') -argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') -arg = argp.parse_args() -ctx = Context.from_args(arg) - -#ledger = None -#ledger_tree = load(arg.ledger_xml_file) -#uidx = UnitIndex.from_tree(ledger_tree) -#ledger = Ledger.from_tree(ledger_tree) -ledger = Ledger.from_file(arg.ledger_xml_file) - -cfg = usawa.config.load_config(config_dir=arg.c) -store_type = cfg.get('STORE_TYPE') -db = None -if store_type == 'valkey': - db = ValkeyStore( - "", - host=cfg.get("VALKEY_HOST"), - port=cfg.get("VALKEY_PORT"), - ) -elif store_type == 'fs': - db = FsStore( - base=cfg.get('FSSTORE_BASE', xdg_data_home()), - dbname='usawa', - ) -else: - raise ValueError('invalid store type: ' + store_type) -store = LedgerStore(db, ledger) -#storedb = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) -#store = LedgerStore(storedb, ledger) -#pk = store.get_key() -#wallet = DemoWallet(privatekey=pk) -#wallet = store.get_key(DemoWallet, passphrase=cfg.get('WALLET_KEY_PASSPHRASE')) -wallet = store.get_default_key(DemoWallet) -acl = ACL.from_wallet(wallet) -store.load(acl=acl) -resolve = FSResolver(ctx.output) - -for k in ledger.entries: - r = resolve.put_entry(ledger.entries[k], lookup='sha512') +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-o', type=str, dest='output', help='output dir for resulting XML entry documents') + argp.add_argument('-c', type=str, help='override config dir') + argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') + argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') + argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') + arg = argp.parse_args() + ctx = Context.from_args(arg) + + #ledger = None + #ledger_tree = load(arg.ledger_xml_file) + #uidx = UnitIndex.from_tree(ledger_tree) + #ledger = Ledger.from_tree(ledger_tree) + ledger = Ledger.from_file(arg.ledger_xml_file) + + cfg = usawa.config.load_config(config_dir=arg.c) + store_type = cfg.get('STORE_TYPE') + db = None + if store_type == 'valkey': + db = ValkeyStore( + "", + host=cfg.get("VALKEY_HOST"), + port=cfg.get("VALKEY_PORT"), + ) + elif store_type == 'fs': + db = FsStore( + base=cfg.get('FSSTORE_BASE', xdg_data_home()), + dbname='usawa', + ) + else: + raise ValueError('invalid store type: ' + store_type) + store = LedgerStore(db, ledger) + #storedb = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) + #store = LedgerStore(storedb, ledger) + #pk = store.get_key() + #wallet = DemoWallet(privatekey=pk) + #wallet = store.get_key(DemoWallet, passphrase=cfg.get('WALLET_KEY_PASSPHRASE')) + wallet = store.get_default_key(DemoWallet) + acl = ACL.from_wallet(wallet) + store.load(acl=acl) + resolve = FSResolver(ctx.output) + + for k in ledger.entries: + r = resolve.put_entry(ledger.entries[k], lookup='sha512') + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/import.py b/dummy/usawa/runnable/import.py @@ -54,22 +54,27 @@ class Context: return ctx -argp = argparse.ArgumentParser() -argp.add_argument('-o', type=str, dest='output', help='output file for resulting XML document') -argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') -argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') -argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') -arg = argp.parse_args() -ctx = Context.from_args(arg) - -ledger = Ledger.from_file(arg.ledger_xml_file) - -cfg = usawa.config.load() -storedb = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) -store = LedgerStore(storedb, ledger) -#pk = store.get_key() -#wallet = DemoWallet(privatekey=pk) -#acl = ACL.from_wallet(wallet) -#store.load(acl=acl) -store.put_all(store_assets=True) -sys.stdout.write(ledger.to_string()) +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-o', type=str, dest='output', help='output file for resulting XML document') + argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') + argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') + argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') + arg = argp.parse_args() + ctx = Context.from_args(arg) + + ledger = Ledger.from_file(arg.ledger_xml_file) + + cfg = usawa.config.load() + storedb = ValkeyStore('', host=ctx.valkey_host, port=ctx.valkey_port) + store = LedgerStore(storedb, ledger) + #pk = store.get_key() + #wallet = DemoWallet(privatekey=pk) + #acl = ACL.from_wallet(wallet) + #store.load(acl=acl) + store.put_all(store_assets=True) + sys.stdout.write(ledger.to_string()) + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/server.py b/dummy/usawa/runnable/server.py @@ -5,7 +5,7 @@ import signal import usawa.config from usawa import Ledger, Entry, EntryPart, DemoWallet, ACL, UnitIndex, load -from usawa.context import Context +from usawa.context import UsawaContext from usawa.service import Handler, UnixServer from whee.valkey import ValkeyStore diff --git a/dummy/usawa/runnable/setup_wallet.py b/dummy/usawa/runnable/setup_wallet.py @@ -1,111 +0,0 @@ -import argparse -import getpass -import logging -import os -from pathlib import Path -from nacl.signing import SigningKey -from nacl.secret import SecretBox -from nacl.pwhash import argon2i -from whee.valkey import ValkeyStore -from whee.fs import FsStore -from xdg_base_dirs import xdg_data_home - -from usawa.store import KeyStore -from usawa.crypto import DemoWallet -from usawa.config import load_config - -logging.basicConfig(level=logging.WARNING) -logg = logging.getLogger() - -PRIVATEKEY_FILE = "privatekey.box" -PUBLICKEY_FILE = "publickey.bin" - -# TODO: change to xdg_data_dir -DEFAULT_WALLET_DIR = ( - Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) / "usawa" -) - - -def encrypt_seed(seed: bytes, passphrase: str) -> bytes: - salt = os.urandom(argon2i.SALTBYTES) - key = argon2i.kdf( - SecretBox.KEY_SIZE, - passphrase.encode(), - salt, - ) - box = SecretBox(key) - encrypted = box.encrypt(seed) - - # Prepend salt so we can re-derive the key on decrypt - return salt + encrypted - - -def setup_wallet(cfg, wallet_dir=None): - db = None - store_type = cfg.get('STORE_TYPE') - if store_type == 'valkey': - db = ValkeyStore( - "", - host=cfg.get("VALKEY_HOST"), - port=cfg.get("VALKEY_PORT"), - ) - elif store_type == 'fs': - db = FsStore( - base=cfg.get('FSSTORE_BASE', xdg_data_home()), - dbname='usawa', - ) - else: - raise ValueError('invalid store type: ' + store_type) - - store = KeyStore(db) - - wallet_dir = Path(wallet_dir) if wallet_dir else DEFAULT_WALLET_DIR - wallet_dir.mkdir(parents=True, exist_ok=True) - logg.info("wallet directory: %s", wallet_dir) - - privatekey_path = wallet_dir / PRIVATEKEY_FILE - publickey_path = wallet_dir / PUBLICKEY_FILE - - passphrase = getpass.getpass("Enter wallet passphrase: ") - passphrase_confirm = getpass.getpass("Confirm wallet passphrase: ") - if passphrase != passphrase_confirm: - logg.error("passphrases do not match") - return 1 - - random_bytes = os.urandom(32) - logg.debug("generated 32-byte seed") - - encrypted = encrypt_seed(random_bytes, passphrase) - with open(privatekey_path, "wb") as f: - f.write(encrypted) - logg.info("encrypted key material saved to: %s", privatekey_path) - - sk = SigningKey(random_bytes) - pk = sk.verify_key - with open(publickey_path, "wb") as f: - f.write(pk.encode()) - logg.info("public key saved to: %s", publickey_path) - ops = int(cfg.get('WALLET_OPSLIMIT', 0)) - mem = int(cfg.get('WALLET_MEMLIMIT', 0)) - - wallet = DemoWallet(privatekey=random_bytes) - store.add_key(wallet, passphrase=passphrase_confirm, opslimit=ops, memlimit=mem) - logg.info("key written to store") - - logg.info("setup complete.") - logg.info("your 32-byte public key (hex): %s", pk.encode().hex()) - - return 0 - - -if __name__ == '__main__': - argp = argparse.ArgumentParser() - argp.add_argument('-c', type=str, help='override config dir') - argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') - args = argp.parse_args() - - if args.v: - logg.setLevel(getattr(logging, args.v.upper())) - - cfg = load_config(config_dir=args.c) - setup_wallet(cfg) diff --git a/dummy/usawa/runnable/view.py b/dummy/usawa/runnable/view.py @@ -57,54 +57,59 @@ class Context: return ctx -argp = argparse.ArgumentParser() -argp.add_argument('-o', type=str, dest='output', help='output file for resulting XML document') -#argp.add_argument('-p', action='store_true', help='prompt for password to open wallet') -argp.add_argument('-c', type=str, help='override config dir') -argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') -argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') -argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') -argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') -arg = argp.parse_args() - -if arg.v: - logg.setLevel(getattr(logging, arg.v.upper())) - -ctx = Context.from_args(arg) - -#ledger = None -#ledger_tree = load(arg.ledger_xml_file) -#uidx = UnitIndex.from_tree(ledger_tree) -#ledger = Ledger.from_tree(ledger_tree) -ledger = Ledger.from_file(arg.ledger_xml_file) - -cfg = usawa.config.load_config(config_dir=arg.c) -store_type = cfg.get('STORE_TYPE') -db = None -if store_type == 'valkey': - db = ValkeyStore( - "", - host=cfg.get("VALKEY_HOST"), - port=cfg.get("VALKEY_PORT"), - ) -elif store_type == 'fs': - db = FsStore( - base=cfg.get('FSSTORE_BASE', xdg_data_home()), - dbname='usawa', - ) -else: - raise ValueError('invalid store type: ' + store_type) -store = LedgerStore(db, ledger) -#pk = store.get_key() -#wallet = DemoWallet(privatekey=pk) -#ops = int(cfg.get('WALLET_OPSLIMIT', 0)) -#mem = int(cfg.get('WALLET_MEMLIMIT', 0)) -#pw = cfg.get('WALLET_KEY_PASSPHRASE') -#if arg.p and pw == None: -# pw = getpass.getpass("passphrase: ") -#logg.debug('ops {}'.format(ops)) -#wallet = store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) -wallet = store.get_default_key(DemoWallet) -acl = ACL.from_wallet(wallet) -store.load(acl=acl) -sys.stdout.write(ledger.to_string()) +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-o', type=str, dest='output', help='output file for resulting XML document') + #argp.add_argument('-p', action='store_true', help='prompt for password to open wallet') + argp.add_argument('-c', type=str, help='override config dir') + argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') + argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') + argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') + argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') + arg = argp.parse_args() + + if arg.v: + logg.setLevel(getattr(logging, arg.v.upper())) + + ctx = Context.from_args(arg) + + #ledger = None + #ledger_tree = load(arg.ledger_xml_file) + #uidx = UnitIndex.from_tree(ledger_tree) + #ledger = Ledger.from_tree(ledger_tree) + ledger = Ledger.from_file(arg.ledger_xml_file) + + cfg = usawa.config.load_config(config_dir=arg.c) + store_type = cfg.get('STORE_TYPE') + db = None + if store_type == 'valkey': + db = ValkeyStore( + "", + host=cfg.get("VALKEY_HOST"), + port=cfg.get("VALKEY_PORT"), + ) + elif store_type == 'fs': + db = FsStore( + base=cfg.get('FSSTORE_BASE', xdg_data_home()), + dbname='usawa', + ) + else: + raise ValueError('invalid store type: ' + store_type) + store = LedgerStore(db, ledger) + #pk = store.get_key() + #wallet = DemoWallet(privatekey=pk) + #ops = int(cfg.get('WALLET_OPSLIMIT', 0)) + #mem = int(cfg.get('WALLET_MEMLIMIT', 0)) + #pw = cfg.get('WALLET_KEY_PASSPHRASE') + #if arg.p and pw == None: + # pw = getpass.getpass("passphrase: ") + #logg.debug('ops {}'.format(ops)) + #wallet = store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) + wallet = store.get_default_key(DemoWallet) + acl = ACL.from_wallet(wallet) + store.load(acl=acl) + sys.stdout.write(ledger.to_string()) + + +if __name__ == '__main__': + main() diff --git a/dummy/usawa/runnable/wallet.py b/dummy/usawa/runnable/wallet.py @@ -0,0 +1,115 @@ +import argparse +import getpass +import logging +import os +from pathlib import Path +from nacl.signing import SigningKey +from nacl.secret import SecretBox +from nacl.pwhash import argon2i +from whee.valkey import ValkeyStore +from whee.fs import FsStore +from xdg_base_dirs import xdg_data_home + +from usawa.store import KeyStore +from usawa.crypto import DemoWallet +from usawa.config import load_config + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +PRIVATEKEY_FILE = "privatekey.box" +PUBLICKEY_FILE = "publickey.bin" + +# TODO: change to xdg_data_dir +DEFAULT_WALLET_DIR = ( + Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) / "usawa" +) + + +def encrypt_seed(seed: bytes, passphrase: str) -> bytes: + salt = os.urandom(argon2i.SALTBYTES) + key = argon2i.kdf( + SecretBox.KEY_SIZE, + passphrase.encode(), + salt, + ) + box = SecretBox(key) + encrypted = box.encrypt(seed) + + # Prepend salt so we can re-derive the key on decrypt + return salt + encrypted + + +def setup_wallet(cfg, wallet_dir=None): + db = None + store_type = cfg.get('STORE_TYPE') + if store_type == 'valkey': + db = ValkeyStore( + "", + host=cfg.get("VALKEY_HOST"), + port=cfg.get("VALKEY_PORT"), + ) + elif store_type == 'fs': + db = FsStore( + base=cfg.get('FSSTORE_BASE', xdg_data_home()), + dbname='usawa', + ) + else: + raise ValueError('invalid store type: ' + store_type) + + store = KeyStore(db) + + wallet_dir = Path(wallet_dir) if wallet_dir else DEFAULT_WALLET_DIR + wallet_dir.mkdir(parents=True, exist_ok=True) + logg.info("wallet directory: %s", wallet_dir) + + privatekey_path = wallet_dir / PRIVATEKEY_FILE + publickey_path = wallet_dir / PUBLICKEY_FILE + + passphrase = getpass.getpass("Enter wallet passphrase: ") + passphrase_confirm = getpass.getpass("Confirm wallet passphrase: ") + if passphrase != passphrase_confirm: + logg.error("passphrases do not match") + return 1 + + random_bytes = os.urandom(32) + logg.debug("generated 32-byte seed") + + encrypted = encrypt_seed(random_bytes, passphrase) + with open(privatekey_path, "wb") as f: + f.write(encrypted) + logg.info("encrypted key material saved to: %s", privatekey_path) + + sk = SigningKey(random_bytes) + pk = sk.verify_key + with open(publickey_path, "wb") as f: + f.write(pk.encode()) + logg.info("public key saved to: %s", publickey_path) + ops = int(cfg.get('WALLET_OPSLIMIT', 0)) + mem = int(cfg.get('WALLET_MEMLIMIT', 0)) + + wallet = DemoWallet(privatekey=random_bytes) + store.add_key(wallet, passphrase=passphrase_confirm, opslimit=ops, memlimit=mem) + logg.info("key written to store") + + logg.info("setup complete.") + logg.info("your 32-byte public key (hex): %s", pk.encode().hex()) + + return 0 + + +def main(): + argp = argparse.ArgumentParser() + argp.add_argument('-c', type=str, help='override config dir') + argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') + args = argp.parse_args() + + if args.v: + logg.setLevel(getattr(logging, args.v.upper())) + + cfg = load_config(config_dir=args.c) + setup_wallet(cfg) + + +if __name__ == '__main__': + main()