cloudunity commited on
Commit
610aab7
·
verified ·
1 Parent(s): 1b23905

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +177 -55
Dockerfile CHANGED
@@ -1,58 +1,180 @@
1
- FROM python:3.12-slim
2
-
3
- ENV DEBIAN_FRONTEND=noninteractive
4
- ENV PYTHONUNBUFFERED=1
5
- ENV PIP_NO_CACHE_DIR=1
6
- ENV LD_LIBRARY_PATH=/opt/llama
7
-
8
- WORKDIR /app
9
-
10
- RUN apt-get update && apt-get install -y --no-install-recommends \
11
- git \
12
- cmake \
13
- build-essential \
14
- curl \
15
- ca-certificates \
16
- libgomp1 \
17
- && rm -rf /var/lib/apt/lists/*
18
-
19
- RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp.git /tmp/llama.cpp \
20
- && cmake -S /tmp/llama.cpp -B /tmp/llama.cpp/build \
21
- -DGGML_NATIVE=OFF \
22
- -DGGML_OPENMP=ON \
23
- -DLLAMA_BUILD_SERVER=ON \
24
- -DLLAMA_BUILD_TESTS=OFF \
25
- -DLLAMA_BUILD_EXAMPLES=ON \
26
- && cmake --build /tmp/llama.cpp/build \
27
- --config Release \
28
- --target llama-server \
29
- -j2 \
30
- && mkdir -p /opt/llama \
31
- && cp /tmp/llama.cpp/build/bin/llama-server /opt/llama/ \
32
- && cp /tmp/llama.cpp/build/bin/*.so* /opt/llama/ \
33
- && rm -rf /tmp/llama.cpp
34
-
35
- RUN pip install huggingface_hub
36
-
37
- RUN python3 -c "\
38
- from huggingface_hub import hf_hub_download; \
39
- hf_hub_download( \
40
- repo_id='LiquidAI/LFM2.5-2.6B-GGUF', \
41
- filename='LFM2.5-2.6B-QAD-Q4_0.gguf', \
42
- local_dir='/app/models' \
43
- )"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  EXPOSE 7860
46
 
47
- CMD ["/opt/llama/llama-server", \
48
- "--model", "/app/models/LFM2.5-2.6B-QAD-Q4_0.gguf", \
49
- "--host", "0.0.0.0", \
50
- "--port", "7860", \
51
- "--alias", "LFM2.5-2.6B", \
52
- "--threads", "2", \
53
- "--ctx-size", "32768", \
54
- "--cache-type-k", "q8_0", \
55
- "--cache-type-v", "q8_0", \
56
- "--parallel", "1", \
57
- "--cont-batching", \
58
- "--flash-attn", "auto"]
 
1
+ # syntax=docker/dockerfile:1
2
+ # =============================================================================
3
+ # Rochester-restricted Bluesky PDS - single Dockerfile, built for HF Spaces.
4
+ #
5
+ # Listens on port 7860 (HF forwards the public Space URL to it automatically).
6
+ #
7
+ # On first boot it generates JWT/admin secrets, persists them in /app/data,
8
+ # and prints the admin password to the Space's container logs.
9
+ #
10
+ # Registration is restricted to emails at PDS_EMAIL_ALLOWED_DOMAINS
11
+ # (default: rochesterschools.org). No SMTP configured - email verification
12
+ # is skipped entirely; the domain check is the only gate.
13
+ #
14
+ # NOTE: HF Spaces have ephemeral storage unless you enable persistent storage
15
+ # in the Space settings. /app/data holds the SQLite db + generated secrets.
16
+ # =============================================================================
17
+
18
+ FROM ghcr.io/bluesky-social/pds:0.4
19
+
20
+ USER root
21
+
22
+ # Write the email-allowlist patcher into the image.
23
+ RUN <<'PATCH'
24
+ cat > /app/allowlist-patch.cjs <<'EOF'
25
+ /*
26
+ * Patches the bundled @atproto/pds dist to enforce an email-domain allowlist
27
+ * on com.atproto.server.createAccount. Idempotent; fails fast if upstream
28
+ * code layout changes so the container never runs with open registration.
29
+ */
30
+ const fs = require('node:fs')
31
+ const path = require('node:path')
32
+
33
+ const pdsDist = path.join(__dirname, 'node_modules', '@atproto', 'pds', 'dist')
34
+
35
+ const domains = (process.env.PDS_EMAIL_ALLOWED_DOMAINS || '')
36
+ .split(',')
37
+ .map((d) => d.trim().toLowerCase().replace(/^\./, ''))
38
+ .filter(Boolean)
39
+
40
+ if (domains.length === 0) {
41
+ console.error('[allowlist] PDS_EMAIL_ALLOWED_DOMAINS is not set; refusing to patch (open registration is not allowed)')
42
+ process.exit(1)
43
+ }
44
+
45
+ function findTarget(dir) {
46
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
47
+ const full = path.join(dir, entry.name)
48
+ if (entry.isDirectory()) {
49
+ const found = findTarget(full)
50
+ if (found) return found
51
+ } else if (
52
+ entry.name === 'createAccount.js' &&
53
+ fs.readFileSync(full, 'utf8').includes('validateInputsForLocalPds') &&
54
+ fs.readFileSync(full, 'utf8').includes('isDisposableEmail')
55
+ ) {
56
+ return full
57
+ }
58
+ }
59
+ return null
60
+ }
61
+
62
+ const target = findTarget(pdsDist)
63
+ if (!target) {
64
+ console.error('[allowlist] could not locate createAccount.js under ' + pdsDist)
65
+ process.exit(1)
66
+ }
67
+
68
+ let source = fs.readFileSync(target, 'utf8')
69
+
70
+ if (source.includes('__ROCHESTER_EMAIL_ALLOWLIST__')) {
71
+ console.log('[allowlist] already patched, skipping')
72
+ process.exit(0)
73
+ }
74
+
75
+ const conditionOld = '!isEmailValid(email) || isDisposableEmail(email)'
76
+ const conditionNew = conditionOld + ' || !__rochesterEmailAllowed(email)'
77
+ if (!source.includes(conditionOld)) {
78
+ console.error('[allowlist] email validity condition not found; upstream layout changed')
79
+ process.exit(1)
80
+ }
81
+ source = source.replace(conditionOld, conditionNew)
82
+
83
+ const messageOld = "'This email address is not supported, please use a different email.'"
84
+ const messageNew = "'Registration is restricted to " + domains.join(', ') + " email addresses.'"
85
+ if (!source.includes(messageOld)) {
86
+ console.error('[allowlist] error message anchor not found; upstream layout changed')
87
+ process.exit(1)
88
+ }
89
+ source = source.replace(messageOld, messageNew)
90
+
91
+ const helper = [
92
+ '',
93
+ '// __ROCHESTER_EMAIL_ALLOWLIST__ start',
94
+ 'function __rochesterEmailAllowed(email) {',
95
+ " const domain = String(email || '').split('@')[1]?.toLowerCase()",
96
+ ' return ' + JSON.stringify(domains) + '.includes(domain)',
97
+ '}',
98
+ '// __ROCHESTER_EMAIL_ALLOWLIST__ end',
99
+ '',
100
+ ].join('\n')
101
+ const helperAnchor = 'export default function (server, ctx)'
102
+ if (!source.includes(helperAnchor)) {
103
+ console.error('[allowlist] helper injection point not found; upstream layout changed')
104
+ process.exit(1)
105
+ }
106
+ source = source.replace(helperAnchor, helper + '\n' + helperAnchor)
107
+
108
+ fs.writeFileSync(target, source)
109
+ console.log('[allowlist] patched ' + target + ' (domains: ' + domains.join(', ') + ')')
110
+ EOF
111
+ PATCH
112
+
113
+ # Write the entrypoint: generate secrets on first boot, patch, start.
114
+ RUN <<'ENTRY'
115
+ cat > /usr/local/bin/entrypoint.sh <<'EOF'
116
+ #!/bin/sh
117
+ set -e
118
+ cd /app
119
+
120
+ SECRETS_FILE=/app/data/generated-secrets.env
121
+ mkdir -p /app/data
122
+
123
+ # Generate persistent secrets on first boot so restarts keep sessions valid.
124
+ if [ ! -f "$SECRETS_FILE" ]; then
125
+ echo "[entrypoint] first boot - generating secrets"
126
+ {
127
+ echo "PDS_JWT_SECRET=$(node -e 'console.log(require("crypto").randomBytes(16).toString("hex"))')"
128
+ echo "PDS_ADMIN_PASSWORD=$(node -e 'console.log(require("crypto").randomBytes(16).toString("hex"))')"
129
+ echo "PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(node -e 'const k=require("@atproto/crypto");k.Secp256k1Keypair.create({exportable:true}).then(kp=>kp.export()).then(j=>console.log(Buffer.from(Object.values(j)).toString("hex")))')"
130
+ } > "$SECRETS_FILE"
131
+ chmod 600 "$SECRETS_FILE" || true
132
+ fi
133
+ . "$SECRETS_FILE"
134
+ export PDS_JWT_SECRET PDS_ADMIN_PASSWORD PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX
135
+
136
+ echo "================================================================"
137
+ echo " Generated secrets (saved in $SECRETS_FILE):"
138
+ echo " PDS_ADMIN_PASSWORD: $PDS_ADMIN_PASSWORD"
139
+ echo "================================================================"
140
+
141
+ node allowlist-patch.cjs
142
+
143
+ exec node --enable-source-maps index.ts
144
+ EOF
145
+ chmod +x /usr/local/bin/entrypoint.sh
146
+ ENTRY
147
+
148
+ # Data directory (SQLite + blobs). Enable persistent storage in the Space
149
+ # settings to keep it across restarts.
150
+ RUN mkdir -p /app/data \
151
+ && chown -R 1000:1000 /app \
152
+ && chown 1000:1000 /usr/local/bin/entrypoint.sh
153
+
154
+ # HF Spaces runs containers as uid 1000; the runtime patch needs write access
155
+ # to node_modules, hence the chown above.
156
+ USER 1000
157
+
158
+ ENV PDS_DATA_DIRECTORY=/app/data
159
+
160
+ # HF Spaces route to port 7860
161
+ ENV PDS_PORT=7860
162
+
163
+ # Registration allowlist (comma-separated domains). The email domain check is
164
+ # the only registration gate - no invite codes, no SMTP verification.
165
+ ENV PDS_EMAIL_ALLOWED_DOMAINS=rochesterschools.org
166
+ ENV PDS_INVITE_REQUIRED=false
167
+ ENV PDS_EMAIL_SMTP_URL=
168
+ ENV PDS_EMAIL_FROM_ADDRESS=
169
+ # Skip email confirmation flow entirely (no SMTP on HF)
170
+ ENV PDS_EMAIL_DISABLE_CONFIRMATION_LINK=true
171
+
172
+ # Public hostname. Set this to your Space's public URL (as a Space secret or
173
+ # variable named PDS_HOSTNAME) so the PDS advertises the right endpoint:
174
+ # https://<owner>-<space>.hf.space
175
+ # Users' handles will be <name>.<that hostname>.
176
+ ENV PDS_HOSTNAME=https://cloudunity-gemma-api1.hf.space
177
 
178
  EXPOSE 7860
179
 
180
+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]