mirror of
https://github.com/hzrd149/nsite-gateway.git
synced 2025-06-23 12:05:01 +00:00
add nginx cache invalidation
bundle nginx in docker image switch from ndk to nostr-tools
This commit is contained in:
parent
88a9229633
commit
b7b43cff10
5
.changeset/many-lemons-exercise.md
Normal file
5
.changeset/many-lemons-exercise.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nsite-ts": minor
|
||||
---
|
||||
|
||||
Bundle nginx in docker image
|
5
.changeset/metal-wasps-cry.md
Normal file
5
.changeset/metal-wasps-cry.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nsite-ts": minor
|
||||
---
|
||||
|
||||
Add NGINX_CACHE_DIR for invalidating nginx cache
|
5
.changeset/popular-plants-beam.md
Normal file
5
.changeset/popular-plants-beam.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nsite-ts": minor
|
||||
---
|
||||
|
||||
Add SUBSCRIPTION_RELAYS for listening for new events
|
@ -2,8 +2,11 @@
|
||||
# can be in-memory, redis:// or sqlite://
|
||||
CACHE_PATH="in-memory"
|
||||
|
||||
# A list of nostr relays to search
|
||||
NOSTR_RELAYS=wss://nos.lol,wss://relay.damus.io
|
||||
# A list of relays to find users relay lists (10002) and blossom servers (10063)
|
||||
LOOKUP_RELAYS=wss://user.kindpag.es,wss://purplepag.es
|
||||
|
||||
# A list of nostr relays to listen to for new nsite events
|
||||
SUBSCRIPTION_RELAYS=wss://nos.lol,wss://relay.damus.io
|
||||
|
||||
# A list of fallback blossom servers
|
||||
BLOSSOM_SERVERS=https://cdn.satellite.earth
|
||||
@ -12,4 +15,4 @@ BLOSSOM_SERVERS=https://cdn.satellite.earth
|
||||
MAX_FILE_SIZE='2 MB'
|
||||
|
||||
# the hostname or ip of the upstream nginx proxy cache
|
||||
NGINX_HOST='nginx'
|
||||
NGINX_CACHE_DIR='/var/nginx/cache'
|
||||
|
26
Dockerfile
26
Dockerfile
@ -5,6 +5,9 @@ ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
RUN apk update && apk add --no-cache nginx supervisor
|
||||
COPY supervisord.conf /etc/supervisord.conf
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
COPY pnpm-lock.yaml .
|
||||
@ -19,12 +22,29 @@ COPY src ./src
|
||||
RUN pnpm build
|
||||
|
||||
FROM base AS main
|
||||
|
||||
# Setup user
|
||||
RUN addgroup -S nsite && adduser -S nsite -G nsite
|
||||
RUN chown -R nsite:nsite /app
|
||||
|
||||
# Setup nginx
|
||||
COPY nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# setup nsite
|
||||
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
COPY --from=build ./app/build ./build
|
||||
|
||||
COPY ./public ./public
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT="80"
|
||||
VOLUME [ "/var/cache/nginx" ]
|
||||
|
||||
ENTRYPOINT [ "node", "." ]
|
||||
EXPOSE 80 3000
|
||||
ENV NSITE_PORT="3000"
|
||||
ENV NGINX_CACHE_DIR="/var/cache/nginx"
|
||||
|
||||
COPY docker-entrypoint.sh /
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
|
||||
|
@ -1,22 +1,17 @@
|
||||
version: "3.7"
|
||||
|
||||
volumes:
|
||||
cache: {}
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- 8080:80
|
||||
volumes:
|
||||
- cache:/var/cache/nginx
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
nsite:
|
||||
build: .
|
||||
image: ghcr.io/hzrd149/nsite-ts:master
|
||||
environment:
|
||||
LOOKUP_RELAYS: wss://user.kindpag.es,wss://purplepag.es
|
||||
SUBSCRIPTION_RELAYS: wss://nostrue.com/,wss://nos.lol/,wss://relay.damus.io/,wss://purplerelay.com/
|
||||
volumes:
|
||||
- type: tmpfs
|
||||
target: /var/cache/nginx
|
||||
tmpfs:
|
||||
size: 100M
|
||||
ports:
|
||||
- 3000:80
|
||||
- 8080:80
|
||||
- 3000:3000
|
||||
|
5
docker-entrypoint.sh
Executable file
5
docker-entrypoint.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
chown -R nginx:nginx /var/cache/nginx
|
||||
|
||||
exec "$@"
|
@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name nsite-proxy;
|
||||
server_name nsite;
|
||||
|
||||
location / {
|
||||
proxy_cache request_cache;
|
||||
@ -17,13 +17,6 @@ server {
|
||||
add_header Cache-Control "public, no-transform";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://nsite;
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
|
||||
# Manual cache invalidation ( cant use proxy_cache_purge )
|
||||
# location ~ /purge(/.*) {
|
||||
# allow 127.0.0.1;
|
||||
# deny all;
|
||||
# proxy_cache_purge request_cache $scheme$proxy_host$1;
|
||||
# }
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log notice;
|
||||
error_log /dev/stderr notice;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
@ -19,7 +19,7 @@ http {
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
access_log /dev/stdout main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
@ -21,7 +21,6 @@
|
||||
"@keyv/redis": "^3.0.1",
|
||||
"@keyv/sqlite": "^4.0.1",
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@nostr-dev-kit/ndk": "^2.10.0",
|
||||
"blossom-client-sdk": "^1.1.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"follow-redirects": "^1.15.6",
|
||||
|
113
pnpm-lock.yaml
generated
113
pnpm-lock.yaml
generated
@ -20,9 +20,6 @@ importers:
|
||||
'@koa/cors':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
'@nostr-dev-kit/ndk':
|
||||
specifier: ^2.10.0
|
||||
version: 2.10.0(typescript@5.6.2)
|
||||
blossom-client-sdk:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@ -215,10 +212,6 @@ packages:
|
||||
'@noble/curves@1.2.0':
|
||||
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
|
||||
|
||||
'@noble/curves@1.6.0':
|
||||
resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
|
||||
'@noble/hashes@1.3.1':
|
||||
resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==}
|
||||
engines: {node: '>= 16'}
|
||||
@ -231,9 +224,6 @@ packages:
|
||||
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
|
||||
'@noble/secp256k1@2.1.0':
|
||||
resolution: {integrity: sha512-XLEQQNdablO0XZOIniFQimiXsZDNwaYgL96dZwC54Q30imSbAOFf3NKtepc+cXyuZf5Q1HCgbqgZ2UFFuHVcEw==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -246,10 +236,6 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@nostr-dev-kit/ndk@2.10.0':
|
||||
resolution: {integrity: sha512-TqCAAo6ylORraAXrzRkCGFN2xTMiFbdER8Y8CtUT0HwOpFG/Wn+PBNeDeDmqkl/6LaPdeyXmVwCWj2KcUjIwYA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@npmcli/fs@1.1.1':
|
||||
resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
|
||||
|
||||
@ -316,9 +302,6 @@ packages:
|
||||
'@scure/base@1.1.1':
|
||||
resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==}
|
||||
|
||||
'@scure/base@1.1.9':
|
||||
resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==}
|
||||
|
||||
'@scure/bip32@1.3.1':
|
||||
resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==}
|
||||
|
||||
@ -666,10 +649,6 @@ packages:
|
||||
cross-spawn@5.1.0:
|
||||
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
@ -794,10 +773,6 @@ packages:
|
||||
fastq@1.17.1:
|
||||
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
|
||||
@ -818,10 +793,6 @@ packages:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -1046,9 +1017,6 @@ packages:
|
||||
resolution: {integrity: sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==}
|
||||
engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4}
|
||||
|
||||
light-bolt11-decoder@3.1.1:
|
||||
resolution: {integrity: sha512-sLg/KCwYkgsHWkefWd6KqpCHrLFWWaXTOX3cf6yD2hAzL0SLpX+lFcaFK2spkjbgzG6hhijKfORDc9WoUHwX0A==}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1176,14 +1144,6 @@ packages:
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-gyp@8.4.1:
|
||||
resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
|
||||
engines: {node: '>= 10.12.0'}
|
||||
@ -1539,9 +1499,6 @@ packages:
|
||||
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
||||
hasBin: true
|
||||
|
||||
tseep@1.2.2:
|
||||
resolution: {integrity: sha512-GgPFuNx+08UaYBYmJQmuI86ykYa2PUUtfXAYb4MLRHGunSCp8k9N+dbsR4PK1yk4/zV9q4e4PrNg8ymXqGYaYA==}
|
||||
|
||||
tslib@2.7.0:
|
||||
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
||||
|
||||
@ -1559,9 +1516,6 @@ packages:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typescript-lru-cache@2.0.0:
|
||||
resolution: {integrity: sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==}
|
||||
|
||||
typescript@5.6.2:
|
||||
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@ -1583,10 +1537,6 @@ packages:
|
||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
utf8-buffer@1.0.0:
|
||||
resolution: {integrity: sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
@ -1594,10 +1544,6 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
websocket-polyfill@1.0.0:
|
||||
resolution: {integrity: sha512-QwfEy8jcOOCVO9su9UP+msEmhZa4a9WSJfePIdCT8GxwVl2Z9toM7nCqFfDDxA/sRmxgf1KNiwL6PXvjJ9qRxw==}
|
||||
|
||||
@ -1867,18 +1813,12 @@ snapshots:
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.2
|
||||
|
||||
'@noble/curves@1.6.0':
|
||||
dependencies:
|
||||
'@noble/hashes': 1.5.0
|
||||
|
||||
'@noble/hashes@1.3.1': {}
|
||||
|
||||
'@noble/hashes@1.3.2': {}
|
||||
|
||||
'@noble/hashes@1.5.0': {}
|
||||
|
||||
'@noble/secp256k1@2.1.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@ -1891,26 +1831,6 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.17.1
|
||||
|
||||
'@nostr-dev-kit/ndk@2.10.0(typescript@5.6.2)':
|
||||
dependencies:
|
||||
'@noble/curves': 1.6.0
|
||||
'@noble/hashes': 1.5.0
|
||||
'@noble/secp256k1': 2.1.0
|
||||
'@scure/base': 1.1.9
|
||||
debug: 4.3.7(supports-color@5.5.0)
|
||||
light-bolt11-decoder: 3.1.1
|
||||
node-fetch: 3.3.2
|
||||
nostr-tools: 2.7.2(typescript@5.6.2)
|
||||
tseep: 1.2.2
|
||||
typescript-lru-cache: 2.0.0
|
||||
utf8-buffer: 1.0.0
|
||||
websocket-polyfill: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
|
||||
'@npmcli/fs@1.1.1':
|
||||
dependencies:
|
||||
'@gar/promisify': 1.1.3
|
||||
@ -1960,8 +1880,6 @@ snapshots:
|
||||
|
||||
'@scure/base@1.1.1': {}
|
||||
|
||||
'@scure/base@1.1.9': {}
|
||||
|
||||
'@scure/bip32@1.3.1':
|
||||
dependencies:
|
||||
'@noble/curves': 1.1.0
|
||||
@ -2360,8 +2278,6 @@ snapshots:
|
||||
shebang-command: 1.2.0
|
||||
which: 1.3.1
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
debug@2.6.9:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
@ -2457,11 +2373,6 @@ snapshots:
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
file-uri-to-path@1.0.0: {}
|
||||
|
||||
fill-range@7.1.1:
|
||||
@ -2475,10 +2386,6 @@ snapshots:
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
fs-constants@1.0.0: {}
|
||||
@ -2766,10 +2673,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
light-bolt11-decoder@3.1.1:
|
||||
dependencies:
|
||||
'@scure/base': 1.1.1
|
||||
|
||||
locate-path@5.0.0:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
@ -2908,14 +2811,6 @@ snapshots:
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-gyp@8.4.1:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
@ -3295,8 +3190,6 @@ snapshots:
|
||||
|
||||
touch@3.1.1: {}
|
||||
|
||||
tseep@1.2.2: {}
|
||||
|
||||
tslib@2.7.0: {}
|
||||
|
||||
tsscmp@1.0.6: {}
|
||||
@ -3312,8 +3205,6 @@ snapshots:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
typescript-lru-cache@2.0.0: {}
|
||||
|
||||
typescript@5.6.2: {}
|
||||
|
||||
undefsafe@2.0.5: {}
|
||||
@ -3332,14 +3223,10 @@ snapshots:
|
||||
|
||||
universalify@0.1.2: {}
|
||||
|
||||
utf8-buffer@1.0.0: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
websocket-polyfill@1.0.0:
|
||||
dependencies:
|
||||
import2: 1.0.3
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { getServersFromServerListEvent, USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
|
||||
import { NDKRelaySet } from "@nostr-dev-kit/ndk";
|
||||
|
||||
import ndk from "./ndk.js";
|
||||
import { BLOSSOM_SERVERS, MAX_FILE_SIZE } from "./env.js";
|
||||
import { makeRequestWithAbort } from "./helpers/http.js";
|
||||
import pool from "./nostr.js";
|
||||
|
||||
export async function getUserBlossomServers(pubkey: string, relays?: string[]) {
|
||||
const blossomServersEvent = await ndk.fetchEvent(
|
||||
[{ kinds: [USER_BLOSSOM_SERVER_LIST_KIND], authors: [pubkey] }],
|
||||
{},
|
||||
relays ? NDKRelaySet.fromRelayUrls(relays, ndk, true) : undefined,
|
||||
);
|
||||
export async function getUserBlossomServers(pubkey: string, relays: string[]) {
|
||||
const blossomServersEvent = await pool.get(relays, { kinds: [USER_BLOSSOM_SERVER_LIST_KIND], authors: [pubkey] });
|
||||
|
||||
return blossomServersEvent ? getServersFromServerListEvent(blossomServersEvent).map((u) => u.toString()) : undefined;
|
||||
}
|
||||
|
@ -26,6 +26,14 @@ store?.on("error", (err) => {
|
||||
|
||||
const opts = store ? { store } : {};
|
||||
|
||||
/** domain -> pubkey */
|
||||
export const userDomains = new Keyv({
|
||||
...opts,
|
||||
namespace: "domains",
|
||||
// cache domains for an hour
|
||||
ttl: 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
/** pubkey -> blossom servers */
|
||||
export const userServers = new Keyv({
|
||||
...opts,
|
||||
|
13
src/env.ts
13
src/env.ts
@ -1,13 +1,16 @@
|
||||
import "dotenv/config";
|
||||
import xbytes from "xbytes";
|
||||
|
||||
const LOOKUP_RELAYS = process.env.LOOKUP_RELAYS?.split(",") ?? ["wss://user.kindpag.es/", "wss://purplepag.es/"];
|
||||
const NOSTR_RELAYS = process.env.NOSTR_RELAYS?.split(",") ?? [];
|
||||
const BLOSSOM_SERVERS = process.env.BLOSSOM_SERVERS?.split(",") ?? [];
|
||||
const LOOKUP_RELAYS = process.env.LOOKUP_RELAYS?.split(",").map((u) => u.trim()) ?? [
|
||||
"wss://user.kindpag.es/",
|
||||
"wss://purplepag.es/",
|
||||
];
|
||||
const SUBSCRIPTION_RELAYS = process.env.SUBSCRIPTION_RELAYS?.split(",").map((u) => u.trim()) ?? [];
|
||||
const BLOSSOM_SERVERS = process.env.BLOSSOM_SERVERS?.split(",").map((u) => u.trim()) ?? [];
|
||||
|
||||
const MAX_FILE_SIZE = process.env.MAX_FILE_SIZE ? xbytes.parseSize(process.env.MAX_FILE_SIZE) : Infinity;
|
||||
|
||||
const NGINX_HOST = process.env.NGINX_HOST;
|
||||
const NGINX_CACHE_DIR = process.env.NGINX_CACHE_DIR;
|
||||
const CACHE_PATH = process.env.CACHE_PATH;
|
||||
|
||||
export { NOSTR_RELAYS, LOOKUP_RELAYS, BLOSSOM_SERVERS, MAX_FILE_SIZE, NGINX_HOST, CACHE_PATH };
|
||||
export { SUBSCRIPTION_RELAYS, LOOKUP_RELAYS, BLOSSOM_SERVERS, MAX_FILE_SIZE, NGINX_CACHE_DIR, CACHE_PATH };
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { extname, isAbsolute, join } from "path";
|
||||
import { NSITE_KIND } from "./const.js";
|
||||
import ndk from "./ndk.js";
|
||||
import { NDKRelaySet } from "@nostr-dev-kit/ndk";
|
||||
import { requestEvents } from "./nostr.js";
|
||||
|
||||
export function getSearchPaths(path: string) {
|
||||
const paths = [path];
|
||||
@ -9,11 +8,6 @@ export function getSearchPaths(path: string) {
|
||||
// if the path does not have an extension, also look for index.html
|
||||
if (extname(path) === "") paths.push(join(path, "index.html"));
|
||||
|
||||
// also look for relative paths
|
||||
for (const p of Array.from(paths)) {
|
||||
if (isAbsolute(p)) paths.push(p.replace(/^\//, ""));
|
||||
}
|
||||
|
||||
return paths.filter((p) => !!p);
|
||||
}
|
||||
|
||||
@ -29,13 +23,10 @@ export function parseNsiteEvent(event: { pubkey: string; tags: string[][] }) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function getNsiteBlobs(pubkey: string, path: string, relays?: string[]) {
|
||||
const paths = getSearchPaths(path);
|
||||
const events = await ndk.fetchEvents(
|
||||
{ kinds: [NSITE_KIND], "#d": paths, authors: [pubkey] },
|
||||
{},
|
||||
relays && NDKRelaySet.fromRelayUrls(relays, ndk, true),
|
||||
);
|
||||
export async function getNsiteBlobs(pubkey: string, path: string, relays: string[]) {
|
||||
// NOTE: hack, remove "/" paths since it breaks some relays
|
||||
const paths = getSearchPaths(path).filter((p) => p !== "/");
|
||||
const events = await requestEvents(relays, { kinds: [NSITE_KIND], "#d": paths, authors: [pubkey] });
|
||||
|
||||
return Array.from(events)
|
||||
.map(parseNsiteEvent)
|
||||
|
66
src/index.ts
66
src/index.ts
@ -10,11 +10,13 @@ import mime from "mime";
|
||||
import morgan from "koa-morgan";
|
||||
|
||||
import { resolveNpubFromHostname } from "./helpers/dns.js";
|
||||
import { getNsiteBlobs } from "./events.js";
|
||||
import { getNsiteBlobs, parseNsiteEvent } from "./events.js";
|
||||
import { downloadFile, getUserBlossomServers } from "./blossom.js";
|
||||
import { BLOSSOM_SERVERS } from "./env.js";
|
||||
import { userRelays, userServers } from "./cache.js";
|
||||
import { getUserOutboxes } from "./ndk.js";
|
||||
import { BLOSSOM_SERVERS, NGINX_CACHE_DIR, SUBSCRIPTION_RELAYS } from "./env.js";
|
||||
import { userDomains, userRelays, userServers } from "./cache.js";
|
||||
import { NSITE_KIND } from "./const.js";
|
||||
import { invalidatePubkeyPath } from "./nginx.js";
|
||||
import pool, { getUserOutboxes, subscribeForEvents } from "./nostr.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@ -45,12 +47,29 @@ app.use(async (ctx, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// map pubkeys to folders in sites dir
|
||||
// handle nsite requests
|
||||
app.use(async (ctx, next) => {
|
||||
const pubkey = (ctx.state.pubkey = await resolveNpubFromHostname(ctx.hostname));
|
||||
let pubkey = await userDomains.get<string | undefined>(ctx.hostname);
|
||||
|
||||
// resolve pubkey if not in cache
|
||||
if (!pubkey) {
|
||||
console.log(`${ctx.hostname}: Resolving`);
|
||||
pubkey = await resolveNpubFromHostname(ctx.hostname);
|
||||
|
||||
if (pubkey) {
|
||||
await userDomains.set(ctx.hostname, pubkey);
|
||||
console.log(`${ctx.hostname}: Found ${pubkey}`);
|
||||
} else {
|
||||
await userDomains.set(ctx.hostname, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (pubkey) {
|
||||
ctx.state.pubkey = pubkey;
|
||||
|
||||
let relays = await userRelays.get<string[] | undefined>(pubkey);
|
||||
|
||||
// fetch relays if not in cache
|
||||
if (!relays) {
|
||||
console.log(`${pubkey}: Fetching relays`);
|
||||
|
||||
@ -69,12 +88,15 @@ app.use(async (ctx, next) => {
|
||||
const blobs = await getNsiteBlobs(pubkey, ctx.path, relays);
|
||||
|
||||
if (blobs.length === 0) {
|
||||
console.log(`${pubkey}: Found 0 events`);
|
||||
ctx.status = 404;
|
||||
ctx.body = "Not Found";
|
||||
return;
|
||||
}
|
||||
|
||||
let servers = await userServers.get<string[] | undefined>(pubkey);
|
||||
|
||||
// fetch blossom servers if not in cache
|
||||
if (!servers) {
|
||||
console.log(`${pubkey}: Searching for blossom servers`);
|
||||
servers = await getUserBlossomServers(pubkey, relays);
|
||||
@ -88,6 +110,8 @@ app.use(async (ctx, next) => {
|
||||
console.log(`${pubkey}: Failed to find servers`);
|
||||
}
|
||||
}
|
||||
|
||||
// always fetch from additional servers
|
||||
servers.push(...BLOSSOM_SERVERS);
|
||||
|
||||
for (const blob of blobs) {
|
||||
@ -121,12 +145,40 @@ try {
|
||||
app.use(serve(www));
|
||||
}
|
||||
|
||||
app.listen(process.env.PORT || 3000, () => {
|
||||
app.listen(
|
||||
{
|
||||
port: process.env.NSITE_PORT || 3000,
|
||||
host: process.env.NSITE_HOST || "0.0.0.0",
|
||||
},
|
||||
() => {
|
||||
console.log("Started on port", process.env.PORT || 3000);
|
||||
},
|
||||
);
|
||||
|
||||
// invalidate nginx cache on new events
|
||||
if (NGINX_CACHE_DIR && SUBSCRIPTION_RELAYS.length > 0) {
|
||||
console.log(`Listening for new nsite events`);
|
||||
|
||||
subscribeForEvents(SUBSCRIPTION_RELAYS, async (event) => {
|
||||
try {
|
||||
const nsite = parseNsiteEvent(event);
|
||||
if (nsite) {
|
||||
console.log(`${nsite.pubkey}: Invalidating ${nsite.path}`);
|
||||
await invalidatePubkeyPath(nsite.pubkey, nsite.path);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Failed to invalidate ${event.id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
});
|
||||
|
||||
async function shutdown() {
|
||||
console.log("Shutting down...");
|
||||
pool.destroy();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
17
src/ndk.ts
17
src/ndk.ts
@ -1,17 +0,0 @@
|
||||
import NDK from "@nostr-dev-kit/ndk";
|
||||
import { LOOKUP_RELAYS, NOSTR_RELAYS } from "./env.js";
|
||||
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: [...LOOKUP_RELAYS, ...NOSTR_RELAYS],
|
||||
});
|
||||
|
||||
ndk.connect();
|
||||
|
||||
export async function getUserOutboxes(pubkey: string) {
|
||||
const mailboxes = await ndk.fetchEvent({ kinds: [10002], authors: [pubkey] });
|
||||
if (!mailboxes) return;
|
||||
|
||||
return mailboxes.tags.filter((t) => t[0] === "r" && (t[2] === undefined || t[2] === "write")).map((t) => t[1]);
|
||||
}
|
||||
|
||||
export default ndk;
|
55
src/nginx.ts
55
src/nginx.ts
@ -1,26 +1,37 @@
|
||||
import http from "node:http";
|
||||
import { NGINX_HOST } from "./env.js";
|
||||
import pfs from "node:fs/promises";
|
||||
import crypto from "node:crypto";
|
||||
import { join } from "node:path";
|
||||
|
||||
export function invalidateCache(host: string, path: string) {
|
||||
if (!NGINX_HOST) return Promise.resolve(false);
|
||||
import { NGINX_CACHE_DIR } from "./env.js";
|
||||
import { userDomains } from "./cache.js";
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: NGINX_HOST,
|
||||
method: "GET",
|
||||
port: 80,
|
||||
path,
|
||||
headers: {
|
||||
Host: host,
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve(true);
|
||||
else reject(new Error("Failed to invalidate"));
|
||||
},
|
||||
);
|
||||
export async function invalidatePubkeyPath(pubkey: string, path: string) {
|
||||
const iterator = userDomains.iterator?.(undefined);
|
||||
if (!iterator) return;
|
||||
|
||||
req.end();
|
||||
});
|
||||
const promises: Promise<boolean | undefined>[] = [];
|
||||
for await (const [domain, key] of iterator) {
|
||||
if (key === pubkey) {
|
||||
promises.push(invalidateNginxCache(domain, path));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
}
|
||||
|
||||
export async function invalidateNginxCache(host: string, path: string) {
|
||||
if (!NGINX_CACHE_DIR) return Promise.resolve(false);
|
||||
|
||||
try {
|
||||
const key = `${host}${path}`;
|
||||
const md5 = crypto.createHash("md5").update(key).digest("hex");
|
||||
|
||||
// NOTE: hard coded to cache levels 1:2
|
||||
const cachePath = join(NGINX_CACHE_DIR, md5.slice(-1), md5.slice(-3, -1), md5);
|
||||
await pfs.rm(cachePath);
|
||||
|
||||
console.log(`Invalidated ${key} (${md5})`);
|
||||
} catch (error) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
|
38
src/nostr.ts
Normal file
38
src/nostr.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Filter, NostrEvent, SimplePool } from "nostr-tools";
|
||||
import { LOOKUP_RELAYS } from "./env.js";
|
||||
import { NSITE_KIND } from "./const.js";
|
||||
|
||||
const pool = new SimplePool();
|
||||
|
||||
export async function getUserOutboxes(pubkey: string) {
|
||||
const mailboxes = await pool.get(LOOKUP_RELAYS, { kinds: [10002], authors: [pubkey] });
|
||||
if (!mailboxes) return;
|
||||
|
||||
return mailboxes.tags.filter((t) => t[0] === "r" && (t[2] === undefined || t[2] === "write")).map((t) => t[1]);
|
||||
}
|
||||
|
||||
export function subscribeForEvents(relays: string[], onevent: (event: NostrEvent) => any) {
|
||||
return pool.subscribeMany(relays, [{ kinds: [NSITE_KIND], since: Math.round(Date.now() / 1000) - 60 * 60 }], {
|
||||
onevent,
|
||||
});
|
||||
}
|
||||
|
||||
export function requestEvents(relays: string[], filter: Filter) {
|
||||
return new Promise<NostrEvent[]>(async (res, rej) => {
|
||||
const events: NostrEvent[] = [];
|
||||
|
||||
await Promise.allSettled(relays.map((url) => pool.ensureRelay(url).catch((e) => {})));
|
||||
|
||||
const sub = pool.subscribeMany(relays, [filter], {
|
||||
onevent: (e) => events.push(e),
|
||||
oneose: () => sub.close(),
|
||||
onclose: (reasons) => {
|
||||
const errs = reasons.filter((r) => r !== "closed by caller");
|
||||
if (errs.length > 0 && events.length === 0) rej(new Error(errs.join(", ")));
|
||||
else res(events);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default pool;
|
22
supervisord.conf
Normal file
22
supervisord.conf
Normal file
@ -0,0 +1,22 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
|
||||
[program:nginx]
|
||||
command=nginx -g "daemon off;"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:nsite]
|
||||
command=node /app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=root
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
Loading…
x
Reference in New Issue
Block a user