diff --git a/Gemfile b/Gemfile index f8aaaaf8..3d9cb0ca 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,11 @@ ruby '3.1.4' gem 'rails', '~> 7.1', '>= 7.1.1' # The HTTP server running the Rails instance. +# NOTE: Once we're migrated, remove puma! I have both to allow the upgrade to +# be incremental: push this out with `bin/deploy`, then change the service file +# with `bin/deploy:setup`. gem 'puma', '~> 6.3', '>= 6.3.1' +gem 'falcon', '~> 0.42.3' # Our database is MySQL, in both development and production. gem 'mysql2', '~> 0.5.5' diff --git a/Gemfile.lock b/Gemfile.lock index 43e1165f..c3e44287 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,9 @@ GEM fiber-annotation io-event (~> 1.1) timers (~> 4.1) + async-container (0.16.12) + async + async-io async-http (0.61.0) async (>= 1.25) async-io (>= 1.28) @@ -95,6 +98,8 @@ GEM protocol-http1 (~> 0.16.0) protocol-http2 (~> 0.15.0) traces (>= 0.10.0) + async-http-cache (0.4.3) + async-http (~> 0.56) async-io (1.37.0) async async-pool (0.4.0) @@ -109,6 +114,7 @@ GEM bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) + build-environment (1.13.0) builder (3.2.4) concurrent-ruby (1.2.2) connection_pool (2.2.5) @@ -133,6 +139,19 @@ GEM ruby2_keywords erubi (1.12.0) execjs (2.5.2) + falcon (0.42.3) + async + async-container (~> 0.16.0) + async-http (~> 0.57) + async-http-cache (~> 0.4.0) + async-io (~> 1.22) + build-environment (~> 1.13) + bundler + localhost (~> 1.1) + openssl (~> 3.0) + process-metrics (~> 0.2.0) + protocol-rack (~> 0.1) + samovar (~> 2.1) ffi (1.15.5) fiber-annotation (0.2.0) fiber-local (1.0.0) @@ -163,6 +182,7 @@ GEM addressable (~> 2.8) letter_opener (1.8.1) launchy (>= 2.2, < 3) + localhost (1.1.10) loofah (2.21.3) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -171,6 +191,7 @@ GEM net-imap net-pop net-smtp + mapping (1.1.1) marcel (1.0.2) memory_profiler (1.0.1) mini_mime (1.1.5) @@ -193,8 +214,12 @@ GEM nokogiri (1.15.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) + openssl (3.2.0) orm_adapter (0.5.0) parallel (1.23.0) + process-metrics (0.2.1) + console (~> 1.8) + samovar (~> 2.1) protocol-hpack (1.4.2) protocol-http (0.25.0) protocol-http1 (0.16.0) @@ -202,10 +227,13 @@ GEM protocol-http2 (0.15.1) protocol-hpack (~> 1.4) protocol-http (~> 0.18) + protocol-rack (0.4.1) + protocol-http (~> 0.23) + rack (>= 1.0) psych (5.1.1.1) stringio public_suffix (5.0.3) - puma (6.4.0) + puma (6.4.2) nio4r (~> 2.0) racc (1.7.1) rack (2.2.8) @@ -272,6 +300,9 @@ GEM actionpack (>= 5.2) railties (>= 5.2) ruby2_keywords (0.0.5) + samovar (2.2.0) + console (~> 1.0) + mapping (~> 1.0) sanitize (6.0.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -338,6 +369,7 @@ DEPENDENCIES devise (~> 4.9, >= 4.9.2) devise-encryptable (~> 0.2.0) dotenv-rails (~> 2.8, >= 2.8.1) + falcon (~> 0.42.3) globalize (~> 6.2, >= 6.2.1) haml (~> 6.1, >= 6.1.1) http_accept_language (~> 2.1, >= 2.1.1) diff --git a/config/application.rb b/config/application.rb index 4e2a497d..4d37e468 100644 --- a/config/application.rb +++ b/config/application.rb @@ -61,6 +61,10 @@ module OpenneoImpressItems config.middleware.insert_after ActionDispatch::Flash, Rack::Attack + # On the Falcon server, requests run on fibers. Isolate Rails internal + # state to the per-fiber level, to avoid conflicts that crash stuff! + config.active_support.isolation_level = :fiber + # It seems like some Neopets servers reject any user agent containing # symbols? So I can't provide anything helpful like a URL, email address, # version number, etc. So let's only send this to Neopets systems, where it diff --git a/deploy/files/impress.service b/deploy/files/impress.service index 498f0747..f77c370b 100644 --- a/deploy/files/impress.service +++ b/deploy/files/impress.service @@ -5,7 +5,7 @@ Description=Dress to Impress webapp User=impress Restart=always WorkingDirectory=/srv/impress/current -ExecStart=/opt/ruby-3.1.4/bin/bundle exec puma --port=3000 +ExecStart=/opt/ruby-3.1.4/bin/bundle exec falcon host Environment="RAILS_ENV=production" ; Set EXECJS_RUNTIME to save us from needing to install Node Environment="EXECJS_RUNTIME=Disabled" diff --git a/deploy/setup.yml b/deploy/setup.yml index 31bbe18a..f99188b8 100644 --- a/deploy/setup.yml +++ b/deploy/setup.yml @@ -7,253 +7,253 @@ email_address: "emi@matchu.dev" # TODO: Extract this to personal config? impress_hostname: impress.openneo.net tasks: - - name: Create SSH folder for logged-in user - become: no - file: - name: .ssh - mode: "700" - state: directory + # - name: Create SSH folder for logged-in user + # become: no + # file: + # name: .ssh + # mode: "700" + # state: directory - - name: Copy authorized SSH keys to logged-in user - become: no - copy: - dest: ~/.ssh/authorized_keys - src: files/authorized-ssh-keys.txt - mode: "600" + # - name: Copy authorized SSH keys to logged-in user + # become: no + # copy: + # dest: ~/.ssh/authorized_keys + # src: files/authorized-ssh-keys.txt + # mode: "600" - - name: Disable root SSH login - lineinfile: - dest: /etc/ssh/sshd_config - regexp: ^#?PermitRootLogin - line: PermitRootLogin no + # - name: Disable root SSH login + # lineinfile: + # dest: /etc/ssh/sshd_config + # regexp: ^#?PermitRootLogin + # line: PermitRootLogin no - - name: Disable password-based SSH authentication - lineinfile: - dest: /etc/ssh/sshd_config - regexp: ^#?PasswordAuthentication - line: PasswordAuthentication no + # - name: Disable password-based SSH authentication + # lineinfile: + # dest: /etc/ssh/sshd_config + # regexp: ^#?PasswordAuthentication + # line: PasswordAuthentication no - - name: Enable public-key SSH authentication - lineinfile: - dest: /etc/ssh/sshd_config - regexp: ^#?PubkeyAuthentication - line: PubkeyAuthentication yes + # - name: Enable public-key SSH authentication + # lineinfile: + # dest: /etc/ssh/sshd_config + # regexp: ^#?PubkeyAuthentication + # line: PubkeyAuthentication yes - - name: Update the apt cache - apt: - update_cache: yes + # - name: Update the apt cache + # apt: + # update_cache: yes - - name: Install fail2ban firewall with default settings - apt: - name: fail2ban + # - name: Install fail2ban firewall with default settings + # apt: + # name: fail2ban - - name: Configure ufw firewall to allow SSH connections on port 22 - community.general.ufw: - rule: allow - port: "22" + # - name: Configure ufw firewall to allow SSH connections on port 22 + # community.general.ufw: + # rule: allow + # port: "22" - - name: Configure ufw firewall to allow HTTP connections on port 80 - community.general.ufw: - rule: allow - port: "80" + # - name: Configure ufw firewall to allow HTTP connections on port 80 + # community.general.ufw: + # rule: allow + # port: "80" - - name: Configure ufw firewall to allow HTTPS connections on port 443 - community.general.ufw: - rule: allow - port: "443" + # - name: Configure ufw firewall to allow HTTPS connections on port 443 + # community.general.ufw: + # rule: allow + # port: "443" - - name: Enable ufw firewall with all other ports closed by default - community.general.ufw: - state: enabled - policy: deny + # - name: Enable ufw firewall with all other ports closed by default + # community.general.ufw: + # state: enabled + # policy: deny - - name: Install unattended-upgrades - apt: - name: unattended-upgrades + # - name: Install unattended-upgrades + # apt: + # name: unattended-upgrades - - name: Enable unattended-upgrades to auto-upgrade our system - copy: - content: | - APT::Periodic::Update-Package-Lists "1"; - APT::Periodic::Unattended-Upgrade "1"; - dest: /etc/apt/apt.conf.d/20auto-upgrades + # - name: Enable unattended-upgrades to auto-upgrade our system + # copy: + # content: | + # APT::Periodic::Update-Package-Lists "1"; + # APT::Periodic::Unattended-Upgrade "1"; + # dest: /etc/apt/apt.conf.d/20auto-upgrades - - name: Configure unattended-upgrades to auto-reboot our server when necessary - lineinfile: - regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot ".*";$ - line: Unattended-Upgrade::Automatic-Reboot "true"; - dest: /etc/apt/apt.conf.d/50unattended-upgrades + # - name: Configure unattended-upgrades to auto-reboot our server when necessary + # lineinfile: + # regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot ".*";$ + # line: Unattended-Upgrade::Automatic-Reboot "true"; + # dest: /etc/apt/apt.conf.d/50unattended-upgrades - - name: Configure unattended-upgrades to delay necessary reboots to 3am - lineinfile: - regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot-Time ".*";$ - line: Unattended-Upgrade::Automatic-Reboot-Time "03:00"; - dest: /etc/apt/apt.conf.d/50unattended-upgrades + # - name: Configure unattended-upgrades to delay necessary reboots to 3am + # lineinfile: + # regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot-Time ".*";$ + # line: Unattended-Upgrade::Automatic-Reboot-Time "03:00"; + # dest: /etc/apt/apt.conf.d/50unattended-upgrades - - name: Configure the system timezone to be US Pacific time - community.general.timezone: - name: America/Los_Angeles + # - name: Configure the system timezone to be US Pacific time + # community.general.timezone: + # name: America/Los_Angeles - - name: Create "impress" user - user: - name: impress - comment: Impress App - home: /srv/impress - create_home: false - shell: /bin/bash + # - name: Create "impress" user + # user: + # name: impress + # comment: Impress App + # home: /srv/impress + # create_home: false + # shell: /bin/bash - - name: Create "impress-deployers" group - group: - name: impress-deployers + # - name: Create "impress-deployers" group + # group: + # name: impress-deployers - - name: Add the current user to the "impress-deployers" group - user: - name: "{{ lookup('env', 'USER') }}" - groups: - - impress-deployers - append: yes + # - name: Add the current user to the "impress-deployers" group + # user: + # name: "{{ lookup('env', 'USER') }}" + # groups: + # - impress-deployers + # append: yes - # We use this so the deploy playbook doesn't have to prompt for a root - # password: this user just is trusted to act as "impress" in the future. - - name: Enable the "impress-deployers" group to freely act as the "impress" user - community.general.sudoers: - name: impress-deployers-as-impress - group: impress-deployers - runas: impress - commands: ALL - nopassword: yes + # # We use this so the deploy playbook doesn't have to prompt for a root + # # password: this user just is trusted to act as "impress" in the future. + # - name: Enable the "impress-deployers" group to freely act as the "impress" user + # community.general.sudoers: + # name: impress-deployers-as-impress + # group: impress-deployers + # runas: impress + # commands: ALL + # nopassword: yes - # Similarly, this enables us to manage the impress service in the deploy playbook - # and in live debugging without a password. - # NOTE: In the sudoers file, you need to specify the full path to the - # command, to avoid tricks where you use PATH to get around the intent! - - name: Enable the "impress-deployers" group to freely start and stop the impress service - community.general.sudoers: - name: impress-deployers-systemctl - group: impress-deployers - commands: - - /bin/systemctl status impress - - /bin/systemctl start impress - - /bin/systemctl stop impress - - /bin/systemctl restart impress - nopassword: yes + # # Similarly, this enables us to manage the impress service in the deploy playbook + # # and in live debugging without a password. + # # NOTE: In the sudoers file, you need to specify the full path to the + # # command, to avoid tricks where you use PATH to get around the intent! + # - name: Enable the "impress-deployers" group to freely start and stop the impress service + # community.general.sudoers: + # name: impress-deployers-systemctl + # group: impress-deployers + # commands: + # - /bin/systemctl status impress + # - /bin/systemctl start impress + # - /bin/systemctl stop impress + # - /bin/systemctl restart impress + # nopassword: yes - - name: Install ACL, to enable us to run commands as the "impress" user - apt: - name: acl + # - name: Install ACL, to enable us to run commands as the "impress" user + # apt: + # name: acl - - name: Install ruby-build - git: - repo: https://github.com/rbenv/ruby-build.git - dest: /opt/ruby-build - version: 4d4678bc1ed89aa6900c0ea0da23495445dbcf50 + # - name: Install ruby-build + # git: + # repo: https://github.com/rbenv/ruby-build.git + # dest: /opt/ruby-build + # version: 4d4678bc1ed89aa6900c0ea0da23495445dbcf50 - - name: Check if Ruby 3.1.4 is already installed - stat: - path: /opt/ruby-3.1.4 - register: ruby_dir + # - name: Check if Ruby 3.1.4 is already installed + # stat: + # path: /opt/ruby-3.1.4 + # register: ruby_dir - - name: Install Ruby 3.1.4 - command: "/opt/ruby-build/bin/ruby-build 3.1.4 /opt/ruby-3.1.4" - when: not ruby_dir.stat.exists + # - name: Install Ruby 3.1.4 + # command: "/opt/ruby-build/bin/ruby-build 3.1.4 /opt/ruby-3.1.4" + # when: not ruby_dir.stat.exists - - name: Add Ruby 3.1.4 to the global PATH, for developer convenience - lineinfile: - dest: /etc/profile - line: 'PATH="/opt/ruby-3.1.4/bin:$PATH" # Added by impress deploy setup script' + # - name: Add Ruby 3.1.4 to the global PATH, for developer convenience + # lineinfile: + # dest: /etc/profile + # line: 'PATH="/opt/ruby-3.1.4/bin:$PATH" # Added by impress deploy setup script' - - name: Install system dependencies for impress's Ruby gems - apt: - name: - - libmysqlclient-dev - - libyaml-dev + # - name: Install system dependencies for impress's Ruby gems + # apt: + # name: + # - libmysqlclient-dev + # - libyaml-dev - - name: Create the app folder - file: - path: /srv/impress - owner: impress - group: impress - mode: "755" - state: directory + # - name: Create the app folder + # file: + # path: /srv/impress + # owner: impress + # group: impress + # mode: "755" + # state: directory - - name: Add a convenient .bash_profile for when we log in as "impress" - copy: - owner: impress - group: impress - dest: /srv/impress/.bash_profile - content: | - set -a # Export all of the below - RAILS_ENV=production - EXECJS_RUNTIME=Disabled - source /srv/impress/shared/production.env - set +a + # - name: Add a convenient .bash_profile for when we log in as "impress" + # copy: + # owner: impress + # group: impress + # dest: /srv/impress/.bash_profile + # content: | + # set -a # Export all of the below + # RAILS_ENV=production + # EXECJS_RUNTIME=Disabled + # source /srv/impress/shared/production.env + # set +a - - name: Create the app's "versions" folder - become_user: impress - file: - path: /srv/impress/versions - state: directory + # - name: Create the app's "versions" folder + # become_user: impress + # file: + # path: /srv/impress/versions + # state: directory - - name: Create the app's "shared" folder - become_user: impress - file: - path: /srv/impress/shared - state: directory + # - name: Create the app's "shared" folder + # become_user: impress + # file: + # path: /srv/impress/shared + # state: directory - - name: Check for a current app version - stat: - path: /srv/impress/current - register: current_app_version + # - name: Check for a current app version + # stat: + # path: /srv/impress/current + # register: current_app_version - - name: Check whether we already have a placeholder app - stat: - path: /srv/impress/versions/initial-placeholder - register: existing_placeholder_app - when: not current_app_version.stat.exists + # - name: Check whether we already have a placeholder app + # stat: + # path: /srv/impress/versions/initial-placeholder + # register: existing_placeholder_app + # when: not current_app_version.stat.exists - - name: Create a placeholder app, to run until we deploy a real version - become_user: impress - copy: - src: files/initial-placeholder - dest: /srv/impress/versions - when: | - not current_app_version.stat.exists and - not existing_placeholder_app.stat.exists + # - name: Create a placeholder app, to run until we deploy a real version + # become_user: impress + # copy: + # src: files/initial-placeholder + # dest: /srv/impress/versions + # when: | + # not current_app_version.stat.exists and + # not existing_placeholder_app.stat.exists - - name: Configure the placeholder app to run in deployment mode - become_user: impress - command: - chdir: /srv/impress/versions/initial-placeholder - cmd: /opt/ruby-3.1.4/bin/bundle config set --local deployment true - when: not current_app_version.stat.exists + # - name: Configure the placeholder app to run in deployment mode + # become_user: impress + # command: + # chdir: /srv/impress/versions/initial-placeholder + # cmd: /opt/ruby-3.1.4/bin/bundle config set --local deployment true + # when: not current_app_version.stat.exists - - name: Install the placeholder app's dependencies - become_user: impress - command: - chdir: /srv/impress/versions/initial-placeholder - cmd: /opt/ruby-3.1.4/bin/bundle install - when: not current_app_version.stat.exists + # - name: Install the placeholder app's dependencies + # become_user: impress + # command: + # chdir: /srv/impress/versions/initial-placeholder + # cmd: /opt/ruby-3.1.4/bin/bundle install + # when: not current_app_version.stat.exists - - name: Set the placeholder app as the current version - become_user: impress - file: - src: /srv/impress/versions/initial-placeholder - dest: /srv/impress/current - state: link - when: not current_app_version.stat.exists + # - name: Set the placeholder app as the current version + # become_user: impress + # file: + # src: /srv/impress/versions/initial-placeholder + # dest: /srv/impress/current + # state: link + # when: not current_app_version.stat.exists - # NOTE: This file is uploaded with stricter permissions, to help protect - # the secrets inside. Most of the app is world-readable for convenience - # for debugging and letting nginx serve static files, but keep this safer! - - name: Upload the production.env file - become_user: impress - copy: - dest: /srv/impress/shared/production.env - src: files/production.env - mode: "600" - notify: - - Reload systemctl - - Restart impress + # # NOTE: This file is uploaded with stricter permissions, to help protect + # # the secrets inside. Most of the app is world-readable for convenience + # # for debugging and letting nginx serve static files, but keep this safer! + # - name: Upload the production.env file + # become_user: impress + # copy: + # dest: /srv/impress/shared/production.env + # src: files/production.env + # mode: "600" + # notify: + # - Reload systemctl + # - Restart impress - name: Create service file for impress copy: diff --git a/falcon.rb b/falcon.rb new file mode 100644 index 00000000..ee96aaa8 --- /dev/null +++ b/falcon.rb @@ -0,0 +1,12 @@ +#!/usr/bin/env -S falcon host +# frozen_string_literal: true + +load :rack, :supervisor + +hostname = File.basename(__dir__) +rack hostname do + endpoint Async::HTTP::Endpoint.parse('http://localhost:3000'). + with(protocol: Async::HTTP::Protocol::HTTP1) +end + +supervisor diff --git a/vendor/cache/async-container-0.16.12.gem b/vendor/cache/async-container-0.16.12.gem new file mode 100644 index 00000000..b3a8dc99 Binary files /dev/null and b/vendor/cache/async-container-0.16.12.gem differ diff --git a/vendor/cache/async-http-cache-0.4.3.gem b/vendor/cache/async-http-cache-0.4.3.gem new file mode 100644 index 00000000..82656037 Binary files /dev/null and b/vendor/cache/async-http-cache-0.4.3.gem differ diff --git a/vendor/cache/build-environment-1.13.0.gem b/vendor/cache/build-environment-1.13.0.gem new file mode 100644 index 00000000..c125f22f Binary files /dev/null and b/vendor/cache/build-environment-1.13.0.gem differ diff --git a/vendor/cache/falcon-0.42.3.gem b/vendor/cache/falcon-0.42.3.gem new file mode 100644 index 00000000..c7d20de0 Binary files /dev/null and b/vendor/cache/falcon-0.42.3.gem differ diff --git a/vendor/cache/localhost-1.1.10.gem b/vendor/cache/localhost-1.1.10.gem new file mode 100644 index 00000000..6038fa17 Binary files /dev/null and b/vendor/cache/localhost-1.1.10.gem differ diff --git a/vendor/cache/mapping-1.1.1.gem b/vendor/cache/mapping-1.1.1.gem new file mode 100644 index 00000000..91055cc8 Binary files /dev/null and b/vendor/cache/mapping-1.1.1.gem differ diff --git a/vendor/cache/openssl-3.2.0.gem b/vendor/cache/openssl-3.2.0.gem new file mode 100644 index 00000000..d57e13d7 Binary files /dev/null and b/vendor/cache/openssl-3.2.0.gem differ diff --git a/vendor/cache/process-metrics-0.2.1.gem b/vendor/cache/process-metrics-0.2.1.gem new file mode 100644 index 00000000..63e421c2 Binary files /dev/null and b/vendor/cache/process-metrics-0.2.1.gem differ diff --git a/vendor/cache/protocol-rack-0.4.1.gem b/vendor/cache/protocol-rack-0.4.1.gem new file mode 100644 index 00000000..5a8c78cd Binary files /dev/null and b/vendor/cache/protocol-rack-0.4.1.gem differ diff --git a/vendor/cache/puma-6.4.0.gem b/vendor/cache/puma-6.4.0.gem deleted file mode 100644 index eac23ada..00000000 Binary files a/vendor/cache/puma-6.4.0.gem and /dev/null differ diff --git a/vendor/cache/puma-6.4.2.gem b/vendor/cache/puma-6.4.2.gem new file mode 100644 index 00000000..3e6c5b60 Binary files /dev/null and b/vendor/cache/puma-6.4.2.gem differ diff --git a/vendor/cache/samovar-2.2.0.gem b/vendor/cache/samovar-2.2.0.gem new file mode 100644 index 00000000..5b278098 Binary files /dev/null and b/vendor/cache/samovar-2.2.0.gem differ