From 8e81bfd8bd5c6d550d39b36b9328d6ca8cdfcd15 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Thu, 28 Nov 2024 14:17:37 +0000 Subject: [PATCH 01/18] chore: add some temporary files and rewrite docs for feat reminder --- .env.example | 14 +++++++--- POP.md | 52 +++++++++++++++++++++++++++++++++++++ README.md | 15 ++++++----- assets/img/docker-esus.png | Bin 0 -> 62416 bytes find-download-link.sh | 26 +++++++++++++++++++ 5 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 POP.md create mode 100644 assets/img/docker-esus.png create mode 100644 find-download-link.sh diff --git a/.env.example b/.env.example index 27fc31c..344ba1e 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,15 @@ +# Configurações gerais + +# Configurações exclusivas para ambiente de desenvolvimento +PGWEB_PORT=8099 + +# Configuração de Timezone +TZ='America/Bahia' + +# Configuração de banco de dados POSTGRES_DB='esus' POSTGRES_USER='postgres' POSTGRES_PASSWORD='esus' POSTGRESQL_PORT=54351 -APP_PORT=88 -PGWEB_PORT=8099 -TZ='America/Bahia' + +# Apenas para banco de dados externo diff --git a/POP.md b/POP.md new file mode 100644 index 0000000..6b37f2a --- /dev/null +++ b/POP.md @@ -0,0 +1,52 @@ +# Procedimento Operacional Padrão para Backup do Banco de Dados PostgreSQL e Envio de Chaves + +Para migração do serviço PEC APS para outra infraestrutura, é necessário alguns recursos para garantir o total funcionamento do prontuário e suas integrações com serviços externos com segurança. + +## 1. Realizando o Backup (Dump) do Banco de Dados PostgreSQL + +### Linux +1. Execute o comando para criar um dump em formato **plain**: + ```bash + pg_dump -U -d -F p > backup.sql + ``` + +2. Compacte o arquivo: + - **ZIP**: + ```bash + zip backup.zip backup.sql + ``` + - **TAR.GZ**: + ```bash + tar -czvf backup.tar.gz backup.sql + ``` + +### Windows +1. No PowerShell, crie o dump em formato **plain**: + ```powershell + pg_dump -U -d -F p > backup.sql + ``` + +2. Compacte o arquivo: + - **ZIP**: + ```powershell + Compress-Archive -Path backup.sql -DestinationPath backup.zip + ``` + +## 2. Envio do Backup para Google Drive (ou Alternativas) +1. Faça o upload do arquivo compactado para o Google Drive (ou outro serviço de nuvem, como Dropbox ou OneDrive). +2. Gere o link de compartilhamento e envie-o ao destinatário. + +## 3. Envio das Chaves Necessárias + +### Contra-Chave para Instalação do PEC e Agendamento Online +Para envio e instalação da contra-chave do PEC, siga o manual oficial: [Criando Contra-Chave](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_02_instalacao/#244-gerar-contra-chave-e-ativar-agendamento-online). + +### Chave para Acesso à RNDS +Siga as instruções para garantir a configuração correta para acessar a RNDS conforme descrito no manual: [Configuração de Acesso à RNDS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#312-acessando-a-rnds-por-meio-do-pec). + +## 4. Certificados HTTPS +Caso a unidade tenha adquirido um certificado HTTPS, compartilhe-o seguindo o tipo: +- **Certificados comprados**: Como SSL DV (Domain Validation), OV (Organization Validation) ou EV (Extended Validation) são recomendados para autenticação segura. +- **Certificados autoassinados ou Certbot**: Podem ser utilizados, mas possuem limitações de confiança em navegadores e aplicativos. + +Envie o arquivo do certificado (`.pfx` ou `.crt`) junto com as credenciais de instalação, caso aplicável. diff --git a/README.md b/README.md index 4b4e28f..93f0e41 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,29 @@ -# eSUS PEC + Compatível e testado com ![version](https://img.shields.io/badge/version-5.3.19-green) -**BREAKING CHANGE:** Desde a versão 5.3 o certificado SSL é autogerenciado e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores +**BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores É um sistema bastante utilizado por profissionais de saúde da Atenção Básica para registros de pacientes e dados de saúde. Esse repositório se propõe a criar uma estrutura docker com linux para viabilizar o deploy do sistema em qualquer ambiente que tenha docker e facilitar a instalação e atualização do sistema [e-SUS PEC](https://sisaps.saude.gov.br/esus/) ## Instalação TD;LR -Baixe o jar da aplicação e execute o script de instalação para um banco de dados novo, use o argumento `-t` se quiser que a versão instalada seja de treinamento: +Esse script irá baixar [a versão mais recente do PEC](https://sisaps.saude.gov.br/esus/) e rodar em [docker](https://docs.docker.com/engine/install/ubuntu/) a versão de treinamento ```sh -wget https://arquivos.esusab.ufsc.br/PEC/e925378f33a611e7/5.3.19/eSUS-AB-PEC-5.3.19-Linux64.jar -sh build.sh -f eSUS-AB-PEC-5.2.38-Linux64.jar +cp .env.exameple .env +sh build.sh -d ``` +## Utilizando banco de dados externo + Para execução com banco de dados externo: -1. Configure as variáveis de ambiente disponíveis em `.env.external-db.example` colando em `.env.external-db` +Configure as variáveis de ambiente disponíveis em `.env.example` depois de copiar para `.env` ```sh +cp .env.example .env sh build.sh -e ``` diff --git a/assets/img/docker-esus.png b/assets/img/docker-esus.png new file mode 100644 index 0000000000000000000000000000000000000000..a7380e2f77ec3fcb9c041271852501e220d21180 GIT binary patch literal 62416 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 Date: Sat, 7 Dec 2024 12:41:11 +0000 Subject: [PATCH 02/18] docs: update pop file with some more updated instructions --- POP.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/POP.md b/POP.md index 6b37f2a..82740af 100644 --- a/POP.md +++ b/POP.md @@ -1,4 +1,4 @@ -# Procedimento Operacional Padrão para Backup do Banco de Dados PostgreSQL e Envio de Chaves +# Procedimento Operacional Padrão para Instalação/Transferência/Atualização de Servidor PEC Para migração do serviço PEC APS para outra infraestrutura, é necessário alguns recursos para garantir o total funcionamento do prontuário e suas integrações com serviços externos com segurança. @@ -36,17 +36,106 @@ Para migração do serviço PEC APS para outra infraestrutura, é necessário al 1. Faça o upload do arquivo compactado para o Google Drive (ou outro serviço de nuvem, como Dropbox ou OneDrive). 2. Gere o link de compartilhamento e envie-o ao destinatário. -## 3. Envio das Chaves Necessárias +## 3. Envio de credenciais do instalador -### Contra-Chave para Instalação do PEC e Agendamento Online -Para envio e instalação da contra-chave do PEC, siga o manual oficial: [Criando Contra-Chave](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_02_instalacao/#244-gerar-contra-chave-e-ativar-agendamento-online). +Para dar maior celeridade ao processo é importante, nesse primeiro momento que tenhamos o usuário e senha do Instalador da aplicação PEC do município que foi transferida, essa senha pode ser alterada depois do processo pelo próprio instalador para manter a sua seguraça e autonomia das configurações. -### Chave para Acesso à RNDS -Siga as instruções para garantir a configuração correta para acessar a RNDS conforme descrito no manual: [Configuração de Acesso à RNDS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#312-acessando-a-rnds-por-meio-do-pec). +Com isso visamos habilitar os serviços: +- [CADSUS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3111-cadsus) - Para ser possível acessar pacientes direto do da Central de Cadastros de Cidadãos do SUS +- [Agendamento Online](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3113-agenda-online) - Com essa funcionalidade o Paciente terá também informações sobre seu agendamento no aplicativo [Meu SUS Digital](https://meususdigital.saude.gov.br/) +- [Teleinterconsulta/atendimento](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3113-teleinterconsulta) - Possibilidade de consultas de seguimento e interconsultas online pela plataforma do PEC +- [Prescrição eletrônica](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3113-prescri%C3%A7%C3%A3o-digital) +- [Hórus](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3112-h%C3%B3rus) - [Mais informações](https://bvsms.saude.gov.br/bvs/folder/horus_folder.pdf) -## 4. Certificados HTTPS -Caso a unidade tenha adquirido um certificado HTTPS, compartilhe-o seguindo o tipo: -- **Certificados comprados**: Como SSL DV (Domain Validation), OV (Organization Validation) ou EV (Extended Validation) são recomendados para autenticação segura. -- **Certificados autoassinados ou Certbot**: Podem ser utilizados, mas possuem limitações de confiança em navegadores e aplicativos. +## 4. Cadastro/Envio das Chaves/Certificados Necessárias -Envie o arquivo do certificado (`.pfx` ou `.crt`) junto com as credenciais de instalação, caso aplicável. +### 4.1 *Contra-Chave* para Instalação do PEC e Ativação do agendamento Online + +Essa chave é necessária para envio e instalação da contra-chave do PEC, siga o manual oficial (Seção 2.4.4): [Criando Contra-Chave](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_02_instalacao/#244-gerar-contra-chave-e-ativar-agendamento-online). + +### 4.2 *Certificado Digital* para integração com RNDS + +Siga as instruções para garantir a configuração correta para acessar a RNDS conforme descrito no manual (Seção 3.12): [Configuração de Acesso à RNDS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#312-acessando-a-rnds-por-meio-do-pec). + +Com isso você terá um certificado digital do Município capaz de reconhecer a autenticidade da identidade, é necessário que, após o processo o certificado (arquivo *.pfx) seja instalado na pasta adequada da instalação. Para isso solicitamos que nos envie o arquivo por email. + +### 4.3 Certificados HTTPS + +A partir do PEC 5.3 essa funcionalidade foi [automatizada](https://saps-ms.github.io/Manual-eSUS_APS/docs/Apoio%20a%20Implanta%C3%A7%C3%A3o/HTTPS_Automatizado/) utilizando o serviço certmgr já incluso no pacote *.jar da instalação. + +## 5. Padrão de Ofícios + +Algumas etapas do proceso necessitam de envio de ofícios aos órgãos competentes, para liberação de algumas funcionalidades, são elas: + +### 5.1 Solicitação de Domínio GOV.br + +Necessário para realizar login por meio de seu [usuário GOV.br](https://saps-ms.github.io/Manual-eSUS_APS/docs/Apoio%20a%20Implanta%C3%A7%C3%A3o/HTTPS_Automatizado/#requisitos-obrigat%C3%B3rios). É possível que outras funcionalidades exijam esse requisito, por isso, pode ser que vale à pena fazer. Verifique qual o órgão competente no seu estado para criação de domínios (por exemplo, na Bahia é a [PRODEB](https://www.prodeb.ba.gov.br/page/registro-de-dominio)) + +Segue um exemplo de email que pode ser enviado: + +```txt +Ao [Nome do Órgão Competente], + +Prezados(as), + +Solicitamos a criação do domínio [nome do domínio solicitado] para uso oficial nas atividades de saúde pública do município de [Nome do Município], em conformidade com as diretrizes do Ministério da Saúde e com foco na implantação do Prontuário Eletrônico do Cidadão (PEC e-SUS APS). + +Dados para registro: +- Nome da Instituição: [Razão Social da Prefeitura]. +- CNPJ: [CNPJ da Prefeitura]. +- Responsável pelo domínio: [Nome do Responsável]. +- Contato: [E-mail e telefone do responsável]. + +Dados do servidor de hospedagem: +- IPv4: [IPv4 da instalação] +* Para redirecionamento à esse IP não é necessário Name Server (NS), apenas que o subdomínio tenha um apontamento do tipo A para o IPv4. + +Justificativa: Este domínio será utilizado para suportar as funcionalidades do PEC, como teleconsultas, prescrições digitais e agendamentos online, além de garantir a integração com serviços como RNDS e CADSUS. + +Agradecemos pela atenção e aguardamos retorno. + +Atenciosamente, +[Nome do Responsável pela Solicitação] +``` + +### 5.2 Solicitação para ativação da Prescrição Digital + +A solicitação de ativação deverá ser feita através de um ticket de suporte que deverá ser criado pela equipe responsável pela instalação na Plataforma de suporte https://esusaps.freshdesk.com segundo padrão abaixo: + +```txt +Prezados(as), + +Solicitamos a ativação da funcionalidade de prescrição digital na instalação do PEC do município de [Nome do Município], conforme as orientações do manual oficial. + +Dados para ativação: +- URL da instalação: [URL do PEC configurado]. +- Dados do responsável pela instalação: [Nome, telefone e e-mail]. +- Município já utiliza certificado digital integrado ao PEC: Sim. + +Aguardamos as credenciais necessárias para habilitar a funcionalidade no perfil do administrador da instalação. + +Atenciosamente, +[Nome do Solicitante] +``` + +Aguarde retorno com usuário e senha para a instalação. + +## 6. Checklist + +O checklist abaixo resume as atividades necessárias para garantir o funcionamento completo do PEC após instalação, transferência ou atualização. Use os seguintes símbolos para indicar o status: + +- ✅ **Concluído**: Atividade já realizada com sucesso. +- ⏸️ **Pendente (Prioridade Baixa)**: Atividade em andamento ou não urgente no momento. +- 🚩 **Pendente (Prioridade Alta)**: Atividade essencial que requer atenção imediata. + +### **Checklist** + +- ✅ **Instalação do certificado digital HTTPS** (necessário para vídeo chamadas, teleconsultas). +- ✅ **Instalação da contra-chave** (disponibilidade de agendamento online e vinculação PEC no e-Gestor). +- ⏸️ **Migrar serviço de DNS para Gov.br** (No momento iremos manter o domínio noharm.ai - Pendente ofício para PRODEB). +- ✅ **Habilitar funcionalidades do perfil do instalador**. +- ✅ **Configuração de SMTP para envio de emails**. +- 🚩 **Instalação de certificado digital do município A1 ou do certificado feito pelo e-Gestor**. + [Referência: Seção 3.12 - Configuração de Acesso à RNDS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#312-acessando-a-rnds-por-meio-do-pec). +- 🚩 **Solicitação (ofício) para liberar emissão de prescrições digitais na instalação**. + [Referência: Seção 3.1.1.3 - Prescrição Digital](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#3113-prescri%C3%A7%C3%A3o-digital). \ No newline at end of file From 1edbbb744a97b7c58de37fd04ff3a7a202c72d28 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Mon, 9 Dec 2024 12:47:22 +0000 Subject: [PATCH 03/18] docs: update pop.md --- POP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/POP.md b/POP.md index 82740af..4d0fc75 100644 --- a/POP.md +++ b/POP.md @@ -57,7 +57,7 @@ Essa chave é necessária para envio e instalação da contra-chave do PEC, siga Siga as instruções para garantir a configuração correta para acessar a RNDS conforme descrito no manual (Seção 3.12): [Configuração de Acesso à RNDS](https://saps-ms.github.io/Manual-eSUS_APS/docs/PEC/PEC_03_adm_conf/#312-acessando-a-rnds-por-meio-do-pec). -Com isso você terá um certificado digital do Município capaz de reconhecer a autenticidade da identidade, é necessário que, após o processo o certificado (arquivo *.pfx) seja instalado na pasta adequada da instalação. Para isso solicitamos que nos envie o arquivo por email. +Com isso você terá um certificado digital do Município capaz de reconhecer a autenticidade da identidade, é necessário que, após o processo o certificado (arquivo *.pfx ou *.p12) seja instalado na pasta adequada da instalação. Para isso solicitamos que nos envie o arquivo por email. ### 4.3 Certificados HTTPS From 8ab94a12b59724441f5b36b4e1f853bfca1cbe2e Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Tue, 10 Dec 2024 00:37:25 -0300 Subject: [PATCH 04/18] chore: add volumes to store certificate keys and persist /opt/e-SUS --- .gitignore | 3 ++- Dockerfile.external-db | 1 + docker-compose.external-db.yml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4b45f3a..691c2d3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ docker-compose.npm.yml *entrypoint/ nginx/ esus-*/ -certificates/ \ No newline at end of file +certificates/ +opt-esus/ \ No newline at end of file diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 37b96f5..f753d8f 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -41,6 +41,7 @@ ENV DB_URL=${DB_URL} ENV DB_PASS=${DB_PASS} ENV DB_USER=${DB_USER} +RUN mkdir -p /opt/e-SUS/webserver/chaves RUN mkdir -p /var/www/html WORKDIR /var/www/html diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml index 01b9d3e..ead4dc0 100644 --- a/docker-compose.external-db.yml +++ b/docker-compose.external-db.yml @@ -4,6 +4,8 @@ services: platform: linux/amd64 restart: unless-stopped env_file: .env.external-db + volumes: + - ./opt-esus:/opt/e-SUS build: context: . dockerfile: Dockerfile.external-db From f2bc63aa8330c3899a537948a761dd276cf11abd Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Tue, 10 Dec 2024 16:37:06 +0000 Subject: [PATCH 05/18] fix: installing obligatory fonts to printclinical documents with pec --- .gitignore | 17 +++++++++-------- Dockerfile | 7 ++++++- Dockerfile.external-db | 7 ++++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 4b45f3a..d4f2e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ -data *.jar -__pycache__ *.pyc *.backup +*.class +*.java +*.deb +*.log +*.sql +*.gz +data/ +__pycache__ .webassets-cache .vscode/ .env +.env.external-db client_secret_*.json credentials.json token.json venv -*.class -*.java pec.log -*.deb -*.log docker-compose.npm.yml -*.sql -.env.external-db *entrypoint/ nginx/ esus-*/ diff --git a/Dockerfile b/Dockerfile index 0feba13..3eb3e04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,17 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive +# Instalando fontes que o PEC utiliza para impressão RUN apt-get update && apt-get install -y \ locales \ && locale-gen "pt_BR.UTF-8" \ && dpkg-reconfigure --frontend=noninteractive locales \ && apt-get install -y \ - wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp + wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp ttf-mscorefonts-installer fontconfig + +RUN fc-cache -fv + +RUN chmod -R 777 /usr/share/fonts/truetype/msttcorefonts # Instalando java 8, pre-requisitos para instalação do sistema PEC RUN wget -O- https://apt.corretto.aws/corretto.key | apt-key add - diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 37b96f5..8cd000a 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -2,12 +2,17 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive +# Instalando fontes que o PEC utiliza para impressão RUN apt-get update && apt-get install -y \ locales \ && locale-gen "pt_BR.UTF-8" \ && dpkg-reconfigure --frontend=noninteractive locales \ && apt-get install -y \ - wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp + wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp ttf-mscorefonts-installer fontconfig + +RUN fc-cache -fv + +RUN chmod -R 777 /usr/share/fonts/truetype/msttcorefonts # Adicionar chave pública diretamente da URL correta RUN wget -O - https://apt.corretto.aws/corretto.key | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg From e5838b67559671503aa1cee1ec1bf728b8785f66 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Tue, 10 Dec 2024 20:33:48 +0000 Subject: [PATCH 06/18] docs(fix): update pop with windows powershell backup command fix --- POP.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/POP.md b/POP.md index 4d0fc75..30a9624 100644 --- a/POP.md +++ b/POP.md @@ -22,6 +22,19 @@ Para migração do serviço PEC APS para outra infraestrutura, é necessário al ### Windows 1. No PowerShell, crie o dump em formato **plain**: + +Segue um exemplo, lembre-se de trocar os paths de acordo com o seu ambiente: + + ```powershell + PS C:\Program Files\e-SUS\database> .\postgresql-9.6.13-4-windows-x64\bin\psql.exe -p 5433 -U postgres + + PS C:\Program Files\e-SUS\webserver\config> cat .\credenciais.txt + + https://youtu.be/D_ATuuZ7ehg?feature=shared&t=44 + + PS C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -F p > backup-helison-202412101132.sql + ``` + ```powershell pg_dump -U -d -F p > backup.sql ``` From 8f91e6e3d7b2e63474fb809c171937ce0a875ecf Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Tue, 10 Dec 2024 20:33:55 +0000 Subject: [PATCH 07/18] docs(fix): update pop with windows powershell backup command fix --- POP.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/POP.md b/POP.md index 30a9624..7e91f3c 100644 --- a/POP.md +++ b/POP.md @@ -21,23 +21,24 @@ Para migração do serviço PEC APS para outra infraestrutura, é necessário al ``` ### Windows -1. No PowerShell, crie o dump em formato **plain**: -Segue um exemplo, lembre-se de trocar os paths de acordo com o seu ambiente: +A primeira opção é [realizar o backup pela interface da instalação](https://youtu.be/D_ATuuZ7ehg?feature=shared&t=44). A outra opção é pelo terminal: - ```powershell - PS C:\Program Files\e-SUS\database> .\postgresql-9.6.13-4-windows-x64\bin\psql.exe -p 5433 -U postgres +1. No PowerShell, crie o dump em formato **plain**: - PS C:\Program Files\e-SUS\webserver\config> cat .\credenciais.txt +Segue um exemplo, lembre-se de trocar os paths de acordo com o seu ambiente: - https://youtu.be/D_ATuuZ7ehg?feature=shared&t=44 +```powershell +# Para verificar a senha de seu banco de dados +cat C:\Program Files\e-SUS\webserver\config\credenciais.txt - PS C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -F p > backup-helison-202412101132.sql - ``` +C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -F p > backup.sql +# O sistema irá peduir a senha +``` - ```powershell - pg_dump -U -d -F p > backup.sql - ``` +```powershell +pg_dump -U -d -F p > backup.sql +``` 2. Compacte o arquivo: - **ZIP**: From 5b0e64bd1319931ea184c001d81b971e009a8468 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Thu, 12 Dec 2024 10:15:48 +0000 Subject: [PATCH 08/18] docs(fix): windows backup commands --- POP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/POP.md b/POP.md index 7e91f3c..7e2d4be 100644 --- a/POP.md +++ b/POP.md @@ -30,9 +30,9 @@ Segue um exemplo, lembre-se de trocar os paths de acordo com o seu ambiente: ```powershell # Para verificar a senha de seu banco de dados -cat C:\Program Files\e-SUS\webserver\config\credenciais.txt +cat 'C:\Program Files\e-SUS\webserver\config\credenciais.txt' -C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -F p > backup.sql +C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -v | Tee-Object -FilePath backup.sql # O sistema irá peduir a senha ``` From daaa7486e8e57d6e00849e1fe0090595cac17810 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Fri, 13 Dec 2024 09:36:53 +0000 Subject: [PATCH 09/18] chore: remove unused files cleaning repo --- .devcontainer/Dockerfile | 23 -------- .devcontainer/devcontainer.json | 33 ------------ .devcontainer/docker-compose.yml | 44 --------------- .env.example | 26 +++++---- .env.external-db.example | 10 ---- cron/Dockerfile | 29 ---------- cron/app/__init__.py | 0 cron/app/app.py | 91 -------------------------------- cron/app/env.py | 13 ----- cron/app/googleoauth.py | 43 --------------- cron/app/requirements.txt | 6 --- cron/crontab.txt | 3 -- cron/entry.sh | 6 --- cron/script.sh | 5 -- cron/wsgi.py | 4 -- docker-compose.all-in-one.yml | 19 +++++++ docker-compose.dev.yml | 44 --------------- docker-compose.external-db.yml | 1 - docker-compose.split-db.yml | 29 ++++++++++ docker-compose.yml | 42 --------------- 20 files changed, 64 insertions(+), 407 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/docker-compose.yml delete mode 100644 .env.external-db.example delete mode 100644 cron/Dockerfile delete mode 100644 cron/app/__init__.py delete mode 100644 cron/app/app.py delete mode 100644 cron/app/env.py delete mode 100644 cron/app/googleoauth.py delete mode 100644 cron/app/requirements.txt delete mode 100644 cron/crontab.txt delete mode 100644 cron/entry.sh delete mode 100644 cron/script.sh delete mode 100644 cron/wsgi.py create mode 100644 docker-compose.all-in-one.yml delete mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.split-db.yml delete mode 100644 docker-compose.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index a5975bd..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/java:1-8-bullseye - -ARG INSTALL_MAVEN="false" -ARG MAVEN_VERSION="" - -ARG INSTALL_GRADLE="false" -ARG GRADLE_VERSION="" - -RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ - && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi - -# [Optional] Uncomment this section to install additional OS packages. -RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends systemd systemd-sysv && \ - apt-get clean && \ - systemctl mask dev-hugepages.mount sys-fs-fuse-connections.mount - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 - -# Systemd specific configuration -VOLUME [ "/sys/fs/cgroup" ] -CMD ["/sbin/init"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 8e59e47..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,33 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/java-postgres -{ - "name": "Java & PostgreSQL", - "dockerComposeFile": "docker-compose.yml", - "service": "app", - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {} - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or with the host. - // "forwardPorts": [5432], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "java -version", - - // Configure tool-specific properties. - "customizations": { - "vscode": { - "extensions": [ - "ms-azuretools.vscode-docker", - "esbenp.prettier-vscode", - "codeium.codeium" - ] - } - } - - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml deleted file mode 100644 index 0a6449b..0000000 --- a/.devcontainer/docker-compose.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: '3.8' - -volumes: - postgres-data: - -services: - app: - container_name: javadev - build: - context: . - dockerfile: Dockerfile - environment: - # NOTE: POSTGRES_DB/USER/PASSWORD should match values in db container - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - POSTGRES_DB: postgres - POSTGRES_HOSTNAME: postgresdb - - volumes: - - ../..:/workspaces:cached - - # Overrides default command so things don't shut down after the process ends. - command: sleep infinity - - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:db - - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - - db: - container_name: postgresdb - image: postgres:latest - restart: unless-stopped - volumes: - - postgres-data:/var/lib/postgresql/data - environment: - # NOTE: POSTGRES_DB/USER/PASSWORD should match values in app container - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - POSTGRES_DB: postgres - - # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) \ No newline at end of file diff --git a/.env.example b/.env.example index 344ba1e..4b2abf1 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,21 @@ -# Configurações gerais +# Configurações de banco de dados +POSTGRES_DB='esus' +POSTGRES_USER='postgres' +POSTGRES_PASSWORD='pass' +POSTGRES_HOST='host.docker.internal' +POSTGRES_PORT=5432 + +# Arquivo específico de instalação para determinar versão a ser instalada, caso não preenchido o script buscará a última versão disponível na página +FILENAME="https://arquivos.esusab.ufsc.br/PEC/e925378f33a611e7/5.3.19/eSUS-AB-PEC-5.3.19-Linux64.jar" -# Configurações exclusivas para ambiente de desenvolvimento -PGWEB_PORT=8099 +# Porta da aplicação de desenvolvimento +APP_PORT=8081 -# Configuração de Timezone +# Timezone da aplicação que definirá o horário do sistema TZ='America/Bahia' -# Configuração de banco de dados -POSTGRES_DB='esus' -POSTGRES_USER='postgres' -POSTGRES_PASSWORD='esus' -POSTGRESQL_PORT=54351 +# path de restauração de banco de dados já existente +DUMPFILE='dumpfile.sql' # Esse arquivo deve estar na raiz do projeto, caso queira um banco de dados em branco, exclua a variável -# Apenas para banco de dados externo +# Configurações de HTTPS +HTTPS_DOMAIN='exemplo.gov.br' diff --git a/.env.external-db.example b/.env.external-db.example deleted file mode 100644 index f4d9a63..0000000 --- a/.env.external-db.example +++ /dev/null @@ -1,10 +0,0 @@ -POSTGRES_DB='esus' -POSTGRES_USER='postgres' -POSTGRES_PASSWORD='pass' -POSTGRES_HOST='host.docker.internal' -POSTGRES_PORT=5432 -FILENAME="https://arquivos.esusab.ufsc.br/PEC/e925378f33a611e7/5.3.19/eSUS-AB-PEC-5.3.19-Linux64.jar" -APP_PORT=8081 -TZ='America/Bahia' -DUMPFILE='dumpfile.sql' # Esse arquivo deve estar na raiz do projeto, caso queira um banco de dados em branco, exclua a variável -HTTPS_DOMAIN='exemplo.gov.br' diff --git a/cron/Dockerfile b/cron/Dockerfile deleted file mode 100644 index 0a3c544..0000000 --- a/cron/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM python:alpine3.16 - -# instalando dependências para o container -RUN apk add --no-cache postgresql-client tzdata curl - -ARG TIMEZONE - -# Configurando timezone -RUN ls /usr/share/zoneinfo -RUN cp "/usr/share/zoneinfo/${TIMEZONE}" /etc/localtime -RUN echo "${TIMEZONE}" > /etc/timezone -RUN date -RUN apk del tzdata - -# Copiando arquivos de uso do cron -ADD crontab.txt /crontab.txt -ADD script.sh /script.sh -COPY entry.sh /entry.sh -RUN chmod 755 /script.sh /entry.sh -RUN /usr/bin/crontab /crontab.txt - -# Criando o cenário para rodar a aplicação python -WORKDIR /home -COPY app . -COPY wsgi.py / -RUN pip install -r requirements.txt - -WORKDIR / -CMD ["/entry.sh"] \ No newline at end of file diff --git a/cron/app/__init__.py b/cron/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cron/app/app.py b/cron/app/app.py deleted file mode 100644 index e0b21f1..0000000 --- a/cron/app/app.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import print_function -from datetime import datetime - -import os -import sys -from flask import Flask, Response - -import os.path -from googleapiclient.discovery import build -from googleapiclient.http import MediaFileUpload -from googleapiclient.errors import HttpError - -from .env import BACKUP_EXPIRATION_TIME, DATABASE_HOST, DATABASE_NAME, DATABASE_USER, FILENAME, GOOGLE_DRIVE_FOLDER_ID, FILE_EXTENSION -from .googleoauth import get_google_credentials - -app = Flask(__name__) - -def get_file_in_path(filename): - return os.path.join(os.path.dirname(os.path.realpath(__file__)), filename) - -def upload_file(service, filename, mime_type, folder_id): - try: - file_metadata = { - 'name': filename, - 'parents': [folder_id], - 'mimeType': mime_type - } - - media = MediaFileUpload(get_file_in_path(filename), mimetype=mime_type, resumable=True) - - file = service.files().create( - body=file_metadata, - media_body=media, - fields='id' - ).execute() - except HttpError as error: - print(f'An error occurred: {error}') - file = None - - return file.get('id') - -@app.route("/backup-database") -def backup_database(): - from sh import pg_dump - - print('Realizando backup do banco de dados...', file=sys.stderr) - pg_dump('--host', DATABASE_HOST, '--port', '5432', '-U', DATABASE_USER, '-w', '--format', 'custom', '--blobs', '--encoding', - 'UTF8', '--no-privileges', '--no-tablespaces', '--no-unlogged-table-data', '--file', f'/home/{FILENAME}', DATABASE_NAME) - - - # upload de arquivo - print('Autenticando no Google...', file=sys.stderr) - service = build('drive', 'v3', credentials=get_google_credentials()) - - print('Realizando upload para Google Drive...', file=sys.stderr) - file_id = upload_file(service=service, filename=FILENAME, mime_type='application/octet-stream', folder_id=GOOGLE_DRIVE_FOLDER_ID) - print('File uploaded:', file_id) - - # Google Drive API: https://developers.google.com/drive/api/v3/reference - print('Listando arquivos na pasta de backup...', file=sys.stderr) - # fields props: https://developers.google.com/drive/api/v3/reference/files - # query props: https://developers.google.com/drive/api/guides/search-files#python - results = service.files().list(q=f'"{GOOGLE_DRIVE_FOLDER_ID}" in parents and trashed = false', orderBy='createdTime desc', - pageSize=20, fields="nextPageToken, files(id, name, mimeType, description, trashed)").execute() - items = results.get('files', []) - - if not items: - print('No files found.', file=sys.stderr) - return - else: - for item in items: - print('{:<40} {:<20} {:<20}'.format(item['name'], item['mimeType'], item['trashed']), file=sys.stderr) - - print('Excluindo arquivos antigos...', file=sys.stderr) - for item in items: - filename_datetime = item['name'].replace(FILE_EXTENSION, '') - try: - print(datetime.now()) - print(BACKUP_EXPIRATION_TIME) - if datetime.strptime(filename_datetime, '%Y_%m_%d_%H_%M_%S') < BACKUP_EXPIRATION_TIME: - filename = f'{filename_datetime}{FILE_EXTENSION}' - print(f'Excluindo {filename}', file=sys.stderr) - os.remove(get_file_in_path(filename)) - service.files().delete(fileId=item['id']).execute() - except Exception as e: - print('error', e) - - return Response('Backup realizado', status=201) - -if __name__ == '__main__': - get_google_credentials() diff --git a/cron/app/env.py b/cron/app/env.py deleted file mode 100644 index 4b15cf6..0000000 --- a/cron/app/env.py +++ /dev/null @@ -1,13 +0,0 @@ -from datetime import datetime, timedelta -import os - -DATABASE_HOST = os.getenv('POSTGRES_HOST', 'psql') -DATABASE_NAME = os.getenv('POSTGRES_DB', 'esus') -DATABASE_USER = os.getenv('POSTGRES_USER', 'postgres') - -NOW = datetime.now() -FILE_EXTENSION = '.backup' -FILENAME = f'{NOW.strftime("%Y_%m_%d_%H_%M_%S")}{FILE_EXTENSION}' - -GOOGLE_DRIVE_FOLDER_ID = os.getenv('GOOGLE_DRIVE_FOLDER_ID', '1osoeAhww2IM2V2W_xbgcRoROHEk_DAPw') -BACKUP_EXPIRATION_TIME = datetime.now() - timedelta(days=7) \ No newline at end of file diff --git a/cron/app/googleoauth.py b/cron/app/googleoauth.py deleted file mode 100644 index 8b47759..0000000 --- a/cron/app/googleoauth.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import print_function - -import os.path - -from google.auth.transport.requests import Request -from google.oauth2.credentials import Credentials -from google_auth_oauthlib.flow import InstalledAppFlow - -# If modifying these scopes, delete the file token.json. -SCOPES = [ - 'https://www.googleapis.com/auth/drive.appdata', - 'https://www.googleapis.com/auth/drive.file', - 'https://www.googleapis.com/auth/drive.install', - 'https://www.googleapis.com/auth/drive', - 'https://www.googleapis.com/auth/drive.metadata.readonly' - ] - -CREDEDNTIALS = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'credentials.json') -TOKEN = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'token.json') - -def get_google_credentials(): - creds = None - # The file token.json stores the user's access and refresh tokens, and is - # created automatically when the authorization flow completes for the first - # time. - if os.path.exists(TOKEN): - creds = Credentials.from_authorized_user_file(TOKEN, SCOPES) - # If there are no (valid) credentials available, let the user log in. - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - flow = InstalledAppFlow.from_client_secrets_file(CREDEDNTIALS, SCOPES) - creds = flow.run_local_server(host='localhost', port=8080, open_browser=False) - # Save the credentials for the next run - with open(TOKEN, 'w') as token: - token.write(creds.to_json()) - - return creds - - -if __name__ == '__main__': - get_google_credentials() \ No newline at end of file diff --git a/cron/app/requirements.txt b/cron/app/requirements.txt deleted file mode 100644 index d7c342a..0000000 --- a/cron/app/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==2.2.2 -gunicorn==20.1.0 -sh==1.14.3 -google-api-python-client -google-auth-httplib2 -google-auth-oauthlib \ No newline at end of file diff --git a/cron/crontab.txt b/cron/crontab.txt deleted file mode 100644 index b67519f..0000000 --- a/cron/crontab.txt +++ /dev/null @@ -1,3 +0,0 @@ -# m h dom mon dow user command -* 0 * * * /script.sh >> /var/log/script.log - \ No newline at end of file diff --git a/cron/entry.sh b/cron/entry.sh deleted file mode 100644 index 3c37199..0000000 --- a/cron/entry.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -# start python app -gunicorn --bind 0.0.0.0:5000 -D --enable-stdio-inheritance --capture-output --log-level debug --reload wsgi:app -# start cron -/usr/sbin/crond -f -l 8 \ No newline at end of file diff --git a/cron/script.sh b/cron/script.sh deleted file mode 100644 index 3584e37..0000000 --- a/cron/script.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -# code goes here. -curl http://localhost:5000/backup-database -echo "This is a script, run by cron!" diff --git a/cron/wsgi.py b/cron/wsgi.py deleted file mode 100644 index 7172746..0000000 --- a/cron/wsgi.py +++ /dev/null @@ -1,4 +0,0 @@ -from home.app import app - -if __name__ == "__main__": - app.run() \ No newline at end of file diff --git a/docker-compose.all-in-one.yml b/docker-compose.all-in-one.yml new file mode 100644 index 0000000..2f734bd --- /dev/null +++ b/docker-compose.all-in-one.yml @@ -0,0 +1,19 @@ +services: + pec: + container_name: pec + platform: linux/amd64 + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + args: + - TZ=${TZ} + stdin_open: true + tty: true + privileged: true + volumes: + - ./data/e-SUS:/opt/e-SUS + ports: + - "${APP_PORT}:8080" + - "80:80" + - "443:443" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index b268272..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - psql: - container_name: esus_psql - restart: always - image: postgres:13.5 - volumes: - - ./data/psql_db:/var/lib/postgresql/data - - ./data/backups:/home - environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - ports: - - "${POSTGRESQL_PORT}:5432" - pgweb: - container_name: esus_pgweb - restart: always - image: sosedoff/pgweb - ports: - - "${PGWEB_PORT}:8081" - environment: - - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@psql:5432/${POSTGRES_DB}?sslmode=disable - depends_on: - - psql - app: - container_name: esus_app - build: - context: . - dockerfile: Dockerfile - args: - - POSTGRES_DATABASE=${POSTGRES_DB} - - POSTGRES_USERNAME=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - TZ=${TZ} - stdin_open: true - tty: true - volumes: - - ./install.sh:/var/www/html/install.sh - - ./run.sh:/var/www/html/run.sh - - ./data/e-SUS:/opt/e-SUS - ports: - - "${APP_PORT}:8080" - depends_on: - - psql diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml index ead4dc0..549f9db 100644 --- a/docker-compose.external-db.yml +++ b/docker-compose.external-db.yml @@ -3,7 +3,6 @@ services: container_name: pec platform: linux/amd64 restart: unless-stopped - env_file: .env.external-db volumes: - ./opt-esus:/opt/e-SUS build: diff --git a/docker-compose.split-db.yml b/docker-compose.split-db.yml new file mode 100644 index 0000000..fd10c3e --- /dev/null +++ b/docker-compose.split-db.yml @@ -0,0 +1,29 @@ +services: + db: + container_name: db + image: postgres + restart: unless-stopped + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + volumes: + - ./data/db:/var/lib/postgresql/data + pec: + container_name: pec + platform: linux/amd64 + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + args: + - TZ=${TZ} + stdin_open: true + tty: true + privileged: true + volumes: + - ./data/e-SUS:/opt/e-SUS + ports: + - "${APP_PORT}:8080" + - "80:80" + - "443:443" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 73e3414..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,42 +0,0 @@ -services: - psql: - container_name: esus_psql - restart: always - image: postgres:13.5 - volumes: - - ./data/psql_db:/var/lib/postgresql/data - - ./data/backups:/home - - ./psql-entrypoint:/docker-entrypoint-initdb.d - environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - ports: - - "${POSTGRESQL_PORT}:5432" - pec: - container_name: esus_app - platform: linux/amd64 - restart: unless-stopped - build: - context: . - dockerfile: Dockerfile - args: - - POSTGRES_DATABASE=${POSTGRES_DB} - - POSTGRES_USERNAME=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - TZ=${TZ} - stdin_open: true - tty: true - privileged: true - volumes: - - ./install.sh:/var/www/html/install.sh - - ./run.sh:/var/www/html/run.sh - - ./data/e-SUS:/opt/e-SUS - - /sys/fs/cgroup:/sys/fs/cgroup:ro - - ./certificates:/certificates - ports: - - "${APP_PORT}:8080" - - "80:80" - - "443:443" - depends_on: - - psql From c98740702a9f68c2137207700b257d797c54fdea Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Fri, 13 Dec 2024 12:05:41 +0000 Subject: [PATCH 10/18] refactor: simplifying build process --- .env | 12 ++- .env.development | 5 ++ .gitignore | 8 +- Dockerfile | 44 +++++------ Dockerfile.external-db | 12 ++- README.md | 24 +++--- build.sh | 135 +++++++++++++++++++++------------ docker-compose.all-in-one.yml | 9 ++- docker-compose.external-db.yml | 1 + entrypoint.sh | 8 +- find-download-link.sh | 18 +++-- install.sh | 58 +++++++++++--- run-java.sh | 20 ----- run.sh | 35 --------- 14 files changed, 213 insertions(+), 176 deletions(-) create mode 100644 .env.development delete mode 100644 run-java.sh delete mode 100755 run.sh diff --git a/.env b/.env index 27d247f..d280977 100644 --- a/.env +++ b/.env @@ -1,7 +1,5 @@ -POSTGRES_DB='esus' -POSTGRES_USER='postgres' -POSTGRES_PASSWORD='esus' -POSTGRESQL_PORT=54322 -APP_PORT=8080 -PGWEB_PORT=8099 -TIMEZONE='America/Bahia' \ No newline at end of file +# Timezone da aplicação que definirá o horário do sistema +TZ='America/Bahia' + +# Configurações de HTTPS +HTTPS_DOMAIN='pec.filipelopes.med.br' diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..b44174b --- /dev/null +++ b/.env.development @@ -0,0 +1,5 @@ +# Timezone da aplicação que definirá o horário do sistema +TZ='America/Bahia' + +# Configurações de HTTPS +HTTPS_DOMAIN='exemplo.gov.br' diff --git a/.gitignore b/.gitignore index 6bcbbd9..a197cc3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,10 @@ token.json venv pec.log docker-compose.npm.yml +decompiled-java/ +certificates/ *entrypoint/ -nginx/ +opt-esus/ esus-*/ -certificates/ -opt-esus/ \ No newline at end of file +nginx/ +jar-*/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3eb3e04..2661c0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,14 @@ RUN fc-cache -fv RUN chmod -R 777 /usr/share/fonts/truetype/msttcorefonts -# Instalando java 8, pre-requisitos para instalação do sistema PEC -RUN wget -O- https://apt.corretto.aws/corretto.key | apt-key add - -RUN add-apt-repository 'deb https://apt.corretto.aws stable main' -RUN apt-get update && apt-get install -y java-1.8.0-amazon-corretto-jdk +# Adicionar chave pública diretamente da URL correta +RUN wget -O - https://apt.corretto.aws/corretto.key | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg + +# Adicionar o repositório do Corretto, vinculando à chave +RUN echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | tee /etc/apt/sources.list.d/corretto.list + +# Atualizar repositórios e instalar o Corretto 17 LTS +RUN apt-get update && apt-get install -y java-17-amazon-corretto-jdk # Enable all repositories RUN sed -i 's/# deb/deb/g' /etc/apt/sources.list @@ -30,32 +34,28 @@ RUN apt-get update && \ rm -rf /usr/share/doc/* /usr/share/man/* /var/lib/apt/lists/* /tmp/* /var/tmp/* ARG JAR_FILENAME -ARG TRAINING -ARG POSTGRES_USERNAME -ARG POSTGRES_PASSWORD -ARG POSTGRES_DATABASE -ARG TZ +ARG HTTPS_DOMAIN ARG DUMPFILE +ARG TRAINING +# Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint ENV JAR_FILENAME=${JAR_FILENAME} -ENV DUMPFILE=${DUMPFILE} ENV TRAINING=${TRAINING} -ENV POSTGRES_USERNAME=${POSTGRES_USERNAME} -ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -ENV POSTGRES_DATABASE=${POSTGRES_DATABASE} -ENV TZ=${TZ} - -RUN export JAR_FILENAME=${JAR_FILENAME} +# criando diretórios para uso posterior +RUN mkdir -p /opt/e-SUS/webserver/chaves +RUN mkdir /backups RUN mkdir -p /var/www/html WORKDIR /var/www/html COPY ./${JAR_FILENAME} ${JAR_FILENAME} -COPY ./${DUMPFILE} ${DUMPFILE} -COPY ./install.sh install.sh -COPY ./run.sh run.sh +COPY ./install.sh . + +# Copiando arquivos de backup +COPY *.sql /backups +COPY *.backup /backups -RUN chmod +x /var/www/html/install.sh -RUN chmod +x /var/www/html/run.sh +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh -CMD ["/bin/bash", "/var/www/html/run.sh"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 60d9238..274eeb0 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -40,24 +40,28 @@ ARG DB_PASS ARG DB_USER ARG DUMPFILE -# Promovendo ARGS para ENV +# Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint ENV JAR_FILENAME=${JAR_FILENAME} ENV DB_URL=${DB_URL} ENV DB_PASS=${DB_PASS} ENV DB_USER=${DB_USER} +# criando diretórios para uso posterior RUN mkdir -p /opt/e-SUS/webserver/chaves +RUN mkdir /backups RUN mkdir -p /var/www/html WORKDIR /var/www/html +# ATENÇÃO: Esse é um Dockerfile de instalação e não de atualização +# Removendo arquivos de configuração anteriores, para instalar do zero. RUN if [ -f /etc/pec.config ]; then rm /etc/pec.config; fi COPY ./${JAR_FILENAME} ${JAR_FILENAME} -COPY ./run-java.sh . +COPY ./install.sh . # Copiando arquivos de backup -COPY *.sql . -COPY *.backup . +COPY *.sql /backups +COPY *.backup /backups COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/README.md b/README.md index 93f0e41..539fc79 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,30 @@ - + Compatível e testado com ![version](https://img.shields.io/badge/version-5.3.19-green) -**BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores - É um sistema bastante utilizado por profissionais de saúde da Atenção Básica para registros de pacientes e dados de saúde. Esse repositório se propõe a criar uma estrutura docker com linux para viabilizar o deploy do sistema em qualquer ambiente que tenha docker e facilitar a instalação e atualização do sistema [e-SUS PEC](https://sisaps.saude.gov.br/esus/) +**BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores + ## Instalação TD;LR -Esse script irá baixar [a versão mais recente do PEC](https://sisaps.saude.gov.br/esus/) e rodar em [docker](https://docs.docker.com/engine/install/ubuntu/) a versão de treinamento +Esse script irá baixar [a versão mais recente do PEC](https://sisaps.saude.gov.br/esus/) e rodar em [docker](https://docs.docker.com/engine/install/ubuntu/) a versão de treinamento por padrão. Edite o arquivo `.env` para configurar suas variáveis de ambiente e rode o script `build.sh` ```sh -cp .env.exameple .env -sh build.sh -d +cp .env.development .env +sh build.sh ``` -## Utilizando banco de dados externo - -Para execução com banco de dados externo: - -Configure as variáveis de ambiente disponíveis em `.env.example` depois de copiar para `.env` +Para instalar a versão de produção em vez da de teste use esse comando, não esqueça de configurar suas variáveis de ambiente em `.env` ```sh cp .env.example .env -sh build.sh -e +sh build.sh -p ``` +Utilize `sh build.sh --help` para mais opções + Acesse [Live/Demo](https://pec.filipelopes.med.br) Dúvidas? Colaboração? Ideias? Entre em contato pelo [WhatsApp](https://wa.me/5571986056232?text=Gostaria+de+informa%C3%A7%C3%B5es+sobre+o+projeto+PEC+SUS) @@ -96,7 +94,7 @@ Agradecimentos à equipe [NoHarm](https://noharm.ai/) que investiu nesse projeto
- + Apoie também esse e outros projetos. diff --git a/build.sh b/build.sh index fa88cb0..a8bcb3a 100755 --- a/build.sh +++ b/build.sh @@ -4,20 +4,38 @@ GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # No Color -training='' cache='' filename='' dumpfile='' https_domain='' use_external_db=false +production=false -while getopts "d:f:tce" flag; do +# Adicionando --help ao build.sh +if [ "$1" = "--help" ]; then + echo " + Script para instalação do PEC + + Uso: build.sh [-f ] [-d ] [-h ] [-c] [-p] [-e] + + -f {nome do arquivo ou URL} para especificar o arquivo jar a ser utilizado, caso não fornecido, será buscado o último + -c para utilizar o cache quando estiver rodando build das imagens docker e + -d para redirecionar a um dump de banco de dados, + -h para determinar o domínio HTTPS a ser utilizado para gerar o certificado, + -p para utilizar a instalação em ambiente de produção, + -e para utilizar banco de dados externo especificado em .env + + " + exit 0 +fi + +while getopts "d:f:h:cpe" flag; do case "${flag}" in f) filename=${OPTARG};; - t) training='-treinamento';; d) dumpfile=${OPTARG};; h) https_domain=${OPTARG};; c) cache='--no-cache';; + p) production=true;; e) use_external_db=true;; \?) echo "${RED}Opção(ões) inválida(s) adicionada(s), apenas @@ -26,30 +44,75 @@ while getopts "d:f:tce" flag; do -c para utilizar o cache quando estiver rodando build das imagens docker e -d para redirecionar a um dump de banco de dados, -h para determinar o domínio HTTPS a ser utilizado para gerar o certificado, - -t para versão de treinamento, - -e para utilizar banco de dados externo especificado em .env.external-db + -p para utilizar a instalação em ambiente de produção, + -e para utilizar banco de dados externo + são consideradas válidas${NC}" exit 1 ;; esac done +# Aumentando o tempo de timeout para não para a instalação export COMPOSE_HTTP_TIMEOUT=8000 -# Carrega variáveis do arquivo .env.external-db se necessário -if $use_external_db; then - echo "Usando banco de dados externo especificado em .env.external-db" - if [ -f ".env.external-db" ]; then - export $(grep -v '^#' .env.external-db | xargs) +# Carrega variáveis de ambiente +echo "Usando dados de .env" +if [ -f ".env" ]; then + export $(grep -v '^#' .env | xargs) + if [ -z "$filename" ]; then filename=$FILENAME + fi + if [ -z "$https_domain" ]; then https_domain=$HTTPS_DOMAIN - else - echo "${RED}Arquivo .env.external-db não encontrado.${NC}" - exit 1 fi + if [ -z "$dumpfile" ]; then + dumpfile=$DUMPFILE + fi + echo "Arquivo .env carregado com sucesso." +else + echo "${RED}Arquivo .env não encontrado.${NC}" + exit 1 +fi + +# Verifica se a variável filename existe +if [ -z "$filename" ]; then + echo "${RED}Arquivo de instalação não foi definido.${NC}" + echo "${GREEN}Buscando link de instalação na página do PEC...${NC}" + # URL da página onde está o link + PAGE_URL="https://sisaps.saude.gov.br/esus/" + + # Captura o conteúdo da página + HTML_CONTENT=$(curl -s "$PAGE_URL") + + # Extrai o link do arquivo de download para Linux + # Modifique o padrão se necessário, baseado no conteúdo real da página + DOWNLOAD_URL=$(echo "$HTML_CONTENT" | grep -o 'href="[^"]*Linux[^"]*' | sed 's/href="//' | head -1) + + # Verifica se encontrou o link + if [ -z "$DOWNLOAD_URL" ]; then + echo "Erro: Link para download não encontrado." + exit 1 + fi + + # Completa o link se for relativo + case "$DOWNLOAD_URL" in + http*) + # O link já é absoluto, não faz nada + ;; + *) + # Constrói o link absoluto a partir da URL base + BASE_URL=$(echo "$PAGE_URL" | sed -n 's#\(https\?://[^/]*\).*#\1#p') + DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL" + ;; + esac + + # Exibe o link encontrado + echo "Link para download encontrado: $DOWNLOAD_URL" + filename="$DOWNLOAD_URL" fi -# Verifica se filename é uma URL +# carregando arquivo de instalação if echo "$filename" | grep -q '^https://'; then # Extrai o nome do arquivo da URL jar_filename=$(basename "$filename") @@ -68,9 +131,11 @@ else jar_filename="$filename" fi + echo "${GREEN}Instalando e-SUS-PEC pelo arquivo $jar_filename${NC}" -docker compose down --volumes --remove-orphans -sudo chmod -R 755 data +docker compose -f docker-compose.all-in-one.yml down --volumes --remove-orphans +docker compose -f docker-compose.external-db.yml down --volumes --remove-orphans +docker compose -f docker-compose.split-db.yml down --volumes --remove-orphans # Verifica se psql está instalado e testa a conexão ao banco de dados if command -v psql > /dev/null; then @@ -94,6 +159,7 @@ else echo "${RED}psql não está instalado. A conexão ao banco de dados não pode ser testada.${NC}" fi +# Por fim instalando o sistema, definindo qual o docker-compose.yml será utilizado if $use_external_db; then # Monta a URL do banco de dados JDBC jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB?ssl=true&sslmode=allow&sslfactory=org.postgresql.ssl.NonValidatingFactory" @@ -104,36 +170,11 @@ if $use_external_db; then docker compose --progress plain -f docker-compose.external-db.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url docker compose -f docker-compose.external-db.yml up -d else - # Se use_external_db é false, não é para utilizar um banco de dados externo + # Roda versão de treinamento + training='-treinamento' echo "\n\n*******************" - echo "docker compose build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training" + echo "docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training" echo "*******************\n\n" - docker compose build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training - docker compose up -d - - echo "Avaliando estado de execução do container" - docker compose ps - # Aguardando container do banco de dados ficar pronto - sleep 15 - docker compose ps - - # Na hora de fazer o build não pode instalar os pacotes porque depende do banco de dados, por isso deve ser instalado por fora - echo "${GREEN}Instalando pacotes do e-SUS-PEC${NC}" - docker compose exec -it pec bash -c "sh /var/www/html/install.sh" - # Executando novamente o ENTRYPOINT do docker file, dessa vez com os pacotes já instalados. - echo "${GREEN}Executando entrypoint do e-SUS-PEC${NC}" - docker compose exec -it pec bash -c "sh /var/www/html/run.sh" -fi - -# Verifica o arquivo /etc/pec.config -# if [ -f "/etc/pec.config" ]; then -# success=$(jq -r '.success' /etc/pec.config) -# if [ "$success" == "true" ]; then -# echo "O arquivo /etc/pec.config está configurado corretamente com sucesso." -# else -# echo "${RED}Configuração do arquivo /etc/pec.config falhou.${NC}" -# exit 1 -# fi -# else -# echo "${RED}Arquivo /etc/pec.config não encontrado.${NC}" -# fi \ No newline at end of file + docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training + docker compose -f docker-compose.all-in-one.yml up -d +fi \ No newline at end of file diff --git a/docker-compose.all-in-one.yml b/docker-compose.all-in-one.yml index 2f734bd..8fdb7c0 100644 --- a/docker-compose.all-in-one.yml +++ b/docker-compose.all-in-one.yml @@ -3,17 +3,18 @@ services: container_name: pec platform: linux/amd64 restart: unless-stopped + volumes: + - ./esus-data:/opt/e-SUS + - ./esus-backups:/backups build: context: . dockerfile: Dockerfile args: - TZ=${TZ} + - DUMPFILE=${DUMPFILE} + - HTTPS_DOMAIN=${HTTPS_DOMAIN} stdin_open: true tty: true - privileged: true - volumes: - - ./data/e-SUS:/opt/e-SUS ports: - - "${APP_PORT}:8080" - "80:80" - "443:443" diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml index 549f9db..9a6c00e 100644 --- a/docker-compose.external-db.yml +++ b/docker-compose.external-db.yml @@ -5,6 +5,7 @@ services: restart: unless-stopped volumes: - ./opt-esus:/opt/e-SUS + - ./backups:/backups build: context: . dockerfile: Dockerfile.external-db diff --git a/entrypoint.sh b/entrypoint.sh index 6c0a570..b94f6b8 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,12 +1,12 @@ #!/bin/bash set -e +# Inicie o servidor HTTPS temporário aqui, se necessário echo ">> Iniciando servidor temporário na porta 80..." -# Inicie o servidor HTTP temporário aqui, se necessário -echo ">> Gerando certificado com CertMgr..." -chmod +x ./run-java.sh -./run-java.sh +echo ">> Gerando certificado com CertMgr e instalando o sistema..." +chmod +x ./install.sh +./install.sh echo ">> Certificado gerado com sucesso. Iniciando aplicação principal..." exec /opt/e-SUS/webserver/standalone.sh \ No newline at end of file diff --git a/find-download-link.sh b/find-download-link.sh index 21acf0e..a1823c1 100644 --- a/find-download-link.sh +++ b/find-download-link.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # URL da página onde está o link PAGE_URL="https://sisaps.saude.gov.br/esus/" @@ -8,7 +8,7 @@ HTML_CONTENT=$(curl -s "$PAGE_URL") # Extrai o link do arquivo de download para Linux # Modifique o padrão se necessário, baseado no conteúdo real da página -DOWNLOAD_URL=$(echo "$HTML_CONTENT" | grep -oP '(?<=href=")[^"]*Linux[^"]*' | head -1) +DOWNLOAD_URL=$(echo "$HTML_CONTENT" | grep -o 'href="[^"]*Linux[^"]*' | sed 's/href="//' | head -1) # Verifica se encontrou o link if [ -z "$DOWNLOAD_URL" ]; then @@ -17,10 +17,16 @@ if [ -z "$DOWNLOAD_URL" ]; then fi # Completa o link se for relativo -if [[ "$DOWNLOAD_URL" != http* ]]; then - BASE_URL=$(echo "$PAGE_URL" | grep -oE 'https?://[^/]+' | head -1) - DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL" -fi +case "$DOWNLOAD_URL" in + http*) + # O link já é absoluto, não faz nada + ;; + *) + # Constrói o link absoluto a partir da URL base + BASE_URL=$(echo "$PAGE_URL" | sed -n 's#\(https\?://[^/]*\).*#\1#p') + DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL" + ;; +esac # Exibe o link encontrado echo "Link para download encontrado: $DOWNLOAD_URL" \ No newline at end of file diff --git a/install.sh b/install.sh index 20c4c7c..2d8db81 100755 --- a/install.sh +++ b/install.sh @@ -1,20 +1,56 @@ +#!/bin/bash + GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # No Color +ARGS="" -if [ -n "$DUMPFILE" ]; then - DUMPFILE_OPT="-restore=${DUMPFILE}" -else - DUMPFILE_OPT="" +# Echo das variáveis de ambiente +echo -e "${GREEN}\n\n*******************" +echo "Variáveis de ambiente:" +echo "*******************" +echo "HTTPS_DOMAIN: ${HTTPS_DOMAIN}" +echo "DB_URL: ${DB_URL}" +echo "DB_USER: ${DB_USER}" +echo "DB_PASS: ${DB_PASS}" +echo "JAR_FILENAME: ${JAR_FILENAME}" +echo "DUMPFILE: ${DUMPFILE}" +echo "TRAINING: ${TRAINING}" +echo "*******************\n\n${NC}" + + +# Verificando variável de certificado https +if [ -n "$HTTPS_DOMAIN" ]; then + ARGS="$ARGS -cert-domain=${HTTPS_DOMAIN}" fi -cd /var/www/html +# Verificando variáveis de banco de dados +if [ -n "$DB_URL" ]; then + ARGS="$ARGS -url=${DB_URL}" +fi + +if [ -n "$DB_USER" ]; then + ARGS="$ARGS -username=${DB_USER}" +fi -echo "${GREEN}Instalando pacote java ${JAR_FILENAME}${NC}" -echo "Verificando variável de treinamento ${TRAINING}" +if [ -n "$DB_PASS" ]; then + ARGS="$ARGS -password=${DB_PASS}" +fi + +# Construa os argumentos para o comando +if [ -n "$DUMPFILE" ]; then + ARGS="-restore=/backups/${DUMPFILE}" +fi + +# Verificando variável de treinamento e se não está vazio +if [ -n "$TRAINING" ]; then + ARGS="$ARGS -treinamento" +fi -echo "\n*******************" -echo "java -jar ${JAR_FILENAME} -console -url="jdbc:postgresql://psql:5432/${POSTGRES_DATABASE}" -username=${POSTGRES_USERNAME} -password=${POSTGRES_PASSWORD} ${DUMPFILE_OPT} ${TRAINING}" -echo "*******************\n" +# A ser executado java -jar +echo -e "${GREEN}\n\n*******************" +echo "java -jar ${JAR_FILENAME} -console ${ARGS} -continue" +echo "*******************\n\n${NC}" -java -jar ${JAR_FILENAME} -console -url="jdbc:postgresql://psql:5432/${POSTGRES_DATABASE}" -username=${POSTGRES_USERNAME} -password=${POSTGRES_PASSWORD} ${DUMPFILE_OPT} ${TRAINING} \ No newline at end of file +# Executa o comando +java -jar ${JAR_FILENAME} -console ${ARGS} -continue \ No newline at end of file diff --git a/run-java.sh b/run-java.sh deleted file mode 100644 index 27a4ced..0000000 --- a/run-java.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Construa os argumentos para o comando -if [ -n "$DUMPFILE" ]; then - ARGS="-restore=/var/www/html/${DUMPFILE}" -else - ARGS="" -fi - -# A ser executado java -jar -echo -e "${GREEN}\n\n*******************" -echo "java -jar ${JAR_FILENAME} -console -cert-domain=${HTTPS_DOMAIN} -url=${DB_URL} -username=${DB_USER} -password=${DB_PASS} ${ARGS} -continue" -echo "*******************\n\n${NC}" - -# Executa o comando -java -jar ${JAR_FILENAME} -console -cert-domain=${HTTPS_DOMAIN} -url=${DB_URL} -username=${DB_USER} -password=${DB_PASS} ${ARGS} -continue \ No newline at end of file diff --git a/run.sh b/run.sh deleted file mode 100755 index d323f71..0000000 --- a/run.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo "${GREEN}Inicializando configuração de data e hora...${NC}" -# ntpd -gq -service ntp start - -echo "Ajustando data para $TIMEZONE ..." -echo $TIMEZONE > /etc/timezone && \ -ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \ -dpkg-reconfigure -f noninteractive tzdata - -echo "${GREEN}Inicializando sistema...${NC}" -FILE=/opt/e-SUS/webserver/standalone.sh - -if test -f "$FILE"; then - echo "$FILE existe. Executando entrypoint do eSUS-PEC" - if ss -tulpn | grep 8080 > /dev/null ; then - echo "Já tem uma aplicação rodando na porta 8080, mantendo terminal aberto." - else - echo "Nada rodando em 8080, executando entrada..." - chmod +x /opt/e-SUS/webserver/standalone.sh - nohup /opt/e-SUS/webserver/standalone.sh & tail -f nohup.out - fi -else - printf "${RED}$FILE não existe, execute manualmente o sistema com\n \ - sh /opt/e-SUS/webserver/standalone.sh\n\n \ - ou instale o sitema primeiro, caso seja a primeira vez instalando:\n\n - sh /install.sh${NC}" -fi - -/bin/bash \ No newline at end of file From d7d55f453c9267b127ee9ccb8ece6ef5a77f3c57 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sun, 15 Dec 2024 12:41:17 +0000 Subject: [PATCH 11/18] fix: all in one mode production is working again --- .env | 11 ++++++++++- Dockerfile | 30 ++++++++++++++++++++++++++++-- Dockerfile.external-db | 1 + build.sh | 8 +++++--- docker-compose.all-in-one.yml | 21 +++++++++++++++++++-- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/.env b/.env index d280977..4bd7082 100644 --- a/.env +++ b/.env @@ -1,5 +1,14 @@ +# Configurações de banco de dados +POSTGRES_DB='esus' +POSTGRES_USER='postgres' +POSTGRES_PASSWORD='pass' +POSTGRES_HOST='db' +POSTGRES_PORT=5432 + # Timezone da aplicação que definirá o horário do sistema TZ='America/Bahia' # Configurações de HTTPS -HTTPS_DOMAIN='pec.filipelopes.med.br' +HTTPS_DOMAIN='dev.esus.noharm.ai' + +FILENAME='https://arquivos.esusab.ufsc.br/PEC/e925378f33a611e7/5.3.19/eSUS-AB-PEC-5.3.19-Linux64.jar' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2661c0f..ddd77ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,15 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive -# Instalando fontes que o PEC utiliza para impressão +# Instalando fontes que o PEC utiliza para impressão: ttf-mscorefonts-installer (limitação de versão 5.3.19-5.3.21) +# fontconfig para reinstalar fontes após modificação +# coreutils para reconhecer arquitetura x64 RUN apt-get update && apt-get install -y \ locales \ && locale-gen "pt_BR.UTF-8" \ && dpkg-reconfigure --frontend=noninteractive locales \ && apt-get install -y \ - wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp ttf-mscorefonts-installer fontconfig + coreutils wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp ttf-mscorefonts-installer fontconfig RUN fc-cache -fv @@ -33,14 +35,38 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /usr/share/doc/* /usr/share/man/* /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Substituir o comando `ps` para burlar a verificação de systemd +RUN mv /bin/ps /bin/ps.original && \ + echo '#!/bin/sh' > /bin/ps && \ + echo 'if [ "$1" = "--no-headers" ] && [ "$2" = "-o" ] && [ "$3" = "comm" ] && [ "$4" = "1" ]; then' >> /bin/ps && \ + echo ' echo "systemd"' >> /bin/ps && \ + echo 'else' >> /bin/ps && \ + echo ' /bin/ps.original "$@"' >> /bin/ps && \ + echo 'fi' >> /bin/ps && \ + chmod +x /bin/ps + +RUN echo '#!/bin/sh' > /bin/systemctl && \ + echo 'case "$1" in' >> /bin/systemctl && \ + echo ' start|stop|restart|status) ;;' >> /bin/systemctl && \ + echo ' *) echo "Simulated systemctl: $@"; exit 0 ;;' >> /bin/systemctl && \ + echo 'esac' >> /bin/systemctl && \ + chmod +x /bin/systemctl + ARG JAR_FILENAME ARG HTTPS_DOMAIN +ARG DB_URL +ARG DB_PASS +ARG DB_USER ARG DUMPFILE ARG TRAINING # Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint ENV JAR_FILENAME=${JAR_FILENAME} ENV TRAINING=${TRAINING} +ENV DB_URL=${DB_URL} +ENV DB_PASS=${DB_PASS} +ENV DB_USER=${DB_USER} +ENV HTTPS_DOMAIN=${HTTPS_DOMAIN} # criando diretórios para uso posterior RUN mkdir -p /opt/e-SUS/webserver/chaves diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 274eeb0..00d4493 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -45,6 +45,7 @@ ENV JAR_FILENAME=${JAR_FILENAME} ENV DB_URL=${DB_URL} ENV DB_PASS=${DB_PASS} ENV DB_USER=${DB_USER} +ENV HTTPS_DOMAIN=${HTTPS_DOMAIN} # criando diretórios para uso posterior RUN mkdir -p /opt/e-SUS/webserver/chaves diff --git a/build.sh b/build.sh index a8bcb3a..327d716 100755 --- a/build.sh +++ b/build.sh @@ -171,10 +171,12 @@ if $use_external_db; then docker compose -f docker-compose.external-db.yml up -d else # Roda versão de treinamento - training='-treinamento' + # training='-treinamento' + training='' + jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" echo "\n\n*******************" - echo "docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training" + echo "docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training" echo "*******************\n\n" - docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training + docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training docker compose -f docker-compose.all-in-one.yml up -d fi \ No newline at end of file diff --git a/docker-compose.all-in-one.yml b/docker-compose.all-in-one.yml index 8fdb7c0..37e661a 100644 --- a/docker-compose.all-in-one.yml +++ b/docker-compose.all-in-one.yml @@ -4,17 +4,34 @@ services: platform: linux/amd64 restart: unless-stopped volumes: - - ./esus-data:/opt/e-SUS - - ./esus-backups:/backups + - ./esus-data/opt:/opt/e-SUS + - ./esus-data/backups:/backups + - /sys/fs/cgroup:/sys/fs/cgroup:ro build: context: . dockerfile: Dockerfile args: - TZ=${TZ} + - DB_USER=${POSTGRES_USER} + - DB_PASS=${POSTGRES_PASSWORD} - DUMPFILE=${DUMPFILE} - HTTPS_DOMAIN=${HTTPS_DOMAIN} + privileged: true stdin_open: true tty: true ports: + - "8080:8080" - "80:80" - "443:443" + depends_on: + - db + db: + container_name: db + image: postgres:9.6-alpine + restart: unless-stopped + environment: + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-pass} + - POSTGRES_DB=${POSTGRES_DB:-esus} + volumes: + - ./esus-data/db:/var/lib/postgresql/data From 5473dc91a0328c1659f3dba603400079aa79a86c Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sun, 15 Dec 2024 12:42:10 +0000 Subject: [PATCH 12/18] fix: remove dotenv file from git --- .env | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 4bd7082..0000000 --- a/.env +++ /dev/null @@ -1,14 +0,0 @@ -# Configurações de banco de dados -POSTGRES_DB='esus' -POSTGRES_USER='postgres' -POSTGRES_PASSWORD='pass' -POSTGRES_HOST='db' -POSTGRES_PORT=5432 - -# Timezone da aplicação que definirá o horário do sistema -TZ='America/Bahia' - -# Configurações de HTTPS -HTTPS_DOMAIN='dev.esus.noharm.ai' - -FILENAME='https://arquivos.esusab.ufsc.br/PEC/e925378f33a611e7/5.3.19/eSUS-AB-PEC-5.3.19-Linux64.jar' \ No newline at end of file From fdce884092811d3238b15915af6175cbc3233242 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sun, 15 Dec 2024 23:51:49 +0000 Subject: [PATCH 13/18] feat: create working update script to automate upgrades --- Dockerfile | 5 +- entrypoint.sh | 51 ++++++++++++++++--- update.sh | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 update.sh diff --git a/Dockerfile b/Dockerfile index ddd77ec..0eb6976 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,15 +39,16 @@ RUN apt-get update && \ RUN mv /bin/ps /bin/ps.original && \ echo '#!/bin/sh' > /bin/ps && \ echo 'if [ "$1" = "--no-headers" ] && [ "$2" = "-o" ] && [ "$3" = "comm" ] && [ "$4" = "1" ]; then' >> /bin/ps && \ - echo ' echo "systemd"' >> /bin/ps && \ + echo ' echo "systemd (simulated: ps $@)"' >> /bin/ps && \ echo 'else' >> /bin/ps && \ echo ' /bin/ps.original "$@"' >> /bin/ps && \ echo 'fi' >> /bin/ps && \ chmod +x /bin/ps RUN echo '#!/bin/sh' > /bin/systemctl && \ + echo 'echo "Simulated systemctl command: $0 $@"' >> /bin/systemctl && \ echo 'case "$1" in' >> /bin/systemctl && \ - echo ' start|stop|restart|status) ;;' >> /bin/systemctl && \ + echo ' start|stop|restart|status) exit 0 ;;' >> /bin/systemctl && \ echo ' *) echo "Simulated systemctl: $@"; exit 0 ;;' >> /bin/systemctl && \ echo 'esac' >> /bin/systemctl && \ chmod +x /bin/systemctl diff --git a/entrypoint.sh b/entrypoint.sh index b94f6b8..b8c51e8 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,12 +1,51 @@ #!/bin/bash set -e -# Inicie o servidor HTTPS temporário aqui, se necessário -echo ">> Iniciando servidor temporário na porta 80..." +: ' +{ + "success" : true, + "directory" : "/opt/e-SUS", + "version" : "5.3.19", + "production" : true, + "customDatabase" : true, + "databaseUrl" : "jdbc:postgresql://db:5432/esus", + "databaseUsername" : "postgres", + "databasePassword" : "pass", + "jreVersion" : "17.0.10-linux_x64", + "jreDirectory" : "/opt/e-SUS/jre/17.0.10-linux_x64", + "webserverVersion" : "5.3.19", + "webserverDirectory" : "/opt/e-SUS/webserver" +} +' -echo ">> Gerando certificado com CertMgr e instalando o sistema..." -chmod +x ./install.sh -./install.sh +# Verifica se o sistema já foi instalado pela conferência da existência de um arquivo /etc/pec.config, caso não exista, instalar +if [ ! -f /etc/pec.config ]; then + echo ">> Sistema ainda não foi instalado. Instalando..." + echo ">> Gerando certificado com CertMgr e instalando o sistema..." + chmod +x ./install.sh + ./install.sh +fi -echo ">> Certificado gerado com sucesso. Iniciando aplicação principal..." +# Verifica existe um /etc/pec.config e se a instalação está em sucesso, caso sim, não instala. a estrutura do pec.config no início do arquivo +if [ -f "/etc/pec.config" ]; then + # Lê o conteúdo do arquivo /etc/pec.config + config=$(cat /etc/pec.config) + + # Verifica se a instalação foi bem-sucedida + # Se a instalação foi bem-sucedida, o campo "success" deve ser true + if echo "$config" | grep -q "\"success\" : true"; then + # Inicie a aplicação principal + echo ">> Iniciando aplicação principal..." + exec /opt/e-SUS/webserver/standalone.sh + else + # Se a instalação não foi bem-sucedida, exiba uma mensagem de erro + echo ">> Erro: Instalação não foi bem-sucedida." + echo ">> Tentando reinstalar sistema..." + chmod +x ./install.sh + ./install.sh + exit 1 + fi +fi + +echo ">> Iniciando aplicação principal..." exec /opt/e-SUS/webserver/standalone.sh \ No newline at end of file diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..4c0b79e --- /dev/null +++ b/update.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +# Variáveis de ambiente para o banco de dados +# DB_URL="jdbc:postgresql://db:5432/esus" +# DB_URL="jdbc:postgresql://db:5432/esus?ssl=true&sslmode=allow&sslfactory=org.postgresql.ssl.NonValidatingFactory" +# DB_USER="postgres" +# DB_PASS="pass" + +# Caminhos e URLs +WORKDIR="/var/www/html" +BACKUP_DIR="/backups" +PAGE_URL="https://sisaps.saude.gov.br/esus/" +UPDATE_SCRIPT_PATH="$WORKDIR/update.sh" + +# Argumento para escolha do arquivo Docker Compose +DOCKER_COMPOSE_FILE=$1 +if [ -z "$DOCKER_COMPOSE_FILE" ]; then + echo "Erro: É necessário informar o arquivo de configuração Docker Compose como argumento." + echo "Opções disponíveis: docker-compose.all-in-one.yml, docker-compose.external-db.yml" + exit 1 +fi + +# Data e hora para nomeação do backup +TIMESTAMP=$(date +"%Y%m%d%H%M") +BACKUP_FILE=$BACKUP_DIR/esus_backup_${TIMESTAMP}.backup +LOG_FILE=$BACKUP_DIR/warnings_errors_${TIMESTAMP}.log + +# Verifica se o container está rodando +if ! docker compose -f "$DOCKER_COMPOSE_FILE" ps | grep -q "pec"; then + echo "Erro: O serviço 'pec' não está rodando no arquivo de configuração $DOCKER_COMPOSE_FILE." + exit 1 +fi + +# Instala os pacotes necessários no container +echo "Instalando pacotes necessários no container..." +docker compose -f "$DOCKER_COMPOSE_FILE" exec pec sh -c " + apt-get update && \ + apt-get install -y postgresql-client curl && \ + echo 'Pacotes instalados com sucesso!' +" + +# Copia o script de atualização para o container, se não existir +if ! docker compose -f "$DOCKER_COMPOSE_FILE" exec pec [ -f "$UPDATE_SCRIPT_PATH" ]; then + echo "Copiando o script de atualização para o container..." + docker compose -f "$DOCKER_COMPOSE_FILE" cp ./update.sh pec:$UPDATE_SCRIPT_PATH +fi + +# Executa o script de dentro do container +echo "Executando o script de atualização dentro do container..." +docker compose -f "$DOCKER_COMPOSE_FILE" exec pec sh -c " + set -e + + # Navega para o diretório de trabalho + echo 'Navegando para o diretório de trabalho cd $WORKDIR...' + cd $WORKDIR + + # Remove arquivos .jar existentes + echo 'Removendo arquivos .jar antigos...' + rm -f *.jar + + # Captua DB_NAME da URL em DB_URL + DB_NAME=\$(echo \"\$DB_URL\" | sed -n 's/.*:\/\/.*\/\\([^?]*\\).*/\\1/p') + + # Verifica se DB_NAME foi extraído corretamente + if [ -z \"\$DB_NAME\" ]; then + echo 'Erro: Não foi possível capturar o nome do banco de dados a partir de DB_URL.' + exit 1 + fi + + echo \"Nome do banco de dados capturado: \$DB_NAME\" + + # Busca o link do novo arquivo + echo 'Buscando o link de download...' + HTML_CONTENT=\"\$(curl -s '$PAGE_URL')\" + DOWNLOAD_URL=\"\$(echo \"\$HTML_CONTENT\" | grep -o 'href=\"[^\"]*Linux[^\"]*\"' | sed 's/href=\"//' | sed 's/\"//' | head -1)\" + + if [ -z \"\$DOWNLOAD_URL\" ]; then + echo 'Erro: Link para download não encontrado.' + exit 1 + fi + + # Completa o link se for relativo + case \"\$DOWNLOAD_URL\" in + http*) + ;; + *) + BASE_URL=\$(echo '$PAGE_URL' | sed -n 's#\\(https\\?://[^/]*\\).*#\\1#p') + DOWNLOAD_URL=\"\$BASE_URL\$DOWNLOAD_URL\" + ;; + esac + + # Exibe o link encontrado e baixa o arquivo + echo \"Link para download encontrado: \$DOWNLOAD_URL\" + JAR_FILENAME=\$(basename \"\$DOWNLOAD_URL\") + wget -O \"$WORKDIR/\$JAR_FILENAME\" \"\$DOWNLOAD_URL\" + + # Realiza o backup do banco de dados + echo 'Realizando backup do banco de dados...' + env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME -f $BACKUP_FILE 2> $LOG_FILE + echo \"Backup realizado em $BACKUP_FILE\" + + # Filtra apenas warnings e erros no log + grep -E '(WARNING|ERROR)' $LOG_FILE > $LOG_FILE.filtered && mv $LOG_FILE.filtered $LOG_FILE + + # Removendo arquivo de configuração antigo para permitir instalação sem systemd + echo 'CAUTION: Removendo arquivo de configuração antigo para permitir instalação sem systemd...' + if [ -f "/etc/pec.config" ]; then + cat /etc/pec.config + rm /etc/pec.config + fi + + # Atualiza o sistema + echo 'Atualizando o sistema...' + # Debugando comando de instalação + java -jar $WORKDIR/\$JAR_FILENAME -console -url=\$DB_URL -username=\$DB_USER -password=\$DB_PASS -continue +" + +# Reinicia o container +echo "Reiniciando o container..." +docker compose -f "$DOCKER_COMPOSE_FILE" restart pec + +echo "Atualização concluida com sucesso!" + + +# docker compose -f docker-compose.all-in-one.yml exec pec sh -c " +# set -e + +# DB_NAME=\$(echo \"\$DB_URL\" | sed -n 's/.*:\/\/.*\/\\([^?]*\\).*/\\1/p') + +# # Realiza o backup do banco de dados +# echo 'Verificando comando de backup antes de executar' +# echo \"env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME\" +# echo 'Realizando backup do banco de dados...' +# env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME +# " \ No newline at end of file From 1ec2c0bad868f4501880ee722615cf89018bedaf Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Mon, 16 Dec 2024 23:06:28 +0000 Subject: [PATCH 14/18] docs: add folder with bash snippets to improve linux setup --- POP.md | 8 ++------ .../find-download-link.sh | 4 +++- linux-standalone-instalation/install-deps.sh | 13 +++++++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) rename find-download-link.sh => linux-standalone-instalation/find-download-link.sh (92%) create mode 100644 linux-standalone-instalation/install-deps.sh diff --git a/POP.md b/POP.md index 7e2d4be..88f26ba 100644 --- a/POP.md +++ b/POP.md @@ -32,15 +32,11 @@ Segue um exemplo, lembre-se de trocar os paths de acordo com o seu ambiente: # Para verificar a senha de seu banco de dados cat 'C:\Program Files\e-SUS\webserver\config\credenciais.txt' -C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -U postgres -p 5433 -d esus -v | Tee-Object -FilePath backup.sql +C:\Users\PC\Desktop> & 'C:\Program Files\e-SUS\database\postgresql-9.6.13-4-windows-x64\bin\pg_dump.exe' -Fc -U postgres -p 5433 -d esus -v -f backup.dump # O sistema irá peduir a senha ``` -```powershell -pg_dump -U -d -F p > backup.sql -``` - -2. Compacte o arquivo: +1. Compacte o arquivo: - **ZIP**: ```powershell Compress-Archive -Path backup.sql -DestinationPath backup.zip diff --git a/find-download-link.sh b/linux-standalone-instalation/find-download-link.sh similarity index 92% rename from find-download-link.sh rename to linux-standalone-instalation/find-download-link.sh index a1823c1..204adc9 100644 --- a/find-download-link.sh +++ b/linux-standalone-instalation/find-download-link.sh @@ -29,4 +29,6 @@ case "$DOWNLOAD_URL" in esac # Exibe o link encontrado -echo "Link para download encontrado: $DOWNLOAD_URL" \ No newline at end of file +echo "Link para download encontrado: $DOWNLOAD_URL" + +wget $DOWNLOAD_URL \ No newline at end of file diff --git a/linux-standalone-instalation/install-deps.sh b/linux-standalone-instalation/install-deps.sh new file mode 100644 index 0000000..a28b891 --- /dev/null +++ b/linux-standalone-instalation/install-deps.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +sudo apt-get update && sudo apt-get install -y \ + locales coreutils wget apt-utils gnupg2 software-properties-common \ + file libfreetype6 ntp ttf-mscorefonts-installer fontconfig + +sudo fc-cache -fv +sudo chmod -R 777 /usr/share/fonts/truetype/msttcorefonts + +wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg +echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list + +sudo apt-get update && sudo apt-get install -y java-17-amazon-corretto-jdk \ No newline at end of file From a6b0b8b5cc2e6661d08bcb52081b939e61e26407 Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sat, 21 Dec 2024 13:28:39 +0000 Subject: [PATCH 15/18] feat: create safe folder env for Amazon Elastic Container Registry creation --- Makefile | 60 ------- aws/.env.example | 16 ++ aws/Dockerfile | 101 +++++++++++ .../docker-compose.yml | 41 ++--- aws/entrypoint.sh | 34 ++++ aws/index.html | 1 + aws/install.sh | 73 ++++++++ build.sh | 164 +++++++----------- ...-in-one.yml => docker-compose.local-db.yml | 2 + install.sh | 30 +++- linux-standalone-instalation/install-deps.sh | 5 +- update.sh | 17 +- 12 files changed, 347 insertions(+), 197 deletions(-) delete mode 100644 Makefile create mode 100644 aws/.env.example create mode 100644 aws/Dockerfile rename docker-compose.split-db.yml => aws/docker-compose.yml (56%) create mode 100644 aws/entrypoint.sh create mode 100644 aws/index.html create mode 100644 aws/install.sh rename docker-compose.all-in-one.yml => docker-compose.local-db.yml (93%) diff --git a/Makefile b/Makefile deleted file mode 100644 index 055f56d..0000000 --- a/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -# Define a variável do domínio -DNS ?= pec.filipelopes.med.br - -# Target para gerar SSL -generate-ssl: - docker run -it --rm --name certbot \ - -v "./certificates:/etc/letsencrypt" \ - -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ - certbot/certbot certonly \ - --manual --preferred-challenges dns \ - -d $(DNS) \ - --agree-tos --no-eff-email - -# Target para converter certificados para JKS e instalar no PEC -install-ssl: - # Transformação de tipo de chave - docker compose exec -it pec sh -c '\ - mkdir -p /opt/e-SUS/webserver/chaves && \ - # Criar PKCS#12 \ - openssl pkcs12 -export -in /certificates/live/$(DNS)/fullchain.pem \ - -inkey /certificates/live/$(DNS)/privkey.pem \ - -out /opt/e-SUS/webserver/chaves/keystore.p12 \ - -name esuspec \ - -CAfile /certificates/live/$(DNS)/chain.pem \ - -caname root \ - -password pass:$(PASS) && \ - # Converter PKCS#12 para JKS \ - keytool -importkeystore -deststorepass $(PASS) \ - -destkeypass $(PASS) \ - -destkeystore /opt/e-SUS/webserver/chaves/keystore.jks \ - -srckeystore /opt/e-SUS/webserver/chaves/keystore.p12 \ - -srcstoretype PKCS12 \ - -srcstorepass $(PASS) \ - -alias esuspec \ - ' - - # Colocando configurações no arquivo de configurações - docker compose exec -it pec sh -c '\ - # Alterando porta \ - sed -i "/^server.port=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.port=443" >> /opt/e-SUS/webserver/config/application.properties && \ - # Alterando tipo de certificado \ - sed -i "/^server.ssl.key-store-type=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.ssl.key-store-type=JKS" >> /opt/e-SUS/webserver/config/application.properties && \ - # Alterando caminho do certificado \ - sed -i "/^server.ssl.key-store=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.ssl.key-store=/opt/e-SUS/webserver/chaves/keystore.jks" >> /opt/e-SUS/webserver/config/application.properties && \ - # Alterando senha do certificado \ - sed -i "/^server.ssl.key-store-password=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.ssl.key-store-password=$(PASS)" >> /opt/e-SUS/webserver/config/application.properties && \ - # Alterando alias do certificado \ - sed -i "/^server.ssl.key-alias=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.ssl.key-alias=esuspec" >> /opt/e-SUS/webserver/config/application.properties && \ - # Alterando flag de SSL \ - sed -i "/^server.ssl.enabled=/d" /opt/e-SUS/webserver/config/application.properties && \ - echo "server.ssl.enabled=true" >> /opt/e-SUS/webserver/config/application.properties \ - ' - -# Target para executar todas as etapas -all: generate-ssl install-ssl \ No newline at end of file diff --git a/aws/.env.example b/aws/.env.example new file mode 100644 index 0000000..5afc28a --- /dev/null +++ b/aws/.env.example @@ -0,0 +1,16 @@ +# Configurações de banco de dados +POSTGRES_DB='esus' +POSTGRES_USER='postgres' +POSTGRES_PASSWORD='pass' +POSTGRES_HOST='db' # host.docker.internal +POSTGRES_PORT=5432 +TRAINING=true + +# Porta da aplicação de desenvolvimento +APP_PORT=80 + +# Timezone da aplicação que definirá o horário do sistema +TZ='America/Bahia' + +# path de restauração de banco de dados já existente +DUMPFILE='dumpfile.sql' # Esse arquivo deve estar na raiz do projeto, caso queira um banco de dados em branco, exclua a variável diff --git a/aws/Dockerfile b/aws/Dockerfile new file mode 100644 index 0000000..0f88715 --- /dev/null +++ b/aws/Dockerfile @@ -0,0 +1,101 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Instalando fontes que o PEC utiliza para impressão: ttf-mscorefonts-installer (limitação de versão 5.3.19-5.3.21) +# fontconfig para reinstalar fontes após modificação +# coreutils para reconhecer arquitetura x64 +RUN apt-get update && apt-get install -y \ + locales \ + && locale-gen "pt_BR.UTF-8" \ + && dpkg-reconfigure --frontend=noninteractive locales \ + && apt-get install -y \ + coreutils wget apt-utils gnupg2 software-properties-common file libfreetype6 ntp ttf-mscorefonts-installer fontconfig + +RUN fc-cache -fv + +RUN chmod -R 777 /usr/share/fonts/truetype/msttcorefonts + +# Adicionar chave pública diretamente da URL correta +RUN wget -O - https://apt.corretto.aws/corretto.key | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg + +# Adicionar o repositório do Corretto, vinculando à chave +RUN echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | tee /etc/apt/sources.list.d/corretto.list + +# Atualizar repositórios e instalar o Corretto 17 LTS +RUN apt-get update && apt-get install -y java-17-amazon-corretto-jdk + +# Enable all repositories +RUN sed -i 's/# deb/deb/g' /etc/apt/sources.list + +# Instalações para uso do cron e configurações para utilização do systemd +RUN apt-get update && \ + apt-get install --no-install-recommends -y \ + dbus systemd systemd-cron rsyslog iproute2 python-is-python3 python3 python3-apt sudo bash ca-certificates && \ + apt-get clean && \ + rm -rf /usr/share/doc/* /usr/share/man/* /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Instalar cliente PostgreSQL +RUN apt-get update && apt-get install -y postgresql-client + +# Criando diretórios para uso posterior +RUN mkdir -p /opt/e-SUS/webserver/chaves +RUN mkdir -p /var/www/html +RUN mkdir /backups + +# Entrando no diretório de trabalho +WORKDIR /var/www/html + +# Baixando o último PEC disponível +RUN apt-get install -y curl && \ + PAGE_URL="https://sisaps.saude.gov.br/esus/" && \ + HTML_CONTENT=$(curl -s "$PAGE_URL") && \ + DOWNLOAD_URL=$(echo "$HTML_CONTENT" | grep -o 'href="[^"]*Linux[^"]*' | sed 's/href="//' | head -1) && \ + if [ -z "$DOWNLOAD_URL" ]; then \ + echo "Erro: Link para download não encontrado."; \ + exit 1; \ + fi && \ + case "$DOWNLOAD_URL" in \ + http*) ;; \ + *) BASE_URL=$(echo "$PAGE_URL" | sed -n 's#\(https\?://[^/]*\).*#\1#p'); \ + DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL"; \ + esac && \ + echo "Link para download encontrado: $DOWNLOAD_URL" && \ + JAR_FILENAME=$(basename "$DOWNLOAD_URL") && \ + if [ -f "/var/www/html/$JAR_FILENAME" ]; then \ + echo "O arquivo $JAR_FILENAME já existe e não será baixado novamente."; \ + else \ + wget -O "/var/www/html/$JAR_FILENAME" "$DOWNLOAD_URL"; \ + echo "Download concluído: $JAR_FILENAME"; \ + fi && \ + echo "JAR_FILENAME=$JAR_FILENAME" > /etc/jar_install_filename + +# Substituir o comando `ps` para burlar a verificação de systemd +RUN mv /bin/ps /bin/ps.original && \ + echo '#!/bin/sh' > /bin/ps && \ + echo 'if [ "$1" = "--no-headers" ] && [ "$2" = "-o" ] && [ "$3" = "comm" ] && [ "$4" = "1" ]; then' >> /bin/ps && \ + echo ' echo "systemd (simulated: ps $@)"' >> /bin/ps && \ + echo 'else' >> /bin/ps && \ + echo ' /bin/ps.original "$@"' >> /bin/ps && \ + echo 'fi' >> /bin/ps && \ + chmod +x /bin/ps + +RUN echo '#!/bin/sh' > /bin/systemctl && \ + echo 'echo "Simulated systemctl command: $0 $@"' >> /bin/systemctl && \ + echo 'case "$1" in' >> /bin/systemctl && \ + echo ' start|stop|restart|status) exit 0 ;;' >> /bin/systemctl && \ + echo ' *) echo "Simulated systemctl: $@"; exit 0 ;;' >> /bin/systemctl && \ + echo 'esac' >> /bin/systemctl && \ + chmod +x /bin/systemctl + +# Copiando arquivos de backup +COPY *.sql /backups +COPY *.backup /backups + +COPY ./install.sh . +COPY ./entrypoint.sh . +RUN chmod +x ./entrypoint.sh ./install.sh + +RUN ls -la + +ENTRYPOINT ["/var/www/html/entrypoint.sh"] \ No newline at end of file diff --git a/docker-compose.split-db.yml b/aws/docker-compose.yml similarity index 56% rename from docker-compose.split-db.yml rename to aws/docker-compose.yml index fd10c3e..02e5267 100644 --- a/docker-compose.split-db.yml +++ b/aws/docker-compose.yml @@ -1,29 +1,30 @@ services: - db: - container_name: db - image: postgres - restart: unless-stopped - environment: - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - POSTGRES_DB=${POSTGRES_DB} - volumes: - - ./data/db:/var/lib/postgresql/data pec: container_name: pec platform: linux/amd64 restart: unless-stopped - build: - context: . - dockerfile: Dockerfile - args: - - TZ=${TZ} + volumes: + - ./esus-data/opt:/opt/e-SUS + - ./esus-data/backups:/backups + - /sys/fs/cgroup:/sys/fs/cgroup:ro + build: . + env_file: .env + privileged: true stdin_open: true tty: true - privileged: true - volumes: - - ./data/e-SUS:/opt/e-SUS ports: - "${APP_PORT}:8080" - - "80:80" - - "443:443" + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - db + db: + container_name: db + image: postgres:9.6-alpine + restart: unless-stopped + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + volumes: + - ./esus-data/db:/var/lib/postgresql/data diff --git a/aws/entrypoint.sh b/aws/entrypoint.sh new file mode 100644 index 0000000..fd9378c --- /dev/null +++ b/aws/entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +# Verifica se o sistema já foi instalado pela conferência da existência de um arquivo /etc/pec.config, caso não exista, instalar +if [ ! -f /etc/pec.config ]; then + echo ">> Sistema ainda não foi instalado. Instalando..." + echo ">> Gerando certificado com CertMgr e instalando o sistema..." + chmod +x ./install.sh + ./install.sh +fi + +# Verifica existe um /etc/pec.config e se a instalação está em sucesso, caso sim, não instala. a estrutura do pec.config no início do arquivo +if [ -f "/etc/pec.config" ]; then + # Lê o conteúdo do arquivo /etc/pec.config + config=$(cat /etc/pec.config) + + # Verifica se a instalação foi bem-sucedida + # Se a instalação foi bem-sucedida, o campo "success" deve ser true + if echo "$config" | grep -q "\"success\" : true"; then + # Inicie a aplicação principal + echo ">> Iniciando aplicação principal..." + exec /opt/e-SUS/webserver/standalone.sh + else + # Se a instalação não foi bem-sucedida, exiba uma mensagem de erro + echo ">> Erro: Instalação não foi bem-sucedida." + echo ">> Tentando reinstalar sistema..." + ./install.sh + exit 1 + fi +fi + +echo ">> Iniciando aplicação principal..." +exec /opt/e-SUS/webserver/standalone.sh \ No newline at end of file diff --git a/aws/index.html b/aws/index.html new file mode 100644 index 0000000..b1cbd10 --- /dev/null +++ b/aws/index.html @@ -0,0 +1 @@ +PEC
\ No newline at end of file diff --git a/aws/install.sh b/aws/install.sh new file mode 100644 index 0000000..a2eed61 --- /dev/null +++ b/aws/install.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color +ARGS="" + +# Verificando variáveis de ambiente +if [ -n "$POSTGRES_HOST" ] && [ -n "$POSTGRES_PORT" ] && [ -n "$POSTGRES_DB" ]; then + DB_URL="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" +fi + +# Captura JAR_FILENAME de /etc/jar_install_filename +JAR_FILENAME=$(cat /etc/jar_install_filename | grep JAR_FILENAME | cut -d '=' -f2) + +# Echo das variáveis de ambiente +echo -e "${GREEN}\n\n*******************" +echo "Variáveis de ambiente:" +echo "*******************" +echo "DB_URL: ${DB_URL}" +echo "POSTGRES_USER: ${POSTGRES_USER}" +echo "POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}" +echo "JAR_FILENAME: ${JAR_FILENAME}" +echo "DUMPFILE: ${DUMPFILE}" +echo "TRAINING: ${TRAINING}" +echo "*******************\n\n${NC}" + +# Verificando variáveis de banco de dados +if [ -n "$DB_URL" ]; then + ARGS="$ARGS -url=${DB_URL}" +fi + +if [ -n "$POSTGRES_USER" ]; then + ARGS="$ARGS -username=${POSTGRES_USER}" +fi + +if [ -n "$POSTGRES_PASSWORD" ]; then + ARGS="$ARGS -password=${POSTGRES_PASSWORD}" +fi + +# Construa os argumentos para o comando +if [ -n "$DUMPFILE" ]; then + ARGS="-restore=/backups/${DUMPFILE}" +fi + +# A ser executado java -jar +echo -e "${GREEN}\n\n*******************" +echo "java -jar ${JAR_FILENAME} -console ${ARGS} -continue" +echo "*******************\n\n${NC}" + +# Executa o comando +java -jar ${JAR_FILENAME} -console ${ARGS} -continue + + +# Verificando se a variável de treinamento existe, caso sim, executa o SQL +if [ -n "$TRAINING" ]; then + echo -e "${GREEN}Treinamento habilitado. Executando SQL de configuração...${NC}" + PSQL_CMD="psql -h ${POSTGRES_HOST} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c \"update tb_config_sistema set ds_texto = null, ds_inteiro = 1 where co_config_sistema = 'TREINAMENTO';\"" + + # Exporta a senha do banco para evitar o prompt + export PGPASSWORD="${POSTGRES_PASSWORD}" + + # Executa o comando SQL + eval $PSQL_CMD + if [ $? -eq 0 ]; then + echo -e "${GREEN}Configuração de treinamento aplicada com sucesso.${NC}" + else + echo -e "${RED}Erro ao aplicar configuração de treinamento.${NC}" + fi + + # Limpa a variável de senha para segurança + unset PGPASSWORD +fi \ No newline at end of file diff --git a/build.sh b/build.sh index 327d716..137ca43 100755 --- a/build.sh +++ b/build.sh @@ -1,9 +1,11 @@ -#!/bin/bash +#!/bin/sh +# Definição de cores GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # No Color +# Variáveis iniciais cache='' filename='' dumpfile='' @@ -11,172 +13,136 @@ https_domain='' use_external_db=false production=false -# Adicionando --help ao build.sh +# Exibe ajuda do script if [ "$1" = "--help" ]; then echo " Script para instalação do PEC - + Uso: build.sh [-f ] [-d ] [-h ] [-c] [-p] [-e] - -f {nome do arquivo ou URL} para especificar o arquivo jar a ser utilizado, caso não fornecido, será buscado o último - -c para utilizar o cache quando estiver rodando build das imagens docker e - -d para redirecionar a um dump de banco de dados, - -h para determinar o domínio HTTPS a ser utilizado para gerar o certificado, - -p para utilizar a instalação em ambiente de produção, + -f {nome do arquivo ou URL} para especificar o arquivo JAR a ser utilizado (busca o último se não informado) + -c para utilizar cache ao construir as imagens Docker + -d {dump de banco de dados} para especificar o dump a ser utilizado + -h {domínio HTTPS} para gerar o certificado + -p para instalar em ambiente de produção -e para utilizar banco de dados externo especificado em .env - " exit 0 fi +# Processa os argumentos while getopts "d:f:h:cpe" flag; do case "${flag}" in - f) filename=${OPTARG};; - d) dumpfile=${OPTARG};; - h) https_domain=${OPTARG};; - c) cache='--no-cache';; - p) production=true;; - e) use_external_db=true;; + f) filename=${OPTARG} ;; + d) dumpfile=${OPTARG} ;; + h) https_domain=${OPTARG} ;; + c) cache='--no-cache' ;; + p) production=true ;; + e) use_external_db=true ;; \?) - echo "${RED}Opção(ões) inválida(s) adicionada(s), apenas - - -f {nome do arquivo ou URL} para especificar o arquivo jar a ser utilizado, - -c para utilizar o cache quando estiver rodando build das imagens docker e - -d para redirecionar a um dump de banco de dados, - -h para determinar o domínio HTTPS a ser utilizado para gerar o certificado, - -p para utilizar a instalação em ambiente de produção, - -e para utilizar banco de dados externo - - são consideradas válidas${NC}" - exit 1 - ;; + echo "${RED}Opção inválida! Utilize --help para ajuda.${NC}" + exit 1 + ;; esac done -# Aumentando o tempo de timeout para não para a instalação +# Define timeout para o Docker Compose export COMPOSE_HTTP_TIMEOUT=8000 -# Carrega variáveis de ambiente -echo "Usando dados de .env" +# Carrega variáveis de ambiente do .env +echo "Carregando variáveis de .env..." if [ -f ".env" ]; then export $(grep -v '^#' .env | xargs) - if [ -z "$filename" ]; then - filename=$FILENAME - fi - if [ -z "$https_domain" ]; then - https_domain=$HTTPS_DOMAIN - fi - if [ -z "$dumpfile" ]; then - dumpfile=$DUMPFILE - fi - echo "Arquivo .env carregado com sucesso." + filename=${filename:-$FILENAME} + https_domain=${https_domain:-$HTTPS_DOMAIN} + dumpfile=${dumpfile:-$DUMPFILE} + echo "${GREEN}Arquivo .env carregado com sucesso.${NC}" else echo "${RED}Arquivo .env não encontrado.${NC}" exit 1 fi -# Verifica se a variável filename existe +# Busca o link do arquivo JAR, caso não especificado if [ -z "$filename" ]; then - echo "${RED}Arquivo de instalação não foi definido.${NC}" echo "${GREEN}Buscando link de instalação na página do PEC...${NC}" - # URL da página onde está o link PAGE_URL="https://sisaps.saude.gov.br/esus/" - - # Captura o conteúdo da página HTML_CONTENT=$(curl -s "$PAGE_URL") - - # Extrai o link do arquivo de download para Linux - # Modifique o padrão se necessário, baseado no conteúdo real da página DOWNLOAD_URL=$(echo "$HTML_CONTENT" | grep -o 'href="[^"]*Linux[^"]*' | sed 's/href="//' | head -1) - # Verifica se encontrou o link if [ -z "$DOWNLOAD_URL" ]; then - echo "Erro: Link para download não encontrado." - exit 1 + echo "${RED}Erro: Link para download não encontrado.${NC}" + exit 1 fi - # Completa o link se for relativo case "$DOWNLOAD_URL" in - http*) - # O link já é absoluto, não faz nada - ;; - *) - # Constrói o link absoluto a partir da URL base - BASE_URL=$(echo "$PAGE_URL" | sed -n 's#\(https\?://[^/]*\).*#\1#p') - DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL" - ;; + http*) ;; + *) + BASE_URL=$(echo "$PAGE_URL" | sed -n 's#\(https\?://[^/]*\).*#\1#p') + DOWNLOAD_URL="$BASE_URL$DOWNLOAD_URL" + ;; esac - # Exibe o link encontrado - echo "Link para download encontrado: $DOWNLOAD_URL" + echo "${GREEN}Link para download encontrado: $DOWNLOAD_URL${NC}" filename="$DOWNLOAD_URL" fi -# carregando arquivo de instalação +# Faz o download do arquivo JAR if echo "$filename" | grep -q '^https://'; then - # Extrai o nome do arquivo da URL jar_filename=$(basename "$filename") - # Caminho onde o arquivo será salvo save_path="./$jar_filename" - # Verifica se o arquivo já existe if [ -f "$save_path" ]; then - echo "O arquivo $jar_filename já existe nessa pasta de execução. Não será baixado novamente." + echo "O arquivo $jar_filename já existe. Não será baixado novamente." else echo "Baixando o arquivo $jar_filename..." wget -O "$save_path" "$filename" - echo "Download concluído." + echo "${GREEN}Download concluído.${NC}" fi else - # Se filename não é uma URL, assume que é um caminho de arquivo local jar_filename="$filename" fi - -echo "${GREEN}Instalando e-SUS-PEC pelo arquivo $jar_filename${NC}" -docker compose -f docker-compose.all-in-one.yml down --volumes --remove-orphans +# Exibe mensagem de instalação +echo "${GREEN}Instalando e-SUS-PEC com o arquivo $jar_filename...${NC}" +docker compose -f docker-compose.local-db.yml down --volumes --remove-orphans docker compose -f docker-compose.external-db.yml down --volumes --remove-orphans -docker compose -f docker-compose.split-db.yml down --volumes --remove-orphans -# Verifica se psql está instalado e testa a conexão ao banco de dados +# Verifica se o psql está disponível if command -v psql > /dev/null; then echo "psql está instalado." if $use_external_db; then - echo "Testando a conexão com o banco de dados externo: $POSTGRES_HOST" - POSTGRES_HOST_FOR_TEST=$POSTGRES_HOST - if [ "$POSTGRES_HOST" = "host.docker.internal" ]; then - POSTGRES_HOST_FOR_TEST=localhost - fi + echo "Testando conexão com o banco de dados externo em $POSTGRES_HOST..." + POSTGRES_HOST_FOR_TEST=$([ "$POSTGRES_HOST" = "host.docker.internal" ] && echo "localhost" || echo "$POSTGRES_HOST") if PGPASSWORD=$POSTGRES_PASSWORD psql -h $POSTGRES_HOST_FOR_TEST -U $POSTGRES_USER -p $POSTGRES_PORT -d $POSTGRES_DB -c '\q'; then - echo "Conexão ao banco de dados externa bem-sucedida." + echo "${GREEN}Conexão ao banco de dados externa bem-sucedida.${NC}" else - echo "${RED}Falha ao conectar ao banco de dados externo. Verifique as credenciais e a URL.${NC}" + echo "${RED}Falha ao conectar ao banco de dados externo. Verifique as credenciais.${NC}" exit 1 fi else - echo "Sem URL do banco de dados externo fornecida, a conexão não será testada." + echo "Sem banco de dados externo fornecido." fi else - echo "${RED}psql não está instalado. A conexão ao banco de dados não pode ser testada.${NC}" + echo "${RED}psql não está instalado. Conexão ao banco de dados não pode ser testada.${NC}" fi -# Por fim instalando o sistema, definindo qual o docker-compose.yml será utilizado +# Executa instalação com o Docker Compose correto if $use_external_db; then - # Monta a URL do banco de dados JDBC - jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB?ssl=true&sslmode=allow&sslfactory=org.postgresql.ssl.NonValidatingFactory" - # Se use_external_db é true, é para utilizar um banco de dados externo - echo "\n\n*******************" - echo "docker compose --progress plain -f docker-compose.external-db.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url" - echo "*******************\n\n" - docker compose --progress plain -f docker-compose.external-db.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url + jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB?ssl=true&sslmode=allow&sslfactory=org.postgresql.ssl.NonValidatingFactory" + echo "\n${GREEN}Construindo e subindo Docker com banco de dados externo...${NC}" + docker compose --progress plain -f docker-compose.external-db.yml build $cache \ + --build-arg JAR_FILENAME=$jar_filename \ + --build-arg HTTPS_DOMAIN=$https_domain \ + --build-arg DB_URL=$jdbc_url docker compose -f docker-compose.external-db.yml up -d else - # Roda versão de treinamento - # training='-treinamento' + jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" training='' - jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" - echo "\n\n*******************" - echo "docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training" - echo "*******************\n\n" - docker compose --progress plain -f docker-compose.all-in-one.yml build $cache --build-arg JAR_FILENAME=$jar_filename --build-arg HTTPS_DOMAIN=$https_domain --build-arg DB_URL=$jdbc_url --build-arg DUMPFILE=$dumpfile --build-arg TRAINING=$training - docker compose -f docker-compose.all-in-one.yml up -d + echo "\n${GREEN}Construindo e subindo Docker com banco de dados local...${NC}" + docker compose --progress plain -f docker-compose.local-db.yml build $cache \ + --build-arg JAR_FILENAME=$jar_filename \ + --build-arg HTTPS_DOMAIN=$https_domain \ + --build-arg DB_URL=$jdbc_url \ + --build-arg DUMPFILE=$dumpfile \ + --build-arg TRAINING=$training + docker compose -f docker-compose.local-db.yml up -d fi \ No newline at end of file diff --git a/docker-compose.all-in-one.yml b/docker-compose.local-db.yml similarity index 93% rename from docker-compose.all-in-one.yml rename to docker-compose.local-db.yml index 37e661a..41b6084 100644 --- a/docker-compose.all-in-one.yml +++ b/docker-compose.local-db.yml @@ -23,6 +23,8 @@ services: - "8080:8080" - "80:80" - "443:443" + extra_hosts: + - "host.docker.internal:host-gateway" depends_on: - db db: diff --git a/install.sh b/install.sh index 2d8db81..64635e8 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,15 @@ -#!/bin/bash +#!/bin/sh GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # No Color ARGS="" +# Verificando variáveis de ambiente +if [ -n "$POSTGRES_HOST" ] && [ -n "$POSTGRES_PORT" ] && [ -n "$POSTGRES_DB" ]; then + DB_URL="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" +fi + # Echo das variáveis de ambiente echo -e "${GREEN}\n\n*******************" echo "Variáveis de ambiente:" @@ -53,4 +58,25 @@ echo "java -jar ${JAR_FILENAME} -console ${ARGS} -continue" echo "*******************\n\n${NC}" # Executa o comando -java -jar ${JAR_FILENAME} -console ${ARGS} -continue \ No newline at end of file +java -jar ${JAR_FILENAME} -console ${ARGS} -continue + + +# Verificando se a variável de treinamento existe, caso sim, executa o SQL +if [ -n "$TRAINING" ]; then + echo -e "${GREEN}Treinamento habilitado. Executando SQL de configuração...${NC}" + PSQL_CMD="psql -h ${POSTGRES_HOST} -p ${POSTGRES_PORT} -U ${DB_USER} -d ${POSTGRES_DB} -c \"update tb_config_sistema set ds_texto = null, ds_inteiro = 1 where co_config_sistema = 'TREINAMENTO';\"" + + # Exporta a senha do banco para evitar o prompt + export PGPASSWORD="${DB_PASS}" + + # Executa o comando SQL + eval $PSQL_CMD + if [ $? -eq 0 ]; then + echo -e "${GREEN}Configuração de treinamento aplicada com sucesso.${NC}" + else + echo -e "${RED}Erro ao aplicar configuração de treinamento.${NC}" + fi + + # Limpa a variável de senha para segurança + unset PGPASSWORD +fi \ No newline at end of file diff --git a/linux-standalone-instalation/install-deps.sh b/linux-standalone-instalation/install-deps.sh index a28b891..861d2db 100644 --- a/linux-standalone-instalation/install-deps.sh +++ b/linux-standalone-instalation/install-deps.sh @@ -10,4 +10,7 @@ sudo chmod -R 777 /usr/share/fonts/truetype/msttcorefonts wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list -sudo apt-get update && sudo apt-get install -y java-17-amazon-corretto-jdk \ No newline at end of file +sudo apt-get update && sudo apt-get install -y java-17-amazon-corretto-jdk + +export JAVA_TOOL_OPTIONS="-Duser.timezone=America/Bahia" +echo 'export JAVA_TOOL_OPTIONS="-Duser.timezone=America/Bahia"' >> ~/.bashrc \ No newline at end of file diff --git a/update.sh b/update.sh index 4c0b79e..ce5e788 100644 --- a/update.sh +++ b/update.sh @@ -16,7 +16,7 @@ UPDATE_SCRIPT_PATH="$WORKDIR/update.sh" DOCKER_COMPOSE_FILE=$1 if [ -z "$DOCKER_COMPOSE_FILE" ]; then echo "Erro: É necessário informar o arquivo de configuração Docker Compose como argumento." - echo "Opções disponíveis: docker-compose.all-in-one.yml, docker-compose.external-db.yml" + echo "Opções disponíveis: docker-compose.local-db.yml, docker-compose.external-db.yml" exit 1 fi @@ -119,17 +119,4 @@ docker compose -f "$DOCKER_COMPOSE_FILE" exec pec sh -c " echo "Reiniciando o container..." docker compose -f "$DOCKER_COMPOSE_FILE" restart pec -echo "Atualização concluida com sucesso!" - - -# docker compose -f docker-compose.all-in-one.yml exec pec sh -c " -# set -e - -# DB_NAME=\$(echo \"\$DB_URL\" | sed -n 's/.*:\/\/.*\/\\([^?]*\\).*/\\1/p') - -# # Realiza o backup do banco de dados -# echo 'Verificando comando de backup antes de executar' -# echo \"env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME\" -# echo 'Realizando backup do banco de dados...' -# env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME -# " \ No newline at end of file +echo "Atualização concluida com sucesso!" \ No newline at end of file From ab171fda5a77bb102d074e8d434f20cfc9c80ceb Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sat, 21 Dec 2024 13:36:58 +0000 Subject: [PATCH 16/18] docs(readme): update testing instructions env --- README.md | 16 ++++++++++++++-- aws/.env.example | 5 +---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 539fc79..1de2097 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ +🥳 **21/12/2024 - FINALMENTE ESTÁ FUNCIONANDO A VERSÃO DE TREINAMENTO** + +```sh +cd aws +cp .env.example .env +docker compose up -d --build +``` + Compatível e testado com - ![version](https://img.shields.io/badge/version-5.3.19-green) + ![version](https://img.shields.io/badge/version-5.3.19-green) ![version](https://img.shields.io/badge/version-5.3.22-green) É um sistema bastante utilizado por profissionais de saúde da Atenção Básica para registros de pacientes e dados de saúde. Esse repositório se propõe a criar uma estrutura docker com linux para viabilizar o deploy do sistema em qualquer ambiente que tenha docker e facilitar a instalação e atualização do sistema [e-SUS PEC](https://sisaps.saude.gov.br/esus/) @@ -23,7 +31,11 @@ cp .env.example .env sh build.sh -p ``` -Utilize `sh build.sh --help` para mais opções +Utilize `sh build.sh --help` para mais opções, por exemplo, para instalar a versão de produção combanco de dados externo após configuração `.env` + +```sh +sh build.sh -e +``` Acesse [Live/Demo](https://pec.filipelopes.med.br) Dúvidas? Colaboração? Ideias? Entre em contato pelo [WhatsApp](https://wa.me/5571986056232?text=Gostaria+de+informa%C3%A7%C3%B5es+sobre+o+projeto+PEC+SUS) diff --git a/aws/.env.example b/aws/.env.example index 5afc28a..13091cf 100644 --- a/aws/.env.example +++ b/aws/.env.example @@ -10,7 +10,4 @@ TRAINING=true APP_PORT=80 # Timezone da aplicação que definirá o horário do sistema -TZ='America/Bahia' - -# path de restauração de banco de dados já existente -DUMPFILE='dumpfile.sql' # Esse arquivo deve estar na raiz do projeto, caso queira um banco de dados em branco, exclua a variável +TZ='America/Bahia' \ No newline at end of file From ef2db9b86879bc3b5748a213fba2335e143f236e Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sat, 21 Dec 2024 13:50:46 +0000 Subject: [PATCH 17/18] docs: remove dumpfile from args cause its not implemented and tested instead restore with pg_restore and then deploy pec container --- .env.example | 3 --- Dockerfile | 3 +-- Dockerfile.external-db | 1 - README.md | 26 +++++--------------------- aws/install.sh | 6 ------ build.sh | 7 +------ docker-compose.external-db.yml | 1 - docker-compose.local-db.yml | 1 - install.sh | 6 ------ 9 files changed, 7 insertions(+), 47 deletions(-) diff --git a/.env.example b/.env.example index 4b2abf1..80bc9a3 100644 --- a/.env.example +++ b/.env.example @@ -14,8 +14,5 @@ APP_PORT=8081 # Timezone da aplicação que definirá o horário do sistema TZ='America/Bahia' -# path de restauração de banco de dados já existente -DUMPFILE='dumpfile.sql' # Esse arquivo deve estar na raiz do projeto, caso queira um banco de dados em branco, exclua a variável - # Configurações de HTTPS HTTPS_DOMAIN='exemplo.gov.br' diff --git a/Dockerfile b/Dockerfile index 0eb6976..3aa6dca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,10 +58,9 @@ ARG HTTPS_DOMAIN ARG DB_URL ARG DB_PASS ARG DB_USER -ARG DUMPFILE ARG TRAINING -# Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint +# Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint.sh e precisa dessas variáveis ENV JAR_FILENAME=${JAR_FILENAME} ENV TRAINING=${TRAINING} ENV DB_URL=${DB_URL} diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 00d4493..986495f 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -38,7 +38,6 @@ ARG HTTPS_DOMAIN ARG DB_URL ARG DB_PASS ARG DB_USER -ARG DUMPFILE # Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint ENV JAR_FILENAME=${JAR_FILENAME} diff --git a/README.md b/README.md index 1de2097..bd023ef 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ 🥳 **21/12/2024 - FINALMENTE ESTÁ FUNCIONANDO A VERSÃO DE TREINAMENTO** +Sem certificado https para permitir o uso de LoadBalancer e redirecionamento de DNS com proxy e seus respectivos certificados + ```sh cd aws cp .env.example .env @@ -13,8 +15,6 @@ Compatível e testado com É um sistema bastante utilizado por profissionais de saúde da Atenção Básica para registros de pacientes e dados de saúde. Esse repositório se propõe a criar uma estrutura docker com linux para viabilizar o deploy do sistema em qualquer ambiente que tenha docker e facilitar a instalação e atualização do sistema [e-SUS PEC](https://sisaps.saude.gov.br/esus/) -**BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores - ## Instalação TD;LR Esse script irá baixar [a versão mais recente do PEC](https://sisaps.saude.gov.br/esus/) e rodar em [docker](https://docs.docker.com/engine/install/ubuntu/) a versão de treinamento por padrão. Edite o arquivo `.env` para configurar suas variáveis de ambiente e rode o script `build.sh` @@ -46,9 +46,8 @@ Dúvidas? Colaboração? Ideias? Entre em contato pelo [WhatsApp](https://wa.me 2. [Preparando pacotes](#preparando-pacotes) 3. [Instalação do PEC](#instalacao-pec) 4. [Versão de Treinamento](#versao-treinamento) -5. [Certificado SSL](#certificado-ssl) -6. [Migração de Versão PEC](#migrando-versao) -7. [Outras informações relevantes](#outros) +5. [Atualização/Migração de Versão PEC](#migrando-versao) +6. [Outras informações relevantes](#outros) Ajude esse e outros projetos OpenSource para saúde: [Patrocínio](#patrocinio) @@ -121,22 +120,6 @@ Apoie também esse e outros projetos.
-## Certificado SSL (Processo semi automatizado) - -O certificado SSL é importante para podermos utilizar o -HTTPS (Habilita video chamadas e prescrição eletrônica, além de ser pré-requisito para login GOV.br). [Mais informações](https://saps-ms.github.io/Manual-eSUS_APS/docs/Apoio%20a%20Implanta%C3%A7%C3%A3o/Certificado_Https_Linux/) - -O métido utilizado para verificação do DNS é o DNS-1, vai ser necessário cadastrar um registro TXT no DNS, para isso fique atento ao prompt no terminal ao executar o primeiro passo abaixo: - -```sh -# https://github.com/filiperochalopes/e-SUS-PEC/issues/14 -make generate-ssl DNS=meu-dominio.com -sudo chmod -R 755 ./certificates -make install-ssl DNS=meu-dominio.com PASS=senha-certificado -``` - -Para renovar basta repetir o processo acima. - ## Versão de Treinamento [O pacote java disponibilizado](https://sisaps.saude.gov.br/esus/) pelo Ministério da Saúde/Secretaria de Atenção Primária à Saúde. [Laboratório Bridge](https://www.linkedin.com/company/laboratoriobridge/)/Universidade Federal de Santa Catarina. [Página de Suporte](https://esusaps.freshdesk.com/support/login) @@ -251,6 +234,7 @@ docker-compose up -d esus_app /opt/e-SUS/webserver/standalone.sh ## Bugs Conhecidos (Known Issues) / Troubleshoot / Q&A / FAQ +- **BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores - O Java 8 só funciona com OpenSSL 1.1, em caso de uso do OpenSSL mais recente 3.X, não irá funcionar as chaves PKCS12 para SSL, será necessário o uso das chaves *.jks nesses casos - Testes realizados com versão `4.2.7` e `4.2.8` não foram bem sucedidos - A versão 4.2.8 está com erro no formulário de cadastro, nas requisições ao banco de dados, pelo endpoint graphql, retorna "Não autorizado" diff --git a/aws/install.sh b/aws/install.sh index a2eed61..821d6d4 100644 --- a/aws/install.sh +++ b/aws/install.sh @@ -21,7 +21,6 @@ echo "DB_URL: ${DB_URL}" echo "POSTGRES_USER: ${POSTGRES_USER}" echo "POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}" echo "JAR_FILENAME: ${JAR_FILENAME}" -echo "DUMPFILE: ${DUMPFILE}" echo "TRAINING: ${TRAINING}" echo "*******************\n\n${NC}" @@ -38,11 +37,6 @@ if [ -n "$POSTGRES_PASSWORD" ]; then ARGS="$ARGS -password=${POSTGRES_PASSWORD}" fi -# Construa os argumentos para o comando -if [ -n "$DUMPFILE" ]; then - ARGS="-restore=/backups/${DUMPFILE}" -fi - # A ser executado java -jar echo -e "${GREEN}\n\n*******************" echo "java -jar ${JAR_FILENAME} -console ${ARGS} -continue" diff --git a/build.sh b/build.sh index 137ca43..f341de1 100755 --- a/build.sh +++ b/build.sh @@ -8,7 +8,6 @@ NC='\033[0m' # No Color # Variáveis iniciais cache='' filename='' -dumpfile='' https_domain='' use_external_db=false production=false @@ -18,11 +17,10 @@ if [ "$1" = "--help" ]; then echo " Script para instalação do PEC - Uso: build.sh [-f ] [-d ] [-h ] [-c] [-p] [-e] + Uso: build.sh [-f ] [-h ] [-c] [-p] [-e] -f {nome do arquivo ou URL} para especificar o arquivo JAR a ser utilizado (busca o último se não informado) -c para utilizar cache ao construir as imagens Docker - -d {dump de banco de dados} para especificar o dump a ser utilizado -h {domínio HTTPS} para gerar o certificado -p para instalar em ambiente de produção -e para utilizar banco de dados externo especificado em .env @@ -34,7 +32,6 @@ fi while getopts "d:f:h:cpe" flag; do case "${flag}" in f) filename=${OPTARG} ;; - d) dumpfile=${OPTARG} ;; h) https_domain=${OPTARG} ;; c) cache='--no-cache' ;; p) production=true ;; @@ -55,7 +52,6 @@ if [ -f ".env" ]; then export $(grep -v '^#' .env | xargs) filename=${filename:-$FILENAME} https_domain=${https_domain:-$HTTPS_DOMAIN} - dumpfile=${dumpfile:-$DUMPFILE} echo "${GREEN}Arquivo .env carregado com sucesso.${NC}" else echo "${RED}Arquivo .env não encontrado.${NC}" @@ -142,7 +138,6 @@ else --build-arg JAR_FILENAME=$jar_filename \ --build-arg HTTPS_DOMAIN=$https_domain \ --build-arg DB_URL=$jdbc_url \ - --build-arg DUMPFILE=$dumpfile \ --build-arg TRAINING=$training docker compose -f docker-compose.local-db.yml up -d fi \ No newline at end of file diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml index 9a6c00e..5b7b69f 100644 --- a/docker-compose.external-db.yml +++ b/docker-compose.external-db.yml @@ -13,7 +13,6 @@ services: - TZ=${TZ} - DB_USER=${POSTGRES_USER} - DB_PASS=${POSTGRES_PASSWORD} - - DUMPFILE=${DUMPFILE} - HTTPS_DOMAIN=${HTTPS_DOMAIN} stdin_open: true tty: true diff --git a/docker-compose.local-db.yml b/docker-compose.local-db.yml index 41b6084..3dc4011 100644 --- a/docker-compose.local-db.yml +++ b/docker-compose.local-db.yml @@ -14,7 +14,6 @@ services: - TZ=${TZ} - DB_USER=${POSTGRES_USER} - DB_PASS=${POSTGRES_PASSWORD} - - DUMPFILE=${DUMPFILE} - HTTPS_DOMAIN=${HTTPS_DOMAIN} privileged: true stdin_open: true diff --git a/install.sh b/install.sh index 64635e8..f1b372b 100755 --- a/install.sh +++ b/install.sh @@ -19,7 +19,6 @@ echo "DB_URL: ${DB_URL}" echo "DB_USER: ${DB_USER}" echo "DB_PASS: ${DB_PASS}" echo "JAR_FILENAME: ${JAR_FILENAME}" -echo "DUMPFILE: ${DUMPFILE}" echo "TRAINING: ${TRAINING}" echo "*******************\n\n${NC}" @@ -42,11 +41,6 @@ if [ -n "$DB_PASS" ]; then ARGS="$ARGS -password=${DB_PASS}" fi -# Construa os argumentos para o comando -if [ -n "$DUMPFILE" ]; then - ARGS="-restore=/backups/${DUMPFILE}" -fi - # Verificando variável de treinamento e se não está vazio if [ -n "$TRAINING" ]; then ARGS="$ARGS -treinamento" From c87a73a93b9badb94d1e7394c7cba6a49d66e40d Mon Sep 17 00:00:00 2001 From: Filipe Lopes Date: Sat, 21 Dec 2024 18:40:18 +0000 Subject: [PATCH 18/18] fix: variable names and convert build.sh script to work with testing env --- .env.example | 2 +- Dockerfile | 11 +++++--- Dockerfile.external-db | 8 +++--- README.md | 51 +++++----------------------------- aws/.env.example | 2 +- aws/docker-compose.yml | 2 +- aws/install.sh | 10 +++---- build.sh | 21 +++++++++++--- docker-compose.external-db.yml | 4 +-- docker-compose.local-db.yml | 11 +++++--- install.sh | 21 ++++++-------- update.sh | 8 +++--- 12 files changed, 64 insertions(+), 87 deletions(-) diff --git a/.env.example b/.env.example index 80bc9a3..de362a0 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Configurações de banco de dados POSTGRES_DB='esus' POSTGRES_USER='postgres' -POSTGRES_PASSWORD='pass' +POSTGRES_PASS='pass' POSTGRES_HOST='host.docker.internal' POSTGRES_PORT=5432 diff --git a/Dockerfile b/Dockerfile index 3aa6dca..d8e8c0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,9 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /usr/share/doc/* /usr/share/man/* /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Instalar cliente PostgreSQL +RUN apt-get update && apt-get install -y postgresql-client + # Substituir o comando `ps` para burlar a verificação de systemd RUN mv /bin/ps /bin/ps.original && \ echo '#!/bin/sh' > /bin/ps && \ @@ -56,16 +59,16 @@ RUN echo '#!/bin/sh' > /bin/systemctl && \ ARG JAR_FILENAME ARG HTTPS_DOMAIN ARG DB_URL -ARG DB_PASS -ARG DB_USER +ARG POSTGRES_PASS +ARG POSTGRES_USER ARG TRAINING # Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint.sh e precisa dessas variáveis ENV JAR_FILENAME=${JAR_FILENAME} ENV TRAINING=${TRAINING} ENV DB_URL=${DB_URL} -ENV DB_PASS=${DB_PASS} -ENV DB_USER=${DB_USER} +ENV POSTGRES_PASS=${POSTGRES_PASS} +ENV POSTGRES_USER=${POSTGRES_USER} ENV HTTPS_DOMAIN=${HTTPS_DOMAIN} # criando diretórios para uso posterior diff --git a/Dockerfile.external-db b/Dockerfile.external-db index 986495f..78778fd 100644 --- a/Dockerfile.external-db +++ b/Dockerfile.external-db @@ -36,14 +36,14 @@ RUN apt-get update && \ ARG JAR_FILENAME ARG HTTPS_DOMAIN ARG DB_URL -ARG DB_PASS -ARG DB_USER +ARG POSTGRES_PASS +ARG POSTGRES_USER # Promovendo ARGS para ENV para uso no install.sh que roda dentro do entrypoint ENV JAR_FILENAME=${JAR_FILENAME} ENV DB_URL=${DB_URL} -ENV DB_PASS=${DB_PASS} -ENV DB_USER=${DB_USER} +ENV POSTGRES_PASS=${POSTGRES_PASS} +ENV POSTGRES_USER=${POSTGRES_USER} ENV HTTPS_DOMAIN=${HTTPS_DOMAIN} # criando diretórios para uso posterior diff --git a/README.md b/README.md index bd023ef..c85eebf 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Utilize `sh build.sh --help` para mais opções, por exemplo, para instalar a ve sh build.sh -e ``` -Acesse [Live/Demo](https://pec.filipelopes.med.br) +Acesse [Live/Demo](https://dev.esus.noharm.ai) Dúvidas? Colaboração? Ideias? Entre em contato pelo [WhatsApp](https://wa.me/5571986056232?text=Gostaria+de+informa%C3%A7%C3%B5es+sobre+o+projeto+PEC+SUS) ## Sumário @@ -180,59 +180,22 @@ sudo cp data/backups/nome_do_arquivo.backup . Ou pode-se optar por fazer o backup pela própria ferramenta do PEC, use: ```sh -java jar esus-pec.jar -help +# substitua a versão pelo que estiver utilizando dentro do container pec +docker compose exec -it pec java jar esus-pec.jar -help ``` Para mais informações. -2. Exclua todo o banco de dados e dados relacionados em volume +2. Após isso, se seu banco de dados for externo, basta executar ```sh -docker-compose down --remove-orphans --volumes -sudo rm -rf data +sh update.sh docker-compose.local-db.yml ``` -3. Crie o banco de dados +Substitua o termo `docker-compose.local-db.yml` pelo termo `docker-compose.external-db.yml` para executar o script com o banco de dados externo. -```sh -docker-compose up -d psql -``` - -4. Copie o arquivo de backup - -```sh -sudo cp nome_do_arquivo.backup data/backups/ -``` - -5. Crie o banco de dados com base no backup - -```sh -docker exec -it esus_psql bash -pg_restore --verbose -U "postgres" -d "esus" -1 /home/seu_arquivo.backup -``` - -6. Instale o programa - -Fora do container, na pasta raiz do projeto execute, substituindo o nome do pacote `eSUS-AB-PEC-5.0.8-Linux64.jar` para a versão que você vai instalar em sua máquina. - -```sh -sh build.sh -f eSUS-AB-PEC-5.0.14-Linux64.jar -``` - -## Comandos interessantes - -Caso o container tenha sido interrompido sem querer, o comando abaixo pode ser útil - -```sh -# Em linux -make run -# Depois de rodar novamente os containers -docker-compose up -d -# Caso nenhum dos anteriores funcione execute diretamente o executável do sistema pec -docker-compose up -d esus_app /opt/e-SUS/webserver/standalone.sh -``` -## Bugs Conhecidos (Known Issues) / Troubleshoot / Q&A / FAQ +## Bugs Conhecidos (Known Issues) / Troubleshoot / Q&A / FAQ - **BREAKING CHANGE:** Desde a versão 5.3 o [certificado SSL é autogerenciado](https://saps-ms.github.io/Manual-eSUS_APS/docs/%C3%9Altimas%20releases/Vers%C3%A3o%205.3/#novidades---ferramentas-administrativas) e a versão Java utilizada é a 17 LTS. A última versão desse docker não funcionará para versões anteriores - O Java 8 só funciona com OpenSSL 1.1, em caso de uso do OpenSSL mais recente 3.X, não irá funcionar as chaves PKCS12 para SSL, será necessário o uso das chaves *.jks nesses casos diff --git a/aws/.env.example b/aws/.env.example index 13091cf..4284e3c 100644 --- a/aws/.env.example +++ b/aws/.env.example @@ -1,7 +1,7 @@ # Configurações de banco de dados POSTGRES_DB='esus' POSTGRES_USER='postgres' -POSTGRES_PASSWORD='pass' +POSTGRES_PASS='pass' POSTGRES_HOST='db' # host.docker.internal POSTGRES_PORT=5432 TRAINING=true diff --git a/aws/docker-compose.yml b/aws/docker-compose.yml index 02e5267..3acf384 100644 --- a/aws/docker-compose.yml +++ b/aws/docker-compose.yml @@ -24,7 +24,7 @@ services: restart: unless-stopped environment: - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=${POSTGRES_PASS} - POSTGRES_DB=${POSTGRES_DB} volumes: - ./esus-data/db:/var/lib/postgresql/data diff --git a/aws/install.sh b/aws/install.sh index 821d6d4..b3799dd 100644 --- a/aws/install.sh +++ b/aws/install.sh @@ -19,7 +19,7 @@ echo "Variáveis de ambiente:" echo "*******************" echo "DB_URL: ${DB_URL}" echo "POSTGRES_USER: ${POSTGRES_USER}" -echo "POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}" +echo "POSTGRES_PASS: ${POSTGRES_PASS}" echo "JAR_FILENAME: ${JAR_FILENAME}" echo "TRAINING: ${TRAINING}" echo "*******************\n\n${NC}" @@ -33,8 +33,8 @@ if [ -n "$POSTGRES_USER" ]; then ARGS="$ARGS -username=${POSTGRES_USER}" fi -if [ -n "$POSTGRES_PASSWORD" ]; then - ARGS="$ARGS -password=${POSTGRES_PASSWORD}" +if [ -n "$POSTGRES_PASS" ]; then + ARGS="$ARGS -password=${POSTGRES_PASS}" fi # A ser executado java -jar @@ -52,12 +52,12 @@ if [ -n "$TRAINING" ]; then PSQL_CMD="psql -h ${POSTGRES_HOST} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c \"update tb_config_sistema set ds_texto = null, ds_inteiro = 1 where co_config_sistema = 'TREINAMENTO';\"" # Exporta a senha do banco para evitar o prompt - export PGPASSWORD="${POSTGRES_PASSWORD}" + export PGPASSWORD="${POSTGRES_PASS}" # Executa o comando SQL eval $PSQL_CMD if [ $? -eq 0 ]; then - echo -e "${GREEN}Configuração de treinamento aplicada com sucesso.${NC}" + echo -e "${GREEN}Configuração de treinamento aplicada com sucesso!${NC}" else echo -e "${RED}Erro ao aplicar configuração de treinamento.${NC}" fi diff --git a/build.sh b/build.sh index f341de1..56eabc8 100755 --- a/build.sh +++ b/build.sh @@ -34,7 +34,7 @@ while getopts "d:f:h:cpe" flag; do f) filename=${OPTARG} ;; h) https_domain=${OPTARG} ;; c) cache='--no-cache' ;; - p) production=true ;; + p) production=false ;; e) use_external_db=true ;; \?) echo "${RED}Opção inválida! Utilize --help para ajuda.${NC}" @@ -43,6 +43,16 @@ while getopts "d:f:h:cpe" flag; do esac done +# Caso production seja false determina training para true +if [ "$production" = false ]; then + training=true +fi + +# Caso o banco de dados for externo modifica a variável logo para produção +if [ "$use_external_db" = true ]; then + production=true +fi + # Define timeout para o Docker Compose export COMPOSE_HTTP_TIMEOUT=8000 @@ -108,7 +118,7 @@ if command -v psql > /dev/null; then if $use_external_db; then echo "Testando conexão com o banco de dados externo em $POSTGRES_HOST..." POSTGRES_HOST_FOR_TEST=$([ "$POSTGRES_HOST" = "host.docker.internal" ] && echo "localhost" || echo "$POSTGRES_HOST") - if PGPASSWORD=$POSTGRES_PASSWORD psql -h $POSTGRES_HOST_FOR_TEST -U $POSTGRES_USER -p $POSTGRES_PORT -d $POSTGRES_DB -c '\q'; then + if PGPASSWORD=$POSTGRES_PASS psql -h $POSTGRES_HOST_FOR_TEST -U $POSTGRES_USER -p $POSTGRES_PORT -d $POSTGRES_DB -c '\q'; then echo "${GREEN}Conexão ao banco de dados externa bem-sucedida.${NC}" else echo "${RED}Falha ao conectar ao banco de dados externo. Verifique as credenciais.${NC}" @@ -132,12 +142,15 @@ if $use_external_db; then docker compose -f docker-compose.external-db.yml up -d else jdbc_url="jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" - training='' echo "\n${GREEN}Construindo e subindo Docker com banco de dados local...${NC}" - docker compose --progress plain -f docker-compose.local-db.yml build $cache \ + echo "docker compose --progress plain -f docker-compose.local-db.yml build $cache \ --build-arg JAR_FILENAME=$jar_filename \ --build-arg HTTPS_DOMAIN=$https_domain \ --build-arg DB_URL=$jdbc_url \ + --build-arg TRAINING=$training" + docker compose --progress plain -f docker-compose.local-db.yml build $cache \ + --build-arg JAR_FILENAME=$jar_filename \ + --build-arg DB_URL=$jdbc_url \ --build-arg TRAINING=$training docker compose -f docker-compose.local-db.yml up -d fi \ No newline at end of file diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml index 5b7b69f..f6f188b 100644 --- a/docker-compose.external-db.yml +++ b/docker-compose.external-db.yml @@ -11,8 +11,8 @@ services: dockerfile: Dockerfile.external-db args: - TZ=${TZ} - - DB_USER=${POSTGRES_USER} - - DB_PASS=${POSTGRES_PASSWORD} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASS=${POSTGRES_PASS} - HTTPS_DOMAIN=${HTTPS_DOMAIN} stdin_open: true tty: true diff --git a/docker-compose.local-db.yml b/docker-compose.local-db.yml index 3dc4011..8f75ae1 100644 --- a/docker-compose.local-db.yml +++ b/docker-compose.local-db.yml @@ -12,9 +12,12 @@ services: dockerfile: Dockerfile args: - TZ=${TZ} - - DB_USER=${POSTGRES_USER} - - DB_PASS=${POSTGRES_PASSWORD} - - HTTPS_DOMAIN=${HTTPS_DOMAIN} + environment: + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASS=${POSTGRES_PASS:-pass} + - POSTGRES_HOST=${POSTGRES_HOST:-db} # host.docker.internal + - POSTGRES_PORT=${POSTGRES_PORT:-5432} + - POSTGRES_DB=${POSTGRES_DB:-esus} privileged: true stdin_open: true tty: true @@ -32,7 +35,7 @@ services: restart: unless-stopped environment: - POSTGRES_USER=${POSTGRES_USER:-postgres} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-pass} + - POSTGRES_PASSWORD=${POSTGRES_PASS:-pass} - POSTGRES_DB=${POSTGRES_DB:-esus} volumes: - ./esus-data/db:/var/lib/postgresql/data diff --git a/install.sh b/install.sh index f1b372b..1263f69 100755 --- a/install.sh +++ b/install.sh @@ -16,8 +16,8 @@ echo "Variáveis de ambiente:" echo "*******************" echo "HTTPS_DOMAIN: ${HTTPS_DOMAIN}" echo "DB_URL: ${DB_URL}" -echo "DB_USER: ${DB_USER}" -echo "DB_PASS: ${DB_PASS}" +echo "POSTGRES_USER: ${POSTGRES_USER}" +echo "POSTGRES_PASS: ${POSTGRES_PASS}" echo "JAR_FILENAME: ${JAR_FILENAME}" echo "TRAINING: ${TRAINING}" echo "*******************\n\n${NC}" @@ -33,17 +33,12 @@ if [ -n "$DB_URL" ]; then ARGS="$ARGS -url=${DB_URL}" fi -if [ -n "$DB_USER" ]; then - ARGS="$ARGS -username=${DB_USER}" +if [ -n "$POSTGRES_USER" ]; then + ARGS="$ARGS -username=${POSTGRES_USER}" fi -if [ -n "$DB_PASS" ]; then - ARGS="$ARGS -password=${DB_PASS}" -fi - -# Verificando variável de treinamento e se não está vazio -if [ -n "$TRAINING" ]; then - ARGS="$ARGS -treinamento" +if [ -n "$POSTGRES_PASS" ]; then + ARGS="$ARGS -password=${POSTGRES_PASS}" fi # A ser executado java -jar @@ -58,10 +53,10 @@ java -jar ${JAR_FILENAME} -console ${ARGS} -continue # Verificando se a variável de treinamento existe, caso sim, executa o SQL if [ -n "$TRAINING" ]; then echo -e "${GREEN}Treinamento habilitado. Executando SQL de configuração...${NC}" - PSQL_CMD="psql -h ${POSTGRES_HOST} -p ${POSTGRES_PORT} -U ${DB_USER} -d ${POSTGRES_DB} -c \"update tb_config_sistema set ds_texto = null, ds_inteiro = 1 where co_config_sistema = 'TREINAMENTO';\"" + PSQL_CMD="psql -h ${POSTGRES_HOST} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c \"update tb_config_sistema set ds_texto = null, ds_inteiro = 1 where co_config_sistema = 'TREINAMENTO';\"" # Exporta a senha do banco para evitar o prompt - export PGPASSWORD="${DB_PASS}" + export PGPASSWORD="${POSTGRES_PASS}" # Executa o comando SQL eval $PSQL_CMD diff --git a/update.sh b/update.sh index ce5e788..44347be 100644 --- a/update.sh +++ b/update.sh @@ -3,8 +3,8 @@ # Variáveis de ambiente para o banco de dados # DB_URL="jdbc:postgresql://db:5432/esus" # DB_URL="jdbc:postgresql://db:5432/esus?ssl=true&sslmode=allow&sslfactory=org.postgresql.ssl.NonValidatingFactory" -# DB_USER="postgres" -# DB_PASS="pass" +# POSTGRES_USER="postgres" +# POSTGRES_PASS="pass" # Caminhos e URLs WORKDIR="/var/www/html" @@ -96,7 +96,7 @@ docker compose -f "$DOCKER_COMPOSE_FILE" exec pec sh -c " # Realiza o backup do banco de dados echo 'Realizando backup do banco de dados...' - env PGPASSWORD=\$DB_PASS pg_dump -Fc -v -h db -U \$DB_USER -d \$DB_NAME -f $BACKUP_FILE 2> $LOG_FILE + env PGPASSWORD=\$POSTGRES_PASS pg_dump -Fc -v -h db -U \$POSTGRES_USER -d \$DB_NAME -f $BACKUP_FILE 2> $LOG_FILE echo \"Backup realizado em $BACKUP_FILE\" # Filtra apenas warnings e erros no log @@ -112,7 +112,7 @@ docker compose -f "$DOCKER_COMPOSE_FILE" exec pec sh -c " # Atualiza o sistema echo 'Atualizando o sistema...' # Debugando comando de instalação - java -jar $WORKDIR/\$JAR_FILENAME -console -url=\$DB_URL -username=\$DB_USER -password=\$DB_PASS -continue + java -jar $WORKDIR/\$JAR_FILENAME -console -url=\$DB_URL -username=\$POSTGRES_USER -password=\$POSTGRES_PASS -continue " # Reinicia o container