Adicione uma página offline para exibir quando o Discourse estiver reconstruindo ou iniciando

Existe um motivo óbvio pelo qual não consigo fazer upload de um .png com mais de 500kb no meu fórum após seguir este guia?

Vejo algumas discussões sobre esta linha client_max_body_size 0;, mas isso não deveria ser o problema, certo?

EDIT: Descobri rapidamente após postar aqui. Precisava marcar :white_check_mark: forçar https nas configurações. Deixarei este post aqui caso futuras pessoas tenham um problema

3 curtidas

Eu observo que aqui:

o nginx recomenda desabilitar o cabeçalho Connection: close, bem como definir proxy_http_version 1.1 — então algo como isto:

    proxy_http_version 1.1;
    # Desabilitar \"Connection: close\" padrão
    proxy_set_header "Connection" "";

Não consigo encontrar nenhuma documentação sobre se Connection: close tem algum impacto em sockets de domínio Unix, mas como esta documentação também é útil para executar um proxy externo em um sistema separado e eu esperaria que remover o cabeçalho não prejudicasse, pode fazer sentido recomendá-lo aqui?

1 curtida

Se você implantou isso em um sistema com SELinux (enforcing), não poderá usar o soquete de domínio unix para que o host se comunique com o contêiner, pois mesmo que você redefina o rótulo do soquete de domínio unix, ele será recriado sem o rótulo toda vez que você reiniciar o contêiner. Em vez disso, você terá que fazer duas alterações.

Você precisará permitir que o nginx acesse as páginas de erro e alternar de proxy sobre um soquete de domínio unix para uma porta. Isso custará alguns µs de latência por solicitação, pois os preços de execução do nginx com SELinux como uma camada de segurança, mas isso não será perceptível para seus usuários.

Primeiro, execute estes comandos para permitir que o nginx faça conexões de rede e acesse as páginas de erro:

setsebool -P httpd_can_network_connect 1
semanage fcontext -a -t httpd_sys_content_t /var/www
restorecon -R -v /var/www

Em seguida, em seu app.yaml, comente ou remova - "templates/web.socketed.template.yml", exponha a porta 80 como uma porta diferente na máquina local e reconstrua o contêiner.

expose:
  - "8008:80"   # http

Não use https aqui — você terminou o SSL no nginx externo, e o cabeçalho X-Forwarded-Proto informa ao Discourse que a solicitação chegou via https. Certifique-se de que a porta 8008 (ou qualquer outra porta que você tenha escolhido) não esteja exposta publicamente pelas configurações do seu firewall.

Em seguida, modifique sua configuração externa do nginx de proxy via nginx.http.sock para http://127.0.0.1:8008 (ou sua porta escolhida) e limpe o cabeçalho padrão Connection: close, para que o nginx externo não precise estabelecer uma nova conexão IP para cada solicitação.

