commit ccb50b1ac2f8985932836fc7ae0136c41aaf0ab9 Author: Brian Lee Date: Sat Jul 29 17:19:36 2023 -0700 Initialize repo for strfry nostr relay Ansible Role. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4baac1e --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +MIT No Attribution License + +Copyright (c) 2023 Brian Lee + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4711432 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Ansible Role: strfry + +This Ansible Role builds and installs [strfry](https://github.com/hoytech/strfry). + +Future role improvements: + +* The git tasks in this role are not yet idempotent due to handling a submodule. +* The installation process could be made to check if strfry is already running and perform a zero-downtime upgrade. + +## Requirements + +None. + +## Role Variables + +```yaml +strfry_version: beta # git repository branch or release tag +strfry_make_jobs: "{{ ansible_processor_cores }}" # number of CPUs to build with +strfry_skip_config: False +``` + +See `defaults/main.yml` + +If you are not using the `beta` branch/version, you should override the template with your own by setting `strfry_skip_config` to true and manage the configuration manually. + +For more configuration info, see the relevant upstream [configuration example](https://github.com/hoytech/strfry/blob/beta/strfry.conf) for your branch/version. + +## Example Playbook + +```yaml +- hosts: all + become: true + roles: + - role: bleetube.strfry + - role: nginxinc.nginx_core.nginx + tasks: + - import_tasks: nginx_conf.yml +``` + +## Troubleshooting + +If `make` fails, try running on a single core: + +```shell +ansible-playbook playbooks/strfry/main.yml -e 'strfry_make_jobs=1' +``` diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..8265540 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,123 @@ +--- +strfry_version: beta +strfry_binary_path: /usr/local/bin/strfry +strfry_make_jobs: "{{ ansible_processor_cores }}" +strfry_skip_config: False +strfry_system_group: strfry +strfry_system_user: strfry + +strfry_data_path: /var/lib/strfry +strfry_db: "./strfry-db/" # Becomes /var/lib/strfry/strfry-db + +strfry_dbParams: + # Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required) + maxreaders: 256 + + # Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required) + mapsize: 10995116277760 + +strfry_relay: + # Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required) + bind: "127.0.0.1" + + # Port to open for the nostr websocket protocol (restart required) + port: 7777 + + # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required) + nofiles: 1000000 + + # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case) + realIpHeader: "" + + info: + # NIP-11: Name of this server. Short/descriptive (< 30 characters) + name: "strfry default" + + # NIP-11: Detailed information about relay, free-form + description: "This is a strfry instance." + + # NIP-11: Administrative nostr pubkey, for contact purposes + pubkey: "unset" + + # NIP-11: Alternative administrative contact (email, website, etc) + contact: "unset" + + # Maximum accepted incoming websocket frame size (should be larger than max event and yesstr msg) (restart required) + maxWebsocketPayloadSize: 131072 + + # Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required) + autoPingSeconds: 55 + + # If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy) + enableTcpKeepalive: false + + # How much uninterrupted CPU time a REQ query should get during its DB scan + queryTimesliceBudgetMicroseconds: 10000 + + # Maximum records that can be returned per filter + maxFilterLimit: 500 + + # Maximum number of subscriptions (concurrent REQs) a connection can have open at any time + maxSubsPerConnection: 20 + + writePolicy: + # If non-empty, path to an executable script that implements the writePolicy plugin logic + plugin: "" + + # Number of seconds to search backwards for lookback events when starting the writePolicy plugin (0 for no lookback) + lookbackSeconds: 0 + + compression: + # Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required) + enabled: "true" + + # Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required) + slidingWindow: "true" + + logging: + # Dump all incoming messages + dumpInAll: "false" + + # Dump all incoming EVENT messages + dumpInEvents: "false" + + # Dump all incoming REQ/CLOSE messages + dumpInReqs: "false" + + # Log performance metrics for initial REQ database scans + dbScanPerf: "false" + + numThreads: + # Ingester threads: route incoming requests, validate events/sigs (restart required) + ingester: 3 + + # reqWorker threads: Handle initial DB scan for events (restart required) + reqWorker: 3 + + # reqMonitor threads: Handle filtering of new events (restart required) + reqMonitor: 3 + + # yesstr threads: Experimental yesstr protocol (restart required) + yesstr: 1 + +strfry_events: + # Maximum size of normalised JSON, in bytes + maxEventSize: 65536 + + # Events newer than this will be rejected + rejectEventsNewerThanSeconds: 900 + + # Events older than this will be rejected + rejectEventsOlderThanSeconds: 94608000 + + # Ephemeral events older than this will be rejected + rejectEphemeralEventsOlderThanSeconds: 60 + + # Ephemeral events will be deleted from the DB when older than this + ephemeralEventsLifetimeSeconds: 300 + + # Maximum number of tags allowed + maxNumTags: 2000 + + # Maximum size for tag values, in bytes + maxTagValSize: 1024 \ No newline at end of file diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..f5ae1f2 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: restart strfry + ansible.builtin.service: + name: strfry + state: restarted + daemon_reload: yes + become: true diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..6099501 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [] \ No newline at end of file diff --git a/tasks/build.yml b/tasks/build.yml new file mode 100644 index 0000000..b538799 --- /dev/null +++ b/tasks/build.yml @@ -0,0 +1,31 @@ +--- +- name: Avoid running as root + ansible.builtin.pause: + prompt: "Please run this role as a regular user with sudo/doas privileges. Or press enter to continue anyway (not recommended)." + when: ansible_user_id == 'root' + +- name: Clone git repository + ansible.builtin.git: + repo: https://github.com/hoytech/strfry.git + dest: "{{ ansible_env.HOME }}/src/strfry" + version: "{{ strfry_version }}" + force: yes # required to avoid weird error when updating submodules via Ansible + register: git_repository + +- name: Update git submodule (golpe) + ansible.builtin.command: + cmd: git submodule update --init + chdir: "{{ ansible_env.HOME }}/src/strfry" + when: git_repository.changed + +- name: Run make setup-golpe + ansible.builtin.command: + cmd: make setup-golpe + chdir: "{{ ansible_env.HOME }}/src/strfry" + when: git_repository.changed + +- name: Build strfry + ansible.builtin.command: + cmd: "make -j{{ strfry_make_jobs|default(1) }}" + chdir: "{{ ansible_env.HOME }}/src/strfry" + when: git_repository.changed \ No newline at end of file diff --git a/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..fd285c8 --- /dev/null +++ b/tasks/install.yml @@ -0,0 +1,51 @@ +--- +- name: Install strfry binary + ansible.builtin.copy: + src: "{{ ansible_env.HOME }}/src/strfry/strfry" + dest: "{{ strfry_binary_path }}" + mode: 0755 + remote_src: true + become: true + +- name: Setup strfry service unit + ansible.builtin.template: + src: strfry.service + dest: /etc/systemd/system/strfry.service + become: true + notify: restart strfry + +- name: Ensure strfry is enabled on boot + ansible.builtin.service: + name: strfry + enabled: true + state: started + become: true + +- name: Configure strfry + ansible.builtin.template: + src: strfry.conf + dest: "{{ strfry_data_path }}/strfry.conf" + owner: "{{ strfry_system_user }}" + group: "{{ strfry_system_group }}" + when: not strfry_skip_config + become: true + tags: config + notify: restart strfry + +- name: Ensure the configured database directory exists. + ansible.builtin.file: + path: "{{ strfry_db }}" + state: directory + owner: "{{ strfry_system_user }}" + group: "{{ strfry_system_group }}" + when: strfry_db.startswith('/') + become: true + +- name: Ensure the configured database directory exists. + ansible.builtin.file: + path: "{{ strfry_data_path }}/{{ strfry_db }}" + state: directory + owner: "{{ strfry_system_user }}" + group: "{{ strfry_system_group }}" + when: not strfry_db.startswith('/') + become: true \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..e931792 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Load a variable file based on the OS type. + include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_distribution }}.yml" + - "{{ ansible_os_family }}.yml" + +- name: Install compiler dependencies (Debian) + ansible.builtin.package: + name: "{{ compiler_packages }}" + state: present + when: ansible_os_family == 'Debian' + become: true + +- name: Set up strfry user + import_tasks: setup-user.yml + +#- name: Run build tasks +# import_tasks: build.yml +# tags: build + +- name: Install strfry + import_tasks: install.yml \ No newline at end of file diff --git a/tasks/setup-user.yml b/tasks/setup-user.yml new file mode 100644 index 0000000..716fbe8 --- /dev/null +++ b/tasks/setup-user.yml @@ -0,0 +1,39 @@ +--- +- name: Get nologin path for strfry user + ansible.builtin.find: + paths: + - /bin + - /sbin + - /usr/bin + - /usr/sbin + patterns: nologin + register: nologin_bin + become: true + +- name: Create the strfry group + ansible.builtin.group: + name: "{{ strfry_system_group }}" + state: present + system: true + when: strfry_system_group != "root" + become: true + +- name: Create the strfry system user + ansible.builtin.user: + name: "{{ strfry_system_user }}" + groups: "{{ strfry_system_group }}" + shell: "{{ nologin_bin.files[0].path }}" + system: true + create_home: false + home: "{{ strfry_data_path }}" + when: strfry_system_user != "root" + become: true + +- name: Ensure strfry_path exists. + ansible.builtin.file: + path: "{{ strfry_data_path }}" + owner: "{{ strfry_system_user }}" + group: "{{ strfry_system_group }}" + state: directory + mode: '0750' + become: true diff --git a/templates/strfry.conf b/templates/strfry.conf new file mode 100644 index 0000000..f3d3961 --- /dev/null +++ b/templates/strfry.conf @@ -0,0 +1,129 @@ +## +## Default strfry config +## + +# Directory that contains the strfry LMDB database (restart required) +# Ansible template for strfry.conf + +db = "{{ strfry_db }}" + +dbParams { + # Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required) + maxreaders = {{ strfry_dbParams.maxreaders }} + + # Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required) + mapsize = {{ strfry_dbParams.mapsize }} +} + +relay { + # Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required) + bind = "{{ strfry_relay.bind }}" + + # Port to open for the nostr websocket protocol (restart required) + port = {{ strfry_relay.port }} + + # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required) + nofiles = {{ strfry_relay.nofiles }} + + # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case) + realIpHeader = "{{ strfry_relay.realIpHeader }}" + + info { + # NIP-11: Name of this server. Short/descriptive (< 30 characters) + name = "{{ strfry_relay.info.name }}" + + # NIP-11: Detailed information about relay, free-form + description = "{{ strfry_relay.info.description }}" + + # NIP-11: Administrative nostr pubkey, for contact purposes + pubkey = "{{ strfry_relay.info.pubkey }}" + + # NIP-11: Alternative administrative contact (email, website, etc) + contact = "{{ strfry_relay.info.contact }}" + } + + # Maximum accepted incoming websocket frame size (should be larger than max event and yesstr msg) (restart required) + maxWebsocketPayloadSize = {{ strfry_relay.maxWebsocketPayloadSize }} + + # Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required) + autoPingSeconds = {{ strfry_relay.autoPingSeconds }} + + # If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy) + enableTcpKeepalive = {{ strfry_relay.enableTcpKeepalive }} + + # How much uninterrupted CPU time a REQ query should get during its DB scan + queryTimesliceBudgetMicroseconds = {{ strfry_relay.queryTimesliceBudgetMicroseconds }} + + # Maximum records that can be returned per filter + maxFilterLimit = {{ strfry_relay.maxFilterLimit }} + + # Maximum number of subscriptions (concurrent REQs) a connection can have open at any time + maxSubsPerConnection = {{ strfry_relay.maxSubsPerConnection }} + + writePolicy { + # If non-empty, path to an executable script that implements the writePolicy plugin logic + plugin = "{{ strfry_relay.writePolicy.plugin }}" + + # Number of seconds to search backwards for lookback events when starting the writePolicy plugin (0 for no lookback) + lookbackSeconds = {{ strfry_relay.writePolicy.lookbackSeconds }} + } + + compression { + # Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required) + enabled = {{ strfry_relay.compression.enabled }} + + # Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required) + slidingWindow = {{ strfry_relay.compression.slidingWindow }} + } + + logging { + # Dump all incoming messages + dumpInAll = {{ strfry_relay.logging.dumpInAll }} + + # Dump all incoming EVENT messages + dumpInEvents = {{ strfry_relay.logging.dumpInEvents }} + + # Dump all incoming REQ/CLOSE messages + dumpInReqs = {{ strfry_relay.logging.dumpInReqs }} + + # Log performance metrics for initial REQ database scans + dbScanPerf = {{ strfry_relay.logging.dbScanPerf }} + } + + numThreads { + # Ingester threads: route incoming requests, validate events/sigs (restart required) + ingester = {{ strfry_relay.numThreads.ingester }} + + # reqWorker threads: Handle initial DB scan for events (restart required) + reqWorker = {{ strfry_relay.numThreads.reqWorker }} + + # reqMonitor threads: Handle filtering of new events (restart required) + reqMonitor = {{ strfry_relay.numThreads.reqMonitor }} + + # yesstr threads: Experimental yesstr protocol (restart required) + yesstr = {{ strfry_relay.numThreads.yesstr }} + } +} + +events { + # Maximum size of normalised JSON, in bytes + maxEventSize = {{ strfry_events.maxEventSize }} + + # Events newer than this will be rejected + rejectEventsNewerThanSeconds = {{ strfry_events.rejectEventsNewerThanSeconds }} + + # Events older than this will be rejected + rejectEventsOlderThanSeconds = {{ strfry_events.rejectEventsOlderThanSeconds }} + + # Ephemeral events older than this will be rejected + rejectEphemeralEventsOlderThanSeconds = {{ strfry_events.rejectEphemeralEventsOlderThanSeconds }} + + # Ephemeral events will be deleted from the DB when older than this + ephemeralEventsLifetimeSeconds = {{ strfry_events.ephemeralEventsLifetimeSeconds }} + + # Maximum number of tags allowed + maxNumTags = {{ strfry_events.maxNumTags }} + + # Maximum size for tag values, in bytes + maxTagValSize = {{ strfry_events.maxTagValSize }} +} \ No newline at end of file diff --git a/templates/strfry.service b/templates/strfry.service new file mode 100644 index 0000000..b98677d --- /dev/null +++ b/templates/strfry.service @@ -0,0 +1,22 @@ +[Unit] +Description=strfry nostr relay + +[Service] +User={{ strfry_system_user }} +Group={{ strfry_system_group }} +WorkingDirectory={{ strfry_data_path }} +ExecStart={{ strfry_binary_path }} --config=strfry.conf relay +Restart=on-failure +RestartSec=15 +LimitNOFILE=65536 + +# Hardening +ProtectHome=yes +NoNewPrivileges=yes +ProtectSystem=full + +[Journal] +MaxRetentionSec=2h + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/vars/Archlinux.yml b/vars/Archlinux.yml new file mode 100644 index 0000000..434855b --- /dev/null +++ b/vars/Archlinux.yml @@ -0,0 +1,11 @@ +--- +compiler_packages: + - base-devel + - flatbuffers + - git + - lmdb + - openssl + - perl-regexp-grammars + - perl-template-toolkit + - perl-yaml + - zlib \ No newline at end of file diff --git a/vars/Debian.yml b/vars/Debian.yml new file mode 100644 index 0000000..b79ae4a --- /dev/null +++ b/vars/Debian.yml @@ -0,0 +1,13 @@ +--- +compiler_packages: + - build-essential + - git + - libflatbuffers-dev + - liblmdb-dev + - libregexp-grammars-perl + - libsecp256k1-dev + - libssl-dev + - libtemplate-perl + - libyaml-perl + - libzstd-dev + - zlib1g-dev \ No newline at end of file diff --git a/vars/RedHat.yml b/vars/RedHat.yml new file mode 100644 index 0000000..f81d20e --- /dev/null +++ b/vars/RedHat.yml @@ -0,0 +1,9 @@ +--- +compiler_packages: + - @development-tools + - git + - lmdb-devel + - openssl-devel + - perl-Template-Toolkit + - perl-YAML + - zlib-devel \ No newline at end of file diff --git a/vars/Ubuntu.yml b/vars/Ubuntu.yml new file mode 100644 index 0000000..3e28e2a --- /dev/null +++ b/vars/Ubuntu.yml @@ -0,0 +1,14 @@ +--- +compiler_packages: + - build-essential + - git + - libflatbuffers-dev + - liblmdb-dev + - libregexp-grammars-perl + - libsecp256k1-dev + - libssl-dev + - libtemplate-perl + - libyaml-perl + - libzstd-dev + - zlib1g-dev + - libb2-dev # Ubuntu 22.04 \ No newline at end of file