simple-image-hosting 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .main import app, create_app
2
+
3
+ __all__ = ["app", "create_app"]
simple_image/cli.py ADDED
@@ -0,0 +1,171 @@
1
+ import argparse
2
+ import getpass
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import uvicorn
8
+
9
+ from .main import create_app, hash_password
10
+ from .models import DEFAULT_COMPRESS_QUALITY, User, create_session_factory
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ parser = argparse.ArgumentParser(prog="simple-image", description="Simple Image server CLI")
15
+ subparsers = parser.add_subparsers(dest="command", required=True)
16
+
17
+ serve = subparsers.add_parser("serve", help="Run HTTP server")
18
+ serve.add_argument("data_dir", type=Path, help="Runtime data directory (DB + images)")
19
+ serve.add_argument("--host", default="0.0.0.0", help="Bind host")
20
+ serve.add_argument("--port", type=int, default=8000, help="Bind port")
21
+ serve.add_argument("--reload", action="store_true", help="Enable auto reload")
22
+ serve.add_argument("--api-url", default=None, help="Public API base URL")
23
+ serve.add_argument("--admin-username", default=None, help="Bootstrap admin username")
24
+ serve.add_argument("--admin-password", default=None, help="Bootstrap admin password")
25
+ serve.add_argument(
26
+ "--database-url",
27
+ default=None,
28
+ help="Database URL, e.g. mysql+pymysql://user:pass@host:3306/simple_image",
29
+ )
30
+ serve.add_argument(
31
+ "--compress-quality",
32
+ type=int,
33
+ default=None,
34
+ help="Default upload compress quality (1-95)",
35
+ )
36
+ serve.add_argument(
37
+ "--base-path",
38
+ default=None,
39
+ help="Deploy under sub path, e.g. /simple_image",
40
+ )
41
+ serve.add_argument(
42
+ "-d", "--daemon", action="store_true",
43
+ help="Run server as a background daemon",
44
+ )
45
+
46
+ reset_pwd = subparsers.add_parser(
47
+ "reset-admin-password", help="Reset admin password"
48
+ )
49
+ reset_pwd.add_argument(
50
+ "data_dir", type=Path, help="Runtime data directory (same as used by serve)"
51
+ )
52
+ reset_pwd.add_argument(
53
+ "--username", default=None,
54
+ help="Admin username to reset (omit to auto-select the only admin)"
55
+ )
56
+ reset_pwd.add_argument(
57
+ "--database-url", default=None,
58
+ help="Database URL (omit to use data_dir/database.db)"
59
+ )
60
+
61
+ return parser
62
+
63
+
64
+ def _daemonize(data_dir: Path) -> None:
65
+ """Double-fork to detach from the controlling terminal."""
66
+ data_dir.mkdir(parents=True, exist_ok=True)
67
+ pid_file = data_dir / "simple-image.pid"
68
+ log_file = data_dir / "simple-image.log"
69
+
70
+ # First fork – exit parent
71
+ if os.fork() > 0:
72
+ raise SystemExit(0)
73
+
74
+ os.setsid()
75
+
76
+ # Second fork – prevent re-acquiring a terminal
77
+ if os.fork() > 0:
78
+ raise SystemExit(0)
79
+
80
+ # Redirect stdio to log file
81
+ log_fd = os.open(str(log_file), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
82
+ os.dup2(log_fd, sys.stdout.fileno())
83
+ os.dup2(log_fd, sys.stderr.fileno())
84
+ os.close(log_fd)
85
+
86
+ # Write PID file
87
+ pid_file.write_text(str(os.getpid()))
88
+
89
+
90
+ def run_serve(args: argparse.Namespace) -> None:
91
+ if args.daemon:
92
+ _daemonize(args.data_dir)
93
+
94
+ app = create_app(
95
+ data_dir=args.data_dir,
96
+ api_url=args.api_url,
97
+ admin_username=args.admin_username,
98
+ admin_password=args.admin_password,
99
+ default_compress_quality=args.compress_quality,
100
+ database_url=args.database_url,
101
+ base_path=args.base_path,
102
+ )
103
+ uvicorn.run(app, host=args.host, port=args.port, reload=args.reload)
104
+
105
+
106
+ def run_reset_admin_password(args: argparse.Namespace) -> None:
107
+ db_path = args.data_dir / "database.db"
108
+ database_url = args.database_url or os.getenv("SIMPLE_IMAGE_DATABASE_URL") or os.getenv("DATABASE_URL")
109
+ if not database_url:
110
+ if not db_path.exists():
111
+ print(f"Error: database not found at {db_path}", file=sys.stderr)
112
+ raise SystemExit(1)
113
+ database_url = f"sqlite:///{db_path}"
114
+
115
+ use_db_path = None if args.database_url else db_path
116
+ session_factory = create_session_factory(
117
+ default_compress_quality=DEFAULT_COMPRESS_QUALITY,
118
+ database_url=database_url,
119
+ db_path=use_db_path,
120
+ )
121
+ db = session_factory()
122
+ try:
123
+ admins = db.query(User).filter(User.is_admin == True).all()
124
+
125
+ if not admins:
126
+ print("Error: no admin user found in the database.", file=sys.stderr)
127
+ raise SystemExit(1)
128
+
129
+ if args.username:
130
+ target = next((u for u in admins if u.username == args.username), None)
131
+ if not target:
132
+ available = ", ".join(u.username for u in admins)
133
+ print(f"Error: admin '{args.username}' not found. Existing admins: {available}", file=sys.stderr)
134
+ raise SystemExit(1)
135
+ elif len(admins) == 1:
136
+ target = admins[0]
137
+ print(f"Found admin user: {target.username}")
138
+ else:
139
+ print("Multiple admin users found, please specify --username:")
140
+ for u in admins:
141
+ print(f" - {u.username}")
142
+ raise SystemExit(1)
143
+
144
+ new_password = getpass.getpass(f"New password for '{target.username}': ")
145
+ if not new_password:
146
+ print("Error: password cannot be empty.", file=sys.stderr)
147
+ raise SystemExit(1)
148
+ confirm = getpass.getpass("Confirm new password: ")
149
+ if new_password != confirm:
150
+ print("Error: passwords do not match.", file=sys.stderr)
151
+ raise SystemExit(1)
152
+
153
+ target.password_hash = hash_password(new_password)
154
+ db.commit()
155
+ print(f"Password for admin '{target.username}' has been reset successfully.")
156
+ finally:
157
+ db.close()
158
+
159
+
160
+ def main() -> None:
161
+ parser = build_parser()
162
+ args = parser.parse_args()
163
+
164
+ if args.command == "serve":
165
+ run_serve(args)
166
+ elif args.command == "reset-admin-password":
167
+ run_reset_admin_password(args)
168
+
169
+
170
+ if __name__ == "__main__":
171
+ main()