diff --git a/nixos/doc/manual/release-notes/rl-2105.xml b/nixos/doc/manual/release-notes/rl-2105.xml index ba42601096b..b5290b374f9 100644 --- a/nixos/doc/manual/release-notes/rl-2105.xml +++ b/nixos/doc/manual/release-notes/rl-2105.xml @@ -118,6 +118,16 @@ Web Services Dynamic Discovery host daemon + + + Discourse, a + modern and open source discussion platform. + + + See the Discourse + section of the NixOS manual for more information. + + diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 6f172720558..6f600a608dc 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -897,6 +897,7 @@ ./services/web-apps/calibre-web.nix ./services/web-apps/convos.nix ./services/web-apps/cryptpad.nix + ./services/web-apps/discourse.nix ./services/web-apps/documize.nix ./services/web-apps/dokuwiki.nix ./services/web-apps/engelsystem.nix diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix new file mode 100644 index 00000000000..03ea002c9de --- /dev/null +++ b/nixos/modules/services/web-apps/discourse.nix @@ -0,0 +1,1035 @@ +{ config, options, lib, pkgs, utils, ... }: + +let + json = pkgs.formats.json {}; + + cfg = config.services.discourse; + + postgresqlPackage = if config.services.postgresql.enable then + config.services.postgresql.package + else + pkgs.postgresql; + + # We only want to create a database if we're actually going to connect to it. + databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null; + + tlsEnabled = (cfg.enableACME + || cfg.sslCertificate != null + || cfg.sslCertificateKey != null); +in +{ + options = { + services.discourse = { + enable = lib.mkEnableOption "Discourse, an open source discussion platform"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.discourse; + defaultText = "pkgs.discourse"; + description = '' + The discourse package to use. + ''; + }; + + hostname = lib.mkOption { + type = lib.types.str; + default = if config.networking.domain != null then + config.networking.fqdn + else + config.networking.hostName; + defaultText = "config.networking.fqdn"; + example = "discourse.example.com"; + description = '' + The hostname to serve Discourse on. + ''; + }; + + secretKeyBaseFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/secret_key_base"; + description = '' + The path to a file containing the + secret_key_base secret. + + Discourse uses secret_key_base to encrypt + the cookie store, which contains session data, and to digest + user auth tokens. + + Needs to be a 64 byte long string of hexadecimal + characters. You can generate one by running + + + $ openssl rand -hex 64 >/path/to/secret_key_base_file + + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + sslCertificate = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/ssl.cert"; + description = '' + The path to the server SSL certificate. Set this to enable + SSL. + ''; + }; + + sslCertificateKey = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/ssl.key"; + description = '' + The path to the server SSL certificate key. Set this to + enable SSL. + ''; + }; + + enableACME = lib.mkOption { + type = lib.types.bool; + default = cfg.sslCertificate == null && cfg.sslCertificateKey == null; + defaultText = "true, unless services.discourse.sslCertificate and services.discourse.sslCertificateKey are set."; + description = '' + Whether an ACME certificate should be used to secure + connections to the server. + ''; + }; + + backendSettings = lib.mkOption { + type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ])); + default = {}; + example = lib.literalExample '' + { + max_reqs_per_ip_per_minute = 300; + max_reqs_per_ip_per_10_seconds = 60; + max_asset_reqs_per_ip_per_10_seconds = 250; + max_reqs_per_ip_mode = "warn+block"; + }; + ''; + description = '' + Additional settings to put in the + discourse.conf file. + + Look in the + discourse_defaults.conf + file in the upstream distribution to find available options. + + Setting an option to null means + define variable, but leave right-hand side + empty. + ''; + }; + + siteSettings = lib.mkOption { + type = json.type; + default = {}; + example = lib.literalExample '' + { + required = { + title = "My Cats"; + site_description = "Discuss My Cats (and be nice plz)"; + }; + login = { + enable_github_logins = true; + github_client_id = "a2f6dfe838cb3206ce20"; + github_client_secret._secret = /run/keys/discourse_github_client_secret; + }; + }; + ''; + description = '' + Discourse site settings. These are the settings that can be + changed from the UI. This only defines their default values: + they can still be overridden from the UI. + + Available settings can be found by looking in the + site_settings.yml + file of the upstream distribution. To find a setting's path, + you only need to care about the first two levels; i.e. its + category and name. See the example. + + Settings containing secret data should be set to an + attribute set containing the attribute + _secret - a string pointing to a file + containing the value the option should be set to. See the + example to get a better picture of this: in the resulting + config/nixos_site_settings.json file, + the login.github_client_secret key will + be set to the contents of the + /run/keys/discourse_github_client_secret + file. + ''; + }; + + admin = { + email = lib.mkOption { + type = lib.types.str; + example = "admin@example.com"; + description = '' + The admin user email address. + ''; + }; + + username = lib.mkOption { + type = lib.types.str; + example = "admin"; + description = '' + The admin user username. + ''; + }; + + fullName = lib.mkOption { + type = lib.types.str; + description = '' + The admin user's full name. + ''; + }; + + passwordFile = lib.mkOption { + type = lib.types.path; + description = '' + A path to a file containing the admin user's password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + }; + + nginx.enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether an nginx virtual host should be + set up to serve Discourse. Only disable if you're planning + to use a different web server, which is not recommended. + ''; + }; + + database = { + pool = lib.mkOption { + type = lib.types.int; + default = 8; + description = '' + Database connection pool size. + ''; + }; + + host = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = '' + Discourse database hostname. null means prefer + local unix socket connection. + ''; + }; + + passwordFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + description = '' + File containing the Discourse database user password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + createLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether a database should be automatically created on the + local host. Set this to false if you plan + on provisioning a local database yourself. This has no effect + if is customized. + ''; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "discourse"; + description = '' + Discourse database name. + ''; + }; + + username = lib.mkOption { + type = lib.types.str; + default = "discourse"; + description = '' + Discourse database user. + ''; + }; + }; + + redis = { + host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + Redis server hostname. + ''; + }; + + passwordFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + description = '' + File containing the Redis password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + dbNumber = lib.mkOption { + type = lib.types.int; + default = 0; + description = '' + Redis database number. + ''; + }; + + useSSL = lib.mkOption { + type = lib.types.bool; + default = cfg.redis.host != "localhost"; + description = '' + Connect to Redis with SSL. + ''; + }; + }; + + mail = { + notificationEmailAddress = lib.mkOption { + type = lib.types.str; + default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}"; + defaultText = '' + "notifications@`config.services.discourse.hostname`" if + config.services.discourse.mail.incoming.enable is "true", + otherwise "noreply`config.services.discourse.hostname`" + ''; + description = '' + The from: email address used when + sending all essential system emails. The domain specified + here must have SPF, DKIM and reverse PTR records set + correctly for email to arrive. + ''; + }; + + contactEmailAddress = lib.mkOption { + type = lib.types.str; + default = ""; + description = '' + Email address of key contact responsible for this + site. Used for critical notifications, as well as on the + /about contact form for urgent matters. + ''; + }; + + outgoing = { + serverAddress = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + The address of the SMTP server Discourse should use to + send email. + ''; + }; + + port = lib.mkOption { + type = lib.types.int; + default = 25; + description = '' + The port of the SMTP server Discourse should use to + send email. + ''; + }; + + username = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = '' + The username of the SMTP server. + ''; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + A file containing the password of the SMTP server account. + + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ''; + }; + + domain = lib.mkOption { + type = lib.types.str; + default = cfg.hostname; + description = '' + HELO domain to use for outgoing mail. + ''; + }; + + authentication = lib.mkOption { + type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]); + default = null; + description = '' + Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html + ''; + }; + + enableStartTLSAuto = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to try to use StartTLS. + ''; + }; + + opensslVerifyMode = lib.mkOption { + type = lib.types.str; + default = "peer"; + description = '' + How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html + ''; + }; + }; + + incoming = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to set up Postfix to receive incoming mail. + ''; + }; + + replyEmailAddress = lib.mkOption { + type = lib.types.str; + default = "%{reply_key}@${cfg.hostname}"; + defaultText = "%{reply_key}@`config.services.discourse.hostname`"; + description = '' + Template for reply by email incoming email address, for + example: %{reply_key}@reply.example.com or + replies+%{reply_key}@example.com + ''; + }; + + mailReceiverPackage = lib.mkOption { + type = lib.types.package; + default = pkgs.discourse-mail-receiver; + defaultText = "pkgs.discourse-mail-receiver"; + description = '' + The discourse-mail-receiver package to use. + ''; + }; + + apiKeyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + A file containing the Discourse API key used to add + posts and messages from mail. If left at its default + value null, one will be automatically + generated. + + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ''; + }; + }; + }; + + plugins = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = []; + example = '' + [ + (pkgs.fetchFromGitHub { + owner = "discourse"; + repo = "discourse-spoiler-alert"; + rev = "e200cfa571d252cab63f3d30d619b370986e4cee"; + sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33"; + }) + ]; + ''; + description = '' + Discourse plugins to install as a + list of derivations. As long as a plugin supports the + standard install method, packaging it should only require + fetching its source with an appropriate fetcher. + ''; + }; + + sidekiqProcesses = lib.mkOption { + type = lib.types.int; + default = 1; + description = '' + How many Sidekiq processes should be spawned. + ''; + }; + + unicornTimeout = lib.mkOption { + type = lib.types.int; + default = 30; + description = '' + Time in seconds before a request to Unicorn times out. + + This can be raised if the system Discourse is running on is + too slow to handle many requests within 30 seconds. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.database.host != null) -> (cfg.database.passwordFile != null); + message = "When services.gitlab.database.host is customized, services.discourse.database.passwordFile must be set!"; + } + { + assertion = cfg.hostname != ""; + message = "Could not automatically determine hostname, set service.discourse.hostname manually."; + } + ]; + + + # Default config values are from `config/discourse_defaults.conf` + # upstream. + services.discourse.backendSettings = lib.mapAttrs (_: lib.mkDefault) { + db_pool = cfg.database.pool; + db_timeout = 5000; + db_connect_timeout = 5; + db_socket = null; + db_host = cfg.database.host; + db_backup_host = null; + db_port = null; + db_backup_port = 5432; + db_name = cfg.database.name; + db_username = if databaseActuallyCreateLocally then "discourse" else cfg.database.username; + db_password = cfg.database.passwordFile; + db_prepared_statements = false; + db_replica_host = null; + db_replica_port = null; + db_advisory_locks = true; + + inherit (cfg) hostname; + backup_hostname = null; + + smtp_address = cfg.mail.outgoing.serverAddress; + smtp_port = cfg.mail.outgoing.port; + smtp_domain = cfg.mail.outgoing.domain; + smtp_user_name = cfg.mail.outgoing.username; + smtp_password = cfg.mail.outgoing.passwordFile; + smtp_authentication = cfg.mail.outgoing.authentication; + smtp_enable_start_tls = cfg.mail.outgoing.enableStartTLSAuto; + smtp_openssl_verify_mode = cfg.mail.outgoing.opensslVerifyMode; + + load_mini_profiler = true; + mini_profiler_snapshots_period = 0; + mini_profiler_snapshots_transport_url = null; + mini_profiler_snapshots_transport_auth_key = null; + + cdn_url = null; + cdn_origin_hostname = null; + developer_emails = null; + + redis_host = cfg.redis.host; + redis_port = 6379; + redis_slave_host = null; + redis_slave_port = 6379; + redis_db = cfg.redis.dbNumber; + redis_password = cfg.redis.passwordFile; + redis_skip_client_commands = false; + redis_use_ssl = cfg.redis.useSSL; + + message_bus_redis_enabled = false; + message_bus_redis_host = "localhost"; + message_bus_redis_port = 6379; + message_bus_redis_slave_host = null; + message_bus_redis_slave_port = 6379; + message_bus_redis_db = 0; + message_bus_redis_password = null; + message_bus_redis_skip_client_commands = false; + + enable_cors = false; + cors_origin = ""; + serve_static_assets = false; + sidekiq_workers = 5; + rtl_css = false; + connection_reaper_age = 30; + connection_reaper_interval = 30; + relative_url_root = null; + message_bus_max_backlog_size = 100; + secret_key_base = cfg.secretKeyBaseFile; + fallback_assets_path = null; + + s3_bucket = null; + s3_region = null; + s3_access_key_id = null; + s3_secret_access_key = null; + s3_use_iam_profile = null; + s3_cdn_url = null; + s3_endpoint = null; + s3_http_continue_timeout = null; + s3_install_cors_rule = null; + + max_user_api_reqs_per_minute = 20; + max_user_api_reqs_per_day = 2880; + max_admin_api_reqs_per_key_per_minute = 60; + max_reqs_per_ip_per_minute = 200; + max_reqs_per_ip_per_10_seconds = 50; + max_asset_reqs_per_ip_per_10_seconds = 200; + max_reqs_per_ip_mode = "block"; + max_reqs_rate_limit_on_private = false; + force_anonymous_min_queue_seconds = 1; + force_anonymous_min_per_10_seconds = 3; + background_requests_max_queue_length = 0.5; + reject_message_bus_queue_seconds = 0.1; + disable_search_queue_threshold = 1; + max_old_rebakes_per_15_minutes = 300; + max_logster_logs = 1000; + refresh_maxmind_db_during_precompile_days = 2; + maxmind_backup_path = null; + maxmind_license_key = null; + enable_performance_http_headers = false; + enable_js_error_reporting = true; + mini_scheduler_workers = 5; + compress_anon_cache = false; + anon_cache_store_threshold = 2; + allowed_theme_repos = null; + enable_email_sync_demon = false; + max_digests_enqueued_per_30_mins_per_site = 10000; + }; + + services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost"); + + services.postgresql = lib.mkIf databaseActuallyCreateLocally { + enable = true; + ensureUsers = [{ name = "discourse"; }]; + }; + + # The postgresql module doesn't currently support concepts like + # objects owners and extensions; for now we tack on what's needed + # here. + systemd.services.discourse-postgresql = + let + pgsql = config.services.postgresql; + in + lib.mkIf databaseActuallyCreateLocally { + after = [ "postgresql.service" ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "discourse.service" ]; + partOf = [ "discourse.service" ]; + path = [ + pgsql.package + ]; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'discourse'" | grep -q 1 || psql -tAc 'CREATE DATABASE "discourse" OWNER "discourse"' + psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" + psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS hstore" + ''; + + serviceConfig = { + User = pgsql.superUser; + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + systemd.services.discourse = { + wantedBy = [ "multi-user.target" ]; + after = [ + "redis.service" + "postgresql.service" + "discourse-postgresql.service" + ]; + bindsTo = [ + "redis.service" + ] ++ lib.optionals (cfg.database.host == null) [ + "postgresql.service" + "discourse-postgresql.service" + ]; + path = cfg.package.runtimeDeps ++ [ + postgresqlPackage + pkgs.replace + cfg.package.rake + ]; + environment = cfg.package.runtimeEnv // { + UNICORN_TIMEOUT = builtins.toString cfg.unicornTimeout; + UNICORN_SIDEKIQS = builtins.toString cfg.sidekiqProcesses; + }; + + preStart = + let + discourseKeyValue = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " = " { + mkValueString = v: with builtins; + if isInt v then toString v + else if isString v then ''"${v}"'' + else if true == v then "true" + else if false == v then "false" + else if null == v then "" + else if isFloat v then lib.strings.floatToString v + else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + + discourseConf = pkgs.writeText "discourse.conf" (discourseKeyValue cfg.backendSettings); + + mkSecretReplacement = file: + lib.optionalString (file != null) '' + ( + password=$(<'${file}') + replace-literal -fe '${file}' "$password" /run/discourse/config/discourse.conf + ) + ''; + in '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + umask u=rwx,g=rx,o= + + cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/ + cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/ + cp -r ${cfg.package}/share/discourse/plugins.dist/* /run/discourse/plugins/ + ${lib.concatMapStrings (p: "ln -sf ${p} /run/discourse/plugins/") cfg.plugins} + ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads + ln -sf /var/lib/discourse/backups /run/discourse/public/backups + + ( + umask u=rwx,g=,o= + + ${utils.genJqSecretsReplacementSnippet + cfg.siteSettings + "/run/discourse/config/nixos_site_settings.json" + } + install -T -m 0400 -o discourse ${discourseConf} /run/discourse/config/discourse.conf + ${mkSecretReplacement cfg.database.passwordFile} + ${mkSecretReplacement cfg.mail.outgoing.passwordFile} + ${mkSecretReplacement cfg.redis.passwordFile} + ${mkSecretReplacement cfg.secretKeyBaseFile} + ) + + discourse-rake db:migrate >>/var/log/discourse/db_migration.log + chmod -R u+w /run/discourse/tmp/ + + export ADMIN_EMAIL="${cfg.admin.email}" + export ADMIN_NAME="${cfg.admin.fullName}" + export ADMIN_USERNAME="${cfg.admin.username}" + export ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})" + discourse-rake admin:create_noninteractively + + discourse-rake themes:update + discourse-rake uploads:regenerate_missing_optimized + ''; + + serviceConfig = { + Type = "simple"; + User = "discourse"; + Group = "discourse"; + RuntimeDirectory = map (p: "discourse/" + p) [ + "config" + "home" + "tmp" + "assets/javascripts/plugins" + "public" + "plugins" + "sockets" + ]; + RuntimeDirectoryMode = 0750; + StateDirectory = map (p: "discourse/" + p) [ + "uploads" + "backups" + ]; + StateDirectoryMode = 0750; + LogsDirectory = "discourse"; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.package}/share/discourse"; + + RemoveIPC = true; + PrivateTmp = true; + NoNewPrivileges = true; + RestrictSUIDSGID = true; + ProtectSystem = "strict"; + ProtectHome = "read-only"; + + ExecStart = "${cfg.package.rubyEnv}/bin/bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb"; + }; + }; + + services.nginx = lib.mkIf cfg.nginx.enable { + enable = true; + additionalModules = [ pkgs.nginxModules.brotli ]; + + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + upstreams.discourse.servers."unix:/run/discourse/sockets/unicorn.sock" = {}; + + appendHttpConfig = '' + # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week) + # levels means it is a 2 deep heirarchy cause we can have lots of files + # max_size limits the size of the cache + proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m; + + # see: https://meta.discourse.org/t/x/74060 + proxy_buffer_size 8k; + ''; + + virtualHosts.${cfg.hostname} = { + inherit (cfg) sslCertificate sslCertificateKey enableACME; + forceSSL = lib.mkDefault tlsEnabled; + + root = "/run/discourse/public"; + + locations = + let + proxy = { extraConfig ? "" }: { + proxyPass = "http://discourse"; + extraConfig = extraConfig + '' + proxy_set_header X-Request-Start "t=''${msec}"; + ''; + }; + cache = time: '' + expires ${time}; + add_header Cache-Control public,immutable; + ''; + cache_1y = cache "1y"; + cache_1d = cache "1d"; + in + { + "/".tryFiles = "$uri @discourse"; + "@discourse" = proxy {}; + "^~ /backups/".extraConfig = '' + internal; + ''; + "/favicon.ico" = { + return = "204"; + extraConfig = '' + access_log off; + log_not_found off; + ''; + }; + "~ ^/uploads/short-url/" = proxy {}; + "~ ^/secure-media-uploads/" = proxy {}; + "~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$".extraConfig = cache_1y + '' + add_header Access-Control-Allow-Origin *; + ''; + "/srv/status" = proxy { + extraConfig = '' + access_log off; + log_not_found off; + ''; + }; + "~ ^/javascripts/".extraConfig = cache_1d; + "~ ^/assets/(?.+)$".extraConfig = cache_1y + '' + # asset pipeline enables this + brotli_static on; + gzip_static on; + ''; + "~ ^/plugins/".extraConfig = cache_1y; + "~ /images/emoji/".extraConfig = cache_1y; + "~ ^/uploads/" = proxy { + extraConfig = cache_1y + '' + proxy_set_header X-Sendfile-Type X-Accel-Redirect; + proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/; + + # custom CSS + location ~ /stylesheet-cache/ { + try_files $uri =404; + } + # this allows us to bypass rails + location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico|webp)$ { + try_files $uri =404; + } + # SVG needs an extra header attached + location ~* \.(svg)$ { + } + # thumbnails & optimized images + location ~ /_?optimized/ { + try_files $uri =404; + } + ''; + }; + "~ ^/admin/backups/" = proxy { + extraConfig = '' + proxy_set_header X-Sendfile-Type X-Accel-Redirect; + proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/; + ''; + }; + "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy { + extraConfig = '' + # if Set-Cookie is in the response nothing gets cached + # this is double bad cause we are not passing last modified in + proxy_ignore_headers "Set-Cookie"; + proxy_hide_header "Set-Cookie"; + proxy_hide_header "X-Discourse-Username"; + proxy_hide_header "X-Runtime"; + + # note x-accel-redirect can not be used with proxy_cache + proxy_cache discourse; + proxy_cache_key "$scheme,$host,$request_uri"; + proxy_cache_valid 200 301 302 7d; + proxy_cache_valid any 1m; + ''; + }; + "/message-bus/" = proxy { + extraConfig = '' + proxy_http_version 1.1; + proxy_buffering off; + ''; + }; + "/downloads/".extraConfig = '' + internal; + alias /run/discourse/public/; + ''; + }; + }; + }; + + systemd.services.discourse-mail-receiver-setup = lib.mkIf cfg.mail.incoming.enable ( + let + mail-receiver-environment = { + MAIL_DOMAIN = cfg.hostname; + DISCOURSE_BASE_URL = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}"; + DISCOURSE_API_KEY = "@api-key@"; + DISCOURSE_API_USERNAME = "system"; + }; + mail-receiver-json = json.generate "mail-receiver.json" mail-receiver-environment; + in + { + before = [ "postfix.service" ]; + after = [ "discourse.service" ]; + wantedBy = [ "discourse.service" ]; + partOf = [ "discourse.service" ]; + path = [ + cfg.package.rake + pkgs.jq + ]; + preStart = lib.optionalString (cfg.mail.incoming.apiKeyFile == null) '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + if [[ ! -e /var/lib/discourse-mail-receiver/api_key ]]; then + discourse-rake api_key:create_master[email-receiver] >/var/lib/discourse-mail-receiver/api_key + fi + ''; + script = + let + apiKeyPath = + if cfg.mail.incoming.apiKeyFile == null then + "/var/lib/discourse-mail-receiver/api_key" + else + cfg.mail.incoming.apiKeyFile; + in '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + export api_key=$(<'${apiKeyPath}') + + jq <${mail-receiver-json} \ + '.DISCOURSE_API_KEY = $ENV.api_key' \ + >'/run/discourse-mail-receiver/mail-receiver-environment.json' + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + RuntimeDirectory = "discourse-mail-receiver"; + RuntimeDirectoryMode = "0700"; + StateDirectory = "discourse-mail-receiver"; + User = "discourse"; + Group = "discourse"; + }; + }); + + services.discourse.siteSettings = { + required = { + notification_email = cfg.mail.notificationEmailAddress; + contact_email = cfg.mail.contactEmailAddress; + }; + email = { + manual_polling_enabled = cfg.mail.incoming.enable; + reply_by_email_enabled = cfg.mail.incoming.enable; + reply_by_email_address = cfg.mail.incoming.replyEmailAddress; + }; + }; + + services.postfix = lib.mkIf cfg.mail.incoming.enable { + enable = true; + sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else ""; + sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else ""; + + origin = cfg.hostname; + relayDomains = [ cfg.hostname ]; + config = { + smtpd_recipient_restrictions = "check_policy_service unix:private/discourse-policy"; + append_dot_mydomain = lib.mkDefault false; + compatibility_level = "2"; + smtputf8_enable = false; + smtpd_banner = lib.mkDefault "ESMTP server"; + myhostname = lib.mkDefault cfg.hostname; + mydestination = lib.mkDefault "localhost"; + }; + transport = '' + ${cfg.hostname} discourse-mail-receiver: + ''; + masterConfig = { + "discourse-mail-receiver" = { + type = "unix"; + privileged = true; + chroot = false; + command = "pipe"; + args = [ + "user=discourse" + "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/receive-mail" + "\${recipient}" + ]; + }; + "discourse-policy" = { + type = "unix"; + privileged = true; + chroot = false; + command = "spawn"; + args = [ + "user=discourse" + "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/discourse-smtp-fast-rejection" + ]; + }; + }; + }; + + users.users = { + discourse = { + group = "discourse"; + isSystemUser = true; + }; + } // (lib.optionalAttrs cfg.nginx.enable { + ${config.services.nginx.user}.extraGroups = [ "discourse" ]; + }); + + users.groups = { + discourse = {}; + }; + + environment.systemPackages = [ + cfg.package.rake + ]; + }; + + meta.doc = ./discourse.xml; + meta.maintainers = [ lib.maintainers.talyz ]; +} diff --git a/nixos/modules/services/web-apps/discourse.xml b/nixos/modules/services/web-apps/discourse.xml new file mode 100644 index 00000000000..bae56242321 --- /dev/null +++ b/nixos/modules/services/web-apps/discourse.xml @@ -0,0 +1,323 @@ + + Discourse + + Discourse is a + modern and open source discussion platform. + + +
+ Basic usage + + A minimal configuration using Let's Encrypt for TLS certificates looks like this: + +services.discourse = { + enable = true; + hostname = "discourse.example.com"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; +}; +security.acme.email = "me@example.com"; +security.acme.acceptTerms = true; + + + + + Provided a proper DNS setup, you'll be able to connect to the + instance at discourse.example.com and log in + using the credentials provided in + services.discourse.admin. + +
+ +
+ Using a regular TLS certificate + + To set up TLS using a regular certificate and key on file, use + the + and + options: + + +services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; +}; + + + +
+ +
+ Database access + + Discourse uses + PostgreSQL to store most of its + data. A database will automatically be enabled and a database + and role created unless is changed from + its default of null or is set + to false. + + + + External database access can also be configured by setting + , and as + appropriate. Note that you need to manually create a database + called discourse (or the name you chose in + ) and + allow the configured database user full access to it. + +
+ +
+ Email + + In addition to the basic setup, you'll want to configure an SMTP + server Discourse can use to send user + registration and password reset emails, among others. You can + also optionally let Discourse receive + email, which enables people to reply to threads and conversations + via email. + + + + A basic setup which assumes you want to use your configured hostname as + email domain can be done like this: + + +services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; + }; + mail.incoming.enable = true; + secretKeyBaseFile = "/path/to/secret_key_base_file"; +}; + + + This assumes you have set up an MX record for the address you've + set in hostname and + requires proper SPF, DKIM and DMARC configuration to be done for + the domain you're sending from, in order for email to be reliably delivered. + + + + If you want to use a different domain for your outgoing email + (for example example.com instead of + discourse.example.com) you should set + and + manually. + + + + + Setup of TLS for incoming email is currently only configured + automatically when a regular TLS certificate is used, i.e. when + and + are + set. + + + +
+ +
+ Additional settings + + Additional site settings and backend settings, for which no + explicit NixOS options are provided, + can be set in and + respectively. + + +
+ Site settings + + Site settings are the settings that can be + changed through the Discourse + UI. Their default values can be set using + . + + + + Settings are expressed as a Nix attribute set which matches the + structure of the configuration in + config/site_settings.yml. + To find a setting's path, you only need to care about the first + two levels; i.e. its category (e.g. login) + and name (e.g. invite_only). + + + + Settings containing secret data should be set to an attribute + set containing the attribute _secret - a + string pointing to a file containing the value the option + should be set to. See the example. + +
+ +
+ Backend settings + + Settings are expressed as a Nix attribute set which matches the + structure of the configuration in + config/discourse.conf. + Empty parameters can be defined by setting them to + null. + +
+ +
+ Example + + The following example sets the title and description of the + Discourse instance and enables + GitHub login in the site settings, + and changes a few request limits in the backend settings: + +services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; + }; + mail.incoming.enable = true; + siteSettings = { + required = { + title = "My Cats"; + site_description = "Discuss My Cats (and be nice plz)"; + }; + login = { + enable_github_logins = true; + github_client_id = "a2f6dfe838cb3206ce20"; + github_client_secret._secret = /run/keys/discourse_github_client_secret; + }; + }; + backendSettings = { + max_reqs_per_ip_per_minute = 300; + max_reqs_per_ip_per_10_seconds = 60; + max_asset_reqs_per_ip_per_10_seconds = 250; + max_reqs_per_ip_mode = "warn+block"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; +}; + + + + In the resulting site settings file, the + login.github_client_secret key will be set + to the contents of the + /run/keys/discourse_github_client_secret + file. + +
+
+
+ Plugins + + You can install Discourse plugins + using the + option. As long as a plugin supports the standard install + method, packaging it should only require fetching its source + with an appropriate fetcher. + + + + Some plugins provide site + settings. Their defaults can be configured using , just like + regular site settings. To find the names of these settings, look + in the config/settings.yml file of the plugin + repo. + + + + For example, to add the discourse-spoiler-alert + plugin and disable it by default: + + +services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; + }; + mail.incoming.enable = true; + plugins = [ + (pkgs.fetchFromGitHub { + owner = "discourse"; + repo = "discourse-spoiler-alert"; + rev = "e200cfa571d252cab63f3d30d619b370986e4cee"; + sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33"; + }) + ]; + siteSettings = { + plugins = { + spoiler_enabled = false; + }; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; +}; + + + +
+
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index f011b527238..52fcce6d17b 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -397,6 +397,9 @@ in default = pkgs.nginxStable; defaultText = "pkgs.nginxStable"; type = types.package; + apply = p: p.override { + modules = p.modules ++ cfg.additionalModules; + }; description = " Nginx package to use. This defaults to the stable version. Note that the nginx team recommends to use the mainline version which @@ -404,6 +407,17 @@ in "; }; + additionalModules = mkOption { + default = []; + type = types.listOf (types.attrsOf types.anything); + example = literalExample "[ pkgs.nginxModules.brotli ]"; + description = '' + Additional third-party nginx modules + to install. Packaged modules are available in + pkgs.nginxModules. + ''; + }; + logError = mkOption { default = "stderr"; type = types.str; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 62188ddf9e8..76755208e6a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -88,6 +88,7 @@ in croc = handleTest ./croc.nix {}; deluge = handleTest ./deluge.nix {}; dhparams = handleTest ./dhparams.nix {}; + discourse = handleTest ./discourse.nix {}; dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {}; doas = handleTest ./doas.nix {}; diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix new file mode 100644 index 00000000000..3c965550fe0 --- /dev/null +++ b/nixos/tests/discourse.nix @@ -0,0 +1,197 @@ +# This tests Discourse by: +# 1. logging in as the admin user +# 2. sending a private message to the admin user through the API +# 3. replying to that message via email. + +import ./make-test-python.nix ( + { pkgs, lib, ... }: + let + certs = import ./common/acme/server/snakeoil-certs.nix; + clientDomain = "client.fake.domain"; + discourseDomain = certs.domain; + adminPassword = "eYAX85qmMJ5GZIHLaXGDAoszD7HSZp5d"; + secretKeyBase = "381f4ac6d8f5e49d804dae72aa9c046431d2f34c656a705c41cd52fed9b4f6f76f51549f0b55db3b8b0dded7a00d6a381ebe9a4367d2d44f5e743af6628b4d42"; + admin = { + email = "alice@${clientDomain}"; + username = "alice"; + fullName = "Alice Admin"; + passwordFile = "${pkgs.writeText "admin-pass" adminPassword}"; + }; + in + { + name = "discourse"; + meta = with pkgs.lib.maintainers; { + maintainers = [ talyz ]; + }; + + nodes.discourse = + { nodes, ... }: + { + virtualisation.memorySize = 2048; + + imports = [ common/user-account.nix ]; + + security.pki.certificateFiles = [ + certs.ca.cert + ]; + + networking.extraHosts = '' + 127.0.0.1 ${discourseDomain} + ${nodes.client.config.networking.primaryIPAddress} ${clientDomain} + ''; + + services.postfix = { + enableSubmission = true; + enableSubmissions = true; + submissionsOptions = { + smtpd_sasl_auth_enable = "yes"; + smtpd_client_restrictions = "permit"; + }; + }; + + environment.systemPackages = [ pkgs.jq ]; + + services.discourse = { + enable = true; + inherit admin; + hostname = discourseDomain; + sslCertificate = "${certs.${discourseDomain}.cert}"; + sslCertificateKey = "${certs.${discourseDomain}.key}"; + secretKeyBaseFile = "${pkgs.writeText "secret-key-base" secretKeyBase}"; + enableACME = false; + mail.outgoing.serverAddress = clientDomain; + mail.incoming.enable = true; + siteSettings = { + posting = { + min_post_length = 5; + min_first_post_length = 5; + min_personal_message_post_length = 5; + }; + }; + unicornTimeout = 900; + }; + + networking.firewall.allowedTCPPorts = [ 25 465 ]; + }; + + nodes.client = + { nodes, ... }: + { + imports = [ common/user-account.nix ]; + + security.pki.certificateFiles = [ + certs.ca.cert + ]; + + networking.extraHosts = '' + 127.0.0.1 ${clientDomain} + ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain} + ''; + + services.dovecot2 = { + enable = true; + protocols = [ "imap" ]; + modules = [ pkgs.dovecot_pigeonhole ]; + }; + + services.postfix = { + enable = true; + origin = clientDomain; + relayDomains = [ clientDomain ]; + config = { + compatibility_level = "2"; + smtpd_banner = "ESMTP server"; + myhostname = clientDomain; + mydestination = clientDomain; + }; + }; + + environment.systemPackages = + let + replyToEmail = pkgs.writeScriptBin "reply-to-email" '' + #!${pkgs.python3.interpreter} + import imaplib + import smtplib + import ssl + import email.header + from email import message_from_bytes + from email.message import EmailMessage + + with imaplib.IMAP4('localhost') as imap: + imap.login('alice', 'foobar') + imap.select() + status, data = imap.search(None, 'ALL') + assert status == 'OK' + + nums = data[0].split() + assert len(nums) == 1 + + status, msg_data = imap.fetch(nums[0], '(RFC822)') + assert status == 'OK' + + msg = email.message_from_bytes(msg_data[0][1]) + subject = str(email.header.make_header(email.header.decode_header(msg['Subject']))) + reply_to = email.header.decode_header(msg['Reply-To'])[0][0] + message_id = email.header.decode_header(msg['Message-ID'])[0][0] + date = email.header.decode_header(msg['Date'])[0][0] + + ctx = ssl.create_default_context() + with smtplib.SMTP_SSL(host='${discourseDomain}', context=ctx) as smtp: + reply = EmailMessage() + reply['Subject'] = 'Re: ' + subject + reply['To'] = reply_to + reply['From'] = 'alice@${clientDomain}' + reply['In-Reply-To'] = message_id + reply['References'] = message_id + reply['Date'] = date + reply.set_content("Test reply.") + + smtp.send_message(reply) + smtp.quit() + ''; + in + [ replyToEmail ]; + + networking.firewall.allowedTCPPorts = [ 25 ]; + }; + + + testScript = { nodes }: + let + request = builtins.toJSON { + title = "Private message"; + raw = "This is a test message."; + target_usernames = admin.username; + archetype = "private_message"; + }; + in '' + discourse.start() + client.start() + + discourse.wait_for_unit("discourse.service") + discourse.wait_for_file("/run/discourse/sockets/unicorn.sock") + discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}") + discourse.succeed( + "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token", + "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'", + "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}", + ) + + client.wait_for_unit("postfix.service") + client.wait_for_unit("dovecot2.service") + + discourse.succeed( + "sudo -u discourse discourse-rake api_key:create_master[master] >api_key", + 'curl -sS -f https://${discourseDomain}/posts -X POST -H "Content-Type: application/json" -H "Api-Key: $(topic_id' + ) + discourse.succeed( + 'curl -sS -f https://${discourseDomain}/t/$(Test reply.

" then true else null end\' ' + ) + ''; + }) diff --git a/pkgs/development/libraries/v8/default.nix b/pkgs/development/libraries/v8/default.nix index c79073f04e4..d513e01b7a3 100644 --- a/pkgs/development/libraries/v8/default.nix +++ b/pkgs/development/libraries/v8/default.nix @@ -11,23 +11,23 @@ let deps = { "base/trace_event/common" = fetchgit { url = "${git_url}/chromium/src/base/trace_event/common.git"; - rev = "936ba8a963284a6b3737cf2f0474a7131073abee"; - sha256 = "14nr22fqdpxma1kzjflj6a865vr3hfnnm2gs4vcixyq4kmfzfcy2"; + rev = "dab187b372fc17e51f5b9fad8201813d0aed5129"; + sha256 = "0dmpj9hj4xv3xb0fl1kb9hm4bhpbs2s5csx3z8cgjd5vwvhdzig4"; }; build = fetchgit { url = "${git_url}/chromium/src/build.git"; - rev = "325e95d6dae64f35b160b3dc7d73218cee5ec079"; - sha256 = "0dddyxa76p2xpjhmxif05v63i5ar6h5v684fdl667sg84f5bhhxf"; + rev = "26e9d485d01d6e0eb9dadd21df767a63494c8fea"; + sha256 = "1jjvsgj0cs97d26i3ba531ic1f9gqan8x7z4aya8yl8jx02l342q"; }; "third_party/googletest/src" = fetchgit { url = "${git_url}/external/github.com/google/googletest.git"; - rev = "5ec7f0c4a113e2f18ac2c6cc7df51ad6afc24081"; - sha256 = "0gmr10042c0xybxnn6g7ndj1na1mmd3l9w7449qlcv4s8gmfs7k6"; + rev = "e3f0319d89f4cbf32993de595d984183b1a9fc57"; + sha256 = "18xz71l2xjrqsc0q317whgw4xi1i5db24zcj7v04f5g6r1hyf1a5"; }; "third_party/icu" = fetchgit { url = "${git_url}/chromium/deps/icu.git"; - rev = "960f195aa87acaec46e6104ec93a596da7ae0843"; - sha256 = "073kh6gpcairgjxf3hlhpqljc13gwl2aj8fz91fv220xibwqs834"; + rev = "f2223961702f00a8833874b0560d615a2cc42738"; + sha256 = "0z5p53kbrjfkjn0i12dpk55cp8976j2zk7a4wk88423s2c5w87zl"; }; "third_party/jinja2" = fetchgit { url = "${git_url}/chromium/src/third_party/jinja2.git"; @@ -39,29 +39,31 @@ let rev = "8f45f5cfa0009d2a70589bcda0349b8cb2b72783"; sha256 = "168ppjmicfdh4i1l0l25s86mdbrz9fgxmiq1rx33x79mph41scfz"; }; + "third_party/zlib" = fetchgit { + url = "${git_url}/chromium/src/third_party/zlib.git"; + rev = "156be8c52f80cde343088b4a69a80579101b6e67"; + sha256 = "0hxbkkzmlv714fjq2jlp5dd2jc339xyh6gkjx1sz3srwv33mlk92"; + }; }; in stdenv.mkDerivation rec { pname = "v8"; - version = "7.4.255"; + version = "8.4.255"; doCheck = true; patches = [ - (fetchpatch { - url = "https://raw.githubusercontent.com/RPi-Distro/chromium-browser/master/debian/patches/revert-Xclang-instcombine-lower-dbg-declare.patch"; - sha256 = "02hczcg43m36q8j1kv5j3hq9czj9niiil9w13w22vzv2f3c67dvn"; - }) ./darwin.patch + ./gcc_arm.patch # Fix building zlib with gcc on aarch64, from https://gist.github.com/Adenilson/d973b6fd96c7709d33ddf08cf1dcb149 ]; src = fetchFromGitHub { owner = "v8"; repo = "v8"; rev = version; - sha256 = "14i0c71hmffzqnq9n73dh9dnabdxhbjhzkhqpk5yv9y90bwrzi2n"; + sha256 = "07ymw4kqbz7kv311gpk5bs5q90wj73n2q7jkyfhqk4hvhs1q5bw7"; }; postUnpack = '' @@ -97,9 +99,7 @@ stdenv.mkDerivation rec { ''v8_snapshot_toolchain="//build/toolchain/linux/unbundle:default"'' ] ++ lib.optional stdenv.cc.isClang ''clang_base_path="${stdenv.cc}"''; - # with gcc8, -Wclass-memaccess became part of -Wall and causes logging limit - # to be exceeded - NIX_CFLAGS_COMPILE = lib.optionalString stdenv.cc.isGNU "-Wno-class-memaccess"; + NIX_CFLAGS_COMPILE = "-O2"; nativeBuildInputs = [ gn ninja pkg-config python ] ++ lib.optionals stdenv.isDarwin [ xcbuild darwin.DarwinTools ]; diff --git a/pkgs/development/libraries/v8/gcc_arm.patch b/pkgs/development/libraries/v8/gcc_arm.patch new file mode 100644 index 00000000000..09579561fd8 --- /dev/null +++ b/pkgs/development/libraries/v8/gcc_arm.patch @@ -0,0 +1,31 @@ +diff --git a/third_party/zlib/contrib/optimizations/insert_string.h b/third_party/zlib/contrib/optimizations/insert_string.h +index 1826601..d123305 100644 +--- a/third_party/zlib/contrib/optimizations/insert_string.h ++++ b/third_party/zlib/contrib/optimizations/insert_string.h +@@ -26,15 +26,23 @@ + #define _cpu_crc32_u32 _mm_crc32_u32 + + #elif defined(CRC32_ARMV8_CRC32) +- #if defined(__clang__) ++ #if defined(__GNUC__) || defined(__clang__) + #undef TARGET_CPU_WITH_CRC +- #define __crc32cw __builtin_arm_crc32cw ++ #if defined(__clang__) ++ #define __crc32cw __builtin_arm_crc32cw ++ #elif defined(__GNUC__) ++ #define __crc32cw __builtin_aarch64_crc32cw ++ #endif + #endif + + #define _cpu_crc32_u32 __crc32cw + + #if defined(__aarch64__) +- #define TARGET_CPU_WITH_CRC __attribute__((target("crc"))) ++ #if defined(__clang__) ++ #define TARGET_CPU_WITH_CRC __attribute__((target("crc"))) ++ #elif defined(__GNUC__) ++ #define TARGET_CPU_WITH_CRC __attribute__((target("+crc"))) ++ #endif + #else // !defined(__aarch64__) + #define TARGET_CPU_WITH_CRC __attribute__((target("armv8-a,crc"))) + #endif // defined(__aarch64__) diff --git a/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch b/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch new file mode 100644 index 00000000000..83c44a466fa --- /dev/null +++ b/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch @@ -0,0 +1,12 @@ +diff --git a/config/environments/production.rb b/config/environments/production.rb +index 75c3a69512..7fc374cd9d 100644 +--- a/config/environments/production.rb ++++ b/config/environments/production.rb +@@ -32,6 +32,7 @@ Discourse::Application.configure do + user_name: GlobalSetting.smtp_user_name, + password: GlobalSetting.smtp_password, + authentication: GlobalSetting.smtp_authentication, ++ ca_file: "/etc/ssl/certs/ca-certificates.crt", + enable_starttls_auto: GlobalSetting.smtp_enable_start_tls + } + diff --git a/pkgs/servers/web-apps/discourse/admin_create.patch b/pkgs/servers/web-apps/discourse/admin_create.patch new file mode 100644 index 00000000000..651e8ce81dc --- /dev/null +++ b/pkgs/servers/web-apps/discourse/admin_create.patch @@ -0,0 +1,48 @@ +diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake +index 80c403616d..cba01202ac 100644 +--- a/lib/tasks/admin.rake ++++ b/lib/tasks/admin.rake +@@ -107,3 +107,43 @@ task "admin:create" => :environment do + end + + end ++ ++desc "Creates a forum administrator noninteractively" ++task "admin:create_noninteractively" => :environment do ++ email = ENV["ADMIN_EMAIL"] ++ existing_user = User.find_by_email(email) ++ ++ # check if user account already exixts ++ if existing_user ++ admin = existing_user ++ else ++ # create new user ++ admin = User.new ++ end ++ ++ admin.email = email ++ admin.name = ENV["ADMIN_NAME"] ++ admin.username = ENV["ADMIN_USERNAME"] ++ ++ password = ENV["ADMIN_PASSWORD"] ++ unless admin.confirm_password?(password) ++ admin.password = password ++ puts "Admin password set!" ++ end ++ ++ admin.active = true ++ ++ # save/update user account ++ saved = admin.save ++ raise admin.errors.full_messages.join("\n") unless saved ++ ++ puts "Account created successfully with username #{admin.username}" unless existing_user ++ ++ # grant admin privileges ++ admin.grant_admin! ++ if admin.trust_level < 1 ++ admin.change_trust_level!(1) ++ end ++ admin.email_tokens.update_all confirmed: true ++ admin.activate ++end diff --git a/pkgs/servers/web-apps/discourse/default.nix b/pkgs/servers/web-apps/discourse/default.nix new file mode 100644 index 00000000000..900d6921092 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/default.nix @@ -0,0 +1,234 @@ +{ stdenv, makeWrapper, runCommandNoCC, lib, nixosTests +, fetchFromGitHub, bundlerEnv, ruby, replace, gzip, gnutar, git +, util-linux, gawk, imagemagick, optipng, pngquant, libjpeg, jpegoptim +, gifsicle, libpsl, redis, postgresql, which, brotli, procps +, nodePackages, v8 +}: + +let + version = "2.6.3"; + + src = fetchFromGitHub { + owner = "discourse"; + repo = "discourse"; + rev = "v${version}"; + sha256 = "sha256-lAIhVxvmjxEiru1KNxbFV+eDMLUGza/Dma3WU0ex0xs="; + }; + + runtimeDeps = [ + # For backups, themes and assets + rubyEnv.wrappedRuby + gzip + gnutar + git + brotli + + # Misc required system utils + which + procps # For ps and kill + util-linux # For renice + gawk + + # Image optimization + imagemagick + optipng + pngquant + libjpeg + jpegoptim + gifsicle + nodePackages.svgo + ]; + + runtimeEnv = { + HOME = "/run/discourse/home"; + RAILS_ENV = "production"; + UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock"; + }; + + rake = runCommandNoCC "discourse-rake" { + nativeBuildInputs = [ makeWrapper ]; + } '' + mkdir -p $out/bin + makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \ + ${lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv)} \ + --prefix PATH : ${lib.makeBinPath runtimeDeps} \ + --set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \ + --run 'cd ${discourse}/share/discourse' + ''; + + rubyEnv = bundlerEnv { + name = "discourse-ruby-env-${version}"; + inherit version ruby; + gemdir = ./rubyEnv; + gemset = + let + gems = import ./rubyEnv/gemset.nix; + in + gems // { + mini_racer = gems.mini_racer // { + buildInputs = [ v8 ]; + dontBuild = false; + # The Ruby extension makefile generator assumes the source + # is C, when it's actually C++ ¯\_(ツ)_/¯ + postPatch = '' + substituteInPlace ext/mini_racer_extension/extconf.rb \ + --replace '" -std=c++0x"' \ + '" -x c++ -std=c++0x"' + ''; + }; + mini_suffix = gems.mini_suffix // { + propagatedBuildInputs = [ libpsl ]; + dontBuild = false; + # Use our libpsl instead of the vendored one, which isn't + # available for aarch64 + postPatch = '' + cp $(readlink -f ${libpsl}/lib/libpsl.so) vendor/libpsl.so + ''; + }; + }; + + groups = [ + "default" "assets" "development" "test" + ]; + }; + + assets = stdenv.mkDerivation { + pname = "discourse-assets"; + inherit version src; + + nativeBuildInputs = [ + rubyEnv.wrappedRuby + postgresql + redis + which + brotli + procps + nodePackages.uglify-js + ]; + + # We have to set up an environment that is close enough to + # production ready or the assets:precompile task refuses to + # run. This means that Redis and PostgreSQL has to be running and + # database migrations performed. + preBuild = '' + redis-server >/dev/null & + + initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null + postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null & + export PGHOST=$NIX_BUILD_TOP + + echo "Waiting for Redis and PostgreSQL to be ready.." + while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do + sleep 0.1 + done + + psql -d postgres -tAc 'CREATE USER "discourse"' + psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"' + psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" + psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore" + + # Create a temporary home dir to stop bundler from complaining + mkdir $NIX_BUILD_TOP/tmp_home + export HOME=$NIX_BUILD_TOP/tmp_home + + export RAILS_ENV=production + + bundle exec rake db:migrate >/dev/null + rm -r tmp/* + ''; + + buildPhase = '' + runHook preBuild + + bundle exec rake assets:precompile + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mv public/assets $out + + runHook postInstall + ''; + }; + + discourse = stdenv.mkDerivation { + pname = "discourse"; + inherit version src; + + buildInputs = [ + rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler + ]; + + patches = [ + # Load a separate NixOS site settings file + ./nixos_defaults.patch + + # Add a noninteractive admin creation task + ./admin_create.patch + + # Disable jhead, which is currently marked as vulnerable + ./disable_jhead.patch + + # Add the path to the CA cert bundle to make TLS work + ./action_mailer_ca_cert.patch + + # Log Unicorn messages to the journal and make request timeout + # configurable + ./unicorn_logging_and_timeout.patch + ]; + + postPatch = '' + # Always require lib-files and application.rb through their store + # path, not their relative state directory path. This gets rid of + # warnings and means we don't have to link back to lib from the + # state directory. + find config -type f -execdir sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \; + + ${replace}/bin/replace-literal -f -r -e 'File.rename(temp_destination, destination)' "FileUtils.mv(temp_destination, destination)" . + ''; + + buildPhase = '' + runHook preBuild + + mv config config.dist + mv public public.dist + mv plugins plugins.dist + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/share + cp -r . $out/share/discourse + rm -r $out/share/discourse/log + ln -sf /var/log/discourse $out/share/discourse/log + ln -sf /run/discourse/tmp $out/share/discourse/tmp + ln -sf /run/discourse/config $out/share/discourse/config + ln -sf /run/discourse/assets/javascripts/plugins $out/share/discourse/app/assets/javascripts/plugins + ln -sf /run/discourse/public $out/share/discourse/public + ln -sf /run/discourse/plugins $out/share/discourse/plugins + ln -sf ${assets} $out/share/discourse/public.dist/assets + + runHook postInstall + ''; + + meta = with lib; { + homepage = "https://www.discourse.org/"; + platforms = platforms.linux; + maintainers = with maintainers; [ talyz ]; + license = licenses.gpl2Plus; + description = "Discourse is an open source discussion platform"; + }; + + passthru = { + inherit rubyEnv runtimeEnv runtimeDeps rake; + ruby = rubyEnv.wrappedRuby; + tests = nixosTests.discourse; + }; + }; +in discourse diff --git a/pkgs/servers/web-apps/discourse/disable_jhead.patch b/pkgs/servers/web-apps/discourse/disable_jhead.patch new file mode 100644 index 00000000000..709a1959d63 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/disable_jhead.patch @@ -0,0 +1,12 @@ +diff --git a/lib/file_helper.rb b/lib/file_helper.rb +index 162de9a40b..9ac8807e9d 100644 +--- a/lib/file_helper.rb ++++ b/lib/file_helper.rb +@@ -124,6 +124,7 @@ class FileHelper + jpegoptim: { strip: strip_image_metadata ? "all" : "none" }, + jpegtran: false, + jpegrecompress: false, ++ jhead: false, + ) + end + end diff --git a/pkgs/servers/web-apps/discourse/mail_receiver/default.nix b/pkgs/servers/web-apps/discourse/mail_receiver/default.nix new file mode 100644 index 00000000000..c1a3a2df106 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/mail_receiver/default.nix @@ -0,0 +1,39 @@ +{ stdenv, lib, fetchFromGitHub, ruby, makeWrapper, replace }: + +stdenv.mkDerivation rec { + pname = "discourse-mail-receiver"; + version = "4.0.7"; + + src = fetchFromGitHub { + owner = "discourse"; + repo = "mail-receiver"; + rev = "v${version}"; + sha256 = "0grifm5qyqazq63va3w26xjqnxwmfixhx0fx0zy7kd39378wwa6i"; + }; + + nativeBuildInputs = [ replace ]; + buildInputs = [ ruby makeWrapper ]; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + + replace-literal -f -r -e /etc/postfix /run/discourse-mail-receiver . + + cp -r receive-mail discourse-smtp-fast-rejection $out/bin/ + cp -r lib $out/ + + wrapProgram $out/bin/receive-mail --set RUBYLIB $out/lib + wrapProgram $out/bin/discourse-smtp-fast-rejection --set RUBYLIB $out/lib + ''; + + meta = with lib; { + homepage = "https://www.discourse.org/"; + platforms = platforms.linux; + maintainers = with maintainers; [ talyz ]; + license = licenses.mit; + description = "A helper program which receives incoming mail for Discourse"; + }; + +} diff --git a/pkgs/servers/web-apps/discourse/nixos_defaults.patch b/pkgs/servers/web-apps/discourse/nixos_defaults.patch new file mode 100644 index 00000000000..3efca97e62c --- /dev/null +++ b/pkgs/servers/web-apps/discourse/nixos_defaults.patch @@ -0,0 +1,13 @@ +diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb +index 89a5e923fc..b60754f50a 100644 +--- a/app/models/site_setting.rb ++++ b/app/models/site_setting.rb +@@ -26,6 +26,8 @@ class SiteSetting < ActiveRecord::Base + end + end + ++ load_settings(File.join(Rails.root, 'config', 'nixos_site_settings.json')) ++ + setup_deprecated_methods + client_settings << :available_locales + diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile new file mode 100644 index 00000000000..cb86d7e4bac --- /dev/null +++ b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' +# if there is a super emergency and rubygems is playing up, try +#source 'http://production.cf.rubygems.org' + +gem 'bootsnap', require: false, platform: :mri + +def rails_master? + ENV["RAILS_MASTER"] == '1' +end + +if rails_master? + gem 'arel', git: 'https://github.com/rails/arel.git' + gem 'rails', git: 'https://github.com/rails/rails.git' +else + # NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit + # this allows us to include the bits of rails we use without pieces we do not. + # + # To issue a rails update bump the version number here + gem 'actionmailer', '6.0.3.3' + gem 'actionpack', '6.0.3.3' + gem 'actionview', '6.0.3.3' + gem 'activemodel', '6.0.3.3' + gem 'activerecord', '6.0.3.3' + gem 'activesupport', '6.0.3.3' + gem 'railties', '6.0.3.3' + gem 'sprockets-rails' +end + +gem 'json' + +# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals +# This is a desired upgrade we should get to. +gem 'sprockets', '3.7.2' + +# this will eventually be added to rails, +# allows us to precompile all our templates in the unicorn master +gem 'actionview_precompiler', require: false + +gem 'seed-fu' + +gem 'mail', require: false +gem 'mini_mime' +gem 'mini_suffix' + +gem 'redis' + +# This is explicitly used by Sidekiq and is an optional dependency. +# We tell Sidekiq to use the namespace "sidekiq" which triggers this +# gem to be used. There is no explicit dependency in sidekiq cause +# redis namespace support is optional +# We already namespace stuff in DiscourseRedis, so we should consider +# just using a single implementation in core vs having 2 namespace implementations +gem 'redis-namespace' + +# NOTE: AM serializer gets a lot slower with recent updates +# we used an old branch which is the fastest one out there +# are long term goal here is to fork this gem so we have a +# better maintained living fork +gem 'active_model_serializers', '~> 0.8.3' + +gem 'onebox' + +gem 'http_accept_language', require: false + +# Ember related gems need to be pinned cause they control client side +# behavior, we will push these versions up when upgrading ember +gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails' +gem 'discourse-ember-source', '~> 3.12.2' +gem 'ember-handlebars-template', '0.8.0' +gem 'discourse-fonts' + +gem 'barber' + +gem 'message_bus' + +gem 'rails_multisite' + +gem 'fast_xs', platform: :ruby + +gem 'xorcist' + +gem 'fastimage' + +gem 'aws-sdk-s3', require: false +gem 'aws-sdk-sns', require: false +gem 'excon', require: false +gem 'unf', require: false + +gem 'email_reply_trimmer' + +# Forked until https://github.com/toy/image_optim/pull/162 is merged +# https://github.com/discourse/image_optim +gem 'discourse_image_optim', require: 'image_optim' +gem 'multi_json' +gem 'mustache' +gem 'nokogiri' +gem 'css_parser', require: false + +gem 'omniauth' +gem 'omniauth-facebook' +gem 'omniauth-twitter' +gem 'omniauth-github' + +gem 'omniauth-oauth2', require: false + +gem 'omniauth-google-oauth2' + +gem 'oj' +gem 'pg' +gem 'mini_sql' +gem 'pry-rails', require: false +gem 'pry-byebug', require: false +gem 'r2', require: false +gem 'rake' + +gem 'thor', require: false +gem 'diffy', require: false +gem 'rinku' +gem 'sidekiq' +gem 'mini_scheduler' + +gem 'execjs', require: false +gem 'mini_racer' + +gem 'highline', require: false + +gem 'rack' + +gem 'rack-protection' # security +gem 'cbor', require: false +gem 'cose', require: false +gem 'addressable' + +# Gems used only for assets and not required in production environments by default. +# Allow everywhere for now cause we are allowing asset debugging in production +group :assets do + gem 'uglifier' + gem 'rtlit', require: false # for css rtling +end + +group :test do + gem 'webmock', require: false + gem 'fakeweb', require: false + gem 'minitest', require: false + gem 'simplecov', require: false + gem "test-prof" +end + +group :test, :development do + gem 'rspec' + gem 'mock_redis' + gem 'listen', require: false + gem 'certified', require: false + gem 'fabrication', require: false + gem 'mocha', require: false + + gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false + + gem 'rspec-rails' + + gem 'shoulda-matchers', require: false + gem 'rspec-html-matchers' + gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri + gem "rubocop-discourse", require: false + gem 'parallel_tests' + + gem 'rswag-specs' +end + +group :development do + gem 'ruby-prof', require: false, platform: :mri + gem 'bullet', require: !!ENV['BULLET'] + gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS'] + gem 'binding_of_caller' + gem 'yaml-lint' + gem 'annotate' +end + +# this is an optional gem, it provides a high performance replacement +# to String#blank? a method that is called quite frequently in current +# ActiveRecord, this may change in the future +gem 'fast_blank', platform: :ruby + +# this provides a very efficient lru cache +gem 'lru_redux' + +gem 'htmlentities', require: false + +# IMPORTANT: mini profiler monkey patches, so it better be required last +# If you want to amend mini profiler to do the monkey patches in the railties +# we are open to it. by deferring require to the initializer we can configure discourse installs without it + +gem 'flamegraph', require: false +gem 'rack-mini-profiler', require: ['enable_rails_patches'] + +gem 'unicorn', require: false, platform: :ruby +gem 'puma', require: false +gem 'rbtrace', require: false, platform: :mri +gem 'gc_tracer', require: false, platform: :mri + +# required for feed importing and embedding +gem 'ruby-readability', require: false + +gem 'stackprof', require: false, platform: :mri +gem 'memory_profiler', require: false, platform: :mri + +gem 'cppjieba_rb', require: false + +gem 'lograge', require: false +gem 'logstash-event', require: false +gem 'logstash-logger', require: false +gem 'logster' + +# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture +# and until resolved should be locked at 2.0.1 +gem 'sassc', '2.0.1', require: false +gem "sassc-rails" + +gem 'rotp', require: false + +gem 'rqrcode' + +gem 'rubyzip', require: false + +gem 'sshkey', require: false + +gem 'rchardet', require: false +gem 'lz4-ruby', require: false, platform: :ruby + +if ENV["IMPORT"] == "1" + gem 'mysql2' + gem 'redcarpet' + + # NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one + gem 'sqlite3', '~> 1.3', '>= 1.3.13' + gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md' + gem 'reverse_markdown' + gem 'tiny_tds' + gem 'csv' +end + +gem 'webpush', require: false +gem 'colored2', require: false +gem 'maxminddb' + +gem 'rails_failover', require: false diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock new file mode 100644 index 00000000000..4f067493227 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock @@ -0,0 +1,561 @@ +GEM + remote: https://rubygems.org/ + specs: + actionmailer (6.0.3.3) + actionpack (= 6.0.3.3) + actionview (= 6.0.3.3) + activejob (= 6.0.3.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (6.0.3.3) + actionview (= 6.0.3.3) + activesupport (= 6.0.3.3) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (6.0.3.3) + activesupport (= 6.0.3.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + actionview_precompiler (0.2.3) + actionview (>= 6.0.a) + active_model_serializers (0.8.4) + activemodel (>= 3.0) + activejob (6.0.3.3) + activesupport (= 6.0.3.3) + globalid (>= 0.3.6) + activemodel (6.0.3.3) + activesupport (= 6.0.3.3) + activerecord (6.0.3.3) + activemodel (= 6.0.3.3) + activesupport (= 6.0.3.3) + activesupport (6.0.3.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + annotate (3.1.1) + activerecord (>= 3.2, < 7.0) + rake (>= 10.4, < 14.0) + ast (2.4.1) + aws-eventstream (1.1.0) + aws-partitions (1.390.0) + aws-sdk-core (3.109.2) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.83.2) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sdk-sns (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + barber (0.12.2) + ember-source (>= 1.0, < 3.1) + execjs (>= 1.2, < 3) + better_errors (2.9.1) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.5.1) + msgpack (~> 1.0) + builder (3.2.4) + bullet (6.1.0) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) + byebug (11.1.3) + cbor (0.5.9.6) + certified (1.0.0) + chunky_png (1.3.14) + coderay (1.1.3) + colored2 (3.1.2) + concurrent-ruby (1.1.7) + connection_pool (2.2.3) + cose (1.2.0) + cbor (~> 0.5.9) + openssl-signature_algorithm (~> 1.0) + cppjieba_rb (0.3.3) + crack (0.4.4) + crass (1.0.6) + css_parser (1.7.1) + addressable + debug_inspector (0.0.3) + diff-lcs (1.4.4) + diffy (3.4.0) + discourse-ember-rails (0.18.6) + active_model_serializers + ember-data-source (>= 1.0.0.beta.5) + ember-handlebars-template (>= 0.1.1, < 1.0) + ember-source (>= 1.1.0) + jquery-rails (>= 1.0.17) + railties (>= 3.1) + discourse-ember-source (3.12.2.2) + discourse-fonts (0.0.5) + discourse_image_optim (0.26.2) + exifr (~> 1.2, >= 1.2.2) + fspath (~> 3.0) + image_size (~> 1.5) + in_threads (~> 1.3) + progress (~> 3.0, >= 3.0.1) + docile (1.3.2) + email_reply_trimmer (0.1.13) + ember-data-source (3.0.2) + ember-source (>= 2, < 3.0) + ember-handlebars-template (0.8.0) + barber (>= 0.11.0) + sprockets (>= 3.3, < 4.1) + ember-source (2.18.2) + erubi (1.10.0) + excon (0.78.0) + execjs (2.7.0) + exifr (1.3.9) + fabrication (2.21.1) + fakeweb (1.3.0) + faraday (1.1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords + fast_blank (1.0.0) + fast_xs (0.8.0) + fastimage (2.2.0) + ffi (1.13.1) + flamegraph (0.9.5) + fspath (3.1.2) + gc_tracer (1.5.1) + globalid (0.4.2) + activesupport (>= 4.2.0) + guess_html_encoding (0.0.11) + hashdiff (1.0.1) + hashie (4.1.0) + highline (2.0.3) + hkdf (0.3.0) + htmlentities (4.3.4) + http_accept_language (2.1.1) + i18n (1.8.5) + concurrent-ruby (~> 1.0) + image_size (1.5.0) + in_threads (1.5.4) + jmespath (1.4.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (2.3.1) + json-schema (2.8.1) + addressable (>= 2.4) + jwt (2.2.2) + kgio (2.11.3) + libv8 (8.4.255.0) + listen (3.3.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + lograge (0.11.2) + actionpack (>= 4) + activesupport (>= 4) + railties (>= 4) + request_store (~> 1.0) + logstash-event (1.2.02) + logstash-logger (0.26.1) + logstash-event (~> 1.2) + logster (2.9.4) + loofah (2.8.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + lru_redux (1.1.0) + lz4-ruby (0.3.3) + mail (2.7.1) + mini_mime (>= 0.1.1) + maxminddb (0.1.22) + memory_profiler (0.9.14) + message_bus (3.3.4) + rack (>= 1.1.3) + method_source (1.0.0) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + mini_racer (0.3.1) + libv8 (~> 8.4.255) + mini_scheduler (0.12.3) + sidekiq + mini_sql (0.3) + mini_suffix (0.3.0) + ffi (~> 1.9) + minitest (5.14.2) + mocha (1.11.2) + mock_redis (0.26.0) + msgpack (1.3.3) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.1.1) + mustache (1.1.1) + nio4r (2.5.4) + nokogiri (1.10.10) + mini_portile2 (~> 2.4.0) + nokogumbo (2.0.2) + nokogiri (~> 1.8, >= 1.8.4) + oauth (0.5.4) + oauth2 (1.4.4) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + oj (3.10.16) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) + omniauth-facebook (8.0.0) + omniauth-oauth2 (~> 1.2) + omniauth-github (1.4.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-google-oauth2 (0.8.0) + jwt (>= 2.0) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.6) + omniauth-oauth (1.1.0) + oauth + omniauth (~> 1.0) + omniauth-oauth2 (1.7.0) + oauth2 (~> 1.4) + omniauth (~> 1.9) + omniauth-twitter (1.4.0) + omniauth-oauth (~> 1.1) + rack + onebox (2.2.1) + addressable (~> 2.7.0) + htmlentities (~> 4.3) + multi_json (~> 1.11) + mustache + nokogiri (~> 1.7) + sanitize + openssl-signature_algorithm (1.0.0) + optimist (3.0.1) + parallel (1.20.1) + parallel_tests (3.4.0) + parallel + parser (2.7.2.0) + ast (~> 2.4.1) + pg (1.2.3) + progress (3.5.2) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.9.0) + byebug (~> 11.0) + pry (~> 0.13.0) + pry-rails (0.3.9) + pry (>= 0.10.4) + public_suffix (4.0.6) + puma (5.0.4) + nio4r (~> 2.0) + r2 (0.2.7) + rack (2.2.3) + rack-mini-profiler (2.2.0) + rack (>= 1.2.0) + rack-protection (2.1.0) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + rails_failover (0.6.2) + activerecord (~> 6.0) + concurrent-ruby + railties (~> 6.0) + rails_multisite (2.5.0) + activerecord (> 5.0, < 7) + railties (> 5.0, < 7) + railties (6.0.3.3) + actionpack (= 6.0.3.3) + activesupport (= 6.0.3.3) + method_source + rake (>= 0.8.7) + thor (>= 0.20.3, < 2.0) + rainbow (3.0.0) + raindrops (0.19.1) + rake (13.0.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rbtrace (0.4.14) + ffi (>= 1.0.6) + msgpack (>= 0.4.3) + optimist (>= 3.0.0) + rchardet (1.8.0) + redis (4.2.5) + redis-namespace (1.8.0) + redis (>= 3.0.4) + regexp_parser (2.0.0) + request_store (1.5.0) + rack (>= 1.4) + rexml (3.2.4) + rinku (2.0.6) + rotp (6.2.0) + rqrcode (1.1.2) + chunky_png (~> 1.0) + rqrcode_core (~> 0.1) + rqrcode_core (0.1.2) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.0) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-html-matchers (0.9.4) + nokogiri (~> 1) + rspec (>= 3.0.0.a, < 4) + rspec-mocks (3.10.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.10.0) + rswag-specs (2.3.1) + activesupport (>= 3.1, < 7.0) + json-schema (~> 2.2) + railties (>= 3.1, < 7.0) + rtlit (0.0.5) + rubocop (1.4.2) + parallel (~> 1.10) + parser (>= 2.7.1.5) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8) + rexml + rubocop-ast (>= 1.1.1) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (1.2.0) + parser (>= 2.7.1.5) + rubocop-discourse (2.4.1) + rubocop (>= 1.1.0) + rubocop-rspec (>= 2.0.0) + rubocop-rspec (2.0.0) + rubocop (~> 1.0) + rubocop-ast (>= 1.1.0) + ruby-prof (1.4.2) + ruby-progressbar (1.10.1) + ruby-readability (0.7.0) + guess_html_encoding (>= 0.0.4) + nokogiri (>= 1.6.0) + ruby2_keywords (0.0.2) + rubyzip (2.3.0) + sanitize (5.2.1) + crass (~> 1.0.2) + nokogiri (>= 1.8.0) + nokogumbo (~> 2.0) + sassc (2.0.1) + ffi (~> 1.9) + rake + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + seed-fu (2.3.9) + activerecord (>= 3.1) + activesupport (>= 3.1) + shoulda-matchers (4.4.1) + activesupport (>= 4.2.0) + sidekiq (6.1.2) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + simplecov (0.20.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.2) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.2) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sshkey (2.0.0) + stackprof (0.2.16) + test-prof (0.12.2) + thor (1.0.1) + thread_safe (0.3.6) + tilt (2.0.10) + tzinfo (1.2.8) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + unicorn (5.7.0) + kgio (~> 2.6) + raindrops (~> 0.7) + uniform_notifier (1.13.0) + webmock (3.10.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webpush (1.1.0) + hkdf (~> 0.2) + jwt (~> 2.0) + xorcist (1.1.2) + yaml-lint (0.0.10) + zeitwerk (2.4.1) + +PLATFORMS + ruby + +DEPENDENCIES + actionmailer (= 6.0.3.3) + actionpack (= 6.0.3.3) + actionview (= 6.0.3.3) + actionview_precompiler + active_model_serializers (~> 0.8.3) + activemodel (= 6.0.3.3) + activerecord (= 6.0.3.3) + activesupport (= 6.0.3.3) + addressable + annotate + aws-sdk-s3 + aws-sdk-sns + barber + better_errors + binding_of_caller + bootsnap + bullet + byebug + cbor + certified + colored2 + cose + cppjieba_rb + css_parser + diffy + discourse-ember-rails (= 0.18.6) + discourse-ember-source (~> 3.12.2) + discourse-fonts + discourse_image_optim + email_reply_trimmer + ember-handlebars-template (= 0.8.0) + excon + execjs + fabrication + fakeweb + fast_blank + fast_xs + fastimage + flamegraph + gc_tracer + highline + htmlentities + http_accept_language + json + listen + lograge + logstash-event + logstash-logger + logster + lru_redux + lz4-ruby + mail + maxminddb + memory_profiler + message_bus + mini_mime + mini_racer + mini_scheduler + mini_sql + mini_suffix + minitest + mocha + mock_redis + multi_json + mustache + nokogiri + oj + omniauth + omniauth-facebook + omniauth-github + omniauth-google-oauth2 + omniauth-oauth2 + omniauth-twitter + onebox + parallel_tests + pg + pry-byebug + pry-rails + puma + r2 + rack + rack-mini-profiler + rack-protection + rails_failover + rails_multisite + railties (= 6.0.3.3) + rake + rb-fsevent + rbtrace + rchardet + redis + redis-namespace + rinku + rotp + rqrcode + rspec + rspec-html-matchers + rspec-rails + rswag-specs + rtlit + rubocop-discourse + ruby-prof + ruby-readability + rubyzip + sassc (= 2.0.1) + sassc-rails + seed-fu + shoulda-matchers + sidekiq + simplecov + sprockets (= 3.7.2) + sprockets-rails + sshkey + stackprof + test-prof + thor + uglifier + unf + unicorn + webmock + webpush + xorcist + yaml-lint + +BUNDLED WITH + 2.1.4 diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix b/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix new file mode 100644 index 00000000000..b2cf191a444 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix @@ -0,0 +1,2272 @@ +{ + actionmailer = { + dependencies = ["actionpack" "actionview" "activejob" "mail" "rails-dom-testing"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1spq0dbfn0qkqg9sq0rsjn360b4j36zly8hawaivkrwr3rsvyz75"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + actionpack = { + dependencies = ["actionview" "activesupport" "rack" "rack-test" "rails-dom-testing" "rails-html-sanitizer"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1p873nqwmpsvmkb5n86d70wndx1qhy15pc9mbcd1mc8sj174578b"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + actionview = { + dependencies = ["activesupport" "builder" "erubi" "rails-dom-testing" "rails-html-sanitizer"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "08pvmjddlw01q5r9zdfgddwp4csndpf5i2w47677z5r36jznz36q"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + actionview_precompiler = { + dependencies = ["actionview"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "07dx8vkwig8han4zccs0chahcf9ibd4abzx9n56qah8zak5cyrhd"; + type = "gem"; + }; + version = "0.2.3"; + }; + active_model_serializers = { + dependencies = ["activemodel"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0k3mgia2ahh7mbk30hjq9pzqbk0kh281s91kq2z6p555nv9y6l3k"; + type = "gem"; + }; + version = "0.8.4"; + }; + activejob = { + dependencies = ["activesupport" "globalid"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0w54ckvc229iaax879hkhyc93j7z8p0v7acp6mk3h8xjfvmwy5jp"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + activemodel = { + dependencies = ["activesupport"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "166jlx6kbby01vr37srh081a9fykgsz873yg5i9gl2ar3vw9gs56"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + activerecord = { + dependencies = ["activemodel" "activesupport"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0y2a4ss6ld6yrhpcbcb3kjn5gj6zk9qklp2aq5rl1awl8vbdbdb7"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + activesupport = { + dependencies = ["concurrent-ruby" "i18n" "minitest" "tzinfo" "zeitwerk"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dmkqbvndbz011a1byg6f990936vfadbnwjwjw9vjzr4kd8bxk96"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + addressable = { + dependencies = ["public_suffix"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1fvchp2rhp2rmigx7qglf69xvjqvzq7x0g49naliw29r2bz656sy"; + type = "gem"; + }; + version = "2.7.0"; + }; + annotate = { + dependencies = ["activerecord" "rake"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dxrfppwfg13vqmambbs56xjj8qsdgcy58r2yc44vvy3z1g5yflw"; + type = "gem"; + }; + version = "3.1.1"; + }; + ast = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1l3468czzjmxl93ap40hp7z94yxp4nbag0bxqs789bm30md90m2a"; + type = "gem"; + }; + version = "2.4.1"; + }; + aws-eventstream = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0r0pn66yqrdkrfdin7qdim0yj2x75miyg4wp6mijckhzhrjb7cv5"; + type = "gem"; + }; + version = "1.1.0"; + }; + aws-partitions = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "17xranmng1mg6238zdmnfvaig82r2ymp2apra9yh5d8rhvn8hkwm"; + type = "gem"; + }; + version = "1.390.0"; + }; + aws-sdk-core = { + dependencies = ["aws-eventstream" "aws-partitions" "aws-sigv4" "jmespath"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05dq7jfv5qf1y00ib96nqsipf08hflw8n8fwkyjw4qav84wjqaq4"; + type = "gem"; + }; + version = "3.109.2"; + }; + aws-sdk-kms = { + dependencies = ["aws-sdk-core" "aws-sigv4"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0ly1m631qm2ciif7sysbzrgczjvz95ga3g6w6vrzvfdv31jjnl9a"; + type = "gem"; + }; + version = "1.39.0"; + }; + aws-sdk-s3 = { + dependencies = ["aws-sdk-core" "aws-sdk-kms" "aws-sigv4"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1mld0yh6q6i2nbb143g5xc6gm70sqpvpwxfknlihrd8jmw3xc0bs"; + type = "gem"; + }; + version = "1.83.2"; + }; + aws-sdk-sns = { + dependencies = ["aws-sdk-core" "aws-sigv4"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dw80ldqhb1mny5irgi2jh36hykcmyd07xalv21xncxqzmf8aiag"; + type = "gem"; + }; + version = "1.35.0"; + }; + aws-sigv4 = { + dependencies = ["aws-eventstream"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ll9382c1x2hp750cilh01h1cycgyhdr4cmmgx23k94hyyb8chv5"; + type = "gem"; + }; + version = "1.2.2"; + }; + barber = { + dependencies = ["ember-source" "execjs"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "07rnlbh7kgamcbnl1sqlcdrjj8src4qc687klqq4a3vqq2slnscx"; + type = "gem"; + }; + version = "0.12.2"; + }; + better_errors = { + dependencies = ["coderay" "erubi" "rack"]; + groups = ["development"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "11220lfzhsyf5fcril3qd689kgg46qlpiiaj00hc9mh4mcbc3vrr"; + type = "gem"; + }; + version = "2.9.1"; + }; + binding_of_caller = { + dependencies = ["debug_inspector"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05syqlks7463zsy1jdfbbdravdhj9hpj5pv2m74blqpv8bq4vv5g"; + type = "gem"; + }; + version = "0.8.0"; + }; + bootsnap = { + dependencies = ["msgpack"]; + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qx1f729bgh391agsqb4ngzn22wdn4cc6mkp0cipf0d5hsg9cpaq"; + type = "gem"; + }; + version = "1.5.1"; + }; + builder = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "045wzckxpwcqzrjr353cxnyaxgf0qg22jh00dcx7z38cys5g1jlr"; + type = "gem"; + }; + version = "3.2.4"; + }; + bullet = { + dependencies = ["activesupport" "uniform_notifier"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "18ifwnvn13755qkfigapyj5bflpby3phxzbb7x5336d0kzv5k7d9"; + type = "gem"; + }; + version = "6.1.0"; + }; + byebug = { + groups = ["development" "test"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0nx3yjf4xzdgb8jkmk2344081gqr22pgjqnmjg2q64mj5d6r9194"; + type = "gem"; + }; + version = "11.1.3"; + }; + cbor = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0511idr8xps9625nh3kxr68sdy6l3xy2kcz7r57g47fxb1v18jj3"; + type = "gem"; + }; + version = "0.5.9.6"; + }; + certified = { + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1706p6p0a8adyvd943af2a3093xakvislgffw3v9dvp7j07dyk5a"; + type = "gem"; + }; + version = "1.0.0"; + }; + chunky_png = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1v52ndgx9r4jybq8yzr8anzfbnjk4y2hvz97nm9924wi4bad3xkf"; + type = "gem"; + }; + version = "1.3.14"; + }; + coderay = { + groups = ["default" "development"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0jvxqxzply1lwp7ysn94zjhh57vc14mcshw1ygw14ib8lhc00lyw"; + type = "gem"; + }; + version = "1.1.3"; + }; + colored2 = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0jlbqa9q4mvrm73aw9mxh23ygzbjiqwisl32d8szfb5fxvbjng5i"; + type = "gem"; + }; + version = "3.1.2"; + }; + concurrent-ruby = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vnxrbhi7cq3p4y2v9iwd10v1c7l15is4var14hwnb2jip4fyjzz"; + type = "gem"; + }; + version = "1.1.7"; + }; + connection_pool = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qikl4av1z8kqnk5ba18136dpqzw8wjawc2w9b4zb5psdd5z8nwf"; + type = "gem"; + }; + version = "2.2.3"; + }; + cose = { + dependencies = ["cbor" "openssl-signature_algorithm"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1gx239d2fracq9az74wfdwmp5zm7zpzkcgchwnv2ng33d8r33p3m"; + type = "gem"; + }; + version = "1.2.0"; + }; + cppjieba_rb = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1sslff7yy8jvp4rcn1b6jn9v0d3iibb68i79shgd94rs2yq8k117"; + type = "gem"; + }; + version = "0.3.3"; + }; + crack = { + groups = ["default" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1awi8jy4jn0f7vxpdvz3xvn1zzjbjh33n28lfkijh77dla5zb7lc"; + type = "gem"; + }; + version = "0.4.4"; + }; + crass = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0pfl5c0pyqaparxaqxi6s4gfl21bdldwiawrc0aknyvflli60lfw"; + type = "gem"; + }; + version = "1.0.6"; + }; + css_parser = { + dependencies = ["addressable"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "04c4dl8cm5rjr50k9qa6yl9r05fk9zcb1zxh0y0cdahxlsgcydfw"; + type = "gem"; + }; + version = "1.7.1"; + }; + debug_inspector = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0vxr0xa1mfbkfcrn71n7c4f2dj7la5hvphn904vh20j3x4j5lrx0"; + type = "gem"; + }; + version = "0.0.3"; + }; + diff-lcs = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0m925b8xc6kbpnif9dldna24q1szg4mk0fvszrki837pfn46afmz"; + type = "gem"; + }; + version = "1.4.4"; + }; + diffy = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0nrg7kpgz6cn1gv2saj2fa5sfiykamvd7vn9lw2v625k7pjwf31l"; + type = "gem"; + }; + version = "3.4.0"; + }; + discourse-ember-rails = { + dependencies = ["active_model_serializers" "ember-data-source" "ember-handlebars-template" "ember-source" "jquery-rails" "railties"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0ax5x2d6q6hkm7r58ai9p0sahlg842aqlm7dpv6svrfpnjlaz7sf"; + type = "gem"; + }; + version = "0.18.6"; + }; + discourse-ember-source = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0mqkwiqb5n64lc5jdjnmpgb9apq08ywkz9yk8mj1sx2lqcsw11pc"; + type = "gem"; + }; + version = "3.12.2.2"; + }; + discourse-fonts = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0xhwgqclh3jncjr55m0hyq3w3iw8jw2r7ickzq1zn1282pc3n2i7"; + type = "gem"; + }; + version = "0.0.5"; + }; + discourse_image_optim = { + dependencies = ["exifr" "fspath" "image_size" "in_threads" "progress"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "11nqmga5ygxyhjmsc07gsa0fwwyhdpwi20yyr4fnh263xs1xylvv"; + type = "gem"; + }; + version = "0.26.2"; + }; + docile = { + groups = ["default" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0qrwiyagxzl8zlx3dafb0ay8l14ib7imb2rsmx70i5cp420v8gif"; + type = "gem"; + }; + version = "1.3.2"; + }; + email_reply_trimmer = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1jgcxifm48xq5dz9k47q43pqm5bfnf14l62l3bqhmv8f6z8dw4ki"; + type = "gem"; + }; + version = "0.1.13"; + }; + ember-data-source = { + dependencies = ["ember-source"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1803nh3knvwl12h63jd48qvbbrp42yy291wcb35960daklip0fd8"; + type = "gem"; + }; + version = "3.0.2"; + }; + ember-handlebars-template = { + dependencies = ["barber" "sprockets"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1wxj3vi4xs3vjxrdbzi4j4w6vv45r5dkz2rg2ldid3p8dp3irlf4"; + type = "gem"; + }; + version = "0.8.0"; + }; + ember-source = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0sixy30ym9j2slhlr0lfq943g958w8arlb0lsizh59iv1w5gmxxy"; + type = "gem"; + }; + version = "2.18.2"; + }; + erubi = { + groups = ["default" "development" "test"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "09l8lz3j00m898li0yfsnb6ihc63rdvhw3k5xczna5zrjk104f2l"; + type = "gem"; + }; + version = "1.10.0"; + }; + excon = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1hi89v53pm2abfv9j8lgqdd7hgkr7fr0gwrczr940iwbb3xv7rrs"; + type = "gem"; + }; + version = "0.78.0"; + }; + execjs = { + groups = ["assets" "default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1yz55sf2nd3l666ms6xr18sm2aggcvmb8qr3v53lr4rir32y1yp1"; + type = "gem"; + }; + version = "2.7.0"; + }; + exifr = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0mylhwmh6n4xihxr9s3zj0lc286f5maxbqd4dgk3paqnd7afz88s"; + type = "gem"; + }; + version = "1.3.9"; + }; + fabrication = { + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pdrl55xf76pbc5kjzp7diawxxvgbk2cm38532in6df823431n6z"; + type = "gem"; + }; + version = "2.21.1"; + }; + fakeweb = { + groups = ["test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1a09z9nb369bvwpghncgd5y4f95lh28w0q258srh02h22fz9dj8y"; + type = "gem"; + }; + version = "1.3.0"; + }; + faraday = { + dependencies = ["multipart-post" "ruby2_keywords"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "16dapwi5pivrl25r4lkr1mxjrzkznj4wlcb08fzkmxnj4g5c6y35"; + type = "gem"; + }; + version = "1.1.0"; + }; + fast_blank = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "16s1ilyvwzmkcgmklbrn0c2pch5n02vf921njx0bld4crgdr6z56"; + type = "gem"; + }; + version = "1.0.0"; + }; + fast_xs = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1iydzaqmvqq7ncxkr182aybkk6xap0cb2w9amr73vbdxi2qf3wjz"; + type = "gem"; + }; + version = "0.8.0"; + }; + fastimage = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "11ny2pj0j6pljszrf1w3iqdv2pcl2iwwghjbgcjlizy424zbh0hb"; + type = "gem"; + }; + version = "2.2.0"; + }; + ffi = { + groups = ["default" "development" "test"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12lpwaw82bb0rm9f52v1498bpba8aj2l2q359mkwbxsswhpga5af"; + type = "gem"; + }; + version = "1.13.1"; + }; + flamegraph = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1p785nmhdzbwj0qpxn5fzrmr4kgimcds83v4f95f387z6w3050x6"; + type = "gem"; + }; + version = "0.9.5"; + }; + fspath = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0xcxikkrjv8ws328nn5ax5pyfjs8pn7djg1hks7qyb3yp6prpb5m"; + type = "gem"; + }; + version = "3.1.2"; + }; + gc_tracer = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1yv3mp8lx74lfzs04fd5h4g89209iwhzpc407y35p7cmzgx6a4kv"; + type = "gem"; + }; + version = "1.5.1"; + }; + globalid = { + dependencies = ["activesupport"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zkxndvck72bfw235bd9nl2ii0lvs5z88q14706cmn702ww2mxv1"; + type = "gem"; + }; + version = "0.4.2"; + }; + guess_html_encoding = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "16700fk6kmif3q3kpc1ldhy3nsc9pkxlgl8sqhznff2zjj5lddna"; + type = "gem"; + }; + version = "0.0.11"; + }; + hashdiff = { + groups = ["default" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1nynpl0xbj0nphqx1qlmyggq58ms1phf5i03hk64wcc0a17x1m1c"; + type = "gem"; + }; + version = "1.0.1"; + }; + hashie = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "02bsx12ihl78x0vdm37byp78jjw2ff6035y7rrmbd90qxjwxr43q"; + type = "gem"; + }; + version = "4.1.0"; + }; + highline = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0yclf57n2j3cw8144ania99h1zinf8q3f5zrhqa754j6gl95rp9d"; + type = "gem"; + }; + version = "2.0.3"; + }; + hkdf = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "04fixg0a51n4vy0j6c1hvisa2yl33m3jrrpxpb5sq6j511vjriil"; + type = "gem"; + }; + version = "0.3.0"; + }; + htmlentities = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1nkklqsn8ir8wizzlakncfv42i32wc0w9hxp00hvdlgjr7376nhj"; + type = "gem"; + }; + version = "4.3.4"; + }; + http_accept_language = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0d0nlfz9vm4jr1l6q0chx4rp2hrnrfbx3gadc1dz930lbbaz0hq0"; + type = "gem"; + }; + version = "2.1.1"; + }; + i18n = { + dependencies = ["concurrent-ruby"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "153sx77p16vawrs4qpkv7qlzf9v5fks4g7xqcj1dwk40i6g7rfzk"; + type = "gem"; + }; + version = "1.8.5"; + }; + image_size = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0zrn2mqaf1kk548wn1y35i1a6kwh3320q62m929kn9m8sqpy4fk7"; + type = "gem"; + }; + version = "1.5.0"; + }; + in_threads = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0m71806p1gm4kxiz4gvkyr8qip16hifn2kdf926jz44jj6kc6bbs"; + type = "gem"; + }; + version = "1.5.4"; + }; + jmespath = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1d4wac0dcd1jf6kc57891glih9w57552zgqswgy74d1xhgnk0ngf"; + type = "gem"; + }; + version = "1.4.0"; + }; + jquery-rails = { + dependencies = ["rails-dom-testing" "railties" "thor"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0dkhm8lan1vnyl3ll0ks2q06576pdils8a1dr354vfc1y5dqw15i"; + type = "gem"; + }; + version = "4.4.0"; + }; + json = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "158fawfwmv2sq4whqqaksfykkiad2xxrrj0nmpnc6vnlzi1bp7iz"; + type = "gem"; + }; + version = "2.3.1"; + }; + json-schema = { + dependencies = ["addressable"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1yv5lfmr2nzd14af498xqd5p89f3g080q8wk0klr3vxgypsikkb5"; + type = "gem"; + }; + version = "2.8.1"; + }; + jwt = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "14ynyq1q483spj20ffl4xayfqx1a8qr761mqjfxczf8lwlap392n"; + type = "gem"; + }; + version = "2.2.2"; + }; + kgio = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0ai6bzlvxbzpdl466p1qi4dlhx8ri2wcrp6x1l19y3yfs3a29rng"; + type = "gem"; + }; + version = "2.11.3"; + }; + libv8 = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0317sr3nrl51sp844bps71smkrwim3fjn47wdfpbycixnbxspivm"; + type = "gem"; + }; + version = "8.4.255.0"; + }; + listen = { + dependencies = ["rb-fsevent" "rb-inotify"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0028p1fss6pvw4mlpjqdmxfzsm8ww79irsadbibrr7f23qfn8ykr"; + type = "gem"; + }; + version = "3.3.1"; + }; + lograge = { + dependencies = ["actionpack" "activesupport" "railties" "request_store"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vrjm4yqn5l6q5gsl72fmk95fl6j9z1a05gzbrwmsm3gp1a1bgac"; + type = "gem"; + }; + version = "0.11.2"; + }; + logstash-event = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1bk7fhhryjxp1klr3hq6i6srrc21wl4p980bysjp0w66z9hdr9w9"; + type = "gem"; + }; + version = "1.2.02"; + }; + logstash-logger = { + dependencies = ["logstash-event"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1nh0jgz4rl46axqb9l0fa866kh34wb7yf11qc3j30xhprdqb8yjp"; + type = "gem"; + }; + version = "0.26.1"; + }; + logster = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ldikj3p0bakxg57didaw05pldjn0i5r20zawhqa34knlsqm66r6"; + type = "gem"; + }; + version = "2.9.4"; + }; + loofah = { + dependencies = ["crass" "nokogiri"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0ndimir6k3kfrh8qrb7ir1j836l4r3qlwyclwjh88b86clblhszh"; + type = "gem"; + }; + version = "2.8.0"; + }; + lru_redux = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1yxghzg7476sivz8yyr9nkak2dlbls0b89vc2kg52k0nmg6d0wgf"; + type = "gem"; + }; + version = "1.1.0"; + }; + lz4-ruby = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12fymsvcb9kw6ycyfzc8b9svriq0afqf1qnl121xrz8c4gpfa6q1"; + type = "gem"; + }; + version = "0.3.3"; + }; + mail = { + dependencies = ["mini_mime"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "00wwz6ys0502dpk8xprwcqfwyf3hmnx6lgxaiq6vj43mkx43sapc"; + type = "gem"; + }; + version = "2.7.1"; + }; + maxminddb = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0zlhqilyggiryywgswfi624bv10qnkm66hggmg79vvgv73j3p4sh"; + type = "gem"; + }; + version = "0.1.22"; + }; + memory_profiler = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "04ivhv1bilwqm33jv28gar2vwzsichb5nipaq395d3axabv8qmfy"; + type = "gem"; + }; + version = "0.9.14"; + }; + message_bus = { + dependencies = ["rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0hckijk9aa628nx66vr7axfsk7zfdkskaxj1mdzikk019q3h54fr"; + type = "gem"; + }; + version = "3.3.4"; + }; + method_source = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pnyh44qycnf9mzi1j6fywd5fkskv3x7nmsqrrws0rjn5dd4ayfp"; + type = "gem"; + }; + version = "1.0.0"; + }; + mini_mime = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1axm0rxyx3ss93wbmfkm78a6x03l8y4qy60rhkkiq0aza0vwq3ha"; + type = "gem"; + }; + version = "1.0.2"; + }; + mini_portile2 = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15zplpfw3knqifj9bpf604rb3wc1vhq6363pd6lvhayng8wql5vy"; + type = "gem"; + }; + version = "2.4.0"; + }; + mini_racer = { + dependencies = ["libv8"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0r7j241mvhyyc017bqgp0pvf3jyrwbcqvz2pzm0r8zn2r85ks1jl"; + type = "gem"; + }; + version = "0.3.1"; + }; + mini_scheduler = { + dependencies = ["sidekiq"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0vigv7f1q5bkcb55ab2lyhq15yqfkg5mq61p7m7mw9b3jac7qjz1"; + type = "gem"; + }; + version = "0.12.3"; + }; + mini_sql = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0qi4bj5jkh3673ybsxvsf7y485znyxb72vxg84gk9x65mf0y0m6h"; + type = "gem"; + }; + version = "0.3"; + }; + mini_suffix = { + dependencies = ["ffi"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0bxd1fgzb20gvfvhbkrxym9fr7skm5x6fzvqfg4a0jijb34ww50h"; + type = "gem"; + }; + version = "0.3.0"; + }; + minitest = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "170y2cvx51gm3cm3nhdf7j36sxnkh6vv8ls36p90ric7w8w16h4v"; + type = "gem"; + }; + version = "5.14.2"; + }; + mocha = { + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0hxmkm8qxd04vwj8mqnpyrf2dwy7g1k9zipdfhl4y71cw7ijm9n4"; + type = "gem"; + }; + version = "1.11.2"; + }; + mock_redis = { + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "06yj6j9x4zjckah4ixiwhy3hb6xzjp7yk7lmmcvcb8hpd0z0x95q"; + type = "gem"; + }; + version = "0.26.0"; + }; + msgpack = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1lva6bkvb4mfa0m3bqn4lm4s4gi81c40jvdcsrxr6vng49q9daih"; + type = "gem"; + }; + version = "1.3.3"; + }; + multi_json = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0pb1g1y3dsiahavspyzkdy39j4q377009f6ix0bh1ag4nqw43l0z"; + type = "gem"; + }; + version = "1.15.0"; + }; + multi_xml = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0lmd4f401mvravi1i1yq7b2qjjli0yq7dfc4p1nj5nwajp7r6hyj"; + type = "gem"; + }; + version = "0.6.0"; + }; + multipart-post = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zgw9zlwh2a6i1yvhhc4a84ry1hv824d6g2iw2chs3k5aylpmpfj"; + type = "gem"; + }; + version = "2.1.1"; + }; + mustache = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1l0p4wx15mi3wnamfv92ipkia4nsx8qi132c6g51jfdma3fiz2ch"; + type = "gem"; + }; + version = "1.1.1"; + }; + nio4r = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1cbwp1kbv6b2qfxv8sarv0d0ilb257jihlvdqj8f5pdm0ksq1sgk"; + type = "gem"; + }; + version = "2.5.4"; + }; + nokogiri = { + dependencies = ["mini_portile2"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0xmf60nj5kg9vaj5bysy308687sgmkasgx06vbbnf94p52ih7si2"; + type = "gem"; + }; + version = "1.10.10"; + }; + nokogumbo = { + dependencies = ["nokogiri"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0sxjnpjvrn10gdmfw2dimhch861lz00f28hvkkz0b1gc2rb65k9s"; + type = "gem"; + }; + version = "2.0.2"; + }; + oauth = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zszdg8q1b135z7l7crjj234k4j0m347hywp5kj6zsq7q78pw09y"; + type = "gem"; + }; + version = "0.5.4"; + }; + oauth2 = { + dependencies = ["faraday" "jwt" "multi_json" "multi_xml" "rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1bhakjh30vi8scqwnhd1c9qkac9r8hh2lr0dbs5ynwmrc5djxknm"; + type = "gem"; + }; + version = "1.4.4"; + }; + oj = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1xqmzqldi9a0wpilwx87yh61xd7647gg8ffammg4ava0bsx375g2"; + type = "gem"; + }; + version = "3.10.16"; + }; + omniauth = { + dependencies = ["hashie" "rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "002vi9gwamkmhf0dsj2im1d47xw2n1jfhnzl18shxf3ampkqfmyz"; + type = "gem"; + }; + version = "1.9.1"; + }; + omniauth-facebook = { + dependencies = ["omniauth-oauth2"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1z0f5sr2ddnvfva0jrfd4926nlv4528rfj7z595288n39304r092"; + type = "gem"; + }; + version = "8.0.0"; + }; + omniauth-github = { + dependencies = ["omniauth" "omniauth-oauth2"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0xbk0dbxqfpyfb33ghz6vrlz3m6442rp18ryf13gwzlnifcawhlb"; + type = "gem"; + }; + version = "1.4.0"; + }; + omniauth-google-oauth2 = { + dependencies = ["jwt" "omniauth" "omniauth-oauth2"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "17pgqasl048irs2c6w6g57zvk0ygb5ml1krwir4qi4b6y53zyr55"; + type = "gem"; + }; + version = "0.8.0"; + }; + omniauth-oauth = { + dependencies = ["oauth" "omniauth"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1n5vk4by7hkyc09d9blrw2argry5awpw4gbw1l4n2s9b3j4qz037"; + type = "gem"; + }; + version = "1.1.0"; + }; + omniauth-oauth2 = { + dependencies = ["oauth2" "omniauth"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0v6rw7sd223k7qw0l13wikgfcqbvbk81r53a9i2z0k7jl5vd97w5"; + type = "gem"; + }; + version = "1.7.0"; + }; + omniauth-twitter = { + dependencies = ["omniauth-oauth" "rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0r5j65hkpgzhvvbs90id3nfsjgsad6ymzggbm7zlaxvnrmvnrk65"; + type = "gem"; + }; + version = "1.4.0"; + }; + onebox = { + dependencies = ["addressable" "htmlentities" "multi_json" "mustache" "nokogiri" "sanitize"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0b2aih0d5cva9bris36gh1mk3ym61wgxlpwvzjd6qphdrjfzqx8v"; + type = "gem"; + }; + version = "2.2.1"; + }; + openssl-signature_algorithm = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0h1pfx49j8d9vbdbi8jyj0mr63l7rhflgvgc0nhfygm1v77d7nkn"; + type = "gem"; + }; + version = "1.0.0"; + }; + optimist = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vg2chy1cfmdj6c1gryl8zvjhhmb3plwgyh1jfnpq4fnfqv7asrk"; + type = "gem"; + }; + version = "3.0.1"; + }; + parallel = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0055br0mibnqz0j8wvy20zry548dhkakws681bhj3ycb972awkzd"; + type = "gem"; + }; + version = "1.20.1"; + }; + parallel_tests = { + dependencies = ["parallel"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1mvdk8vgzqjv2pvadxwc8w2vf8dmiw145rjf47c36nn6l5hh02j6"; + type = "gem"; + }; + version = "3.4.0"; + }; + parser = { + dependencies = ["ast"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1f7gmm60yla325wlnd3qkxs59qm2y0aan8ljpg6k18rwzrrfil6z"; + type = "gem"; + }; + version = "2.7.2.0"; + }; + pg = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "13mfrysrdrh8cka1d96zm0lnfs59i5x2g6ps49r2kz5p3q81xrzj"; + type = "gem"; + }; + version = "1.2.3"; + }; + progress = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pm3bv5n8c8j0vfm7wghd7xf6yq4m068cksxjldmna11qi0h0s8s"; + type = "gem"; + }; + version = "3.5.2"; + }; + pry = { + dependencies = ["coderay" "method_source"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0iyw4q4an2wmk8v5rn2ghfy2jaz9vmw2nk8415nnpx2s866934qk"; + type = "gem"; + }; + version = "0.13.1"; + }; + pry-byebug = { + dependencies = ["byebug" "pry"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "096y5vmzpyy4x9h4ky4cs4y7d19vdq9vbwwrqafbh5gagzwhifiv"; + type = "gem"; + }; + version = "3.9.0"; + }; + pry-rails = { + dependencies = ["pry"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1cf4ii53w2hdh7fn8vhqpzkymmchjbwij4l3m7s6fsxvb9bn51j6"; + type = "gem"; + }; + version = "0.3.9"; + }; + public_suffix = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1xqcgkl7bwws1qrlnmxgh8g4g9m10vg60bhlw40fplninb3ng6d9"; + type = "gem"; + }; + version = "4.0.6"; + }; + puma = { + dependencies = ["nio4r"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0mkmfbf4qyiknwi9bb5432cpbbz06r855gknxb8grn24gmgs4d9i"; + type = "gem"; + }; + version = "5.0.4"; + }; + r2 = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wk0p55zp3l96xy5ps28b33dn5z0jwsjl74bwfdn6z81pzjs5sfk"; + type = "gem"; + }; + version = "0.2.7"; + }; + rack = { + groups = ["default" "development" "test"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0i5vs0dph9i5jn8dfc6aqd6njcafmb20rwqngrf759c9cvmyff16"; + type = "gem"; + }; + version = "2.2.3"; + }; + rack-mini-profiler = { + dependencies = ["rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05s7y56ayn56bn7y5ah3krm5d53vsj7apmcxlwc2qp7ik0xlypvq"; + type = "gem"; + }; + version = "2.2.0"; + }; + rack-protection = { + dependencies = ["rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "159a4j4kragqh0z0z8vrpilpmaisnlz3n7kgiyf16bxkwlb3qlhz"; + type = "gem"; + }; + version = "2.1.0"; + }; + rack-test = { + dependencies = ["rack"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0rh8h376mx71ci5yklnpqqn118z3bl67nnv5k801qaqn1zs62h8m"; + type = "gem"; + }; + version = "1.1.0"; + }; + rails-dom-testing = { + dependencies = ["activesupport" "nokogiri"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1lfq2a7kp2x64dzzi5p4cjcbiv62vxh9lyqk2f0rqq3fkzrw8h5i"; + type = "gem"; + }; + version = "2.0.3"; + }; + rails-html-sanitizer = { + dependencies = ["loofah"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1icpqmxbppl4ynzmn6dx7wdil5hhq6fz707m9ya6d86c7ys8sd4f"; + type = "gem"; + }; + version = "1.3.0"; + }; + rails_failover = { + dependencies = ["activerecord" "concurrent-ruby" "railties"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0ibxn7lk6rqk7q76cd9ir3xnh19p2pqr9mzam46n3h37f12yyax5"; + type = "gem"; + }; + version = "0.6.2"; + }; + rails_multisite = { + dependencies = ["activerecord" "railties"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0p7g9gkcmw030zfqlw3k933i40j31wf3jh4bj1niihzk7slha97y"; + type = "gem"; + }; + version = "2.5.0"; + }; + railties = { + dependencies = ["actionpack" "activesupport" "method_source" "rake" "thor"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05b79r0ms8jrs91zml1190qfxmnmks90g0sd820ks9msyr8xdp7j"; + type = "gem"; + }; + version = "6.0.3.3"; + }; + rainbow = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0bb2fpjspydr6x0s8pn1pqkzmxszvkfapv0p4627mywl7ky4zkhk"; + type = "gem"; + }; + version = "3.0.0"; + }; + raindrops = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0zjja00mzgx2lddb7qrn14k7qrnwhf4bpmnlqj78m1pfxh7svync"; + type = "gem"; + }; + version = "0.19.1"; + }; + rake = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0w6qza25bq1s825faaglkx1k6d59aiyjjk3yw3ip5sb463mhhai9"; + type = "gem"; + }; + version = "13.0.1"; + }; + rb-fsevent = { + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1k9bsj7ni0g2fd7scyyy1sk9dy2pg9akniahab0iznvjmhn54h87"; + type = "gem"; + }; + version = "0.10.4"; + }; + rb-inotify = { + dependencies = ["ffi"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1jm76h8f8hji38z3ggf4bzi8vps6p7sagxn3ab57qc0xyga64005"; + type = "gem"; + }; + version = "0.10.1"; + }; + rbtrace = { + dependencies = ["ffi" "msgpack" "optimist"]; + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0s8prj0klfgpmpfcpdzbf149qrrsdxgnb6w6kkqc9gyars4vyaqn"; + type = "gem"; + }; + version = "0.4.14"; + }; + rchardet = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1isj1b3ywgg2m1vdlnr41lpvpm3dbyarf1lla4dfibfmad9csfk9"; + type = "gem"; + }; + version = "1.8.0"; + }; + redis = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15x2sr6h094rjbvg8pkq6m3lcd5abpyx93aifvfdz3wv6x55xa48"; + type = "gem"; + }; + version = "4.2.5"; + }; + redis-namespace = { + dependencies = ["redis"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05i6s898z5w31z385cba1683pgg5nnmj4m686cbravg7j4pgbcgv"; + type = "gem"; + }; + version = "1.8.0"; + }; + regexp_parser = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1racz3w9s4w0ls32bvjypfifk4a7qxngm2cv1rh16jyz0c1wjd70"; + type = "gem"; + }; + version = "2.0.0"; + }; + request_store = { + dependencies = ["rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cx74kispmnw3ljwb239j65a2j14n8jlsygy372hrsa8mxc71hxi"; + type = "gem"; + }; + version = "1.5.0"; + }; + rexml = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1mkvkcw9fhpaizrhca0pdgjcrbns48rlz4g6lavl5gjjq3rk2sq3"; + type = "gem"; + }; + version = "3.2.4"; + }; + rinku = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0zcdha17s1wzxyc5814j6319wqg33jbn58pg6wmxpws36476fq4b"; + type = "gem"; + }; + version = "2.0.6"; + }; + rotp = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "11q7rkjx40yi6lpylgl2jkpy162mjw7mswrcgcax86vgpbpjx6i3"; + type = "gem"; + }; + version = "6.2.0"; + }; + rqrcode = { + dependencies = ["chunky_png" "rqrcode_core"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "06lw8b6wfshxd61xw98xyp1a0zsz6av4nls2c9fwb7q59wb05sci"; + type = "gem"; + }; + version = "1.1.2"; + }; + rqrcode_core = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "071jqmhk3hf0grsvi0jx5sl449pf82p40ls5b3likbq4q516zc0j"; + type = "gem"; + }; + version = "0.1.2"; + }; + rspec = { + dependencies = ["rspec-core" "rspec-expectations" "rspec-mocks"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dwai7jnwmdmd7ajbi2q0k0lx1dh88knv5wl7c34wjmf94yv8w5q"; + type = "gem"; + }; + version = "3.10.0"; + }; + rspec-core = { + dependencies = ["rspec-support"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0n2rdv8f26yw8c6asymc0mgddyr5d2b5n6mfvpd3n6lnpf1jdyv2"; + type = "gem"; + }; + version = "3.10.0"; + }; + rspec-expectations = { + dependencies = ["diff-lcs" "rspec-support"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0j37dvnvfbjwj8dqx27yfvz0frl7f2jc1abqg99h0ppriz9za6dc"; + type = "gem"; + }; + version = "3.10.0"; + }; + rspec-html-matchers = { + dependencies = ["nokogiri" "rspec"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0883rqv77n2wawnk5lp3la48l7pckyz8l013qddngzmksi5p1v3f"; + type = "gem"; + }; + version = "0.9.4"; + }; + rspec-mocks = { + dependencies = ["diff-lcs" "rspec-support"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pz89y1522i6f8wzrg72ykmch3318ih87nlpl0y1ghsrs5hqymw3"; + type = "gem"; + }; + version = "3.10.0"; + }; + rspec-rails = { + dependencies = ["actionpack" "activesupport" "railties" "rspec-core" "rspec-expectations" "rspec-mocks" "rspec-support"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0lzik01ziaskgpdpy8knffpw0fsy9151f5lfigyhb89wq4q45hfs"; + type = "gem"; + }; + version = "4.0.1"; + }; + rspec-support = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0j0n28i6zci5j7gg370bdy87dy43hlwx6dw428d9kamf5a0i2klz"; + type = "gem"; + }; + version = "3.10.0"; + }; + rswag-specs = { + dependencies = ["activesupport" "json-schema" "railties"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0lyp2m76p960bvgy4xcz0dilp4w5lq2cwh8md5z7cwxdg8qsbr83"; + type = "gem"; + }; + version = "2.3.1"; + }; + rtlit = { + groups = ["assets"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0srfh7cl95srjiwbyc9pmn3w739zlvyj89hyj0bm7g92zrsd27qm"; + type = "gem"; + }; + version = "0.0.5"; + }; + rubocop = { + dependencies = ["parallel" "parser" "rainbow" "regexp_parser" "rexml" "rubocop-ast" "ruby-progressbar" "unicode-display_width"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1kvzhzhzcdd5bqwjilb0fpp51sqjniww2b0g713n0cvhnlgchn2y"; + type = "gem"; + }; + version = "1.4.2"; + }; + rubocop-ast = { + dependencies = ["parser"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0q0kdi89ad7dd1xmzrdf5ikk32bllzr68hf4x8fd7azcv5jnch2l"; + type = "gem"; + }; + version = "1.2.0"; + }; + rubocop-discourse = { + dependencies = ["rubocop" "rubocop-rspec"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1z1h8spsjnsqz6c25n9ib1yimkwr7a76bas8w1k9c404hcqhlahv"; + type = "gem"; + }; + version = "2.4.1"; + }; + rubocop-rspec = { + dependencies = ["rubocop" "rubocop-ast"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1gl7hdd9lq0si4gb510g33dbysmk3iydas2b0sbl5pwfkhv0k4g1"; + type = "gem"; + }; + version = "2.0.0"; + }; + ruby-prof = { + groups = ["development"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1lm3wdxc6gjldkb5pdwwipapf84lgrvxck4h5kg8jdfd8arrpyis"; + type = "gem"; + }; + version = "1.4.2"; + }; + ruby-progressbar = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1k77i0d4wsn23ggdd2msrcwfy0i376cglfqypkk2q77r2l3408zf"; + type = "gem"; + }; + version = "1.10.1"; + }; + ruby-readability = { + dependencies = ["guess_html_encoding" "nokogiri"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15ivhbry7hf82lww1bzcrwfyjymijfb3rb0wdd32g2z0942wdspa"; + type = "gem"; + }; + version = "0.7.0"; + }; + ruby2_keywords = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "17pcc0wgvh3ikrkr7bm3nx0qhyiqwidd13ij0fa50k7gsbnr2p0l"; + type = "gem"; + }; + version = "0.0.2"; + }; + rubyzip = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0590m2pr9i209pp5z4mx0nb1961ishdiqb28995hw1nln1d1b5ji"; + type = "gem"; + }; + version = "2.3.0"; + }; + sanitize = { + dependencies = ["crass" "nokogiri" "nokogumbo"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "18m3zcf207gcrmghx288w3n2kpphc22lbmbc1wdx1nzcn8g2yddh"; + type = "gem"; + }; + version = "5.2.1"; + }; + sassc = { + dependencies = ["ffi" "rake"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1sr4825rlwsrl7xrsm0sgalcpf5zgp4i56dbi3qxfa9lhs8r6zh4"; + type = "gem"; + }; + version = "2.0.1"; + }; + sassc-rails = { + dependencies = ["railties" "sassc" "sprockets" "sprockets-rails" "tilt"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1d9djmwn36a5m8a83bpycs48g8kh1n2xkyvghn7dr6zwh4wdyksz"; + type = "gem"; + }; + version = "2.1.2"; + }; + seed-fu = { + dependencies = ["activerecord" "activesupport"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0y7lzcshsq6i20qn1p8zczir4fivr6nbl1km91ns320vvh92v43d"; + type = "gem"; + }; + version = "2.3.9"; + }; + shoulda-matchers = { + dependencies = ["activesupport"]; + groups = ["development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1wd1bblxr4dfmrnh3j83kvfds6a7nak4ifq37ab0pg1kdi6iiw7l"; + type = "gem"; + }; + version = "4.4.1"; + }; + sidekiq = { + dependencies = ["connection_pool" "rack" "redis"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0mjxrxppv08a1hwqi8gpg6n168cxqhp7c2r2jwc4rbz9j5k41vcw"; + type = "gem"; + }; + version = "6.1.2"; + }; + simplecov = { + dependencies = ["docile" "simplecov-html" "simplecov_json_formatter"]; + groups = ["test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1mm20dvd64w46l5k11il9z5sjgdpp0bknml76glcngvl2w03k3cb"; + type = "gem"; + }; + version = "0.20.0"; + }; + simplecov-html = { + groups = ["default" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0yx01bxa8pbf9ip4hagqkp5m0mqfnwnw2xk8kjraiywz4lrss6jb"; + type = "gem"; + }; + version = "0.12.3"; + }; + simplecov_json_formatter = { + groups = ["default" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cl3j7p3b5q7sxsx1va63c8imc5x6g99xablz08qrmqhpi0d6g6j"; + type = "gem"; + }; + version = "0.1.2"; + }; + sprockets = { + dependencies = ["concurrent-ruby" "rack"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "182jw5a0fbqah5w9jancvfmjbk88h8bxdbwnl4d3q809rpxdg8ay"; + type = "gem"; + }; + version = "3.7.2"; + }; + sprockets-rails = { + dependencies = ["actionpack" "activesupport" "sprockets"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0mwmz36265646xqfyczgr1mhkm1hfxgxxvgdgr4xfcbf2g72p1k2"; + type = "gem"; + }; + version = "3.2.2"; + }; + sshkey = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "03bkn55qsng484iqwz2lmm6rkimj01vsvhwk661s3lnmpkl65lbp"; + type = "gem"; + }; + version = "2.0.0"; + }; + stackprof = { + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "147rb66p3n062vc433afqhkd99iazvkrqnghxgh871r62yhha93f"; + type = "gem"; + }; + version = "0.2.16"; + }; + test-prof = { + groups = ["test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1jfq8ylxpxanc3f0i6qb3nchawx9hj6qcqj6ccfyixrnvzswwjvi"; + type = "gem"; + }; + version = "0.12.2"; + }; + thor = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1xbhkmyhlxwzshaqa7swy2bx6vd64mm0wrr8g3jywvxy7hg0cwkm"; + type = "gem"; + }; + version = "1.0.1"; + }; + thread_safe = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0nmhcgq6cgz44srylra07bmaw99f5271l0dpsvl5f75m44l0gmwy"; + type = "gem"; + }; + version = "0.3.6"; + }; + tilt = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0rn8z8hda4h41a64l0zhkiwz2vxw9b1nb70gl37h1dg2k874yrlv"; + type = "gem"; + }; + version = "2.0.10"; + }; + tzinfo = { + dependencies = ["thread_safe"]; + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0skr6ih9cr3pwp8l84f0z7fy3q9kiq8hw0sg3zqw0hpbbyj05743"; + type = "gem"; + }; + version = "1.2.8"; + }; + uglifier = { + dependencies = ["execjs"]; + groups = ["assets"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wgh7bzy68vhv9v68061519dd8samcy8sazzz0w3k8kqpy3g4s5f"; + type = "gem"; + }; + version = "4.2.0"; + }; + unf = { + dependencies = ["unf_ext"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0bh2cf73i2ffh4fcpdn9ir4mhq8zi50ik0zqa1braahzadx536a9"; + type = "gem"; + }; + version = "0.1.4"; + }; + unf_ext = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wc47r23h063l8ysws8sy24gzh74mks81cak3lkzlrw4qkqb3sg4"; + type = "gem"; + }; + version = "0.0.7.7"; + }; + unicode-display_width = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "06i3id27s60141x6fdnjn5rar1cywdwy64ilc59cz937303q3mna"; + type = "gem"; + }; + version = "1.7.0"; + }; + unicorn = { + dependencies = ["kgio" "raindrops"]; + groups = ["default"]; + platforms = [{ + engine = "maglev"; + } { + engine = "rbx"; + } { + engine = "ruby"; + }]; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qzdhbmab2w034wpdj5ippnyyvgqm8gpx9wbchb4zgs4i1mswzhv"; + type = "gem"; + }; + version = "5.7.0"; + }; + uniform_notifier = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0vm4aix8jmv42s1x58m3lj3xwkbxyn9qn6lzhhig0d1j8fv6j30c"; + type = "gem"; + }; + version = "1.13.0"; + }; + webmock = { + dependencies = ["addressable" "crack" "hashdiff"]; + groups = ["test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wbdjagk2qpr76k3zw2gmkfp5aqlrc1a4qrpjv7sq1q39qbn8xax"; + type = "gem"; + }; + version = "3.10.0"; + }; + webpush = { + dependencies = ["hkdf" "jwt"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1z9ma580q80czw46gi1bvsr2iwxr63aiyr7i9gilav6hbhg3sxv3"; + type = "gem"; + }; + version = "1.1.0"; + }; + xorcist = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1q7hr3qyn1hczv9fglqc2cbaax0fb37gjjr0y24x19mmp817csdn"; + type = "gem"; + }; + version = "1.1.2"; + }; + yaml-lint = { + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1m9n4sg7i0334yac7dcrhnhv5rzvrccgnh687n9x77ba3awk4yx1"; + type = "gem"; + }; + version = "0.0.10"; + }; + zeitwerk = { + groups = ["default" "development" "test"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12n0hiawqayzchi0yga5n19hi63b2snd49fv3n23n2i4pp05jzrp"; + type = "gem"; + }; + version = "2.4.1"; + }; +} diff --git a/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch b/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch new file mode 100644 index 00000000000..1dbfed67919 --- /dev/null +++ b/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch @@ -0,0 +1,25 @@ +diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb +index 373e235b3f..57d4d7a55b 100644 +--- a/config/unicorn.conf.rb ++++ b/config/unicorn.conf.rb +@@ -27,18 +27,10 @@ pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid") + + if ENV["RAILS_ENV"] == "development" || !ENV["RAILS_ENV"] + logger Logger.new($stdout) +- # we want a longer timeout in dev cause first request can be really slow +- timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60) +-else +- # By default, the Unicorn logger will write to stderr. +- # Additionally, some applications/frameworks log to stderr or stdout, +- # so prevent them from going to /dev/null when daemonized here: +- stderr_path "#{discourse_path}/log/unicorn.stderr.log" +- stdout_path "#{discourse_path}/log/unicorn.stdout.log" +- # nuke workers after 30 seconds instead of 60 seconds (the default) +- timeout 30 + end + ++timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60) ++ + # important for Ruby 2.0 + preload_app true + diff --git a/pkgs/servers/web-apps/discourse/update.py b/pkgs/servers/web-apps/discourse/update.py new file mode 100755 index 00000000000..c401ab552bb --- /dev/null +++ b/pkgs/servers/web-apps/discourse/update.py @@ -0,0 +1,164 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i python3 -p bundix bundler nix-update python3 python3Packages.requests python3Packages.click python3Packages.click-log + +import click +import click_log +import shutil +import tempfile +import re +import logging +import subprocess +import pathlib +from distutils.version import LooseVersion +from typing import Iterable + +import requests + +logger = logging.getLogger(__name__) + + +class DiscourseRepo: + version_regex = re.compile(r'^v\d+\.\d+\.\d+$') + def __init__(self, owner: str = 'discourse', repo: str = 'discourse'): + self.owner = owner + self.repo = repo + + @property + def tags(self) -> Iterable[str]: + r = requests.get(f'https://api.github.com/repos/{self.owner}/{self.repo}/git/refs/tags').json() + tags = [x['ref'].replace('refs/tags/', '') for x in r] + + # filter out versions not matching version_regex + versions = list(filter(self.version_regex.match, tags)) + + # sort, but ignore v for sorting comparisons + versions.sort(key=lambda x: LooseVersion(x.replace('v', '')), reverse=True) + return versions + + @staticmethod + def rev2version(tag: str) -> str: + """ + normalize a tag to a version number. + This obviously isn't very smart if we don't pass something that looks like a tag + :param tag: the tag to normalize + :return: a normalized version number + """ + # strip v prefix + return re.sub(r'^v', '', tag) + + def get_file(self, filepath, rev): + """returns file contents at a given rev :param filepath: the path to + the file, relative to the repo root :param rev: the rev to + fetch at :return: + + """ + return requests.get(f'https://raw.githubusercontent.com/{self.owner}/{self.repo}/{rev}/{filepath}').text + + +def _call_nix_update(pkg, version): + """calls nix-update from nixpkgs root dir""" + nixpkgs_path = pathlib.Path(__file__).parent / '../../../../' + return subprocess.check_output(['nix-update', pkg, '--version', version], cwd=nixpkgs_path) + + +def _get_current_package_version(pkg: str): + nixpkgs_path = pathlib.Path(__file__).parent / '../../../../' + return subprocess.check_output(['nix', 'eval', '--raw', f'nixpkgs.{pkg}.version'], text=True) + + +def _diff_file(filepath: str, old_version: str, new_version: str): + repo = DiscourseRepo() + + current_dir = pathlib.Path(__file__).parent + + old = repo.get_file(filepath, 'v' + old_version) + new = repo.get_file(filepath, 'v' + new_version) + + if old == new: + click.secho(f'{filepath} is unchanged', fg='green') + return + + with tempfile.NamedTemporaryFile(mode='w') as o, tempfile.NamedTemporaryFile(mode='w') as n: + o.write(old), n.write(new) + width = shutil.get_terminal_size((80, 20)).columns + diff_proc = subprocess.run( + ['diff', '--color=always', f'--width={width}', '-y', o.name, n.name], + stdout=subprocess.PIPE, + cwd=current_dir, + text=True + ) + + click.secho(f'Diff for {filepath} ({old_version} -> {new_version}):', fg='bright_blue', bold=True) + click.echo(diff_proc.stdout + '\n') + return + + +@click_log.simple_verbosity_option(logger) + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.argument('rev', default='latest') +@click.option('--reverse/--no-reverse', default=False, help='Print diffs from REV to current.') +def print_diffs(rev, reverse): + """Print out diffs for files used as templates for the NixOS module. + + The current package version found in the nixpkgs worktree the + script is run from will be used to download the "from" file and + REV used to download the "to" file for the diff, unless the + '--reverse' flag is specified. + + REV should be the git rev to find changes in ('vX.Y.Z') or + 'latest'; defaults to 'latest'. + + """ + if rev == 'latest': + repo = DiscourseRepo() + rev = repo.tags[0] + + old_version = _get_current_package_version('discourse') + new_version = DiscourseRepo.rev2version(rev) + + if reverse: + old_version, new_version = new_version, old_version + + for f in ['config/nginx.sample.conf', 'config/discourse_defaults.conf']: + _diff_file(f, old_version, new_version) + + +@cli.command() +@click.argument('rev', default='latest') +def update(rev): + """Update gem files and version. + + REV should be the git rev to update to ('vX.Y.Z') or 'latest'; + defaults to 'latest'. + + """ + repo = DiscourseRepo() + + if rev == 'latest': + rev = repo.tags[0] + logger.debug(f"Using rev {rev}") + + version = repo.rev2version(rev) + logger.debug(f"Using version {version}") + + rubyenv_dir = pathlib.Path(__file__).parent / "rubyEnv" + + for fn in ['Gemfile.lock', 'Gemfile']: + with open(rubyenv_dir / fn, 'w') as f: + f.write(repo.get_file(fn, rev)) + + subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir) + subprocess.check_output(['bundix'], cwd=rubyenv_dir) + + _call_nix_update('discourse', repo.rev2version(rev)) + + +if __name__ == '__main__': + cli() diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 11115d0f26c..470b56c42d8 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -2252,6 +2252,14 @@ in discount = callPackage ../tools/text/discount { }; + discourse = callPackage ../servers/web-apps/discourse { + ruby = ruby_2_7; + }; + + discourse-mail-receiver = callPackage ../servers/web-apps/discourse/mail_receiver { + ruby = ruby_2_7; + }; + discocss = callPackage ../tools/misc/discocss { }; disfetch = callPackage ../tools/misc/disfetch { }; @@ -17625,12 +17633,8 @@ in stdenv = gcc6Stdenv; }); - v8_6_x = v8; v8 = callPackage ../development/libraries/v8 { inherit (python2Packages) python; - } // lib.optionalAttrs stdenv.isLinux { - # doesn't build with gcc7 - stdenv = gcc6Stdenv; }; vaapiIntel = callPackage ../development/libraries/vaapi-intel { }; diff --git a/pkgs/top-level/php-packages.nix b/pkgs/top-level/php-packages.nix index 7db0eb4d6a4..3bbcb453a3c 100644 --- a/pkgs/top-level/php-packages.nix +++ b/pkgs/top-level/php-packages.nix @@ -146,8 +146,8 @@ lib.makeScope pkgs.newScope (self: with self; { sha256 = "103nys7zkpi1hifqp9miyl0m1mn07xqshw3sapyz365nb35g5q71"; - buildInputs = [ pkgs.v8_6_x ]; - configureFlags = [ "--with-v8=${pkgs.v8_6_x}" ]; + buildInputs = [ pkgs.v8 ]; + configureFlags = [ "--with-v8=${pkgs.v8}" ]; meta.maintainers = lib.teams.php.members; meta.broken = true; @@ -159,8 +159,8 @@ lib.makeScope pkgs.newScope (self: with self; { sha256 = "0g63dyhhicngbgqg34wl91nm3556vzdgkq19gy52gvmqj47rj6rg"; - buildInputs = [ pkgs.v8_6_x ]; - configureFlags = [ "--with-v8js=${pkgs.v8_6_x}" ]; + buildInputs = [ pkgs.v8 ]; + configureFlags = [ "--with-v8js=${pkgs.v8}" ]; meta.maintainers = lib.teams.php.members; meta.broken = true;