...
  location / {
    proxy_pass http://127.0.0.1:8008;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    # Desabilitar \"Connection: close\" padrão
    proxy_set_header "Connection" "";
...
1 curtida

Olá @sam (e talvez @falco). Fui encarregado de organizar alguns destes documentos #documentation:sysadmin. Este tem um alto número de leituras e acho que é um dos menos úteis.

Você acha que faz sentido escrever uma substituição que inicie haproxy - Official Image | Docker Hub e nginx - Official Image | Docker Hub, talvez com docker compose, fazendo o contêiner nginx montar os certificados do contêiner discourse e ter o haproxy em modo tcp para fazer algo como isto (tenho certeza que isso não funcionará, mas presumo que poderei descobrir o que funcionará):

backend my_app_be
	balance roundrobin
	option httpchk HEAD /srv/status
        server discourse app:443 check
	server fallback nginx:80 check backup

Acho que essa pode ser uma solução viável e mais fácil de seguir do que este tópico. Eu então deixaria este para fins históricos (e talvez o fecharia?), mas linkaria para o descrito acima.

3 curtidas

Note que este tópico tem uma sobreposição significativa com aquele:

Esse tópico recebeu atualizações significativas recentemente (o que aumentou ainda mais a sobreposição). Talvez faça sentido mesclar a parte da página offline daqui para lá como uma nota (já que é fácil de adicionar se você já executa uma instância separada do Nginx), então marcar este como obsoleto (linkando para as alternativas)?

Seu tópico sugerido sobre HAProxy ainda faria sentido adicionalmente, como a maneira padrão para pessoas que não querem instalar um Nginx frontal por outros motivos.

2 curtidas

Como eu deveria saber?

Ah.

Mas falando sério, eu gosto mais da sua solução do que da minha!

E esse tópico diz em letras grandes que é um tópico avançado.

Mas talvez isso também não seja necessário, já que a maioria das pessoas entende melhor o nginx de qualquer maneira. Eu comecei a pensar sobre isso agora, então a parte difícil será me fazer parar. :slight_smile:

3 curtidas

É sempre bom se houver muitas alternativas. Mas esta é uma das mais fáceis (bem, muitas delas…), e familiar para muitas pessoas.

Portanto, por favor, não mexa nela.

E deixar isso como está tem outro ponto também: resultados de busca. Devido ao alto tráfego (e uso muito limitado de tags…) tentar encontrar algo específico aqui é bastante difícil hoje em dia. Mas esta é bastante fácil de encontrar e tem um propósito muito direcionado. Se este tópico for movido para outro, encontrá-lo será mais difícil.

Há uma razão pela qual isso é tão popular… não são muitos que se inspiram a usar docker ou haproxy.

2 curtidas

Suspiro. Bem, acho que isso também é verdade, mas está desatualizado há pelo menos 4 anos. Não o faço há algum tempo, mas você não precisa mais modificar os arquivos manualmente, pois o acme (ou algo parecido?) fará isso por você.

O que eu realmente acho é que faz muito mais sentido usar uma instalação de dois contêineres, que tem pouco tempo de inatividade, em vez de passar por esses obstáculos para colocar uma página no ar enquanto você reconstrói, mas eu também não consigo convencer as pessoas disso.

Então, talvez a coisa a fazer seja reescrever isso para como as coisas funcionam hoje.

1 curtida

E é muito mais difícil. Acho que é mais fácil corrigir as instruções de como instalar e usar o certbot do que começar a ensinar como, quando e onde atualizar contêineres do lado do sql, etc.

Além disso, há outro ponto contra o docker (mesmo o Discourse funciona dessa maneira…): podemos encontrar muitas perguntas de nível totalmente básico, como como usar o docker em primeiro lugar. Ou como evitar erros de digitação em arquivos yml :wink:

E ainda funciona (exceto que a seção SSL é um pouco confusa, mas já estava desativada há 4 anos :wink: )

Não. Não sou contra outras soluções. Sou muito contra quando links e textos antigos devem ser movidos para novos locais sem motivos muito sólidos.

2 curtidas

Teremos que concordar em discordar aí. Mas acredito que há muitas pessoas que concordam com você. (Talvez esta solução seja uma solução “configure e esqueça”, e a solução de dois contêineres exige atenção quando há uma atualização do Postgres, que acontece a cada 2 anos.)

OK. Nisso concordamos! Então acho que o caminho a seguir é ver o que posso fazer para limpar essa parte e deixar a solução do haproxy em espera.

1 curtida

Adoraria ver isso feito como parte do Discourse, mas obrigado @fefrei por isso! Trabalho incrível! Usarei o Apache para fazer isso, mas pelo menos os passos básicos devem ser os mesmos.

1 curtida

OK, só me levou 2 horas de mexer para deixá-lo como eu queria!

Página de Manutenção do Discourse com Apache2

Como root

cd /var/discourse
nano containers/app.yml

Comente estas linhas:

  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  #- "80:80"   # http
  #- "443:443" # https

Adicione no FINAL da seção de templates (deve ser o último):

  - "templates/web.socketed.template.yml"

Nota: Isso fará com que o Discourse escute apenas no IP interno e o apache2 assumirá as portas 80/443 e a terminação SSL.

Nota: O Discourse deve ser reconstruído para que isso tenha efeito:

cd /var/discourse
./launcher rebuild app

Instale apache2 e certbot

apt install -y apache2 certbot python3-certbot-apache

Crie um diretório para a página html:

mkdir /var/www/discourse_maintenance

Página HTML:
/var/www/discourse_maintenance/discourse_maintenance.html

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="refresh" content="5">
        <title>Discourse Maintenance</title>
        <style>
            .center {
                display: flex;
                justify-content: center;
            }
            .container {
                max-width: 500px;
                padding: 50px 50px 30px 50px;
            }
            .title {
                padding-top: 20px;
            }
            h1, p {
                font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
            }
        </style>
    </head>
    <body>
        <div class="center">
            <div class="container">
                <h1 class="title">Discourse Maintenance&hellip;</h1>
                <p>We are currently upgrading the site, or performing scheduled maintenance.</p>
                <p>You'll automatically be redirected to the site once it's available.</p>
            </div>
        </div>
    </body>
</html>

Habilite o Módulo Proxy:

a2enmod proxy
a2enmod proxy_http
a2enmod headers

Arquivo vhost do Apache:

<IfModule mod_ssl.c>
<VirtualHost *:443>
  ServerName your.discourse.domain
  ServerAdmin your@email.com
  DocumentRoot /var/www/discourse_maintenance

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

  # Maintenance Mode
  RewriteEngine On
  RewriteCond /var/www/under_maintenance -f
  # safety check to prevent redirect loops 
  RewriteCond %{REQUEST_URI} !/discourse_maintenance.html$
  # redirect internally all requests to maintenance.html 
  RewriteRule ^.*$ /var/www/discourse_maintenance/discourse_maintenance.html

  ProxyPass / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/
  ProxyPassReverse / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/

  SSLCertificateFile /etc/letsencrypt/live/your.discourse.domain/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/your.discourse.domain/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Para habilitar a manutenção, execute touch /var/www/under_maintenance

Para desabilitar a manutenção, execute touch /var/www/under_maintenance

Créditos: Add an offline page to display when Discourse is rebuilding or starting up pela ideia inicial, página html (cortada/editada ao meu gosto) e configuração nginx na qual baseei a configuração do Apache.

Edição: Sugestões são bem-vindas para torná-lo automático quando a resposta for 502/503. Tentei, mas não consegui fazer funcionar como eu queria, então optei por um método conhecido que uso em outros servidores web quando a aplicação backend está fora para manutenção, etc.

2 curtidas

Na reinicialização do sistema, isso atrasará a página de erro/manutenção até que o docker seja iniciado, o que leva bem mais tempo do que inicializar o sistema. Também não oferece a opção de proteções SELinux fornecidas pelo sistema para o nginx do sistema. Usar o nginx do sistema pode, pelo menos em um sistema gerenciado pelo systemd com inicialização rápida, apresentar a página de erro em poucos segundos após a inicialização. Para mim, isso significa que meus sistemas respondem com a página de manutenção muito rapidamente durante as atualizações do sistema que exigem reinicialização. (Estou executando o AlmaLinux 9 no host, e ele inicializa o nginx muito rapidamente.)

Pode fazer sentido documentar uma alternativa de haproxy e comparar experiências, mas o haproxy no docker não é uma substituição direta para o nginx externo, e fechar este tópico seria um erro.

Não se trata apenas de disponibilidade.

Usar o docker para tráfego externo via IPv4 oculta os endereços IPv6 externos do nginx interno e do Discourse. Você terá o mesmo problema com o haproxy. Olhe seus logs para endereços IP de espaço local apenas 127.0.0.1 ou 172.* RFC1918. Não usar um proxy externo significa que todo o tráfego IPv6 aparece como o mesmo IP, o que quebra a limitação de taxa de zona interna do nginx, considerando todo o tráfego IPv6 como uma única zona.

O IPv6 importa cada vez mais.

2 curtidas

Descobri acidentalmente esta manhã que esta etapa não só evita a aplicação do socket Unix, mas também remove o uso do módulo real_ip, de modo que a limitação de taxa é aplicada com base em todas as conexões juntas, em vez de todas as conexões por IP. Eu deveria provavelmente contribuir com um novo template com variáveis, mas no momento eu apenas adicionei isso ao meu arquivo YAML do contêiner do aplicativo:

run:
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from 172.0.0.0/24;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from 172.0.0.0/24;

Não sei se faz sentido ter, por exemplo, um arquivo templates/web.httpratelimit.yml com algo assim, com uma variável para o endereço, mas sem usar sockets de domínio Unix. O que vocês acham?

2 curtidas
server {
  listen 80; listen [::]:80; listen 443 ssl http2; listen [::]:443 ssl http2;
  server_name DOMAIN;
  ssl_certificate      /etc/letsencrypt/live/DOMAIN/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/DOMAIN/privkey.pem;

ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
  ssl_protocols TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  add_header Strict-Transport-Security "max-age=63072000;";
  ssl_stapling on;
  ssl_stapling_verify on;

  client_max_body_size 0;

  location / {
    error_page 502 =502 /errorpages/offline.html;
    proxy_intercept_errors on;

    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
  }

  location /errorpages/ {
    alias /var/www/errorpages/;
  }
}

! # altere seu domínio e o caminho do arquivo de erro

Eu recebi este script com ssl e funciona

2 curtidas

Acabei de tentar usar este guia e não consegui fazê-lo funcionar.

Comecei com um proxy Nginx para executar dois sites a partir de um único contêiner Discourse. Eu só queria adicionar a parte da página de erro, então pulei as partes que pareciam se sobrepor a Run other websites on the same machine as Discourse. No entanto, devo ter perdido um passo crucial. No final, obtive o que precisava deste tutorial da DigitalOcean. Não é difícil configurar isso manualmente, mas parece que deve haver uma maneira melhor.

Dado o Docker como a maneira padrão de executar o Discourse, isso parece melhor. Presumo que seria o tipo de coisa que você configuraria e esqueceria.

3 curtidas

A ideia dentro deste tópico também é ótima para aqueles que executam o Caddy como um proxy reverso, seja em um aplicativo autônomo ou usando Cloudflare Tunnels.

discourse.example.org {
        reverse_proxy <host | ip>:port

        handle_errors 5xx {
                root * /path/to/error-pages
                rewrite * /error.html
                file_server {
                        status 404
                }
        }
}

A seção status 404 é importante apenas se estiver usando Cloudflare Tunnels. Se o Caddy retornar 5xx para o Cloudflare, o Cloudflare Tunnel exibirá seu próprio erro de desconexão. Alterar o status indica ao Cloudflare que há uma conexão ativa válida que servirá uma página de erro.

2 curtidas

Talvez eu esteja entendendo mal como isso funciona, mas uma atualização não apenas atualiza a página em que você já está? Como isso te leva de volta a um URL diferente?

Não é necessário ir para um URL diferente – o truque é que a página de erro é servida diretamente no URL que o usuário tentou acessar (por exemplo, https://meta.discourse.org/t/add-an-offline-page-to-display-when-discourse-is-rebuilding-or-starting-up/45238/158), e esse é o URL exato que será atualizado, resultando no mesmo erro novamente, ou na página que o usuário queria :slight_smile:

Ah, entendi. Eu não tinha lido cuidadosamente toda a configuração, pois estou usando outro servidor para a página offline, só no caso de toda a minha máquina ficar indisponível por algum motivo.
Faz sentido, embora. Agora estou apenas tentando fazer o meu JS, que deveria estar redirecionando de volta para a URL original, funcionar…