feat: Docker Secrets para credenciais sensíveis
- Criado DockerSecretsConfigurationProvider para ler secrets de /run/secrets/ - Removidas credenciais sensíveis do appsettings.Production.json - Adicionado indicador visual no rodapé (✓/✗) para verificar se secrets foram carregados - Atualizado deploy.yml para usar Docker Secrets no Swarm - Criado script create-docker-secrets.sh para gerenciar secrets - Criado template secrets.env.template para facilitar configuração - Documentação completa em DOCKER_SECRETS_SETUP.md Secrets gerenciados: - stripe_secret_key - stripe_webhook_secret - mongodb_connection_string - google_client_id / google_client_secret - microsoft_client_id / microsoft_client_secret IMPORTANTE: Após este deploy, é necessário criar os secrets no Swarm e recriar o service. Consulte DOCKER_SECRETS_SETUP.md. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
00c5a132ab
commit
6d4a8904f2
@ -33,7 +33,10 @@
|
|||||||
"Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)",
|
"Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)",
|
||||||
"Bash(netstat -tln)",
|
"Bash(netstat -tln)",
|
||||||
"Bash(npm install)",
|
"Bash(npm install)",
|
||||||
"Bash(npm install:*)"
|
"Bash(npm install:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git push:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
71
.github/workflows/deploy.yml
vendored
71
.github/workflows/deploy.yml
vendored
@ -158,7 +158,7 @@ jobs:
|
|||||||
# Se a conexão funcionou, continua com o deploy
|
# Se a conexão funcionou, continua com o deploy
|
||||||
echo "=== Iniciando Deploy no Docker Swarm ==="
|
echo "=== Iniciando Deploy no Docker Swarm ==="
|
||||||
|
|
||||||
# Deploy via Docker Swarm (igual ao BCards)
|
# Deploy via Docker Swarm with Secrets
|
||||||
ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 << 'EOF'
|
ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 << 'EOF'
|
||||||
# Puxa nova imagem
|
# Puxa nova imagem
|
||||||
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
@ -170,29 +170,52 @@ jobs:
|
|||||||
# Cria a rede overlay se não existir
|
# Cria a rede overlay se não existir
|
||||||
docker network create --driver overlay --attachable qrrapido-network || echo "Rede já existe"
|
docker network create --driver overlay --attachable qrrapido-network || echo "Rede já existe"
|
||||||
|
|
||||||
# Atualiza o service ou cria se não existir
|
# Verifica se os secrets existem (devem ser criados manualmente uma vez)
|
||||||
docker service update \
|
echo "Verificando Docker Secrets..."
|
||||||
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
|
docker secret ls | grep -q "stripe_secret_key" || echo "AVISO: Secret stripe_secret_key não existe!"
|
||||||
--env-add Serilog__OpenSearchUrl="http://141.148.162.114:19201" \
|
docker secret ls | grep -q "stripe_webhook_secret" || echo "AVISO: Secret stripe_webhook_secret não existe!"
|
||||||
--env-add Serilog__OpenSearchFallback="http://129.146.116.218:19202" \
|
docker secret ls | grep -q "mongodb_connection_string" || echo "AVISO: Secret mongodb_connection_string não existe!"
|
||||||
qrrapido-prod \
|
docker secret ls | grep -q "google_client_id" || echo "AVISO: Secret google_client_id não existe!"
|
||||||
|| \
|
docker secret ls | grep -q "google_client_secret" || echo "AVISO: Secret google_client_secret não existe!"
|
||||||
docker service create \
|
docker secret ls | grep -q "microsoft_client_id" || echo "AVISO: Secret microsoft_client_id não existe!"
|
||||||
--name qrrapido-prod \
|
docker secret ls | grep -q "microsoft_client_secret" || echo "AVISO: Secret microsoft_client_secret não existe!"
|
||||||
--replicas 2 \
|
|
||||||
--network qrrapido-network \
|
# Verifica se o service existe
|
||||||
--publish published=5001,target=8080 \
|
if docker service inspect qrrapido-prod > /dev/null 2>&1; then
|
||||||
--mount type=bind,source=/app/keys,target=/app/keys \
|
echo "Service existe, atualizando imagem..."
|
||||||
--env ASPNETCORE_ENVIRONMENT=Production \
|
docker service update \
|
||||||
--env ASPNETCORE_URLS=http://+:8080 \
|
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
|
||||||
--env Serilog__OpenSearchUrl="http://141.148.162.114:19201" \
|
--env-add Serilog__OpenSearchUrl="http://141.148.162.114:19201" \
|
||||||
--env Serilog__OpenSearchFallback="http://129.146.116.218:19202" \
|
--env-add Serilog__OpenSearchFallback="http://129.146.116.218:19202" \
|
||||||
--update-delay 30s \
|
--with-registry-auth \
|
||||||
--update-parallelism 1 \
|
qrrapido-prod
|
||||||
--update-order start-first \
|
else
|
||||||
--restart-condition on-failure \
|
echo "Service não existe, criando com secrets..."
|
||||||
--restart-max-attempts 3 \
|
docker service create \
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
--name qrrapido-prod \
|
||||||
|
--replicas 2 \
|
||||||
|
--network qrrapido-network \
|
||||||
|
--publish published=5001,target=8080 \
|
||||||
|
--mount type=bind,source=/app/keys,target=/app/keys \
|
||||||
|
--secret stripe_secret_key \
|
||||||
|
--secret stripe_webhook_secret \
|
||||||
|
--secret mongodb_connection_string \
|
||||||
|
--secret google_client_id \
|
||||||
|
--secret google_client_secret \
|
||||||
|
--secret microsoft_client_id \
|
||||||
|
--secret microsoft_client_secret \
|
||||||
|
--env ASPNETCORE_ENVIRONMENT=Production \
|
||||||
|
--env ASPNETCORE_URLS=http://+:8080 \
|
||||||
|
--env Serilog__OpenSearchUrl="http://141.148.162.114:19201" \
|
||||||
|
--env Serilog__OpenSearchFallback="http://129.146.116.218:19202" \
|
||||||
|
--update-delay 30s \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-order start-first \
|
||||||
|
--restart-condition on-failure \
|
||||||
|
--restart-max-attempts 3 \
|
||||||
|
--with-registry-auth \
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
fi
|
||||||
|
|
||||||
# Aguarda o service estar estável
|
# Aguarda o service estar estável
|
||||||
echo "Aguardando service estabilizar..."
|
echo "Aguardando service estabilizar..."
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -362,6 +362,8 @@ temp/
|
|||||||
*.env
|
*.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.production
|
.env.production
|
||||||
|
secrets.env
|
||||||
|
scripts/secrets.env
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
111
Configuration/DockerSecretsConfigurationProvider.cs
Normal file
111
Configuration/DockerSecretsConfigurationProvider.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration provider that reads Docker Secrets from /run/secrets/
|
||||||
|
/// Secrets are mounted as files in Swarm mode.
|
||||||
|
/// </summary>
|
||||||
|
public class DockerSecretsConfigurationProvider : ConfigurationProvider
|
||||||
|
{
|
||||||
|
private readonly string _secretsPath;
|
||||||
|
private readonly Dictionary<string, string> _secretKeyMappings;
|
||||||
|
|
||||||
|
public DockerSecretsConfigurationProvider(string secretsPath, Dictionary<string, string> secretKeyMappings)
|
||||||
|
{
|
||||||
|
_secretsPath = secretsPath;
|
||||||
|
_secretKeyMappings = secretKeyMappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Load()
|
||||||
|
{
|
||||||
|
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!Directory.Exists(_secretsPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretsLoaded = 0;
|
||||||
|
|
||||||
|
foreach (var mapping in _secretKeyMappings)
|
||||||
|
{
|
||||||
|
var secretFileName = mapping.Key;
|
||||||
|
var configKey = mapping.Value;
|
||||||
|
var secretFilePath = Path.Combine(_secretsPath, secretFileName);
|
||||||
|
|
||||||
|
if (File.Exists(secretFilePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var secretValue = File.ReadAllText(secretFilePath).Trim();
|
||||||
|
if (!string.IsNullOrEmpty(secretValue))
|
||||||
|
{
|
||||||
|
Data[configKey] = secretValue;
|
||||||
|
secretsLoaded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Silently ignore read errors - will fall back to other config sources
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set indicator that secrets were loaded from Docker
|
||||||
|
if (secretsLoaded > 0)
|
||||||
|
{
|
||||||
|
Data["App:SecretsLoaded"] = "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DockerSecretsConfigurationSource : IConfigurationSource
|
||||||
|
{
|
||||||
|
public string SecretsPath { get; set; } = "/run/secrets";
|
||||||
|
public Dictionary<string, string> SecretKeyMappings { get; set; } = new();
|
||||||
|
|
||||||
|
public IConfigurationProvider Build(IConfigurationBuilder builder)
|
||||||
|
{
|
||||||
|
return new DockerSecretsConfigurationProvider(SecretsPath, SecretKeyMappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DockerSecretsConfigurationExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds Docker Secrets as a configuration source.
|
||||||
|
/// Maps secret file names to configuration keys.
|
||||||
|
/// </summary>
|
||||||
|
public static IConfigurationBuilder AddDockerSecrets(
|
||||||
|
this IConfigurationBuilder builder,
|
||||||
|
Action<DockerSecretsConfigurationSource>? configure = null)
|
||||||
|
{
|
||||||
|
var source = new DockerSecretsConfigurationSource
|
||||||
|
{
|
||||||
|
// Default mappings for QRRapido secrets
|
||||||
|
SecretKeyMappings = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
// Stripe
|
||||||
|
["stripe_secret_key"] = "Stripe:SecretKey",
|
||||||
|
["stripe_webhook_secret"] = "Stripe:WebhookSecret",
|
||||||
|
|
||||||
|
// MongoDB
|
||||||
|
["mongodb_connection_string"] = "ConnectionStrings:MongoDB",
|
||||||
|
|
||||||
|
// OAuth - Google
|
||||||
|
["google_client_id"] = "Authentication:Google:ClientId",
|
||||||
|
["google_client_secret"] = "Authentication:Google:ClientSecret",
|
||||||
|
|
||||||
|
// OAuth - Microsoft
|
||||||
|
["microsoft_client_id"] = "Authentication:Microsoft:ClientId",
|
||||||
|
["microsoft_client_secret"] = "Authentication:Microsoft:ClientSecret",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
configure?.Invoke(source);
|
||||||
|
builder.Add(source);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
DOCKER_SECRETS_SETUP.md
Normal file
195
DOCKER_SECRETS_SETUP.md
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# Docker Secrets Setup - QR Rapido
|
||||||
|
|
||||||
|
Este documento descreve como configurar Docker Secrets para o QR Rapido em ambiente de produção.
|
||||||
|
|
||||||
|
## Por que usar Docker Secrets?
|
||||||
|
|
||||||
|
- Credenciais sensíveis não ficam expostas no código ou em arquivos de configuração
|
||||||
|
- Secrets são criptografados em repouso e em trânsito
|
||||||
|
- Apenas containers autorizados têm acesso
|
||||||
|
- Facilita rotação de credenciais sem rebuild de imagens
|
||||||
|
|
||||||
|
## Secrets Necessários
|
||||||
|
|
||||||
|
| Secret Name | Descrição | Exemplo |
|
||||||
|
|-------------|-----------|---------|
|
||||||
|
| `stripe_secret_key` | Stripe API Secret Key | `sk_live_xxx` |
|
||||||
|
| `stripe_webhook_secret` | Stripe Webhook Signing Secret | `whsec_xxx` |
|
||||||
|
| `mongodb_connection_string` | String de conexão MongoDB completa | `mongodb://user:pass@host:port/db?options` |
|
||||||
|
| `google_client_id` | Google OAuth Client ID | `xxx.apps.googleusercontent.com` |
|
||||||
|
| `google_client_secret` | Google OAuth Client Secret | `GOCSPX-xxx` |
|
||||||
|
| `microsoft_client_id` | Microsoft OAuth Client ID | `guid` |
|
||||||
|
| `microsoft_client_secret` | Microsoft OAuth Client Secret | `xxx` |
|
||||||
|
|
||||||
|
## Criando os Secrets (Uma única vez)
|
||||||
|
|
||||||
|
Conecte no servidor manager do Swarm e execute os comandos abaixo.
|
||||||
|
|
||||||
|
### Opção 1: Via linha de comando (menos seguro - fica no histórico)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stripe
|
||||||
|
echo "sk_live_SUBSTITUA_PELA_SUA_CHAVE" | docker secret create stripe_secret_key -
|
||||||
|
echo "whsec_SUBSTITUA_PELO_SEU_SECRET" | docker secret create stripe_webhook_secret -
|
||||||
|
|
||||||
|
# MongoDB
|
||||||
|
echo "mongodb://admin:SENHA@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin" | docker secret create mongodb_connection_string -
|
||||||
|
|
||||||
|
# Google OAuth
|
||||||
|
echo "SEU_GOOGLE_CLIENT_ID.apps.googleusercontent.com" | docker secret create google_client_id -
|
||||||
|
echo "GOCSPX-SEU_CLIENT_SECRET" | docker secret create google_client_secret -
|
||||||
|
|
||||||
|
# Microsoft OAuth
|
||||||
|
echo "SEU_MICROSOFT_CLIENT_ID" | docker secret create microsoft_client_id -
|
||||||
|
echo "SEU_MICROSOFT_CLIENT_SECRET" | docker secret create microsoft_client_secret -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opção 2: Via arquivo (mais seguro)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cria arquivo temporário, insere o valor, cria o secret, e remove o arquivo
|
||||||
|
nano /tmp/stripe_secret && docker secret create stripe_secret_key /tmp/stripe_secret && rm /tmp/stripe_secret
|
||||||
|
```
|
||||||
|
|
||||||
|
Repita para cada secret.
|
||||||
|
|
||||||
|
## Verificando Secrets Criados
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lista todos os secrets
|
||||||
|
docker secret ls
|
||||||
|
|
||||||
|
# Deve mostrar:
|
||||||
|
# ID NAME CREATED
|
||||||
|
# xxx stripe_secret_key X minutes ago
|
||||||
|
# xxx stripe_webhook_secret X minutes ago
|
||||||
|
# xxx mongodb_connection_string X minutes ago
|
||||||
|
# xxx google_client_id X minutes ago
|
||||||
|
# xxx google_client_secret X minutes ago
|
||||||
|
# xxx microsoft_client_id X minutes ago
|
||||||
|
# xxx microsoft_client_secret X minutes ago
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recriando o Service com Secrets
|
||||||
|
|
||||||
|
Se o service já existe sem os secrets, você precisa recriá-lo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove o service existente
|
||||||
|
docker service rm qrrapido-prod
|
||||||
|
|
||||||
|
# O próximo deploy vai criar o service com os secrets automaticamente
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou manualmente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service create \
|
||||||
|
--name qrrapido-prod \
|
||||||
|
--replicas 2 \
|
||||||
|
--network qrrapido-network \
|
||||||
|
--publish published=5001,target=8080 \
|
||||||
|
--mount type=bind,source=/app/keys,target=/app/keys \
|
||||||
|
--secret stripe_secret_key \
|
||||||
|
--secret stripe_webhook_secret \
|
||||||
|
--secret mongodb_connection_string \
|
||||||
|
--secret google_client_id \
|
||||||
|
--secret google_client_secret \
|
||||||
|
--secret microsoft_client_id \
|
||||||
|
--secret microsoft_client_secret \
|
||||||
|
--env ASPNETCORE_ENVIRONMENT=Production \
|
||||||
|
--env ASPNETCORE_URLS=http://+:8080 \
|
||||||
|
--update-delay 30s \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-order start-first \
|
||||||
|
--restart-condition on-failure \
|
||||||
|
--restart-max-attempts 3 \
|
||||||
|
registry.redecarneir.us/qrrapido:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Atualizando um Secret
|
||||||
|
|
||||||
|
Docker Secrets são imutáveis. Para atualizar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Cria novo secret com nome diferente
|
||||||
|
echo "novo_valor" | docker secret create stripe_secret_key_v2 -
|
||||||
|
|
||||||
|
# 2. Atualiza o service para usar o novo secret
|
||||||
|
docker service update \
|
||||||
|
--secret-rm stripe_secret_key \
|
||||||
|
--secret-add stripe_secret_key_v2 \
|
||||||
|
qrrapido-prod
|
||||||
|
|
||||||
|
# 3. Remove o secret antigo
|
||||||
|
docker secret rm stripe_secret_key
|
||||||
|
|
||||||
|
# 4. (Opcional) Renomeia o novo secret
|
||||||
|
# Infelizmente não é possível renomear, então mantenha a convenção de versões
|
||||||
|
# ou recrie o service com o nome correto
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verificando se os Secrets estão funcionando
|
||||||
|
|
||||||
|
No rodapé da página, você verá:
|
||||||
|
|
||||||
|
- **QR Rapido v1.0.0 | Production ✓** - Secrets carregados corretamente
|
||||||
|
- **QR Rapido v1.0.0 | Production ✗** - Secrets NÃO carregados (problema!)
|
||||||
|
|
||||||
|
Você também pode verificar os logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service logs qrrapido-prod --tail 50 | grep -i secret
|
||||||
|
```
|
||||||
|
|
||||||
|
## Como funciona internamente
|
||||||
|
|
||||||
|
1. O Docker monta cada secret como arquivo em `/run/secrets/<nome_do_secret>`
|
||||||
|
2. O `DockerSecretsConfigurationProvider` lê esses arquivos na inicialização
|
||||||
|
3. Os valores sobrescrevem as configurações do `appsettings.json`
|
||||||
|
4. A variável `App:SecretsLoaded` é definida como `true` quando pelo menos um secret é carregado
|
||||||
|
|
||||||
|
## Testando Localmente
|
||||||
|
|
||||||
|
Para simular Docker Secrets localmente (sem Swarm):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cria diretório de secrets
|
||||||
|
mkdir -p /tmp/secrets
|
||||||
|
|
||||||
|
# Cria arquivos de teste
|
||||||
|
echo "sk_test_xxx" > /tmp/secrets/stripe_secret_key
|
||||||
|
echo "whsec_test" > /tmp/secrets/stripe_webhook_secret
|
||||||
|
|
||||||
|
# Executa o container com os secrets montados
|
||||||
|
docker run -d \
|
||||||
|
-v /tmp/secrets:/run/secrets:ro \
|
||||||
|
-p 5001:8080 \
|
||||||
|
-e ASPNETCORE_ENVIRONMENT=Development \
|
||||||
|
qrrapido:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Secret não está sendo lido
|
||||||
|
|
||||||
|
1. Verifique se o container tem acesso ao secret:
|
||||||
|
```bash
|
||||||
|
docker exec -it $(docker ps -q -f name=qrrapido) ls -la /run/secrets/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verifique o conteúdo (apenas para debug):
|
||||||
|
```bash
|
||||||
|
docker exec -it $(docker ps -q -f name=qrrapido) cat /run/secrets/stripe_secret_key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service não inicia
|
||||||
|
|
||||||
|
Verifique se todos os secrets existem antes de criar o service:
|
||||||
|
```bash
|
||||||
|
docker secret ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erro "secret not found"
|
||||||
|
|
||||||
|
O secret precisa existir ANTES de criar/atualizar o service que o usa.
|
||||||
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.StaticFiles;
|
|||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
|
using QRRapidoApp.Configuration;
|
||||||
using QRRapidoApp.Data;
|
using QRRapidoApp.Data;
|
||||||
using QRRapidoApp.Middleware;
|
using QRRapidoApp.Middleware;
|
||||||
using QRRapidoApp.Providers;
|
using QRRapidoApp.Providers;
|
||||||
@ -42,6 +43,10 @@ Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUP__STATICWEBASSETS__
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(options);
|
var builder = WebApplication.CreateBuilder(options);
|
||||||
|
|
||||||
|
// Add Docker Secrets as configuration source (for Swarm deployments)
|
||||||
|
// Secrets override values from appsettings.json
|
||||||
|
builder.Configuration.AddDockerSecrets();
|
||||||
|
|
||||||
// Configure Serilog
|
// Configure Serilog
|
||||||
var loggerConfig = new LoggerConfiguration()
|
var loggerConfig = new LoggerConfiguration()
|
||||||
.ReadFrom.Configuration(builder.Configuration)
|
.ReadFrom.Configuration(builder.Configuration)
|
||||||
|
|||||||
309
Scripts/create-docker-secrets.sh
Normal file
309
Scripts/create-docker-secrets.sh
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# Script para criar/recriar Docker Secrets no Swarm
|
||||||
|
# QR Rapido - Produção
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# ./create-docker-secrets.sh # Modo interativo (pede cada valor)
|
||||||
|
# ./create-docker-secrets.sh --from-env # Lê de variáveis de ambiente
|
||||||
|
# ./create-docker-secrets.sh --from-file secrets.env # Lê de arquivo .env
|
||||||
|
# ./create-docker-secrets.sh --delete # Remove todos os secrets
|
||||||
|
# ./create-docker-secrets.sh --list # Lista secrets existentes
|
||||||
|
#
|
||||||
|
# IMPORTANTE: Execute este script no servidor MANAGER do Docker Swarm
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Cores para output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Lista de secrets necessários
|
||||||
|
SECRETS=(
|
||||||
|
"stripe_secret_key"
|
||||||
|
"stripe_webhook_secret"
|
||||||
|
"mongodb_connection_string"
|
||||||
|
"google_client_id"
|
||||||
|
"google_client_secret"
|
||||||
|
"microsoft_client_id"
|
||||||
|
"microsoft_client_secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Descrições dos secrets
|
||||||
|
declare -A SECRET_DESCRIPTIONS=(
|
||||||
|
["stripe_secret_key"]="Stripe API Secret Key (sk_live_xxx)"
|
||||||
|
["stripe_webhook_secret"]="Stripe Webhook Signing Secret (whsec_xxx)"
|
||||||
|
["mongodb_connection_string"]="MongoDB Connection String completa"
|
||||||
|
["google_client_id"]="Google OAuth Client ID"
|
||||||
|
["google_client_secret"]="Google OAuth Client Secret"
|
||||||
|
["microsoft_client_id"]="Microsoft OAuth Client ID (GUID)"
|
||||||
|
["microsoft_client_secret"]="Microsoft OAuth Client Secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Função para exibir uso
|
||||||
|
show_usage() {
|
||||||
|
echo -e "${BLUE}=== QR Rapido - Docker Secrets Manager ===${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Uso: $0 [opção]"
|
||||||
|
echo ""
|
||||||
|
echo "Opções:"
|
||||||
|
echo " (sem opção) Modo interativo - pede cada valor"
|
||||||
|
echo " --from-env Lê valores de variáveis de ambiente"
|
||||||
|
echo " --from-file F Lê valores de arquivo .env"
|
||||||
|
echo " --delete Remove todos os secrets"
|
||||||
|
echo " --list Lista secrets existentes"
|
||||||
|
echo " --check Verifica quais secrets estão faltando"
|
||||||
|
echo " --help Exibe esta ajuda"
|
||||||
|
echo ""
|
||||||
|
echo "Variáveis de ambiente esperadas (para --from-env):"
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
local env_var=$(echo "$secret" | tr '[:lower:]' '[:upper:]')
|
||||||
|
echo " $env_var"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para verificar se está no Swarm
|
||||||
|
check_swarm() {
|
||||||
|
if ! docker info 2>/dev/null | grep -q "Swarm: active"; then
|
||||||
|
echo -e "${RED}ERRO: Docker Swarm não está ativo neste nó.${NC}"
|
||||||
|
echo "Execute este script no servidor manager do Swarm."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker info 2>/dev/null | grep -q "Is Manager: true"; then
|
||||||
|
echo -e "${RED}ERRO: Este nó não é um manager do Swarm.${NC}"
|
||||||
|
echo "Execute este script no servidor manager do Swarm."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para listar secrets
|
||||||
|
list_secrets() {
|
||||||
|
echo -e "${BLUE}=== Secrets existentes ===${NC}"
|
||||||
|
docker secret ls
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}=== Status dos secrets do QR Rapido ===${NC}"
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
if docker secret inspect "$secret" > /dev/null 2>&1; then
|
||||||
|
echo -e " ${GREEN}✓${NC} $secret"
|
||||||
|
else
|
||||||
|
echo -e " ${RED}✗${NC} $secret (não existe)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para verificar secrets faltantes
|
||||||
|
check_secrets() {
|
||||||
|
echo -e "${BLUE}=== Verificando secrets do QR Rapido ===${NC}"
|
||||||
|
local missing=0
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
if docker secret inspect "$secret" > /dev/null 2>&1; then
|
||||||
|
echo -e " ${GREEN}✓${NC} $secret"
|
||||||
|
else
|
||||||
|
echo -e " ${RED}✗${NC} $secret - ${SECRET_DESCRIPTIONS[$secret]}"
|
||||||
|
missing=$((missing + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $missing -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}$missing secret(s) faltando.${NC}"
|
||||||
|
echo "Execute: $0 (modo interativo) para criar os secrets faltantes."
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Todos os secrets estão configurados!${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para deletar secrets
|
||||||
|
delete_secrets() {
|
||||||
|
echo -e "${YELLOW}=== Removendo secrets do QR Rapido ===${NC}"
|
||||||
|
echo -e "${RED}ATENÇÃO: Isso vai remover TODOS os secrets listados!${NC}"
|
||||||
|
read -p "Tem certeza? (digite 'sim' para confirmar): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "sim" ]; then
|
||||||
|
echo "Operação cancelada."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
if docker secret inspect "$secret" > /dev/null 2>&1; then
|
||||||
|
echo -n "Removendo $secret... "
|
||||||
|
if docker secret rm "$secret" 2>/dev/null; then
|
||||||
|
echo -e "${GREEN}OK${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}ERRO (pode estar em uso por um service)${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " $secret - ${YELLOW}não existe${NC}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}IMPORTANTE: Se algum secret estava em uso, você precisa:${NC}"
|
||||||
|
echo "1. Parar o service: docker service rm qrrapido-prod"
|
||||||
|
echo "2. Remover os secrets: $0 --delete"
|
||||||
|
echo "3. Recriar os secrets: $0"
|
||||||
|
echo "4. Recriar o service (via CI/CD ou manualmente)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para criar um secret
|
||||||
|
create_secret() {
|
||||||
|
local name=$1
|
||||||
|
local value=$2
|
||||||
|
|
||||||
|
# Verifica se já existe
|
||||||
|
if docker secret inspect "$name" > /dev/null 2>&1; then
|
||||||
|
echo -e " ${YELLOW}!${NC} $name já existe (pulando)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cria o secret
|
||||||
|
echo -n " Criando $name... "
|
||||||
|
if echo -n "$value" | docker secret create "$name" - > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}OK${NC}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}ERRO${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para modo interativo
|
||||||
|
interactive_mode() {
|
||||||
|
echo -e "${BLUE}=== Modo Interativo ===${NC}"
|
||||||
|
echo "Você será perguntado sobre cada secret."
|
||||||
|
echo "Pressione ENTER para pular secrets que já existem."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
# Verifica se já existe
|
||||||
|
if docker secret inspect "$secret" > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}✓${NC} $secret já existe"
|
||||||
|
read -p " Deseja recriar? (s/N): " recreate
|
||||||
|
if [ "$recreate" != "s" ] && [ "$recreate" != "S" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -n " Removendo secret antigo... "
|
||||||
|
docker secret rm "$secret" > /dev/null 2>&1 || true
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}$secret${NC}"
|
||||||
|
echo " Descrição: ${SECRET_DESCRIPTIONS[$secret]}"
|
||||||
|
|
||||||
|
# Lê o valor (sem mostrar na tela)
|
||||||
|
read -s -p " Valor: " value
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo -e " ${YELLOW}Pulando (valor vazio)${NC}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_secret "$secret" "$value"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}=== Concluído ===${NC}"
|
||||||
|
list_secrets
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para ler de variáveis de ambiente
|
||||||
|
from_env_mode() {
|
||||||
|
echo -e "${BLUE}=== Lendo de variáveis de ambiente ===${NC}"
|
||||||
|
|
||||||
|
local errors=0
|
||||||
|
for secret in "${SECRETS[@]}"; do
|
||||||
|
local env_var=$(echo "$secret" | tr '[:lower:]' '[:upper:]')
|
||||||
|
local value="${!env_var}"
|
||||||
|
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo -e " ${RED}✗${NC} $env_var não está definida"
|
||||||
|
errors=$((errors + 1))
|
||||||
|
else
|
||||||
|
create_secret "$secret" "$value"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $errors -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}$errors variável(is) de ambiente não encontrada(s).${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}=== Concluído ===${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Função para ler de arquivo
|
||||||
|
from_file_mode() {
|
||||||
|
local file=$1
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo -e "${RED}ERRO: Arquivo não encontrado: $file${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== Lendo de arquivo: $file ===${NC}"
|
||||||
|
|
||||||
|
# Carrega as variáveis do arquivo
|
||||||
|
set -a
|
||||||
|
source "$file"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
# Usa o modo from_env
|
||||||
|
from_env_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MAIN
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Verifica se está no Swarm (exceto para --help)
|
||||||
|
if [ "$1" != "--help" ] && [ "$1" != "-h" ]; then
|
||||||
|
check_swarm
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Processa argumentos
|
||||||
|
case "$1" in
|
||||||
|
--help|-h)
|
||||||
|
show_usage
|
||||||
|
;;
|
||||||
|
--list)
|
||||||
|
list_secrets
|
||||||
|
;;
|
||||||
|
--check)
|
||||||
|
check_secrets
|
||||||
|
;;
|
||||||
|
--delete)
|
||||||
|
delete_secrets
|
||||||
|
;;
|
||||||
|
--from-env)
|
||||||
|
from_env_mode
|
||||||
|
;;
|
||||||
|
--from-file)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo -e "${RED}ERRO: Especifique o arquivo .env${NC}"
|
||||||
|
echo "Uso: $0 --from-file secrets.env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
from_file_mode "$2"
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
interactive_mode
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Opção desconhecida: $1${NC}"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
28
Scripts/secrets.env.template
Normal file
28
Scripts/secrets.env.template
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Template de Secrets para QR Rapido
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# INSTRUÇÕES:
|
||||||
|
# 1. Copie este arquivo para 'secrets.env' (ou outro nome)
|
||||||
|
# 2. Preencha os valores abaixo
|
||||||
|
# 3. Execute: ./create-docker-secrets.sh --from-file secrets.env
|
||||||
|
# 4. DELETE o arquivo após criar os secrets (contém dados sensíveis!)
|
||||||
|
#
|
||||||
|
# NUNCA faça commit de arquivos .env com valores reais!
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Stripe - Obter em https://dashboard.stripe.com/apikeys
|
||||||
|
STRIPE_SECRET_KEY=sk_live_SUBSTITUA_AQUI
|
||||||
|
STRIPE_WEBHOOK_SECRET=whsec_SUBSTITUA_AQUI
|
||||||
|
|
||||||
|
# MongoDB - Connection string completa com usuário e senha
|
||||||
|
# Formato: mongodb://user:password@host1:port,host2:port/database?options
|
||||||
|
MONGODB_CONNECTION_STRING=mongodb://admin:SENHA_AQUI@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin
|
||||||
|
|
||||||
|
# Google OAuth - Obter em https://console.cloud.google.com/apis/credentials
|
||||||
|
GOOGLE_CLIENT_ID=SUBSTITUA_AQUI.apps.googleusercontent.com
|
||||||
|
GOOGLE_CLIENT_SECRET=GOCSPX-SUBSTITUA_AQUI
|
||||||
|
|
||||||
|
# Microsoft OAuth - Obter em https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps
|
||||||
|
MICROSOFT_CLIENT_ID=SUBSTITUA_AQUI
|
||||||
|
MICROSOFT_CLIENT_SECRET=SUBSTITUA_AQUI
|
||||||
@ -1,15 +1,23 @@
|
|||||||
@using QRRapidoApp.Services
|
@using QRRapidoApp.Services
|
||||||
@using Microsoft.AspNetCore.Http.Extensions
|
@using Microsoft.AspNetCore.Http.Extensions
|
||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
|
@using Microsoft.Extensions.Configuration
|
||||||
@inject AdDisplayService AdService
|
@inject AdDisplayService AdService
|
||||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment
|
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment
|
||||||
|
@inject IConfiguration Configuration
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var isDevelopment = HostEnvironment?.IsDevelopment() ?? false;
|
var isDevelopment = HostEnvironment?.IsDevelopment() ?? false;
|
||||||
var isPremiumUser = false;
|
var isPremiumUser = false;
|
||||||
var currentCulture = System.Globalization.CultureInfo.CurrentUICulture?.Name ?? "pt-BR";
|
var currentCulture = System.Globalization.CultureInfo.CurrentUICulture?.Name ?? "pt-BR";
|
||||||
|
|
||||||
|
// App info for footer
|
||||||
|
var appName = Configuration["App:Name"] ?? "QR Rapido";
|
||||||
|
var appVersion = Configuration["App:Version"] ?? "1.0.0";
|
||||||
|
var appEnvironment = Configuration["App:Environment"] ?? HostEnvironment?.EnvironmentName ?? "Unknown";
|
||||||
|
var secretsLoaded = Configuration.GetValue<bool>("App:SecretsLoaded");
|
||||||
|
|
||||||
if (User?.Identity?.IsAuthenticated == true)
|
if (User?.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
@ -421,6 +429,20 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<small>© 2024 QR Rapido. @Localizer["AllRightsReserved"]</small>
|
<small>© 2024 QR Rapido. @Localizer["AllRightsReserved"]</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<small class="text-muted" style="font-size: 0.7rem;">
|
||||||
|
@appName v@appVersion |
|
||||||
|
@appEnvironment
|
||||||
|
@if (secretsLoaded)
|
||||||
|
{
|
||||||
|
<span class="text-success" title="Docker Secrets loaded">✓</span>
|
||||||
|
}
|
||||||
|
else if (appEnvironment == "Production")
|
||||||
|
{
|
||||||
|
<span class="text-danger" title="Secrets NOT loaded from Docker!">✗</span>
|
||||||
|
}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"ApplicationName": "QRRapido-Prod",
|
"ApplicationName": "QRRapido-Prod",
|
||||||
"Environment": "Prod",
|
"App": {
|
||||||
|
"Environment": "Production",
|
||||||
|
"SecretsLoaded": false
|
||||||
|
},
|
||||||
"Support": {
|
"Support": {
|
||||||
"TelegramUrl": "https://t.me/jobmakerbr",
|
"TelegramUrl": "https://t.me/jobmakerbr",
|
||||||
"FormspreeUrl": "https://formspree.io/f/xpwynqpj"
|
"FormspreeUrl": "https://formspree.io/f/xpwynqpj"
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"MongoDB": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin"
|
"MongoDB": "LOADED_FROM_DOCKER_SECRET"
|
||||||
},
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"SeqUrl": "http://172.17.0.1:5341",
|
"SeqUrl": "http://172.17.0.1:5341",
|
||||||
@ -22,10 +25,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Authentication": {
|
||||||
|
"Google": {
|
||||||
|
"ClientId": "LOADED_FROM_DOCKER_SECRET",
|
||||||
|
"ClientSecret": "LOADED_FROM_DOCKER_SECRET"
|
||||||
|
},
|
||||||
|
"Microsoft": {
|
||||||
|
"ClientId": "LOADED_FROM_DOCKER_SECRET",
|
||||||
|
"ClientSecret": "LOADED_FROM_DOCKER_SECRET"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Stripe": {
|
"Stripe": {
|
||||||
"PublishableKey": "pk_live_51Rs42SB6bFjHQirAXIhK2fetsfH7MDYWeTT5jiRGYpIS7g5fCCT0XzLK1tIOdxUYXG4gwN4OEAzuVFw9GTmvq7iM00iJmUSZWB",
|
"PublishableKey": "pk_live_51Rs42SB6bFjHQirAXIhK2fetsfH7MDYWeTT5jiRGYpIS7g5fCCT0XzLK1tIOdxUYXG4gwN4OEAzuVFw9GTmvq7iM00iJmUSZWB",
|
||||||
"SecretKey": "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",
|
"SecretKey": "LOADED_FROM_DOCKER_SECRET",
|
||||||
"WebhookSecret": "whsec_gYri7qNVHc18sREu2eXnjdRzklwRf3hI"
|
"WebhookSecret": "LOADED_FROM_DOCKER_SECRET"
|
||||||
},
|
},
|
||||||
"ResourceMonitoring": {
|
"ResourceMonitoring": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
"TaglinePT": "Gere QR codes em segundos!",
|
"TaglinePT": "Gere QR codes em segundos!",
|
||||||
"TaglineES": "¡Genera códigos QR en segundos!",
|
"TaglineES": "¡Genera códigos QR en segundos!",
|
||||||
"TaglineEN": "Generate QR codes in seconds!",
|
"TaglineEN": "Generate QR codes in seconds!",
|
||||||
"Version": "1.0.0"
|
"Version": "1.0.0",
|
||||||
|
"Environment": "Development",
|
||||||
|
"SecretsLoaded": false
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"MongoDB": "mongodb://localhost:27017/QrRapido"
|
"MongoDB": "mongodb://localhost:27017/QrRapido"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user