commit ae947e2f1f1e52ba947fc41bd71fa48d46f98c22 Author: Biola Date: Sun Sep 8 02:53:52 2024 +0100 initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e7229cf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +db +# templates diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6eeee36 --- /dev/null +++ b/.env.example @@ -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" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f4c7e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +db/ +.env +wot-relay \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4c7e391 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..46a4aa8 --- /dev/null +++ b/README.md @@ -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. diff --git a/docker-compose.tor.yml b/docker-compose.tor.yml new file mode 100644 index 0000000..f73c79d --- /dev/null +++ b/docker-compose.tor.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4cf16c4 --- /dev/null +++ b/docker-compose.yml @@ -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" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..82ad671 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bcae549 --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ba3ce7f --- /dev/null +++ b/main.go @@ -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() + } +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c61c143 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,64 @@ + + + + + + + + {{.RelayName}} + + + + +
+ +
+ +

+ {{.RelayName}} +

+ + +

+ {{.RelayDescription}} +

+ + + + {{.RelayURL}} + +
+
+ + + + + diff --git a/templates/static/android-chrome-192x192.png b/templates/static/android-chrome-192x192.png new file mode 100644 index 0000000..8f148a7 Binary files /dev/null and b/templates/static/android-chrome-192x192.png differ diff --git a/templates/static/android-chrome-256x256.png b/templates/static/android-chrome-256x256.png new file mode 100644 index 0000000..6391f81 Binary files /dev/null and b/templates/static/android-chrome-256x256.png differ diff --git a/templates/static/apple-touch-icon.png b/templates/static/apple-touch-icon.png new file mode 100644 index 0000000..a14d17c Binary files /dev/null and b/templates/static/apple-touch-icon.png differ diff --git a/templates/static/browserconfig.xml b/templates/static/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/templates/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/templates/static/favicon-16x16.png b/templates/static/favicon-16x16.png new file mode 100644 index 0000000..4d5b6ed Binary files /dev/null and b/templates/static/favicon-16x16.png differ diff --git a/templates/static/favicon-32x32.png b/templates/static/favicon-32x32.png new file mode 100644 index 0000000..2f293bf Binary files /dev/null and b/templates/static/favicon-32x32.png differ diff --git a/templates/static/favicon.ico b/templates/static/favicon.ico new file mode 100644 index 0000000..6da5dc0 Binary files /dev/null and b/templates/static/favicon.ico differ diff --git a/templates/static/mstile-150x150.png b/templates/static/mstile-150x150.png new file mode 100644 index 0000000..1abf1a2 Binary files /dev/null and b/templates/static/mstile-150x150.png differ diff --git a/templates/static/safari-pinned-tab.svg b/templates/static/safari-pinned-tab.svg new file mode 100644 index 0000000..4176fd1 --- /dev/null +++ b/templates/static/safari-pinned-tab.svg @@ -0,0 +1,15 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/tor/data/.gitignore b/tor/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tor/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tor/torrc b/tor/torrc new file mode 100644 index 0000000..e8e0702 --- /dev/null +++ b/tor/torrc @@ -0,0 +1,2 @@ +HiddenServiceDir /var/lib/tor/relay +HiddenServicePort 80 relay:3334