Compare commits

..

5 commits

Author SHA1 Message Date
610177d3f5 Remove supervisor from the Falcon process?
Idk what it's doing, but what I do know is the Falcon process is
weirdly slow to restart on deploy. My hope was this would fix it cuz
the supervisor was maybe blocking process exits? Idk, something to look
into, I don't think this fixed it but I also don't think it broke
anything, and I think systemd is doing a fine job monitoring already?
idk
2024-01-24 00:20:23 -08:00
b1eca2f3a5 Disable modeling while we fix Alt Styles rollout bugs 2024-01-24 00:19:28 -08:00
09ad6d5fbb Convert /cpn/ loading errors to warnings
Idk why this broke again, it works locally for me, did our IP get
banned or something? We'll look into it another time!
2024-01-23 22:11:10 -08:00
44d9f98313 Increase pet loading timeout 2024-01-23 22:10:42 -08:00
76af587e7c Replace falcon server with puma
Been wanting this for a while in theory, gonna actually do it now!

The motivation is that I want to turn up the timeout for loading pets,
because the Neopets endpoints are slower today with the NC UC release -
but I can already predict that under our current architecture that will
be a problem, because it'll block up our request queue!

Falcon uses Ruby's relatively-new async system to *not* have requests
block on upstream requests, and my understanding is that this behavior
is plug-and-play. Let's see how it goes!
2024-01-23 21:55:26 -08:00
23 changed files with 283 additions and 219 deletions

View file

@ -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'

View file

@ -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)

View file

@ -2,13 +2,13 @@ class PetsController < ApplicationController
rescue_from Pet::PetNotFound, :with => :pet_not_found
rescue_from PetType::DownloadError, SwfAsset::DownloadError, :with => :asset_download_error
rescue_from Pet::DownloadError, :with => :pet_download_error
rescue_from Pet::ModelingDisabled, with: :modeling_disabled
def load
raise Pet::PetNotFound unless params[:name]
@pet = Pet.load(
params[:name],
:item_scope => Item.includes(:translations),
:timeout => 1
)
points = contribute(current_user, @pet)
@ -75,4 +75,9 @@ class PetsController < ApplicationController
end
end
end
def modeling_disabled
pet_load_error long_message: t('pets.load.modeling_disabled'),
status: :forbidden
end
end

View file

@ -17,7 +17,9 @@ class Pet < ApplicationRecord
joins(:pet_type).where(PetType.arel_table[:id].in(color_ids))
}
class ModelingDisabled < RuntimeError;end
def load!(options={})
raise ModelingDisabled
options[:locale] ||= I18n.default_locale
I18n.with_locale(options.delete(:locale)) do
use_viewer_data(fetch_viewer_data(options.delete(:timeout)), options)
@ -44,7 +46,10 @@ class Pet < ApplicationRecord
options[:item_scope])
end
def fetch_viewer_data(timeout=4, locale=nil)
# NOTE: Ideally pet requests shouldn't take this long, but Neopets can be
# slow sometimes! Since we're on the Falcon server, long timeouts shouldn't
# slow down the rest of the request queue, like it used to be in the past.
def fetch_viewer_data(timeout=10, locale=nil)
locale ||= I18n.default_locale
begin
neopets_language_code = I18n.compatible_neopets_language_code_for(locale)

View file

@ -135,9 +135,9 @@ class PetType < ApplicationRecord
begin
res.error!
rescue Exception => e
raise DownloadError, "Error loading CPN image at #{cpn_uri}: #{e.message}"
Rails.logger.warn "Error loading CPN image at #{cpn_uri}: #{e.message}"
else
raise DownloadError, "Error loading CPN image at #{cpn_uri}. Response: #{res.inspect}"
Rails.logger.warn "Error loading CPN image at #{cpn_uri}. Response: #{res.inspect}"
end
end
new_url = res['location']
@ -146,7 +146,7 @@ class PetType < ApplicationRecord
self.image_hash = new_image_hash
Rails.logger.info "Successfully loaded #{cpn_uri}, saved image hash #{new_image_hash}"
else
raise DownloadError, "CPN image pointed to #{new_url}, which does not match CP image format"
Rails.logger.warn "CPN image pointed to #{new_url}, which does not match CP image format"
end
end
end

View file

@ -2,9 +2,10 @@
= advertise_campaign_progress @campaign
.notice
.warning
%strong Happy NC UC day!
We're working on some changes to get everything set up, see you soon! 💖
We've temporarily disabled pet loading while we get everything set up and
investigate some new compatibility issues. We'll have it back soon!
%p#pet-not-found.alert= t 'pets.load.not_found'

View file

@ -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

View file

@ -781,6 +781,9 @@ en:
pet_download_error:
We couldn't connect to Neopets to look up the pet. Maybe they're down.
Please try again later!
modeling_disabled: We've turned off pet loading for a bit, while we
investigate some changes in how it works. We'll be back as soon as we
can!
swf_assets:
links:

View file

@ -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"

View file

@ -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:

10
falcon.rb Normal file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env -S falcon host
# frozen_string_literal: true
load :rack
hostname = File.basename(__dir__)
rack hostname do
endpoint Async::HTTP::Endpoint.parse('http://localhost:3000').
with(protocol: Async::HTTP::Protocol::HTTP1)
end

BIN
vendor/cache/async-container-0.16.12.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/async-http-cache-0.4.3.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/falcon-0.42.3.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/localhost-1.1.10.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/mapping-1.1.1.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/openssl-3.2.0.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/process-metrics-0.2.1.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/protocol-rack-0.4.1.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/puma-6.4.2.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/samovar-2.2.0.gem vendored Normal file

Binary file not shown.