diff --git a/.dockerignore b/.dockerignore index 66e8760..07c7df7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. # Ignore git directory. -/.git/ shot.png # Ignore bundler config. diff --git a/.gitignore b/.gitignore index 39e3719..c0b2199 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ config/deploy.yml config/deploy.*.yml +# Kamal 2 secrets +.kamal/secrets* +!.kamal/secrets.sample + # Ignore all logfiles and tempfiles. /log/* /tmp/* diff --git a/.env.sample b/.kamal/secrets.sample similarity index 60% rename from .env.sample rename to .kamal/secrets.sample index 95f0ef7..fdb7b3e 100644 --- a/.env.sample +++ b/.kamal/secrets.sample @@ -1,5 +1,2 @@ REGISTRY_PASSWORD=YOU_REGISTRY_TOKEN SECRET_KEY_BASE=MASTER_KEY -MAX_FILESIZE=1G -RETRIES=5 -PATHS=public/dl diff --git a/Dockerfile b/Dockerfile index 4299bc9..ba1e918 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ FROM base # Install packages needed for deployment RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl ffmpeg cron && \ + apt-get install --no-install-recommends -y curl ffmpeg cron git && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Copy built artifacts: gems, application @@ -54,8 +54,9 @@ ADD https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux /usr/ RUN chmod 755 /usr/local/bin/yt-dlp # Run and own only the runtime files as a non-root user for security -RUN useradd rails --create-home --shell /bin/bash && \ - chown -R rails:rails log tmp public/dl +RUN useradd rails --home /home/rails --shell /bin/bash && \ + mkdir -p /home/rails && \ + chown -R rails:rails log tmp public/dl /home/rails RUN chmod u+s /usr/sbin/cron @@ -64,6 +65,8 @@ RUN chmod -R 700 /rails/public/dl USER rails:rails +RUN git config --global --add safe.directory /rails + # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] diff --git a/README.md b/README.md index da4bcb1..18d3a91 100644 --- a/README.md +++ b/README.md @@ -11,34 +11,36 @@
- 馃拵 Ruby 3.3 路 馃洡 Rails 7.2 路 鈿★笍 Stimulus 路 馃吅 Kamal + 馃拵 Ruby 3.3 路 馃洡 Rails 7.2 路 鈿★笍 Stimulus 路 馃吅 Kamal 2

