From ae947e2f1f1e52ba947fc41bd71fa48d46f98c22 Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 02:53:52 +0100 Subject: [PATCH 1/6] initial commit --- .dockerignore | 2 + .env.example | 7 + .gitignore | 3 + Dockerfile | 31 +++ README.md | 158 +++++++++++ docker-compose.tor.yml | 13 + docker-compose.yml | 18 ++ go.mod | 39 +++ go.sum | 89 ++++++ main.go | 291 ++++++++++++++++++++ templates/index.html | 64 +++++ templates/static/android-chrome-192x192.png | Bin 0 -> 6977 bytes templates/static/android-chrome-256x256.png | Bin 0 -> 10135 bytes templates/static/apple-touch-icon.png | Bin 0 -> 6510 bytes templates/static/browserconfig.xml | 9 + templates/static/favicon-16x16.png | Bin 0 -> 846 bytes templates/static/favicon-32x32.png | Bin 0 -> 1251 bytes templates/static/favicon.ico | Bin 0 -> 15086 bytes templates/static/mstile-150x150.png | Bin 0 -> 4970 bytes templates/static/safari-pinned-tab.svg | 15 + tor/data/.gitignore | 2 + tor/torrc | 2 + 22 files changed, 743 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.tor.yml create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 templates/index.html create mode 100644 templates/static/android-chrome-192x192.png create mode 100644 templates/static/android-chrome-256x256.png create mode 100644 templates/static/apple-touch-icon.png create mode 100644 templates/static/browserconfig.xml create mode 100644 templates/static/favicon-16x16.png create mode 100644 templates/static/favicon-32x32.png create mode 100644 templates/static/favicon.ico create mode 100644 templates/static/mstile-150x150.png create mode 100644 templates/static/safari-pinned-tab.svg create mode 100644 tor/data/.gitignore create mode 100644 tor/torrc 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 0000000000000000000000000000000000000000..8f148a79eba6e7fbc7dfb8c6104a5a03832fa4c7 GIT binary patch literal 6977 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 literal 0 HcmV?d00001 diff --git a/templates/static/apple-touch-icon.png b/templates/static/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a14d17c5df25a6bd720048a232e8dd5fa0812292 GIT binary patch literal 6510 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 Fe*q!noNWLA literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d5b6ed558e41a82eed3c704d7e74209fbb240f9 GIT binary patch literal 846 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>`Z@Ck8ERcVHxWW~lL zg~nv1reuZ26s4vt?e^~IMU&H3w1>>g)M{(+nmM<4!_xX~)3R2V*-lJRZmM#e^7`@n ze}Dgdc=`F%mMia`fBgIR-~a#r|Ni;=^wz8C*{iFar@nvr>Hoj~=XTy$QnzhJ&YF3p z8;`8M@blZRw@*Jb`Ofhq^JUuxZ^CCJui>vU|Jeq?sxx^p|PZ<=`U%lmH)-ZKkL zdM@m_xwv*~Z_MJ;Td&?e^XS;Ri|OjE)3a7*>vrTDbr)Oom00ypNL+ew`PnT~4_CWP zy?o&I%Li|t-+lA>-PenIZ)Iq-p5J|AMf1*!`))nD`n5E&EqTJl*J#$*-s+NE`sjAIvs%pR>W?(7_@(b>o zX{R$y63Amr@^*KTDouZ41mv)nc>21szu;sOXO(I{vP}ahwB6IiF+}2Wa)JZ9kB`q8 zoijdbbaZmwIG70sG&E1{p4~pZKi(mLL+5~=j*^y|o}#9zt}?gagvsFnAwgk*p_7BI zC$Ms7WM#g7@#>|tho__yYfsNiU!SvQbamFQQH)Z_y)h%_?HgH{xfKdbLFd-I(OZ}I zPHtXY0As$sn7pRKhgQxXKiOFpadfb3WLIW!a$0)m&`sqfOO`HG+IzsSe^ckPj!w~z zZs899gr>^A&bh1{5siN7F3b#yC!`|dSDVNIy`Wm+8c~vxSdwa$T$Bo=7>o>zOmqz_ zbq&lyjEt=eOs$N}v<(cb3=G_+2dqZXkei>9nO2EggZZf!;y?}IRUr{2L5bxG1x5L3 znK`KnC6xuK3Yi5Z$qWn?a~^-<;V2B#&^YCP`i$q(AO>b-ZoOn~VP#?O$s)|c3N8&M xhf|o9H-{*kzH#NmkuyhRj}VUz#> literal 0 HcmV?d00001 diff --git a/templates/static/favicon-32x32.png b/templates/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2f293bf8f9aad5f7dc43e8e7f2634bfb26786acb GIT binary patch literal 1251 zcmZ`%do&Yz9REp0RHqI%-EOBg>){c0R-V1ku(e3DO1C;^_Hq}l$x?*288#(qL^U=& z+?DbOEixOH$2Pj3^#>qs8vqP80CR>GLkqw;H~^z~ z06cO5u%hv+kL&@!=w09;oDX?R(k}r^B0^b2D3fT-B3ZMjb}8YG+^`*-(Cx`#4xGF3 zm6h2I6){ltX5WbmR^-|jtQu?DG9zeCP$|i z7Z>|p4J94E{fJwm?HkwW^xwYBHkG_i^vk(~%AFef`gLMPx1by7(z1{6^?Lo6Pt(0G zH7ape-9N380!71vwsu+1m}XM1)4x;nN4sUTK6}H!+>CJIhHhiWdh*J7^|RC8i#X3` zroMBI3J9ASBv?`+D(7CpQ+6PqvW;CMZ29o7hPLy{gmxRWfLHbkj;#6C$75vV_Bw5`Lr|2IsL3ldL-iVx!sxh=gLYmq(kpUqus6+GQ@8h z`!wAn{hh-j>dz%tYg2;q3u4RXe=G=Mo)95Ss$J@9Y0tA;jU(#uP@80y|E=D(!3cQT zm6LaV%+Ar>Gk9dfste;olR05U7Y}6ThCli|{3Xsi>&BU)$Co7!Q>rDxmKs5eM5quY zRaIm)RNZcRao3^$=ZDa-0YQJf8Fq}ir#oukMg)u>5-Ja0wa!3H=xBc*6YYxSR?9Y| z%AeX8WF8vng>zG8w}%GRd#+iI?=Ww%6>8spI#CKGL~=wnGar7tI3aNzleq3!?8K~p zMQSF+OEr|?syr11#zVjGw?;tLS~2dN0`4nX0idc!?guKU`-Hd;`_5FBY)0yhm|k_z zf{^~TlTLW+yKpr9P&j{iH1{xyeaQ=pINAAEQ0dNUoUiFu>d5amPrP-nisd&uc`m`* z?6R|!Ti#5=1Ph&#QiE5jVKHkiHx0;W z-s=W&_N&|492m9FEKOREn|2pR%IhAlSn}y~nwvFTa z3FSR~`*D_@y#nVLzuJI;=y6*PggY2S^FwWt+Rwh+j{)zuT2E&-yAGhjhO=ETrwtd| zO7{t-lS1fZ58_#}0RRGy*yR9scYwR#5Ia5K&K?LCdpO(!4)@IoEc(R|5lNy_V*lUZ zs?b^)49DYe!E|iMc?j)nB!wDIhR|baWC)c;CjxMRKRC70H0qd*jpDFUg&x8JW0<)k z(cA)Nf#F#iTR@PET@55-SUC=&?CxlmtK=KyCQ1fMc+e4yTCJr|c{^ttMgX)=0J7K{ G&-xEH+EeoY literal 0 HcmV?d00001 diff --git a/templates/static/favicon.ico b/templates/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6da5dc08df9fcb70b88cc55f9e4514b33f6903e0 GIT binary patch literal 15086 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 OGlqKibg8fhQU3vk-X-V& literal 0 HcmV?d00001 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 From f2f3d83ebacf290fcd198511947bb25b24cca77e Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 03:25:42 +0100 Subject: [PATCH 2/6] chore: Optimized docker container with github action --- .dockerignore | 1 - .github/workflows/docker-build.yml | 36 ++++++++++++++++++++++++ .gitignore | 1 - Dockerfile-optimized | 44 ++++++++++++++++++++++++++++++ docker-compose.yml | 6 ++-- 5 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/docker-build.yml create mode 100644 Dockerfile-optimized diff --git a/.dockerignore b/.dockerignore index 885eac2..65eef93 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1 @@ db -templates diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..66cb630 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,36 @@ +name: Docker Build and Publish + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile-optimized + push: ${{ github.event_name != 'pull_request' }} + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 6f4c7e1..76ec0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ db/ .env -wot-relay \ No newline at end of file diff --git a/Dockerfile-optimized b/Dockerfile-optimized new file mode 100644 index 0000000..2c80ed9 --- /dev/null +++ b/Dockerfile-optimized @@ -0,0 +1,44 @@ +# Stage 1: Build the Go application +FROM golang:bookworm AS builder + +# 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 for build +ENV DB_PATH="db" +ENV INDEX_PATH="templates/index.html" +ENV STATIC_PATH="templates/static" + +# touch a .env +RUN touch .env + +# Build the Go application +RUN go build -o main . + +# Stage 2: Create a minimal image to run the Go application +FROM debian:bookworm-slim + +# Set the working directory within the container +WORKDIR /app + +# Copy the Go binary from the builder stage +COPY --from=builder /app/main /app/ + +# Copy any necessary files like templates, static assets, etc. +COPY --from=builder /app/templates /app/templates +COPY --from=builder /app/.env /app/ + +# Expose the port that the application will run on +EXPOSE 3334 + +# Set the command to run the executable +CMD ["./main"] diff --git a/docker-compose.yml b/docker-compose.yml index 13ca973..65c2693 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,12 +3,12 @@ services: container_name: wot-relay build: context: . - dockerfile: Dockerfile + dockerfile: Dockerfile-optimized env_file: - .env 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 + # - "./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" From 3bcf881473c5182d4c5afd8b4a9e4f9cee2b3e15 Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 06:33:58 +0100 Subject: [PATCH 3/6] added traefik support for easy deployment with ssl certificate --- docker-compose.yml | 80 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 65c2693..08fdb33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,84 @@ services: - relay: - container_name: wot-relay + # relay: + # container_name: wot-relay + # env_file: + # - .env + # 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" + + relay-optimized: + container_name: wot-relay-optimized build: context: . dockerfile: Dockerfile-optimized + # image: ghcr.io/gbozee/wot-relay:latest env_file: - .env + # build: + # context: . + # dockerfile: Dockerfile-optimized 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 + labels: + - "traefik.enable=true" + - "traefik.http.routers.relay.rule=Host(`relay.beeola.me`)" + - "traefik.http.routers.relay.priority=1" + - "traefik.http.routers.relay.entryPoints=web-secure" + - "traefik.http.routers.relay.middlewares=csrf" + - "traefik.http.services.relay.loadBalancer.sticky.cookie={}" + - "traefik.http.services.relay.loadBalancer.server.port=3334" + - "traefik.http.routers.relay.tls.certResolver=letsencrypt_web" + + traefik: + image: traefik:v2.2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - production_traefik:/etc/traefik/acme:z + command: + - --providers.docker + - --providers.docker.exposedbydefault=false + # - --providers.docker.swarmmode + # Enable the access log, with HTTP requests + - --accesslog + - --log + - "--api.dashboard=true" + - "--api.insecure=true" + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entryPoint.to=web-secure" + - "--entrypoints.web-secure.address=:443" + # - "--entrypoints.web.forwardedHeaders.insecure=true" + # - "--entrypoints.web-secure.forwardedHeaders.insecure=true" + - "--certificatesResolvers.letsencrypt_web.acme.email=jamie@example.com" + - "--certificatesResolvers.letsencrypt_web.acme.storage=/etc/traefik/acme/acme.json" + - "--certificatesResolvers.letsencrypt_web.acme.httpChallenge.entryPoint=web" + - "--certificatesResolvers.letsencrypt.acme.email=jamie@example.com" + - "--certificatesResolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json" + # - "--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=digitalocean" + # - "--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=route53" + # - "--certificatesResolvers.letsencrypt.acme.dnsChallenge.delayBeforeCheck=0" + + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + ports: - - "3334:3334" + - target: 80 + published: 80 + mode: host + - target: 443 + published: 443 + mode: host + - target: 5555 + published: 5555 + mode: host + # - "0.0.0.0:80:80" + # - "0.0.0.0:443:443" + # - "0.0.0.0:5555:5555" + # - "0.0.0.0:8080:8080" + +volumes: + production_traefik: {} From 89a0552ce883604dd38228315968acf532c9793e Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 06:36:35 +0100 Subject: [PATCH 4/6] restart services always --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 08fdb33..c99d286 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: context: . dockerfile: Dockerfile-optimized # image: ghcr.io/gbozee/wot-relay:latest + restart: always env_file: - .env # build: @@ -38,6 +39,7 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - production_traefik:/etc/traefik/acme:z + restart: always command: - --providers.docker - --providers.docker.exposedbydefault=false From 6a6f1c9108f4861a9ae658c372ffff3663007317 Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 10:02:35 +0100 Subject: [PATCH 5/6] added wot-relay in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 76ec0b2..c878b34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ db/ .env +wot-relay From 814706b22cf4916793a93096c1fe176d269acff6 Mon Sep 17 00:00:00 2001 From: Biola Date: Sun, 8 Sep 2024 10:11:49 +0100 Subject: [PATCH 6/6] resolved white space issues --- main.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 59a629b..f418641 100644 --- a/main.go +++ b/main.go @@ -46,17 +46,17 @@ func main() { reset := "\033[0m" art := ` -888 888 88888888888 8888888b. 888 -888 o 888 888 888 Y88b 888 -888 d8b 888 888 888 888 888 -888 d888b 888 .d88b. 888 888 d88P .d88b. 888 8888b. 888 888 -888d88888b888 d88""88b 888 8888888P" d8P Y8b 888 "88b 888 888 -88888P Y88888 888 888 888 888 T88b 88888888 888 .d888888 888 888 -8888P Y8888 Y88..88P 888 888 T88b Y8b. 888 888 888 Y88b 888 -888P Y888 "Y88P" 888 888 T88b "Y8888 888 "Y888888 "Y88888 - 888 - Y8b d88P - powered by: khatru "Y88P" +888 888 88888888888 8888888b. 888 +888 o 888 888 888 Y88b 888 +888 d8b 888 888 888 888 888 +888 d888b 888 .d88b. 888 888 d88P .d88b. 888 8888b. 888 888 +888d88888b888 d88""88b 888 8888888P" d8P Y8b 888 "88b 888 888 +88888P Y88888 888 888 888 888 T88b 88888888 888 .d888888 888 888 +8888P Y8888 Y88..88P 888 888 T88b Y8b. 888 888 888 Y88b 888 +888P Y888 "Y88P" 888 888 T88b "Y8888 888 "Y888888 "Y88888 + 888 + Y8b d88P + powered by: khatru "Y88P" ` fmt.Println(green + art + reset)