import sys from pathlib import Path from docopt import docopt import cista from cista import app, config, droppy, serve, server80 from cista.util import pwgen del app, server80.app # Only import needed, for Sanic multiprocessing doc = f"""Cista {cista.__version__} - A file storage for the web. Usage: cista [-c ] [-l ] [--import-droppy] [--dev] [] cista [-c ] --user [--privileged] [--password] Options: -c CONFDIR Custom config directory -l LISTEN-ADDR Listen on :8000 (localhost port, plain http) :3000 (bind another address, port) /path/to/unix.sock (unix socket) example.com (run on 80 and 443 with LetsEncrypt) --import-droppy Import Droppy config from ~/.droppy/config --dev Developer mode (reloads, friendlier crashes, more logs) Listen address, path and imported options are preserved in config, and only custom config dir and dev mode need to be specified on subsequent runs. User management: --user NAME Create or modify user --privileged Give the user full admin rights --password Reset password """ def main(): # Dev mode doesn't catch exceptions if "--dev" in sys.argv: return _main() # Normal mode keeps it quiet try: return _main() except Exception as e: print("Error:", e) return 1 def _main(): args = docopt(doc) if args["--user"]: return _user(args) listen = args["-l"] # Validate arguments first if args[""]: path = Path(args[""]).resolve() if not path.is_dir(): raise ValueError(f"No such directory: {path}") else: path = None _confdir(args) exists = config.conffile.exists() import_droppy = args["--import-droppy"] necessary_opts = exists or import_droppy or path and listen if not necessary_opts: # Maybe run without arguments print(doc) print("No config file found! Get started with:\n cista -l :8000 /path/to/files, or\n cista -l example.com --import-droppy # Uses Droppy files\n") return 1 settings = {} if import_droppy: if exists: raise ValueError(f"Importing Droppy: First remove the existing configuration:\n rm {config.conffile}") settings = droppy.readconf() if path: settings["path"] = path if listen: settings["listen"] = listen operation = config.update_config(settings) print(f"Config {operation}: {config.conffile}") # Prepare to serve domain = unix = port = None url, _ = serve.parse_listen(config.config.listen) if not config.config.path.is_dir(): raise ValueError(f"No such directory: {config.config.path}") extra = f" ({unix})" if unix else "" dev = args["--dev"] if dev: extra += " (dev mode)" print(f"Serving {config.config.path} at {url}{extra}") # Run the server serve.run(dev=dev) def _confdir(args): if args["-c"]: # Custom config directory confdir = Path(args["-c"]).resolve() if confdir.exists() and not confdir.is_dir(): if confdir.name != config.conffile.name: raise ValueError("Config path is not a directory") # Accidentally pointed to the cista.toml, use parent confdir = confdir.parent config.conffile = config.conffile.with_parent(confdir) def _user(args): _confdir(args) config.load_config() name = args["--user"] if not name or not name.isidentifier(): raise ValueError("Invalid username") config.load_config() u = config.config.users.get(name) info = f"User {name}" if u else f"New user {name}" changes = {} oldadmin = u and u.privileged if args["--privileged"]: changes["privileged"] = True info += " (already admin)" if oldadmin else " (made admin)" else: info += " (admin)" if oldadmin else "" if args["--password"] or not u: changes["password"] = pw = pwgen.generate() info += f"\n Password: {pw}" res = config.update_user(args["--user"], changes) print(info) if res == "read": print(" No changes") if __name__ == "__main__": sys.exit(main())