initial commit
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
db
|
||||
# templates
|
7
.env.example
Normal file
@ -0,0 +1,7 @@
|
||||
RELAY_NAME="utxo WoT relay"
|
||||
RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"
|
||||
RELAY_DESCRIPTION="Only notes in utxo WoT"
|
||||
RELAY_URL="wss://wot.utxo.one"
|
||||
DB_PATH="db"
|
||||
INDEX_PATH="templates/index.html"
|
||||
STATIC_PATH="templates/static"
|
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
db/
|
||||
.env
|
||||
wot-relay
|
31
Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
# Use Golang image based on Debian Bookworm
|
||||
FROM golang:bookworm
|
||||
|
||||
# Set the working directory within the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go.mod and go.sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy the rest of the application source code
|
||||
COPY . .
|
||||
|
||||
# Set fixed environment variables
|
||||
ENV DB_PATH="db"
|
||||
ENV INDEX_PATH="templates/index.html"
|
||||
ENV STATIC_PATH="templates/static"
|
||||
|
||||
# touch a .env (https://github.com/bitvora/wot-relay/pull/4)
|
||||
RUN touch .env
|
||||
|
||||
# Build the Go application
|
||||
RUN go build -o main .
|
||||
|
||||
# Expose the port that the application will run on
|
||||
EXPOSE 3334
|
||||
|
||||
# Set the command to run the executable
|
||||
CMD ["./main"]
|
158
README.md
Normal file
@ -0,0 +1,158 @@
|
||||
# WOT Relay
|
||||
|
||||
WOT Relay is a Nostr relay that saves all the notes that people you follow, and people they follow are posting.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go**: Ensure you have Go installed on your system. You can download it from [here](https://golang.org/dl/).
|
||||
- **Build Essentials**: If you're using Linux, you may need to install build essentials. You can do this by running `sudo apt install build-essential`.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
Follow these steps to get the WOT Relay running on your local machine:
|
||||
|
||||
### 1. Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bitvora/wot-relay.git
|
||||
cd wot-relay
|
||||
```
|
||||
|
||||
### 2. Copy `.env.example` to `.env`
|
||||
|
||||
You'll need to create an `.env` file based on the example provided in the repository.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 3. Set your environment variables
|
||||
|
||||
Open the `.env` file and set the necessary environment variables. Example variables include:
|
||||
|
||||
```bash
|
||||
RELAY_NAME="YourRelayName"
|
||||
RELAY_PUBKEY="YourPublicKey"
|
||||
RELAY_DESCRIPTION="Your relay description"
|
||||
DB_PATH="/home/ubuntu/wot-relay/db" # any path you would like the database to be saved.
|
||||
INDEX_PATH="/home/ubuntu/wot-relay/templates/index.html" # path to the index.html file
|
||||
STATIC_PATH="/home/ubuntu/wot-relay/templates/static" # path to the static folder
|
||||
```
|
||||
|
||||
### 4. Build the project
|
||||
|
||||
Run the following command to build the relay:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
|
||||
### 5. Create a Systemd Service (optional)
|
||||
|
||||
To have the relay run as a service, create a systemd unit file. Here's an example:
|
||||
|
||||
1. Create the file:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/wot-relay.service
|
||||
```
|
||||
|
||||
2. Add the following contents:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=WOT Relay Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/path/to/wot-relay
|
||||
WorkingDirectory=/path/to/wot-relay
|
||||
Restart=always
|
||||
EnvironmentFile=/path/to/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Replace `/path/to/` with the actual paths where you cloned the repository and stored the `.env` file.
|
||||
|
||||
3. Reload systemd to recognize the new service:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
4. Start the service:
|
||||
|
||||
```bash
|
||||
sudo systemctl start wot-relay
|
||||
```
|
||||
|
||||
5. (Optional) Enable the service to start on boot:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable wot-relay
|
||||
```
|
||||
|
||||
### 6. Start the Project with Docker Compose (optional)
|
||||
|
||||
To start the project using Docker Compose, follow these steps:
|
||||
|
||||
1. Ensure Docker and Docker Compose are installed on your system.
|
||||
2. Navigate to the project directory.
|
||||
3. Ensure the `.env` file is present in the project directory and has the necessary environment variables set.
|
||||
4. You can also change the paths of the `db` folder and `templates` folder in the `docker-compose.yml` file.
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- "./db:/app/db" # only change the left side before the colon
|
||||
- "./templates/index.html:/app/templates/index.html" # only change the left side before the colon
|
||||
- "./templates/static:/app/templates/static" # only change the left side before the colon
|
||||
```
|
||||
|
||||
5. Run the following command:
|
||||
|
||||
```sh
|
||||
# in foreground
|
||||
docker compose up --build
|
||||
# in background
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
6. For updating the relay, run the following command:
|
||||
|
||||
```sh
|
||||
git pull
|
||||
docker compose build --no-cache
|
||||
# in foreground
|
||||
docker compose up
|
||||
# in background
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334.
|
||||
|
||||
### 7. Hidden Service with Tor (optional)
|
||||
|
||||
Same as the step 6, but with the following command:
|
||||
|
||||
```sh
|
||||
# in foreground
|
||||
docker compose -f docker-compose.tor.yml up --build
|
||||
# in background
|
||||
docker compose -f docker-compose.tor.yml up --build -d
|
||||
```
|
||||
|
||||
You can find the onion address here: `tor/data/relay/hostname`
|
||||
|
||||
### 8. Access the relay
|
||||
|
||||
Once everything is set up, the relay will be running on `localhost:3334`.
|
||||
|
||||
```bash
|
||||
http://localhost:3334
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
13
docker-compose.tor.yml
Normal file
@ -0,0 +1,13 @@
|
||||
services:
|
||||
|
||||
|
||||
tor:
|
||||
image: lncm/tor:0.4.7.9@sha256:86c2fe9d9099e6376798979110b8b9a3ee5d8adec27289ac4a5ee892514ffe92
|
||||
container_name: wot-relay-tor
|
||||
depends_on:
|
||||
- relay
|
||||
volumes:
|
||||
- ./tor/torrc:/etc/tor/torrc
|
||||
- ./tor/data:/var/lib/tor
|
||||
restart: on-failure
|
||||
stop_grace_period: 10m30s
|
18
docker-compose.yml
Normal file
@ -0,0 +1,18 @@
|
||||
services:
|
||||
relay:
|
||||
container_name: wot-relay
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-optimized
|
||||
# env_file:
|
||||
# - .env
|
||||
environment:
|
||||
RELAY_NAME: "YourRelayName"
|
||||
RELAY_PUBKEY: "YourPublicKey"
|
||||
RELAY_DESCRIPTION: "Your relay description"
|
||||
volumes:
|
||||
- "./db:/app/db" # only change the left side before the colon
|
||||
- "./templates/index.html:/app/templates/index.html" # only change the left side before the colon
|
||||
- "./templates/static:/app/templates/static" # only change the left side before the colon
|
||||
ports:
|
||||
- "3334:3334"
|
39
go.mod
Normal file
@ -0,0 +1,39 @@
|
||||
module github.com/bitvora/wot-relay
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.1
|
||||
|
||||
require (
|
||||
github.com/PowerDNS/lmdb-go v1.9.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/fasthttp/websocket v1.5.7 // indirect
|
||||
github.com/fiatjaf/eventstore v0.8.1 // indirect
|
||||
github.com/fiatjaf/khatru v0.8.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.3.1 // indirect
|
||||
github.com/greatroar/blobloom v0.8.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/nbd-wtf/go-nostr v0.34.14 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/tidwall/gjson v1.17.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
)
|
89
go.sum
Normal file
@ -0,0 +1,89 @@
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU=
|
||||
github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
|
||||
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
|
||||
github.com/fiatjaf/eventstore v0.5.1 h1:tTh+JYP0RME51VY2QB2Gvtzj6QTaZAnSVhgZtrYqY2A=
|
||||
github.com/fiatjaf/eventstore v0.5.1/go.mod h1:r5yCFmrVNT2b1xUOuMnDVS3xPGh97y8IgTcLyY2rYP8=
|
||||
github.com/fiatjaf/eventstore v0.8.1 h1:51LchQNy0Hpb0YQHwqYR5pKBpfDs/KjySlWCbbz2pkc=
|
||||
github.com/fiatjaf/eventstore v0.8.1/go.mod h1:bsp0Ibv0CIcVuFcoM2AEerMWmXRhF8uWXMf+dClhuow=
|
||||
github.com/fiatjaf/khatru v0.8.0 h1:hofUi4qbSqkJiKD4rC9EyNdi9obzBvp3ykJOBxuu/h8=
|
||||
github.com/fiatjaf/khatru v0.8.0/go.mod h1:jRmqbbIbEH+y0unt3wMUBwqY/btVussqx5SmBoGhXtg=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
|
||||
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
|
||||
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/greatroar/blobloom v0.8.0 h1:I9RlEkfqK9/6f1v9mFmDYegDQ/x0mISCpiNpAm23Pt4=
|
||||
github.com/greatroar/blobloom v0.8.0/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/nbd-wtf/go-nostr v0.34.14 h1:o4n2LkuAtdIjNYJ23sFbcx68UXLnji4j8hYR1Sd2wgI=
|
||||
github.com/nbd-wtf/go-nostr v0.34.14/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
291
main.go
Normal file
@ -0,0 +1,291 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/fiatjaf/eventstore/lmdb"
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/greatroar/blobloom"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
RelayName string
|
||||
RelayPubkey string
|
||||
RelayDescription string
|
||||
DBPath string
|
||||
RelayURL string
|
||||
IndexPath string
|
||||
StaticPath string
|
||||
}
|
||||
|
||||
var archivePool *nostr.SimplePool
|
||||
var fetchingPool *nostr.SimplePool
|
||||
|
||||
var relays []string
|
||||
var config Config
|
||||
var trustNetwork []string
|
||||
var mu sync.Mutex
|
||||
|
||||
func main() {
|
||||
fmt.Println("starting")
|
||||
relay := khatru.NewRelay()
|
||||
ctx := context.Background()
|
||||
|
||||
config = LoadConfig()
|
||||
|
||||
relay.Info.Name = config.RelayName
|
||||
relay.Info.PubKey = config.RelayPubkey
|
||||
relay.Info.Description = config.RelayDescription
|
||||
appendPubkey(config.RelayPubkey)
|
||||
|
||||
db := lmdb.LMDBBackend{
|
||||
Path: getEnv("DB_PATH"),
|
||||
}
|
||||
if err := db.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
||||
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
|
||||
relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
|
||||
for _, pk := range trustNetwork {
|
||||
if pk == event.PubKey {
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
return true, "you are not in the web of trust"
|
||||
})
|
||||
|
||||
mu.Lock()
|
||||
relays = []string{
|
||||
"wss://nos.lol",
|
||||
"wss://nostr.mom",
|
||||
"wss://purplepag.es",
|
||||
"wss://purplerelay.com",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.mostr.pub",
|
||||
"wss://relay.nos.social",
|
||||
"wss://relay.nostr.band",
|
||||
"wss://relay.snort.social",
|
||||
"wss://relayable.org",
|
||||
"wss://pyramid.fiatjaf.com",
|
||||
"wss://relay.primal.net",
|
||||
"wss://relay.nostr.bg",
|
||||
"wss://no.str.cr",
|
||||
"wss://nostr21.com",
|
||||
"wss://nostrue.com",
|
||||
"wss://relay.siamstr.com",
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
go refreshTrustNetwork(relay, ctx)
|
||||
go archiveTrustedNotes(relay, ctx)
|
||||
|
||||
mux := relay.Router()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl := template.Must(template.ParseFiles(os.Getenv("INDEX_PATH")))
|
||||
data := struct {
|
||||
RelayName string
|
||||
RelayPubkey string
|
||||
RelayDescription string
|
||||
RelayURL string
|
||||
}{
|
||||
RelayName: config.RelayName,
|
||||
RelayPubkey: config.RelayPubkey,
|
||||
RelayDescription: config.RelayDescription,
|
||||
RelayURL: config.RelayURL,
|
||||
}
|
||||
err := tmpl.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
||||
mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir(config.StaticPath))))
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
err := http.ListenAndServe(":3334", relay)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig() Config {
|
||||
godotenv.Load(".env")
|
||||
|
||||
config := Config{
|
||||
RelayName: getEnv("RELAY_NAME"),
|
||||
RelayPubkey: getEnv("RELAY_PUBKEY"),
|
||||
RelayDescription: getEnv("RELAY_DESCRIPTION"),
|
||||
DBPath: getEnv("DB_PATH"),
|
||||
RelayURL: getEnv("RELAY_URL"),
|
||||
IndexPath: getEnv("INDEX_PATH"),
|
||||
StaticPath: getEnv("STATIC_PATH"),
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getEnv(key string) string {
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists {
|
||||
log.Fatalf("Environment variable %s not set", key)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string {
|
||||
fetchingPool = nostr.NewSimplePool(ctx)
|
||||
|
||||
// Function to refresh the trust network
|
||||
runTrustNetworkRefresh := func() {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
filters := []nostr.Filter{{
|
||||
Authors: []string{config.RelayPubkey},
|
||||
Kinds: []int{nostr.KindContactList},
|
||||
}}
|
||||
|
||||
for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) {
|
||||
for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
|
||||
appendPubkey(contact[1])
|
||||
}
|
||||
}
|
||||
|
||||
chunks := make([][]string, 0)
|
||||
for i := 0; i < len(trustNetwork); i += 100 {
|
||||
end := i + 100
|
||||
if end > len(trustNetwork) {
|
||||
end = len(trustNetwork)
|
||||
}
|
||||
chunks = append(chunks, trustNetwork[i:end])
|
||||
}
|
||||
|
||||
for _, chunk := range chunks {
|
||||
threeTimeoutCtx, tenCancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer tenCancel()
|
||||
|
||||
filters = []nostr.Filter{{
|
||||
Authors: chunk,
|
||||
Kinds: []int{nostr.KindContactList},
|
||||
}}
|
||||
|
||||
for ev := range fetchingPool.SubManyEose(threeTimeoutCtx, relays, filters) {
|
||||
for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
|
||||
if len(contact) > 1 {
|
||||
appendPubkey(contact[1])
|
||||
} else {
|
||||
fmt.Println("Skipping malformed tag: ", contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("trust network size:", len(trustNetwork))
|
||||
getTrustNetworkProfileMetadata(relay, ctx)
|
||||
}
|
||||
|
||||
runTrustNetworkRefresh()
|
||||
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
runTrustNetworkRefresh()
|
||||
}
|
||||
|
||||
return trustNetwork
|
||||
}
|
||||
|
||||
func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) {
|
||||
chunks := make([][]string, 0)
|
||||
for i := 0; i < len(trustNetwork); i += 100 {
|
||||
end := i + 100
|
||||
if end > len(trustNetwork) {
|
||||
end = len(trustNetwork)
|
||||
}
|
||||
chunks = append(chunks, trustNetwork[i:end])
|
||||
}
|
||||
|
||||
for _, chunk := range chunks {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
filters := []nostr.Filter{{
|
||||
Authors: chunk,
|
||||
Kinds: []int{nostr.KindProfileMetadata},
|
||||
}}
|
||||
|
||||
for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) {
|
||||
relay.AddEvent(ctx, ev.Event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendPubkey(pubkey string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
for _, pk := range trustNetwork {
|
||||
if pk == pubkey {
|
||||
return
|
||||
}
|
||||
}
|
||||
trustNetwork = append(trustNetwork, pubkey)
|
||||
}
|
||||
|
||||
func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
archivePool = nostr.NewSimplePool(ctx)
|
||||
for range ticker.C {
|
||||
timeout, cancel := context.WithTimeout(ctx, 58*time.Second)
|
||||
filters := []nostr.Filter{{
|
||||
Kinds: []int{
|
||||
nostr.KindArticle,
|
||||
nostr.KindDeletion,
|
||||
nostr.KindContactList,
|
||||
nostr.KindEncryptedDirectMessage,
|
||||
nostr.KindMuteList,
|
||||
nostr.KindReaction,
|
||||
nostr.KindRelayListMetadata,
|
||||
nostr.KindRepost,
|
||||
nostr.KindZapRequest,
|
||||
nostr.KindZap,
|
||||
nostr.KindTextNote,
|
||||
},
|
||||
}}
|
||||
|
||||
nKeys := uint64(len(trustNetwork))
|
||||
bloomFilter := blobloom.NewOptimized(blobloom.Config{
|
||||
Capacity: nKeys,
|
||||
FPRate: 1e-4,
|
||||
})
|
||||
for _, trustedPubkey := range trustNetwork {
|
||||
bloomFilter.Add(xxhash.Sum64([]byte(trustedPubkey)))
|
||||
}
|
||||
|
||||
for ev := range archivePool.SubManyEose(timeout, relays, filters) {
|
||||
|
||||
if bloomFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) {
|
||||
if len(ev.Event.Tags) > 2000 {
|
||||
fmt.Println("skiping note with over 2000 tags")
|
||||
continue
|
||||
}
|
||||
relay.AddEvent(ctx, ev.Event)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}
|
64
templates/index.html
Normal file
@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{.RelayName}}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap");
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-white">
|
||||
<div class="flex-grow flex flex-col justify-center items-center px-4">
|
||||
<!-- Container -->
|
||||
<div class="text-center max-w-2xl">
|
||||
<!-- Heading (Relay Name) -->
|
||||
<h1 class="text-5xl md:text-6xl font-bold text-purple-400 mb-6">
|
||||
{{.RelayName}}
|
||||
</h1>
|
||||
|
||||
<!-- Relay Description -->
|
||||
<p class="text-xl md:text-2xl text-gray-300 mb-8">
|
||||
{{.RelayDescription}}
|
||||
</p>
|
||||
|
||||
<!-- Relay URL -->
|
||||
<a
|
||||
href="#"
|
||||
class="text-lg md:text-xl text-purple-300 hover:text-purple-400 underline"
|
||||
>
|
||||
{{.RelayURL}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="text-center py-6 bg-gray-800 w-full">
|
||||
<p class="text-lg text-gray-400">
|
||||
Proudly powered by
|
||||
<a
|
||||
href="https://khatru.nostr.technology/"
|
||||
target="_blank"
|
||||
class="text-purple-300 hover:text-purple-400 underline"
|
||||
>Khatru</a
|
||||
>
|
||||
|
|
||||
<a
|
||||
href="https://github.com/bitvora/wot-relay"
|
||||
target="_blank"
|
||||
class="text-purple-300 hover:text-purple-400 underline"
|
||||
>Web of Trust Relay on Github</a
|
||||
>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
BIN
templates/static/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
templates/static/android-chrome-256x256.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
templates/static/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
9
templates/static/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
templates/static/favicon-16x16.png
Normal file
After Width: | Height: | Size: 846 B |
BIN
templates/static/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
templates/static/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
templates/static/mstile-150x150.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
15
templates/static/safari-pinned-tab.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="274.000000pt" height="274.000000pt" viewBox="0 0 274.000000 274.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,274.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M0 1365 l0 -1285 1370 0 1370 0 0 1285 0 1285 -1370 0 -1370 0 0
|
||||
-1285z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 603 B |
2
tor/data/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|