chore: establish project baseline
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
GTI Fiscal Agent 1.0.0-rc1 - pacote Windows
|
||||
==========================================
|
||||
|
||||
Arquitetura: Hub central --HTTPS--> Agent Windows --local--> Firebird do SGV. O Hub nunca acessa o Firebird.
|
||||
Este pacote NAO contem segredos (token do Agent e senha do Firebird sao informados localmente).
|
||||
|
||||
Requisitos no host do cliente: Python 3.12 (64 bits) no PATH, cliente Firebird (fbclient.dll, o mesmo do SGV),
|
||||
acesso HTTPS ao Hub, uma conta Firebird somente leitura.
|
||||
|
||||
Passo a passo (PowerShell como Administrador, dentro desta pasta):
|
||||
.\gti-agent.ps1 install cria o venv, instala as dependencias, registra o servico "GTIFiscalAgent" (nao inicia)
|
||||
.\gti-agent.ps1 configure (ou: configure -FromTemplate .\pilot-construcao.env.example) grava o .env local com ACL restrita
|
||||
.\gti-agent.ps1 preflight PREFLIGHT=PASS|FAIL e os bloqueios, se houver
|
||||
.\gti-agent.ps1 start so inicia se o preflight passar
|
||||
.\gti-agent.ps1 status | stop | restart | uninstall [-Purge]
|
||||
|
||||
Modo do piloto: FISCAL_MODE=DRY_RUN e FIREBIRD_WRITES=0. Nesse modo o Agent e tecnicamente incapaz de escrever no Firebird.
|
||||
Detalhes: docs/PILOT_DEPLOYMENT.md e docs/OPERATIONS.md do repositorio.
|
||||
@@ -0,0 +1,22 @@
|
||||
# GTI Fiscal Agent - generic installation template
|
||||
# NO SECRETS in this file. Copy to <install>\.env (gti-agent configure -FromTemplate) and fill AGENT_TOKEN / FB_PASSWORD locally.
|
||||
GTI_HUB_URL=https://fiscal.example.gti
|
||||
AGENT_TOKEN=
|
||||
AGENT_ID=
|
||||
|
||||
# Company context (deterministic resolver; never the first row of C000004)
|
||||
COMPANY_CODE=
|
||||
COMPANY_CNPJ=
|
||||
# AUTO = resolve automatically ONLY when there is no CRT conflict; otherwise preflight FAILS until EMISSOR or C000004 is chosen explicitly
|
||||
COMPANY_CRT_SOURCE=AUTO
|
||||
# COMPANY_UF= is an explicit override only when the database proves nothing; it never replaces the resolver silently.
|
||||
|
||||
# Real path of the client's SGV database ON THIS HOST (do not reuse the path of any local copy)
|
||||
FIREBIRD_DATABASE_PATH=
|
||||
FB_USER=
|
||||
FB_PASSWORD=
|
||||
|
||||
FISCAL_MODE=DRY_RUN
|
||||
FIREBIRD_WRITES=0
|
||||
POLL_INTERVAL_SECONDS=5
|
||||
AGENT_VERSION=1.0.0-rc1
|
||||
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
rem GTI Fiscal Agent - launcher: gti-agent.cmd install|configure|preflight|start|stop|restart|status|uninstall
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0gti-agent.ps1" %*
|
||||
@@ -0,0 +1,127 @@
|
||||
<#
|
||||
GTI Fiscal Agent - Windows operations script (RC1). No Python editing needed.
|
||||
|
||||
Usage (elevated PowerShell for install/start/stop/restart/uninstall):
|
||||
.\gti-agent.ps1 install create venv, install dependencies, register the Windows Service (does NOT start it)
|
||||
.\gti-agent.ps1 configure [-FromTemplate f] write <install>\.env (secrets are prompted, never echoed, never stored in the package)
|
||||
.\gti-agent.ps1 preflight PREFLIGHT=PASS|FAIL with blockers (read-only)
|
||||
.\gti-agent.ps1 start | stop | restart service control (start refuses to run when preflight fails)
|
||||
.\gti-agent.ps1 status service state + last log lines
|
||||
.\gti-agent.ps1 uninstall [-Purge] remove the service; -Purge also deletes venv/logs/state (never the .env unless -Purge)
|
||||
|
||||
The package folder is the install folder. Requires Python 3.12 (64-bit) on PATH and the Firebird client (fbclient.dll) already used by the SGV.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Position = 0, Mandatory = $true)]
|
||||
[ValidateSet('install', 'configure', 'preflight', 'start', 'stop', 'restart', 'status', 'uninstall')]
|
||||
[string]$Command,
|
||||
[string]$FromTemplate,
|
||||
[switch]$Purge
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$ServiceName = 'GTIFiscalAgent'
|
||||
$Venv = Join-Path $Root 'venv'
|
||||
$Py = Join-Path $Venv 'Scripts\python.exe'
|
||||
$EnvFile = Join-Path $Root '.env'
|
||||
$LogFile = Join-Path $Root 'agent\logs\agent.log'
|
||||
|
||||
function Assert-Admin {
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Run this command from an elevated (Administrator) PowerShell.' }
|
||||
}
|
||||
|
||||
function Read-Plain([string]$Prompt, [string]$Default = '') {
|
||||
$suffix = if ($Default) { " [$Default]" } else { '' }
|
||||
$value = Read-Host "$Prompt$suffix"
|
||||
if ([string]::IsNullOrWhiteSpace($value)) { return $Default } else { return $value.Trim() }
|
||||
}
|
||||
|
||||
function Read-Secret([string]$Prompt) {
|
||||
$secure = Read-Host $Prompt -AsSecureString
|
||||
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
|
||||
try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
|
||||
}
|
||||
|
||||
switch ($Command) {
|
||||
'install' {
|
||||
Assert-Admin
|
||||
if (-not (Get-Command python -ErrorAction SilentlyContinue)) { throw 'Python 3.12 (64-bit) is required on PATH.' }
|
||||
if (-not (Test-Path $Py)) { python -m venv $Venv }
|
||||
& $Py -m pip install --upgrade pip | Out-Null
|
||||
& $Py -m pip install -r (Join-Path $Root 'requirements-agent.txt')
|
||||
$post = Join-Path $Venv 'Scripts\pywin32_postinstall.py'
|
||||
if (Test-Path $post) { & $Py $post -install | Out-Null }
|
||||
$svc = Join-Path $Root 'agent\service\gti_agent_windows_service.py'
|
||||
& $Py $svc --startup=auto install
|
||||
sc.exe failure $ServiceName reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null
|
||||
Write-Host "Service $ServiceName registered (Automatic). Next: .\gti-agent.ps1 configure ; .\gti-agent.ps1 preflight ; .\gti-agent.ps1 start"
|
||||
}
|
||||
'configure' {
|
||||
if ($FromTemplate) {
|
||||
Copy-Item -Path $FromTemplate -Destination $EnvFile -Force
|
||||
Write-Host "Template copied to $EnvFile. Fill the empty secret values (AGENT_TOKEN, FB_PASSWORD) with a text editor, then run preflight."
|
||||
} else {
|
||||
$hub = Read-Plain 'Hub URL (https://...)'
|
||||
$token = Read-Secret 'AGENT_TOKEN (from register_agent.py)'
|
||||
$agentId = Read-Plain 'AGENT_ID (label for this installation)'
|
||||
$db = Read-Plain 'FIREBIRD_DATABASE_PATH (real path of the SGV .FDB on THIS host)'
|
||||
$fbUser = Read-Plain 'FB_USER (read-only account)' 'SYSDBA'
|
||||
$fbPass = Read-Secret 'FB_PASSWORD'
|
||||
$code = Read-Plain 'COMPANY_CODE (C000004.CODIGO; empty = auto when unambiguous)'
|
||||
$cnpj = Read-Plain 'COMPANY_CNPJ (digits; optional alternative to COMPANY_CODE)'
|
||||
$crtSource = Read-Plain 'COMPANY_CRT_SOURCE (AUTO | EMISSOR | C000004)' 'AUTO'
|
||||
$lines = @(
|
||||
"GTI_HUB_URL=$hub", "AGENT_TOKEN=$token", "AGENT_ID=$agentId",
|
||||
"COMPANY_CODE=$code", "COMPANY_CNPJ=$cnpj", "COMPANY_CRT_SOURCE=$crtSource",
|
||||
"FIREBIRD_DATABASE_PATH=$db", "FB_USER=$fbUser", "FB_PASSWORD=$fbPass",
|
||||
'FISCAL_MODE=DRY_RUN', 'FIREBIRD_WRITES=0', 'POLL_INTERVAL_SECONDS=5', 'AGENT_VERSION=1.0.0-rc1'
|
||||
)
|
||||
Set-Content -Path $EnvFile -Value $lines -Encoding UTF8
|
||||
}
|
||||
# Only Administrators and SYSTEM may read the file that holds the secrets.
|
||||
icacls $EnvFile /inheritance:r /grant:r 'Administrators:(F)' 'SYSTEM:(F)' | Out-Null
|
||||
Write-Host "Configuration saved to $EnvFile (restricted ACL). Secrets are not part of the package."
|
||||
}
|
||||
'preflight' {
|
||||
if (-not (Test-Path $Py)) { throw 'Run install first.' }
|
||||
& $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
'start' {
|
||||
Assert-Admin
|
||||
& $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile
|
||||
if ($LASTEXITCODE -ne 0) { throw 'PREFLIGHT=FAIL: fix the blockers above before starting the service.' }
|
||||
Start-Service $ServiceName
|
||||
Write-Host "$ServiceName started."
|
||||
}
|
||||
'stop' { Assert-Admin; Stop-Service $ServiceName -Force; Write-Host "$ServiceName stopped." }
|
||||
'restart' {
|
||||
Assert-Admin
|
||||
& $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile
|
||||
if ($LASTEXITCODE -ne 0) { throw 'PREFLIGHT=FAIL: not restarting.' }
|
||||
Restart-Service $ServiceName -Force
|
||||
Write-Host "$ServiceName restarted."
|
||||
}
|
||||
'status' {
|
||||
$svc = Get-Service $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $svc) { Write-Host 'Service not installed.'; exit 1 }
|
||||
Write-Host ("Service {0}: {1} (StartType {2})" -f $svc.Name, $svc.Status, $svc.StartType)
|
||||
if (Test-Path $LogFile) { Write-Host '--- last log lines ---'; Get-Content $LogFile -Tail 15 }
|
||||
}
|
||||
'uninstall' {
|
||||
Assert-Admin
|
||||
$svcScript = Join-Path $Root 'agent\service\gti_agent_windows_service.py'
|
||||
if (Get-Service $ServiceName -ErrorAction SilentlyContinue) {
|
||||
Stop-Service $ServiceName -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $Py) { & $Py $svcScript remove }
|
||||
}
|
||||
if ($Purge) {
|
||||
foreach ($p in @($Venv, (Join-Path $Root 'agent\logs'), (Join-Path $Root 'agent\agent_state.sqlite'), $EnvFile)) { if (Test-Path $p) { Remove-Item $p -Recurse -Force } }
|
||||
Write-Host 'Service removed; venv, logs, state and .env deleted.'
|
||||
} else {
|
||||
Write-Host 'Service removed. Configuration (.env), logs and state were kept (use -Purge to delete them).'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# GTI Fiscal Agent - PILOT PROFILE: CONSTRUCAO (materiais de construcao). Audited: company 000001, UF BA, CRT 1, AUTO_RESOLVED, confidence HIGH
|
||||
# NO SECRETS in this file. Copy to <install>\.env (gti-agent configure -FromTemplate) and fill AGENT_TOKEN / FB_PASSWORD locally.
|
||||
GTI_HUB_URL=https://fiscal.example.gti
|
||||
AGENT_TOKEN=
|
||||
AGENT_ID=
|
||||
|
||||
# Company context (deterministic resolver; never the first row of C000004)
|
||||
COMPANY_CODE=000001
|
||||
COMPANY_CNPJ=
|
||||
# AUTO is enough here: EMISSOR.EMITENTE_CRT and C000004.CRT agree (1). The host preflight must confirm UF=BA / CRT=1.
|
||||
COMPANY_CRT_SOURCE=AUTO
|
||||
# COMPANY_UF= is an explicit override only when the database proves nothing; it never replaces the resolver silently.
|
||||
|
||||
# Real path of the client's SGV database ON THIS HOST (do not reuse the path of any local copy)
|
||||
FIREBIRD_DATABASE_PATH=
|
||||
FB_USER=
|
||||
FB_PASSWORD=
|
||||
|
||||
FISCAL_MODE=DRY_RUN
|
||||
FIREBIRD_WRITES=0
|
||||
POLL_INTERVAL_SECONDS=5
|
||||
AGENT_VERSION=1.0.0-rc1
|
||||
@@ -0,0 +1,22 @@
|
||||
# GTI Fiscal Agent - PLANNED PROFILE (not installed): MMMODAS (moda). C000004.CRT=2 vs EMISSOR.EMITENTE_CRT=1
|
||||
# NO SECRETS in this file. Copy to <install>\.env (gti-agent configure -FromTemplate) and fill AGENT_TOKEN / FB_PASSWORD locally.
|
||||
GTI_HUB_URL=https://fiscal.example.gti
|
||||
AGENT_TOKEN=
|
||||
AGENT_ID=
|
||||
|
||||
# Company context (deterministic resolver; never the first row of C000004)
|
||||
COMPANY_CODE=000001
|
||||
COMPANY_CNPJ=
|
||||
# REQUIRED: CRT sources conflict (EMISSOR=1, C000004=2); the emission programs read EMISSOR.
|
||||
COMPANY_CRT_SOURCE=EMISSOR
|
||||
# COMPANY_UF= is an explicit override only when the database proves nothing; it never replaces the resolver silently.
|
||||
|
||||
# Real path of the client's SGV database ON THIS HOST (do not reuse the path of any local copy)
|
||||
FIREBIRD_DATABASE_PATH=
|
||||
FB_USER=
|
||||
FB_PASSWORD=
|
||||
|
||||
FISCAL_MODE=DRY_RUN
|
||||
FIREBIRD_WRITES=0
|
||||
POLL_INTERVAL_SECONDS=5
|
||||
AGENT_VERSION=1.0.0-rc1
|
||||
@@ -0,0 +1,22 @@
|
||||
# GTI Fiscal Agent - PLANNED PROFILE (not installed): RINCAO (restaurante). C000004 has 3 company rows; EMISSOR.EMITENTE_CRT=3 vs C000004(row 000001).CRT=1
|
||||
# NO SECRETS in this file. Copy to <install>\.env (gti-agent configure -FromTemplate) and fill AGENT_TOKEN / FB_PASSWORD locally.
|
||||
GTI_HUB_URL=https://fiscal.example.gti
|
||||
AGENT_TOKEN=
|
||||
AGENT_ID=
|
||||
|
||||
# Company context (deterministic resolver; never the first row of C000004)
|
||||
COMPANY_CODE=000001
|
||||
COMPANY_CNPJ=
|
||||
# REQUIRED: CRT sources conflict (EMISSOR=3, C000004=1); the emission programs read EMISSOR. COMPANY_CODE=000001 is the row whose CNPJ equals the EMISSOR CNPJ.
|
||||
COMPANY_CRT_SOURCE=EMISSOR
|
||||
# COMPANY_UF= is an explicit override only when the database proves nothing; it never replaces the resolver silently.
|
||||
|
||||
# Real path of the client's SGV database ON THIS HOST (do not reuse the path of any local copy)
|
||||
FIREBIRD_DATABASE_PATH=
|
||||
FB_USER=
|
||||
FB_PASSWORD=
|
||||
|
||||
FISCAL_MODE=DRY_RUN
|
||||
FIREBIRD_WRITES=0
|
||||
POLL_INTERVAL_SECONDS=5
|
||||
AGENT_VERSION=1.0.0-rc1
|
||||
@@ -0,0 +1,22 @@
|
||||
# GTI Fiscal Agent - PLANNED PROFILE (not installed): UNIAO (mercado). C000004.CRT=3 vs EMISSOR.EMITENTE_CRT=1
|
||||
# NO SECRETS in this file. Copy to <install>\.env (gti-agent configure -FromTemplate) and fill AGENT_TOKEN / FB_PASSWORD locally.
|
||||
GTI_HUB_URL=https://fiscal.example.gti
|
||||
AGENT_TOKEN=
|
||||
AGENT_ID=
|
||||
|
||||
# Company context (deterministic resolver; never the first row of C000004)
|
||||
COMPANY_CODE=000001
|
||||
COMPANY_CNPJ=
|
||||
# REQUIRED: CRT sources conflict (EMISSOR=1, C000004=3; OPTANTE_SIMPLES=S); the emission programs read EMISSOR.
|
||||
COMPANY_CRT_SOURCE=EMISSOR
|
||||
# COMPANY_UF= is an explicit override only when the database proves nothing; it never replaces the resolver silently.
|
||||
|
||||
# Real path of the client's SGV database ON THIS HOST (do not reuse the path of any local copy)
|
||||
FIREBIRD_DATABASE_PATH=
|
||||
FB_USER=
|
||||
FB_PASSWORD=
|
||||
|
||||
FISCAL_MODE=DRY_RUN
|
||||
FIREBIRD_WRITES=0
|
||||
POLL_INTERVAL_SECONDS=5
|
||||
AGENT_VERSION=1.0.0-rc1
|
||||
@@ -0,0 +1,3 @@
|
||||
httpx>=0.27,<1
|
||||
fdb>=2.0.2,<3
|
||||
pywin32>=306; sys_platform == "win32"
|
||||
@@ -0,0 +1,18 @@
|
||||
# GTI Fiscal Hub — production environment (copy to .env, never commit .env)
|
||||
GTI_FISCAL_VERSION=1.0.0-rc1
|
||||
|
||||
# REQUIRED. URL-safe secret (used inside a connection URL): openssl rand -hex 24
|
||||
POSTGRES_PASSWORD=
|
||||
|
||||
# REQUIRED. Public HTTPS URL that Agents use (TLS terminates in the reverse proxy in front of the Hub).
|
||||
BASE_URL=https://fiscal.example.gti
|
||||
|
||||
# Interface the Hub listens on. Keep 127.0.0.1 when a reverse proxy runs on the same host; use the LAN IP only behind a firewall.
|
||||
HUB_BIND=127.0.0.1
|
||||
HUB_PORT=8000
|
||||
|
||||
LOG_LEVEL=INFO
|
||||
AGENT_ONLINE_THRESHOLD_SECONDS=120
|
||||
|
||||
# Directory (host) where pg_dump backups are written by backup.sh
|
||||
BACKUP_DIR=./backups
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env sh
|
||||
# PostgreSQL backup of the GTI Fiscal Hub (custom format, restorable with restore.sh). Run from deploy/hub.
|
||||
# ./backup.sh -> ./backups/gti_fiscal_YYYYmmdd_HHMMSS.dump (last 14 kept)
|
||||
set -eu
|
||||
COMPOSE="docker compose -f docker-compose.prod.yml --env-file .env"
|
||||
DIR="${BACKUP_DIR:-./backups}"
|
||||
mkdir -p "$DIR"
|
||||
STAMP="$(date +%Y%m%d_%H%M%S)"
|
||||
FILE="gti_fiscal_${STAMP}.dump"
|
||||
$COMPOSE exec -T postgres pg_dump -U gti -d gti_fiscal -Fc -f "/backups/${FILE}"
|
||||
[ -s "$DIR/$FILE" ] || { echo "backup empty or missing: $DIR/$FILE" >&2; exit 1; }
|
||||
ls -1t "$DIR"/gti_fiscal_*.dump | tail -n +15 | xargs -r rm -f
|
||||
echo "backup ok: $DIR/$FILE"
|
||||
@@ -0,0 +1,68 @@
|
||||
# GTI Fiscal Hub — production compose (RC1). Only two containers: PostgreSQL + Hub. No Kubernetes/Redis/RabbitMQ.
|
||||
#
|
||||
# cd deploy/hub && cp .env.example .env && (edit .env) && docker compose -f docker-compose.prod.yml --env-file .env up -d --build
|
||||
#
|
||||
# PostgreSQL is on an internal network only: it publishes NO port and is not reachable from the LAN/internet.
|
||||
# The Hub binds to HUB_BIND (default 127.0.0.1): put a TLS reverse proxy (HTTPS) in front of it for the Agents.
|
||||
# Migrations run automatically at Hub start (Dockerfile CMD: alembic upgrade head).
|
||||
name: gti-fiscal-hub
|
||||
|
||||
x-logging: &logging
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: gti_fiscal
|
||||
POSTGRES_USER: gti
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required (URL-safe, e.g. openssl rand -hex 24)}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ${BACKUP_DIR:-./backups}:/backups
|
||||
networks: [internal]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U gti -d gti_fiscal"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
logging: *logging
|
||||
|
||||
fiscal-hub:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: central/Dockerfile
|
||||
image: gti-fiscal-hub:${GTI_FISCAL_VERSION:-1.0.0-rc1}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
APP_ENV: production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||
BASE_URL: ${BASE_URL:?BASE_URL is required (public HTTPS URL of the Hub)}
|
||||
AGENT_ONLINE_THRESHOLD_SECONDS: ${AGENT_ONLINE_THRESHOLD_SECONDS:-120}
|
||||
# AGENT_DEV_TOKEN is intentionally NOT set: real Agents are registered with central/scripts/register_agent.py.
|
||||
ports:
|
||||
- "${HUB_BIND:-127.0.0.1}:${HUB_PORT:-8000}:8000"
|
||||
networks: [internal, edge]
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
logging: *logging
|
||||
|
||||
networks:
|
||||
internal:
|
||||
internal: true # no route to the outside: PostgreSQL is never exposed
|
||||
edge: {}
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env sh
|
||||
# Run on the machine that holds the validated fiscal datasets (development Hub). Exports ONLY the reference tables
|
||||
# (datasets + fiscal entries), never tenants/agents/jobs/classification data, so a fresh production Hub can be seeded:
|
||||
#
|
||||
# ./export_datasets_seed.sh projeto-postgres-1 > gti_datasets_seed.dump
|
||||
# (copy to the production host, then) ./seed_datasets.sh gti_datasets_seed.dump
|
||||
set -eu
|
||||
CONTAINER="${1:-projeto-postgres-1}"
|
||||
TABLES="datasets ncm_entries legacy_ncm_entries cest_entries cest_ncm_entries tipi_entries ba_st_mva_entries ba_st_mva_ncm_sh fiscal_code_entries cfop_entries fcp_rates anp_product_codes ncm_utrib_entries rtc_biofuel_index_entries rtc_cred_pres_entries rtc_cclasstrib_entries rtc_cst_ibscbs_entries rtc_cbs_rate_entries"
|
||||
ARGS=""
|
||||
for t in $TABLES; do ARGS="$ARGS -t $t"; done
|
||||
docker exec "$CONTAINER" pg_dump -U gti -d gti_fiscal -Fc --data-only $ARGS
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env sh
|
||||
# Restore a Hub backup into PostgreSQL. DESTRUCTIVE for the current data: asks for confirmation.
|
||||
# ./restore.sh ./backups/gti_fiscal_20260919_120000.dump
|
||||
set -eu
|
||||
[ $# -eq 1 ] || { echo "usage: $0 <backup.dump>" >&2; exit 2; }
|
||||
FILE="$(basename "$1")"
|
||||
COMPOSE="docker compose -f docker-compose.prod.yml --env-file .env"
|
||||
[ -s "${BACKUP_DIR:-./backups}/$FILE" ] || { echo "backup not found in ${BACKUP_DIR:-./backups}: $FILE" >&2; exit 1; }
|
||||
printf "This REPLACES the current Hub database with %s. Type RESTORE to continue: " "$FILE"
|
||||
read -r answer
|
||||
[ "$answer" = "RESTORE" ] || { echo "aborted"; exit 1; }
|
||||
$COMPOSE stop fiscal-hub
|
||||
$COMPOSE exec -T postgres pg_restore -U gti -d gti_fiscal --clean --if-exists --no-owner "/backups/$FILE"
|
||||
$COMPOSE up -d fiscal-hub # migrations run at start (alembic upgrade head)
|
||||
echo "restore ok: $FILE"
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
# Seed a NEW production Hub (already migrated: the Hub container ran `alembic upgrade head`) with the validated fiscal datasets.
|
||||
# ./seed_datasets.sh gti_datasets_seed.dump
|
||||
set -eu
|
||||
[ $# -eq 1 ] || { echo "usage: $0 <gti_datasets_seed.dump>" >&2; exit 2; }
|
||||
COMPOSE="docker compose -f docker-compose.prod.yml --env-file .env"
|
||||
EXISTING="$($COMPOSE exec -T postgres psql -U gti -d gti_fiscal -At -c 'select count(*) from datasets')"
|
||||
[ "$EXISTING" = "0" ] || { echo "refusing: datasets table is not empty ($EXISTING rows)" >&2; exit 1; }
|
||||
$COMPOSE exec -T postgres pg_restore -U gti -d gti_fiscal --data-only --no-owner --disable-triggers < "$1"
|
||||
$COMPOSE exec -T postgres psql -U gti -d gti_fiscal -At -c "select kind, status, record_count from datasets where status='ACTIVE' order by kind"
|
||||
Reference in New Issue
Block a user