1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
"""Persistent Pocket ID deployment for Modal.
The ``pocket-id`` secret must contain ``ENCRYPTION_KEY``. Pocket ID keeps its
SQLite database, signing keys, and uploads in ``/app/data``, mounted here as a
Modal Volume so they survive container replacement and scale-to-zero.
"""
import subprocess
import time
import modal
APP_NAME = "pocket-id"
APP_URL = "https://codegod100--pocket-id-serve.modal.run"
DATA_VOLUME_NAME = "pocket-id-data"
app = modal.App(APP_NAME)
data_volume = modal.Volume.from_name(DATA_VOLUME_NAME, create_if_missing=True)
pocket_id_secret = modal.Secret.from_name("pocket-id", required_keys=["ENCRYPTION_KEY"])
image = (
modal.Image.debian_slim(python_version="3.12")
.apt_install("ca-certificates", "curl")
.run_commands(
"mkdir -p /app "
"&& curl --fail --location --silent --show-error "
"https://github.com/pocket-id/pocket-id/releases/latest/download/pocket-id_linux_amd64 "
"--output /app/pocket-id "
"&& chmod 0755 /app/pocket-id"
)
)
@app.function(
image=image,
secrets=[pocket_id_secret],
volumes={"/app/data": data_volume},
min_containers=0,
max_containers=1,
scaledown_window=300,
timeout=15 * 60,
env={
"APP_URL": APP_URL,
"ALLOW_INSECURE_CALLBACK_URLS": "false",
# Modal Volumes are network filesystems; SQLite's WAL mode is unsafe
# there. Keep a single writer and use DELETE journaling instead.
"DB_CONNECTION_STRING": "/app/data/pocket-id.db?_journal_mode=DELETE",
"UPLOAD_PATH": "/app/data/uploads",
},
)
@modal.concurrent(max_inputs=100)
@modal.web_server(1411, startup_timeout=120)
def serve() -> None:
"""Start Pocket ID and retain the web-server Function for its lifetime."""
process = subprocess.Popen(["/app/pocket-id"])
time.sleep(1)
if process.poll() is not None:
raise RuntimeError(f"Pocket ID exited during startup ({process.returncode})")
|