diff --git a/.woodpecker.yml b/.woodpecker.yml index fff00ce4..763a38f7 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -61,7 +61,6 @@ pipeline: branch: - "rails" - "panel.sutty.nl" - - "17.3.alpine.panel.sutty.nl" path: include: - "app/assets/**/*" diff --git a/Gemfile.lock b/Gemfile.lock index 5401fe06..d4156b2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,7 +25,7 @@ GIT groupdate (>= 5.2) GEM - remote: https://17.3.alpine.gems.sutty.nl/ + remote: https://gems.sutty.nl/ specs: actioncable (6.1.7.3) actionpack (= 6.1.7.3) @@ -89,9 +89,7 @@ GEM addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) - autoprefixer-rails (10.4.13.0) - execjs (~> 2) - bcrypt (3.1.18-x86_64-linux-musl) + bcrypt (3.1.19-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.12.0) bindex (0.8.1-x86_64-linux-musl) @@ -100,15 +98,10 @@ GEM chartkick (>= 3.2) railties (>= 5) safely_block (>= 0.1.1) - bootstrap (4.6.2) - autoprefixer-rails (>= 9.1.0) - popper_js (>= 1.16.1, < 2) - sassc-rails (>= 2.0.0) brakeman (5.4.1) builder (3.2.4) - capybara (3.39.1) + capybara (2.18.0) addressable - matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) @@ -119,7 +112,7 @@ GEM climate_control (1.2.0) coderay (1.1.3) colorator (1.1.0) - commonmarker (0.23.9-x86_64-linux-musl) + commonmarker (0.23.10-x86_64-linux-musl) concurrent-ruby (1.2.2) concurrent-ruby-ext (1.2.2-x86_64-linux-musl) concurrent-ruby (= 1.2.2) @@ -155,7 +148,7 @@ GEM devise_invitable (2.0.8) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.2.3) + distributed-press-api-client (0.2.4) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema @@ -198,20 +191,18 @@ GEM em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) + errbase (0.2.2) erubi (1.12.0) eventmachine (1.2.7-x86_64-linux-musl) exception_notification (4.5.0) actionmailer (>= 5.2, < 8) activesupport (>= 5.2, < 8) - execjs (2.8.1) factory_bot (6.2.1) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - fast_blank (1.0.1) fast_blank (1.0.1-x86_64-linux-musl) - fast_jsonparser (0.5.0) fast_jsonparser (0.5.0-x86_64-linux-musl) ffi (1.15.5-x86_64-linux-musl) flamegraph (0.9.5) @@ -228,7 +219,7 @@ GEM activerecord (>= 6.0, < 8) ruby2ruby (~> 2.4) ruby_parser (~> 3.10) - haml (6.1.1-x86_64-linux-musl) + haml (6.1.2-x86_64-linux-musl) temple (>= 0.8.2) thor tilt @@ -251,14 +242,12 @@ GEM railties (>= 4.0.1) heapy (0.2.0) thor - hiredis (0.6.3) hiredis (0.6.3-x86_64-linux-musl) - http_parser.rb (0.8.0) http_parser.rb (0.8.0-x86_64-linux-musl) httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) icalendar (2.8.0) ice_cube (~> 0.16) @@ -343,13 +332,13 @@ GEM net-pop net-smtp marcel (1.0.2) - matrix (0.4.2) memory_profiler (1.0.1) mercenary (0.4.0) method_source (1.0.0) mini_histogram (0.3.1) mini_magick (4.12.0) mini_mime (1.1.2) + mini_portile2 (2.8.2) minitest (5.18.0) mobility (1.2.9) i18n (>= 0.6.10, < 2) @@ -370,7 +359,8 @@ GEM njalla-api-client (0.2.0) dry-schema httparty (~> 0.18) - nokogiri (1.15.1-x86_64-linux) + nokogiri (1.15.4-x86_64-linux-musl) + mini_portile2 (~> 2.8.2) racc (~> 1.4) orm_adapter (0.5.0) pairing_heap (3.0.1) @@ -383,19 +373,18 @@ GEM pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) - popper_js (1.16.1) prometheus_exporter (2.0.8) webrick pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.1) - puma (6.2.2-x86_64-linux-musl) + public_suffix (5.0.3) + puma (6.3.1-x86_64-linux-musl) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) que (2.2.1) - racc (1.6.2-x86_64-linux-musl) + racc (1.7.1-x86_64-linux-musl) rack (2.2.7) rack-cors (2.0.1) rack (>= 2.0.0) @@ -503,15 +492,10 @@ GEM rubyzip (2.3.2) rugged (1.6.3-x86_64-linux-musl) safe_yaml (1.0.6) - safely_block (0.4.0) + safely_block (0.3.0) + errbase (>= 0.1.1) sassc (2.4.0-x86_64-linux-musl) ffi (~> 1.9) - sassc-rails (2.1.2) - railties (>= 4.0.0) - sassc (>= 2.0) - sprockets (> 3.0) - sprockets-rails - tilt selenium-webdriver (4.9.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -532,10 +516,11 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.3-x86_64-linux) + sqlite3 (1.6.3-x86_64-linux-musl) + mini_portile2 (~> 2.8.0) stackprof (0.2.25-x86_64-linux-musl) stream (0.5.5) - sutty-liquid (0.11.10) + sutty-liquid (0.11.11) fast_blank (~> 1.0) jekyll (~> 4) symbol-fstring (1.0.2-x86_64-linux-musl) @@ -552,8 +537,6 @@ GEM turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - uglifier (4.2.0) - execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.8.2-x86_64-linux-musl) @@ -575,7 +558,7 @@ GEM semantic_range (>= 2.3.0) webrick (1.8.1) websocket (1.2.9) - websocket-driver (0.7.5-x86_64-linux-musl) + websocket-driver (0.7.6-x86_64-linux-musl) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) @@ -589,7 +572,6 @@ DEPENDENCIES bcrypt (~> 3.1.7) bcrypt_pbkdf blazer - bootstrap (~> 4) brakeman capybara chartkick @@ -658,8 +640,7 @@ DEPENDENCIES rugged safe_yaml safely_block (~> 0.3.0) - sassc-rails - selenium-webdriver (~> 4.8.0) + selenium-webdriver sourcemap spring spring-watcher-listen @@ -670,14 +651,13 @@ DEPENDENCIES terminal-table timecop turbolinks (~> 5) - uglifier (>= 1.3.0) validates_hostname web-console webpacker yaml_db! RUBY VERSION - ruby 3.1.3p185 + ruby 3.1.4p223 BUNDLED WITH - 2.4.13 + 2.4.17 diff --git a/app/models/deploy.rb b/app/models/deploy.rb index 3d8caef0..67a5c7ab 100644 --- a/app/models/deploy.rb +++ b/app/models/deploy.rb @@ -93,6 +93,11 @@ class Deploy < ApplicationRecord @local_env ||= {} end + # Devuelve opciones para jekyll build + # + # @return [String,nil] + def flags_for_build(**args); end + # Trae todas las dependencias # # @return [Array] @@ -102,6 +107,21 @@ class Deploy < ApplicationRecord private + # Escribe el contenido en un archivo temporal y ejecuta el bloque + # provisto con el archivo como parámetro + # + # @param :content [String] + def with_tempfile(content, &block) + Tempfile.create(SecureRandom.hex) do |file| + file.write content.to_s + file.rewind + file.close + + # @yieldparam :file [File] + yield file + end + end + # @param [String] # @return [String] def readable_cmd(cmd) diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index 9d87a6cb..3c3652a5 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -147,7 +147,11 @@ class DeployLocal < Deploy end def jekyll_build(output: false) - run %(bundle exec jekyll build --trace --profile --destination "#{escaped_destination}"), output: output + with_tempfile(site.private_key_pem) do |file| + flags = extra_flags(private_key: file) + + run %(bundle exec jekyll build --trace --profile #{flags} --destination "#{escaped_destination}"), output: output + end end # no debería haber espacios ni caracteres especiales, pero por si @@ -174,4 +178,14 @@ class DeployLocal < Deploy end end end + + # Genera opciones extra desde los otros deploys + # + # @param :args [Hash] + # @return [String] + def extra_flags(**args) + non_local_deploys.map do |deploy| + deploy.flags_for_build(**args) + end.compact.join(' ') + end end diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb new file mode 100644 index 00000000..db555ab7 --- /dev/null +++ b/app/models/deploy_social_distributed_press.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'distributed_press/v1/social/client' + +# Publicar novedades al Fediverso +class DeploySocialDistributedPress < Deploy + # Solo luego de publicar remotamente + DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync] + + # Envía las notificaciones + def deploy(output: false) + with_tempfile(site.private_key_pem) do |file| + key = Shellwords.escape file.path + dest = Shellwords.escape destination + + run %(bundle exec jekyll notify --trace --key #{key} --destination "#{dest}"), output: output + end + end + + # Igual que DeployLocal + # + # @return [String] + def destination + File.join(Rails.root, '_deploy', site.hostname) + end + + # Solo uno + # + # @return [Integer] + def limit + 1 + end + + # Espacio ocupado, pero no podemos calcularlo + # + # @return [Integer] + def size + 0 + end + + # El perfil de actor + # + # @return [String,nil] + def url + site.data.dig('activity_pub', 'actor') + end + + # Genera la opción de llave privada para jekyll build + # + # @params :args [Hash] + # @return [String] + def flags_for_build(**args) + "--key #{Shellwords.escape args[:private_key].path}" + end +end diff --git a/app/models/site.rb b/app/models/site.rb index 9d4f610c..469e202e 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -10,6 +10,7 @@ class Site < ApplicationRecord include Site::DeployDependencies include Site::BuildStats include Site::LayoutOrdering + include Site::SocialDistributedPress include Tienda # Cifrar la llave privada que cifra y decifra campos ocultos. Sutty @@ -18,10 +19,6 @@ class Site < ApplicationRecord # protege de acceso al panel de Sutty! encrypts :private_key - # TODO: Hacer que los diferentes tipos de deploy se auto registren - # @see app/services/site_service.rb - DEPLOYS = %i[local private www zip hidden_service distributed_press].freeze - validates :name, uniqueness: true, hostname: { allow_root_label: true } diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb new file mode 100644 index 00000000..5d469f03 --- /dev/null +++ b/app/models/site/social_distributed_press.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class Site + # Agrega soporte para Social Distributed Press en los sitios + module SocialDistributedPress + extend ActiveSupport::Concern + + included do + encrypts :private_key_pem + + before_save :generate_private_key_pem!, unless: :private_key_pem? + + private + + # Genera la llave privada y la almacena + # + # @return [nil] + def generate_private_key_pem! + self.private_key_pem ||= DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export + end + end + end +end diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 5128cf2f..b9a121e6 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -55,9 +55,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # Genera los Deploy necesarios para el sitio a menos que ya los tenga. def build_deploys - Site::DEPLOYS.map { |deploy| "Deploy#{deploy.to_s.camelcase}" } - .each do |deploy| - next if site.deploys.find_by type: deploy + Deploy.subclasses.each do |deploy| + next if site.deploys.find_by type: deploy.name site.deploys.build type: deploy end diff --git a/app/views/deploy_mailer/_deploy_social_distributed_press.haml b/app/views/deploy_mailer/_deploy_social_distributed_press.haml new file mode 100644 index 00000000..5c73b262 --- /dev/null +++ b/app/views/deploy_mailer/_deploy_social_distributed_press.haml @@ -0,0 +1,21 @@ +-# Publicar a la web distribuida + +.row + .col + = deploy.hidden_field :id + = deploy.hidden_field :type + .custom-control.custom-switch + -# + El checkbox invierte la lógica de destrucción porque queremos + crear el deploy si está activado y destruirlo si está + desactivado. + = deploy.check_box :_destroy, + { checked: deploy.object.persisted?, class: 'custom-control-input' }, + '0', '1' + = deploy.label :_destroy, class: 'custom-control-label' do + %h3= t('.title') + = sanitize_markdown t('.help'), + tags: %w[p strong em a] + + +%hr/ diff --git a/config/locales/en.yml b/config/locales/en.yml index 3d942718..c7d11ee4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -124,6 +124,10 @@ en: title: Distributed Web success: Success! error: Error + deploy_social_distributed_press: + title: Fediverse + success: Success! + error: Error deploy_reindex: title: Reindex success: Success! @@ -308,6 +312,14 @@ en: indefinitely. [Learn more](https://sutty.nl/en/learn-more-about-publish-to-dweb-functionality/) + deploy_social_distributed_press: + title: 'Publish on the Fediverse' + help: | + By using the ActivityPub protocol, people on the Fediverse + ([Mastodon](https://joinmastodon.org/servers), + [Pixelfed](https://pixelfed.social/site/about), and + [others](https://fediverse.party/)) can follow your site, + receive news and interact with them. stats: index: title: Statistics diff --git a/config/locales/es.yml b/config/locales/es.yml index cfefe0a8..46e4fc0e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -124,6 +124,10 @@ es: title: Web distribuida success: ¡Éxito! error: Hubo un error + deploy_social_distributed_press: + title: Fediverso + success: ¡Éxito! + error: Hubo un error deploy_localized_domain: title: Dominio según idioma success: ¡Éxito! @@ -313,6 +317,14 @@ es: copias de tu contenido indefinidamente. [Saber más](https://sutty.nl/saber-mas-sobre-publicar-a-la-web-distribuida/) + deploy_social_distributed_press: + title: 'Publicar al Fediverso' + help: | + Utilizando el protocolo ActivityPub, otras personas en el + Fediverso ([Mastodon](https://joinmastodon.org/servers), + [Pixelfed](https://pixelfed.social/site/about) y + [otros](https://fediverse.party/)) pueden seguir a tu sitio, + recibir novedades e interactuar con ellas. stats: index: title: Estadísticas diff --git a/db/migrate/20230829204127_add_private_key_pem_ciphertext_to_sites.rb b/db/migrate/20230829204127_add_private_key_pem_ciphertext_to_sites.rb new file mode 100644 index 00000000..9f26f21a --- /dev/null +++ b/db/migrate/20230829204127_add_private_key_pem_ciphertext_to_sites.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Almacena las llaves privadas de cada sitio +class AddPrivateKeyPemCiphertextToSites < ActiveRecord::Migration[6.1] + # Agrega la columna cifrada + def change + add_column :sites, :private_key_pem_ciphertext, :text + end +end