# Deploy -0. Clone the repository -1. Install `kamal` (see [Kamal docs](https://kamal-deploy.org/docs/installation/)): +> [!NOTE] +> For Kamal v1 with Traefik, see [legacy v0.1.7.1](https://github.com/vladyio/quicktube/tree/v0.1.7.1). + +1. Clone the repository +2. Install `kamal` (see [Kamal docs](https://kamal-deploy.org/docs/installation/)): `gem install kamal` -2. Create a `deploy/config.yml` file from sample: +3. Create a `config/deploy.yml` file from sample: - `cp deploy/config.yml.sample deploy/config.yml` -3. Change `deploy/config.yml` to suit your needs -4. Create a `.env` file from sample: + `cp config/deploy.yml.sample config/deploy.yml` +4. Set values in `config/deploy.yml` to match your setup +5. Create a `.kamal/secrets` file from sample: - `cp .env.sample .env` -5. Change `.env` to suit your needs -6. Prepare server(s) - everything from copying an SSH key to setting up UFW, users and permissions: + `cp .kamal/secrets.sample .kamal/secrets` +6. Set values in `.kamal/secrets` to match your setup +7. Prepare server(s) - everything from copying an SSH key to setting up UFW, users and permissions: ``` ./bin/prepare_server ``` -7. Finally, deploy: +8. Finally, deploy: ``` - kamal env push kamal accessory boot redis kamal deploy ``` @@ -47,13 +49,12 @@ It's possible to prepare & deploy a custom environment too. -Make sure you have a `config/deploy.[environment].yml` and `.env.[environment]` files. +Make sure you have a `config/deploy.[environment].yml` and `.kamal/secrets.[environment]` files. For example, for a `staging` environment: ./bin/prepare_server staging - kamal env push -d staging kamal accessory boot redis -d staging kamal deploy -d staging. diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 443239e..3cf6436 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -25,11 +25,20 @@ diff --git a/bin/prepare_server b/bin/prepare_server index baa2291..989978b 100755 --- a/bin/prepare_server +++ b/bin/prepare_server @@ -36,13 +36,6 @@ prepare_storage = <<~EOF chown 1000:1000 /rails/public/dl EOF -# Prepare Let's Encrypt -prepare_letsencrypt = <<~EOF - mkdir -p /etc/letsencrypt; - touch /etc/letsencrypt/acme.json; - chmod 600 /etc/letsencrypt/acme.json -EOF - # Add non-root user add_user = <<~EOF useradd --create-home #{user_name}; @@ -56,12 +49,11 @@ add_user = <<~EOF visudo -c -f /etc/sudoers.d/#{user_name} EOF -# Install Docker and add the private network +# Install Docker install_docker = <<~EOF docker -v || curl -fsSL https://get.docker.com | sh; systemctl enable docker; systemctl start docker; - docker network create quicktube; groupadd docker; usermod -aG docker #{user_name} EOF @@ -127,8 +119,6 @@ end ssh.exec!(install_essentials) puts_info "Preparing storage for disk service..." ssh.exec!(prepare_storage) - puts_info "Preparing Let's Encrypt..." - ssh.exec!(prepare_letsencrypt) puts_info "Adding user with sudo privileges..." ssh.exec!(add_user) puts_info "Installing and configuring Docker..." @@ -148,7 +138,6 @@ puts_info " ssh #{user_name}@#{hosts.first} #{'-p ' + ssh_port.to_s if ssh_port puts "To deploy, run:" puts <<-EOF - kamal env push #{'-d ' + environment if environment} kamal accessory boot redis #{'-d ' + environment if environment} kamal deploy #{'-d ' + environment if environment} EOF diff --git a/config/deploy.yml.sample b/config/deploy.yml.sample index ff4f6c0..9012faa 100644 --- a/config/deploy.yml.sample +++ b/config/deploy.yml.sample @@ -1,37 +1,33 @@ service: quicktube + image: username/quicktube + servers: web: - options: - network: quicktube hosts: - HOST_IP - labels: - traefik.http.routers.quicktube-web.rule: "Host(`domain.com`) || Host(`www.domain.com`)" - traefik.http.routers.quicktube-web-secure.entrypoints: websecure - traefik.http.routers.quicktube-web-secure.rule: "Host(`domain.com`) || Host(`www.domain.com`)" - traefik.http.routers.quicktube-web-secure.tls: true - traefik.http.routers.quicktube-web-secure.tls.certresolver: letsencrypt - - traefik.http.routers.quicktube-dl.rule: "Host(`domain.com`) && PathPrefix(`/dl/`)" - traefik.http.routers.quicktube-dl.entrypoints: websecure - traefik.http.routers.quicktube-dl.middlewares: quicktube-dl-middleware - traefik.http.middlewares.quicktube-dl-middleware.headers.accesscontrolalloworiginlist: "*" - traefik.http.middlewares.quicktube-dl-middleware.headers.accesscontrolallowmethods: "GET,OPTIONS,HEAD" job: hosts: - HOST_IP cmd: bundle exec sidekiq - options: - network: quicktube cron: hosts: - HOST_IP - cmd: bash -c "cat config/crontab | crontab - && cron -f" + cmd: bash -c "cat config/crontab | crontab - && declare -p | grep -Ev '\b(BASHOPTS|BASH_VERSINFO|EUID|PPID|SHELLOPTS|UID)\b' >> /rails/tmp/.cron.env && cron -f" + +builder: + arch: amd64 + +proxy: + ssl: true + host: example.com,www.example.com + app_port: 3000 volumes: - "/rails/public/dl:/rails/public/dl" +asset_path: /rails/public/assets + registry: server: ghcr.io username: username @@ -41,44 +37,15 @@ registry: env: clear: REDIS_URL: redis://quicktube-redis:6379/0 + MAX_FILESIZE: 1G + RETRIES: 5 + PATHS: public/dl secret: - SECRET_KEY_BASE ssh: user: admin -traefik: - image: traefik:3.0.3 - options: - publish: - - "443:443" - volume: - - "/etc/letsencrypt:/letsencrypt" - network: quicktube - args: - log: true - log.level: ERROR - api.dashboard: true - api.insecure: false - entryPoints.web.address: ":80" - entryPoints.websecure.address: ":443" - certificatesResolvers.letsencrypt.acme.email: "admin@domain.com" - certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" - certificatesResolvers.letsencrypt.acme.httpchallenge: true - certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web - labels: - traefik.http.routers.dashboard.rule: "Host(`traefik.domain.com`) || Host(`www.traefik.domain.com`)" - traefik.http.routers.dashboard.service: api@internal - traefik.http.routers.dashboard.middlewares: redirect-to-https, auth - traefik.http.routers.dashboard.tls: true - traefik.http.middlewares.auth.basicauth.users: [Run `htpasswd -nb username password` and copy here] - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https - traefik.http.routers.catchall.rule: hostregexp(`{host:.+}`) - traefik.http.routers.catchall.service: noop@internal - traefik.http.routers.catchall.entrypoints: websecure - traefik.http.routers.catchall.middlewares: redirect-to-https - traefik.http.routers.catchall.tls: true - accessories: redis: roles: @@ -87,7 +54,3 @@ accessories: image: redis:7.2.5-alpine directories: - data:/data - options: - network: quicktube - -asset_path: /rails/public/assets diff --git a/config/environments/production.rb b/config/environments/production.rb index bd1c6d8..53cf24a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -37,13 +37,13 @@ # Assume all access to the app is happening through a SSL-terminating reverse proxy. # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. - # config.assume_ssl = true + config.assume_ssl = true # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true # Skip http-to-https redirect for the default health check endpoint. - # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } # Log to STDOUT by default config.logger = ActiveSupport::Logger.new(STDOUT)