2023-11-21 15:32:49 +00:00
|
|
|
import os
|
2023-10-19 00:06:14 +01:00
|
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from docopt import docopt
|
|
|
|
|
2023-10-21 20:30:47 +01:00
|
|
|
import cista
|
2023-10-21 17:17:09 +01:00
|
|
|
from cista import app, config, droppy, serve, server80
|
|
|
|
from cista.util import pwgen
|
2023-10-19 00:06:14 +01:00
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
del app, server80.app # Only import needed, for Sanic multiprocessing
|
2023-10-19 00:06:14 +01:00
|
|
|
|
2023-10-21 20:30:47 +01:00
|
|
|
doc = f"""Cista {cista.__version__} - A file storage for the web.
|
2023-10-19 00:06:14 +01:00
|
|
|
|
|
|
|
Usage:
|
|
|
|
cista [-c <confdir>] [-l <host>] [--import-droppy] [--dev] [<path>]
|
2023-10-19 02:06:21 +01:00
|
|
|
cista [-c <confdir>] --user <name> [--privileged] [--password]
|
2023-10-19 00:06:14 +01:00
|
|
|
|
|
|
|
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.
|
2023-10-19 02:06:21 +01:00
|
|
|
|
|
|
|
User management:
|
|
|
|
--user NAME Create or modify user
|
|
|
|
--privileged Give the user full admin rights
|
|
|
|
--password Reset password
|
2023-10-19 00:06:14 +01:00
|
|
|
"""
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-19 00:06:14 +01:00
|
|
|
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
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-19 00:06:14 +01:00
|
|
|
def _main():
|
|
|
|
args = docopt(doc)
|
2023-10-19 02:06:21 +01:00
|
|
|
if args["--user"]:
|
|
|
|
return _user(args)
|
2023-10-19 00:06:14 +01:00
|
|
|
listen = args["-l"]
|
|
|
|
# Validate arguments first
|
|
|
|
if args["<path>"]:
|
2023-10-19 21:52:37 +01:00
|
|
|
path = Path(args["<path>"]).resolve()
|
2023-10-19 00:06:14 +01:00
|
|
|
if not path.is_dir():
|
|
|
|
raise ValueError(f"No such directory: {path}")
|
|
|
|
else:
|
|
|
|
path = None
|
2023-10-19 02:06:21 +01:00
|
|
|
_confdir(args)
|
2023-10-19 00:06:14 +01:00
|
|
|
exists = config.conffile.exists()
|
2023-11-21 15:32:49 +00:00
|
|
|
print(config.conffile, exists)
|
2023-10-19 00:06:14 +01:00
|
|
|
import_droppy = args["--import-droppy"]
|
2023-11-14 16:03:15 +00:00
|
|
|
necessary_opts = exists or import_droppy or path
|
2023-10-19 00:06:14 +01:00
|
|
|
if not necessary_opts:
|
|
|
|
# Maybe run without arguments
|
|
|
|
print(doc)
|
2023-10-26 15:18:59 +01:00
|
|
|
print(
|
2023-11-14 16:03:15 +00:00
|
|
|
"No config file found! Get started with one of:\n"
|
|
|
|
" cista --user yourname --privileged\n"
|
|
|
|
" cista --import-droppy\n"
|
|
|
|
" cista -l :8000 /path/to/files\n"
|
2023-10-26 15:18:59 +01:00
|
|
|
)
|
2023-10-19 00:06:14 +01:00
|
|
|
return 1
|
|
|
|
settings = {}
|
|
|
|
if import_droppy:
|
|
|
|
if exists:
|
2023-10-26 15:18:59 +01:00
|
|
|
raise ValueError(
|
2023-11-08 20:38:40 +00:00
|
|
|
f"Importing Droppy: First remove the existing configuration:\n rm {config.conffile}",
|
2023-10-26 15:18:59 +01:00
|
|
|
)
|
2023-10-19 00:06:14 +01:00
|
|
|
settings = droppy.readconf()
|
2023-10-26 15:18:59 +01:00
|
|
|
if path:
|
|
|
|
settings["path"] = path
|
2023-11-14 16:03:15 +00:00
|
|
|
elif not exists:
|
|
|
|
settings["path"] = Path.home() / "Downloads"
|
2023-10-26 15:18:59 +01:00
|
|
|
if listen:
|
|
|
|
settings["listen"] = listen
|
2023-11-14 16:03:15 +00:00
|
|
|
elif not exists:
|
|
|
|
settings["listen"] = ":8000"
|
|
|
|
if not exists and not import_droppy:
|
|
|
|
# We have no users, so make it public
|
|
|
|
settings["public"] = True
|
2023-10-19 00:06:14 +01:00
|
|
|
operation = config.update_config(settings)
|
|
|
|
print(f"Config {operation}: {config.conffile}")
|
|
|
|
# Prepare to serve
|
2023-10-26 15:18:59 +01:00
|
|
|
unix = None
|
2023-10-19 00:06:14 +01:00
|
|
|
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)
|
2023-11-08 20:38:40 +00:00
|
|
|
return 0
|
|
|
|
|
2023-10-19 00:06:14 +01:00
|
|
|
|
2023-10-19 02:06:21 +01:00
|
|
|
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")
|
2023-11-08 20:56:43 +00:00
|
|
|
# Accidentally pointed to the db.toml, use parent
|
2023-10-19 02:06:21 +01:00
|
|
|
confdir = confdir.parent
|
2023-11-21 15:32:49 +00:00
|
|
|
os.environ["CISTA_HOME"] = confdir.as_posix()
|
|
|
|
config.init_confdir() # Uses environ if available
|
2023-10-19 02:06:21 +01:00
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-19 02:06:21 +01:00
|
|
|
def _user(args):
|
|
|
|
_confdir(args)
|
2023-11-14 16:03:15 +00:00
|
|
|
if config.conffile.exists():
|
|
|
|
config.load_config()
|
|
|
|
operation = False
|
|
|
|
else:
|
|
|
|
# Defaults for new config when user is created
|
|
|
|
operation = config.update_config(
|
|
|
|
{
|
|
|
|
"listen": ":8000",
|
|
|
|
"path": Path.home() / "Downloads",
|
|
|
|
"public": False,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
print(f"Config {operation}: {config.conffile}\n")
|
|
|
|
|
2023-10-19 02:06:21 +01:00
|
|
|
name = args["--user"]
|
|
|
|
if not name or not name.isidentifier():
|
|
|
|
raise ValueError("Invalid username")
|
|
|
|
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()
|
2023-11-14 16:03:15 +00:00
|
|
|
info += f"\n Password: {pw}\n"
|
|
|
|
res = config.update_user(name, changes)
|
2023-10-19 02:06:21 +01:00
|
|
|
print(info)
|
|
|
|
if res == "read":
|
|
|
|
print(" No changes")
|
|
|
|
|
2023-11-14 16:03:15 +00:00
|
|
|
if operation == "created":
|
|
|
|
print(
|
|
|
|
"Now you can run the server:\n cista # defaults set: -l :8000 ~/Downloads\n"
|
|
|
|
)
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-19 00:06:14 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|