diff --git a/Gemfile b/Gemfile index 224170b5..708b4cd6 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,7 @@ gem 'pg', '~> 1.1' # Use the Puma web server [https://github.com/puma/puma] gem 'puma', '~> 6.0' +gem 'sd_notify', '~> 0.1.1' # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] gem 'importmap-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 5de7a134..fa10a6df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -295,6 +295,7 @@ GEM ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) + sd_notify (0.1.1) selenium-webdriver (4.5.0) childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) @@ -391,6 +392,7 @@ DEPENDENCIES rubocop-minitest (~> 0.22.2) rubocop-performance (~> 1.15) rubocop-rails (~> 2.17) + sd_notify (~> 0.1.1) selenium-webdriver simplecov (~> 0.21.2) simplecov-cobertura (~> 2.1) diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 00000000..8bdc8b1a --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,81 @@ +# Install in production + +1. Install git, [rbenv build deps][rbenv-build-deps], PostgreSQL, `libpq-dev`, `postfix` (follow https://poly.rezoleo.fr/doku.php?id=private:services:mail) +2. Create `lea5` user +3. Create `/opt/lea5` and chown to `lea5` +4. `su - lea5` +5. Install rbenv and rbenv-build +6. `git clone https://github.com/rezoleo/lea5.git` +7. `rbenv install`, check with `ruby -v` +8. `gem install bundler` +9. `bundle config set --local deployment 'true'` +10. `bundle install` +11. `bundle binstubs puma` +12. `su - postgres -c 'createuser lea5 --createdb --pwprompt'` +13. `su - postgres -c 'createdb lea5_production --owner=lea5'` +14. `read -rs RAILS_MASTER_KEY`, enter the key from Vault (secret `lea5:production.key`) +15. `RAILS_ENV=production rails db:migrate` +16. `RAILS_ENV=production rails assets:precompile` +17. `RAILS_ENV=production bin/puma -b unix:///opt/lea5/puma-lea5.sock` +18. `RAILS_ENV=production bin/rails server --log-to-stdout --binding 127.0.0.1` +19. Configure nginx: + ```nginx + upstream lea5 { + server unix:///opt/lea5/tmp/sockets/puma-lea5.sock; + } + + server { + listen 80; + server_name _; + + root /opt/lea5/public; + access_log /opt/lea5/log/nginx.access.log; + error_log /opt/lea5/log/nginx.error.log info; + + try_files $uri/index.html $uri @proxy; + error_page 500 502 503 504 /500.html; + + location @proxy { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Ssl on; # Optional + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Host $host; + proxy_pass http://lea5; + } + + location /assets { + gzip_static on; + root /opt/lea5/public; + } + } + ``` +20. Configure systemd `/etc/systemd/system/lea5.service` + ```systemd + [Unit] + Description=Lea5 - Main server + Documentation=https://github.com/rezoleo/lea5 + After=network.target + After=nginx.service + Requires=nginx.service + + [Service] + Type=simple + User=lea5 + Group=lea5 + WorkingDirectory=/opt/lea5 + ExecStart=/opt/lea5/bin/rails server --log-to-stdout --binding 127.0.0.1 + Restart=on-failure + Environment=PATH=/home/lea5/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin + Environment=RAILS_ENV=production + Environment=RAILS_MASTER_KEY= + + [Install] + WantedBy=multi-user.target + ``` + +**⚠️TODO:** Use master key file instead of env variable? + +--- +[rbenv-build-deps]: https://github.com/rbenv/ruby-build/wiki#suggested-build-environment diff --git a/lib/support/systemd/lea5-sync-accounts.service b/lib/support/systemd/lea5-sync-accounts.service index fa0d85ce..8dcb6fa5 100644 --- a/lib/support/systemd/lea5-sync-accounts.service +++ b/lib/support/systemd/lea5-sync-accounts.service @@ -1,8 +1,38 @@ [Unit] Description=Lea5 - Sync users between Keycloak and Lea5 +Documentation=https://github.com/rezoleo/lea5 [Service] -# Command runs once then exists, it is not a background service +# Command runs once then exits, it is not a background service Type=oneshot - +User=lea5 +Group=lea5 +WorkingDirectory=/opt/lea5 +# Change start command for a new service ExecStart=/opt/lea5/bin/rails lea5:sync_accounts + +Environment=PATH=/home/lea5/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Environment=RAILS_ENV=production +Environment=RAILS_LOG_TO_STDOUT=1 +Environment=RAILS_MASTER_KEY= + +NoNewPrivileges=true +#PrivateNetwork=true +PrivateDevices=true +PrivateMounts=true +PrivateTmp=true +PrivateUsers=true +ProtectHome=tmpfs +BindReadOnlyPaths=/home/lea5/.rbenv +ProtectSystem=strict +ReadWritePaths=/opt/lea5/log +ReadWritePaths=/opt/lea5/tmp +ReadWritePaths=/opt/lea5/storage +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +CapabilityBoundingSet= +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible diff --git a/lib/support/systemd/lea5.service b/lib/support/systemd/lea5.service new file mode 100644 index 00000000..4f5f8732 --- /dev/null +++ b/lib/support/systemd/lea5.service @@ -0,0 +1,43 @@ +[Unit] +Description=Lea5 - Main server +Documentation=https://github.com/rezoleo/lea5 +After=network.target +After=nginx.service +Requires=nginx.service + +[Service] +Type=notify +WatchdogSec=10 +User=lea5 +Group=lea5 +WorkingDirectory=/opt/lea5 +ExecStart=/opt/lea5/bin/puma --config config/puma.rb --bind unix:///opt/lea5/tmp/sockets/puma-lea5.sock +Restart=always +Environment=PATH=/home/lea5/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Environment=RAILS_ENV=production +Environment=RAILS_LOG_TO_STDOUT=1 +Environment=RAILS_MASTER_KEY= + +NoNewPrivileges=true +#PrivateNetwork=true +PrivateDevices=true +PrivateMounts=true +PrivateTmp=true +PrivateUsers=true +ProtectHome=tmpfs +BindReadOnlyPaths=/home/lea5/.rbenv +ProtectSystem=strict +ReadWritePaths=/opt/lea5/log +ReadWritePaths=/opt/lea5/tmp +ReadWritePaths=/opt/lea5/storage +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +CapabilityBoundingSet= +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible + +[Install] +WantedBy=multi-user.target