128 lines
4.2 KiB
Python
Executable File
128 lines
4.2 KiB
Python
Executable File
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from docopt import docopt
|
|
|
|
from . import app, config, droppy, httpredir, pwgen, serve
|
|
from ._version import version
|
|
|
|
app, httpredir.app # Needed for Sanic multiprocessing
|
|
|
|
doc = f"""Cista {version} - A file storage for the web.
|
|
|
|
Usage:
|
|
cista [-c <confdir>] [-l <host>] [--import-droppy] [--dev] [<path>]
|
|
cista [-c <confdir>] --user <name> [--privileged] [--password]
|
|
|
|
Options:
|
|
-c CONFDIR Custom config directory
|
|
-l LISTEN-ADDR Listen on
|
|
:8000 (localhost port, plain http)
|
|
<addr>: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 = Path(args["<path>"]).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())
|