# Installazione

## Installazione ambiente di Test / Sviluppo

Per una installazione di test su singolo nodo si consiglia di usare il *service compose* presente nei due repository principali:&#x20;

* [docker-compose.yml](https://gitlab.com/opencity-labs/sito-istituzionale/cms/-/blob/master/docker-compose.yml?ref_type=heads) dal repo dei sito istituzionale e leggere con attenzione il [README](https://gitlab.com/opencity-labs/sito-istituzionale/cms/-/blob/master/README.md?ref_type=heads).
* [docker-compose.yml](https://gitlab.com/opencity-labs/area-personale/core/-/blob/master/docker-compose.yml) dal core dei servizi digitali  e leggere con attenzione il [README](https://gitlab.com/opencity-labs/area-personale/core/-/blob/master/README.md).

## Installazione ambiente di Produzione

Per un ambiente di produzione è indispensabile predisporre un ambiente con adeguati livelli di ridondanza e dimensionamento dei singoli servizi in base alla propria infrastruttura e ai propri requisiti in termini di affidabilità e livelli di servizio da garantire.

Non c'e' una ricetta valida a priori per dimensionare il sistema, si consiglia di partire da una configurazione con 2 repliche di ogni servizio stateless e un dimensionamento molto largo (es: 1GB di ram, 1 vCPU) e poi monitorare il proprio sistema per stabilire i requisiti di ogni singolo container: si potrà verificare facilmente che molti microservizi richiedono pochi Mb di ram.

### Elenco dei servizi necessari

Ogni servizio si basa su una immagine docker specifica e necessita di adeguate configurazioni. Le configurazioni e i valori di default delle stesse sono documentate nei singoli repository dei microservizi.

Al fine di dare una corretta configurazione dei vari servizi vengono indicate inoltre:

1. la dipendenza dalle persistenze usate dalla piattaforma
2. la necessità di pubblicare un endpoint per quel servizio pubblicamente raggiungibile&#x20;

#### Sito istituzionale

<table><thead><tr><th>Servizio</th><th>Descrizione</th><th>Docker Image</th><th data-type="checkbox">Pubblico</th><th><select multiple><option value="ds0e4A6sg2f4" label="PostgreSQL" color="blue"></option><option value="MQBAms8u8EuK" label="Redis" color="blue"></option><option value="96lwpc9slLVe" label="Files storage" color="blue"></option></select></th></tr></thead><tbody><tr><td>cms-core</td><td>Servizio principale del CMS, consente l'accesso della radazione per la gestione dei contenuti e fornisce l'interfaccia di navigazione per i cittadini</td><td><a href="https://gitlab.com/opencity-labs/sito-istituzionale/cms">cms/app</a></td><td>false</td><td><span data-option="ds0e4A6sg2f4">PostgreSQL, </span><span data-option="MQBAms8u8EuK">Redis, </span><span data-option="96lwpc9slLVe">Files storage</span></td></tr><tr><td>solr</td><td>Motore di ricerca in cui il cms indicizza i contenuti per rendere performante la ricerca sul sito</td><td><a href="https://gitlab.com/opencity-labs/sito-istituzionale/varnish">cms/solr</a></td><td>false</td><td><span data-option="96lwpc9slLVe">Files storage</span></td></tr><tr><td>varnish</td><td>Http cache usata davanti al CMS per ridurre il carico sul CMS stesso. </td><td><a href="https://gitlab.com/opencity-labs/sito-istituzionale/solr">cms/varnish</a></td><td>true</td><td></td></tr></tbody></table>

#### Servizi digitali e Area Personale

<table><thead><tr><th width="140">Servizio</th><th width="187">Descrizione</th><th width="117">Docker Image</th><th width="86" data-type="checkbox">Pubblico</th><th>Persistenza<select multiple><option value="72b1be9ca55d417098d49ea01b179689" label="PostgreSQL" color="blue"></option><option value="ce4deb3f042a44d6ba4a82134a473d2c" label="MongoDB" color="blue"></option><option value="75aaa398d0934f7e900bf7535d2d4017" label="File Storage" color="blue"></option><option value="f75681c061884e4c80373228e0a870cd" label="ClickHouse" color="blue"></option><option value="f2babf9108c84c7f83a5869b92bf622a" label="Kafka" color="blue"></option><option value="17cb558c7adb41ec95cb70bc8a19fc74" label="Redis" color="blue"></option></select></th></tr></thead><tbody><tr><td>app-web<br></td><td>Servizio principale della piattaforma, gestisce pratiche, appuntamenti, offre interfacce di configurazione per gli amministratori e di gestione operativa per i funzionari degli enti. E' inoltre l'interfaccia principale anche per l'esperienza utente.</td><td><a href="https://gitlab.com/opencity-labs/area-personale/core">Core</a></td><td>false</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL, </span><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage, </span><span data-option="17cb558c7adb41ec95cb70bc8a19fc74">Redis</span></td></tr><tr><td>app-worker</td><td>Esecuzione delle operazioni asincrone del Core (creazione PDF, invio di messaggi, webhooks, ...)</td><td><a href="https://gitlab.com/opencity-labs/area-personale/core">Core</a></td><td>false</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL, </span><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage</span></td></tr><tr><td>app-manager</td><td>Esecuzione delle migrazioni database</td><td><a href="https://gitlab.com/opencity-labs/area-personale/core">Core</a></td><td>false</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL</span></td></tr><tr><td>app-varnish</td><td>Http cache per scaricare l'app-web</td><td><a href="https://gitlab.com/opencontent/varnish">opencontent/arnish</a></td><td>true</td><td></td></tr><tr><td>form-server</td><td>Server delle form (Form.IO)</td><td><a href="https://gitlab.com/opencity-labs/area-personale/form-server">form-io</a></td><td>false</td><td><span data-option="ce4deb3f042a44d6ba4a82134a473d2c">MongoDB</span></td></tr><tr><td>form-varnish</td><td>Http cache per scaricare il form-server</td><td><a href="https://gitlab.com/opencontent/varnish">opencontent/varnish</a></td><td>true</td><td></td></tr><tr><td>form-builder</td><td>Applicativo per la gestione dei subform condivisi da tutti i moduli form.io</td><td><a href="https://gitlab.com/opencity-labs/area-personale/formbuilderjs/-/tags/0.5.5">formbuilderjs</a></td><td>false</td><td></td></tr><tr><td>payment- dispatcher-v1</td><td>Genera, se necessario, un evento Versione 1 di pagamento da un evento di una pratica</td><td><a href="https://gitlab.com/opencity-labs/area-personale/payment-dispatcher">payment-dispatcher</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>payment- dispatcher-v2</td><td>Genera, se necessario, un evento Versione 2 di pagamento da un evento di una pratica</td><td><a href="https://gitlab.com/opencity-labs/area-personale/payment-dispatcher">payment-dispatcher</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>payment-updater</td><td>Ascolta sul topic di kafka dei pagamenti e aggiorna lo stato delle pratiche di conseguenza</td><td><a href="https://gitlab.com/opencity-labs/area-personale/payment-updater">payment-updater</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>gotenberg</td><td>API interna per la creazione di file PDF</td><td><a href="https://hub.docker.com/r/gotenberg/gotenberg">gotenberg</a></td><td>false</td><td></td></tr><tr><td>ksqldb</td><td>Ascolta su tutti i topic di kafka ed espone via API dati aggregati sugli eventi della piattaforma</td><td><a href="https://gitlab.com/opencity-labs/area-personale/payment-ksqldb">payment-ksqldb</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>kafka-core-api</td><td>Espone un endpoint http che il core usa per inviare eventi sui topic di kafka</td><td><a href="https://hub.docker.com/r/timberio/vector">timberio/vector</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka, </span><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage</span></td></tr><tr><td>payments poller</td><td>Legge da Ksql i pagamenti <em>pendenti</em> e per ognuno di essi chiama il proxy che lo gestisce per aggiornare lo stato del pagamento</td><td><a href="https://gitlab.com/opencity-labs/area-personale/payments-poller">payments-poller</a></td><td>false</td><td></td></tr><tr><td>XXX-payment proxy</td><td>Servizio per l'integrazione con l'intermediario di pagamento XXX  (vedi <a href="integrazioni">Integrazioni</a> per la lista completa)</td><td></td><td>true</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka, </span><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage</span></td></tr><tr><td>YYY-protocol- proxy</td><td>Servizio per l'integrazione con il sistema di protocollo YYY  (vedi <a href="integrazioni">Integrazioni</a> per la lista completa)</td><td></td><td>false</td><td><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage, </span><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>registry-api</td><td>API per l'app Django con cui abbiamo integrato una decina di protocolli</td><td><a href="https://gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry">application-registry</a></td><td>false</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL</span></td></tr><tr><td>registry-scheduler</td><td>Scheduler per gli errori registrati durante un tentativo di protocollazione</td><td><a href="https://gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry">application-registry</a></td><td>false</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL, </span><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>analytics-services-aggregator</td><td>Servizio per il calcolo dei KPI sui Servizi</td><td><a href="https://gitlab.com/opencity-labs/area-personale/analytics">analytics</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka, </span><span data-option="f75681c061884e4c80373228e0a870cd">ClickHouse</span></td></tr><tr><td>analytics-first-availability-aggregator</td><td>Servizio per il calcolo dei KPI sui Calendari</td><td><a href="https://gitlab.com/opencity-labs/area-personale/analytics">analytics</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka, </span><span data-option="f75681c061884e4c80373228e0a870cd">ClickHouse</span></td></tr><tr><td>analytics-bookings-aggregator</td><td>Servizio per il calcolo dei KPI sui Calendari</td><td><a href="https://gitlab.com/opencity-labs/area-personale/analytics">analytics</a></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka, </span><span data-option="f75681c061884e4c80373228e0a870cd">ClickHouse</span></td></tr><tr><td>analytics-charts-exporter</td><td>API per la generazione dei grafici degli Analytics</td><td><a href="https://gitlab.com/opencity-labs/area-personale/analytics">analytics</a></td><td>true</td><td><span data-option="f75681c061884e4c80373228e0a870cd">ClickHouse</span></td></tr><tr><td>satisfy-hasura</td><td>API per il servizio di Customer Satisfaction</td><td><a href="https://hub.docker.com/r/hasura/graphql-engine">hasura/graphql-engine</a></td><td>true</td><td><span data-option="72b1be9ca55d417098d49ea01b179689">PostgreSQL</span></td></tr><tr><td>satisfy-api</td><td>API per la raccolta di ratings e questionari di Customer Satisfaction</td><td><a href="https://hub.docker.com/r/timberio/vector">timberio/vector</a></td><td>true</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>satisfy-ratings-aggregator</td><td>Servizio per il calcolo dei KPI della Customer Satisfaction</td><td></td><td>false</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr><tr><td>pdnd-connector</td><td>Servizio che gestisce l'interazione con la pdnd e gli enti erogatori degli e-service utilizzati dalla piattaforma</td><td><a href="https://gitlab.com/opencity-labs/area-personale/pdnd-connector">pdnd-connector</a></td><td>false</td><td><span data-option="75aaa398d0934f7e900bf7535d2d4017">File Storage</span></td></tr><tr><td>retry-orchestrator</td><td>Servizio che gestisce i retry dei messaggi provenienti dai vari topic di kafka</td><td><a href="https://gitlab.com/opencity-labs/retry-orchestrator">retry-orchestrator</a></td><td>true</td><td><span data-option="f2babf9108c84c7f83a5869b92bf622a">Kafka</span></td></tr></tbody></table>

Esempio di un file di deployment per orchestratore [Docker Swarm](https://docs.docker.com/engine/swarm/swarm-tutorial/):

<details>

<summary>File di esempio</summary>

<pre class="language-yaml" data-full-width="true"><code class="lang-yaml">version: "3.8"
# vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=yaml fileencoding=utf-8

networks:
  oc-prod-public:             # usata per pubblicare servizi
    external: true
  oc-prod-internal:           # usata per la comunicazione tra i componenti dello stack
    external: true

x-deploy: &#x26;deploy-snippet
      endpoint_mode: dnsrr
      placement:
        max_replicas_per_node: 2
        constraints: [ node.labels.apps == true ]
      replicas: 1
      update_config:
        parallelism: 1
        order: start-first
        delay: 30s
        failure_action: rollback
        monitor: 10s
      rollback_config:
        parallelism: 1
        delay: 10s
        failure_action: pause         # default: pause or continue
        monitor: 10s                  # default: 5s
        order: start-first
      restart_policy:
        condition: any
        delay: 15s                    # default: 5s, between attempts
        max_attempts: 50
        window: 20s                   # time to decide if the restart has been sucessfull
      resources:
        limits:
          cpus: '0.2'
          memory: 512M
        reservations:
          memory: 32M


x-sdc-env: &#x26;sdc-env
    ENV: prod
    APP_ENV: prod
    DB_DRIVER: pdo_pgsql
    DB_HOST: 'postgres'
    DB_PORT: 5432
    DB_NAME: oc
    DB_USER: oc
    DB_VERSION: 14
    DB_PASSWORD: 'oc'
    MAILER_URL: 'smtp://mailserver:1025'
    SECRET: change_me
    DEFAULT_FROM_EMAIL_ADDRESS: no-reply@opencity.it
    EZ_PASSWORD: change_me
    OCSDC_SCHEME: https
    OCSDC_HOST: app.localtest.me
    LOGS_PATH: php://stderr
    ENABLE_MIGRATIONS: 'false'
    ENABLE_INSTANCE_CONFIG: 'false'
    # accesso in scrittura da PHP
    FORMSERVER_PRIVATE_URL: http://form-server:8000
    # accesso con cache varnish
    FORMSERVER_PUBLIC_URL: http://form.localtest.me
    # accesso readonly per admin
    FORMSERVER_ADMIN_URL: http://forms-readonly.localtest.me
    WKHTMLTOPDF_SERVICE: http://gotenberg:3000
    PHP_FPM_USER: wodby
    PHP_FPM_GROUP: wodby
    PHP_APCU_ENABLED: 1
    PHP_OPCACHE_ENABLE: 1
    PHP_FPM_CLEAR_ENV: 'no'
    PHP_FPM_PM_MAX_CHILDREN: 50
    PHP_FPM_PM_MAX_REQUESTS: 2000
    PHP_FPM_PM_MAX_SPARE_SERVERS: 8
    PHP_FPM_PM_MIN_SPARE_SERVERS: 2
    PHP_FPM_REQUEST_SLOWLOG_TIMEOUT: 4000
    PHP_FPM_PM_START_SERVERS: 5
    PHP_DISPLAY_ERRORS: 'off'
    PHP_DISPLAY_STARTUP_ERRORS: 'off'
    PHP_ERROR_REPORTING: 'E_ERROR'
    PHP_DATE_TIMEZONE: 'Europe/Rome'
    PHP_SESSION_SAVE_HANDLER: redis
    PHP_SESSION_SAVE_PATH: 'tcp://redis:6379'
    PHP_SESSION_GC_MAXLIFETIME: 2880
    RECAPTCHA_KEY: recaptcha_key
    RECAPTCHA_SECRET: recaptcha_secret
    EWZ_RECAPTCHA_SITE_KEY: recaptcha_key
    EWZ_RECAPTCHA_SECRET: recaptcha_secret
    FEATURE_NEW_OUTDATED_BROWSER: 'true'
    FEATURE_APPLICATION_DETAIL: 'true'
    FEATURE_CALENDAR_TYPE: 'true'
    #FEATURE_DUE_AMOUNT: 'true'
    #FEATURE_ANALYTICS: 'true'
    TOKEN_TTL: 864000 # 10gg
    CACHE_MAX_AGE: 3600
    KAFKA_URL: vector
    KSQLDB_URL: ksqldb-server:8088
    SKIP_CACHE_WARMUP: 'false'
    TRUSTED_PROXIES: '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
    DEFAULT_CACHE_REDIS_PROVIDER: 'redis://redis:6379'
    DEFAULT_CACHE_LIFETIME: 3600
    DEFAULT_CACHE_PREFIX_SEED: v-2.27.0
    REGISTRY_API_URL: https://registry.qa.stanzadelcittadino.it
    REGISTRY_API_KEY: change_me
    API_USER_PASSWORD: change_me
    # In assenza di una vera autenticazione basata su SPID si può simulare
    # un login di un utente con le seguenti variabili d'ambiente:
    shibb_pat_attribute_codicefiscale: CLNVTR76P01G822Q
    shibb_pat_attribute_cognome: Coliandro
    shibb_pat_attribute_nome: Vittorino
    shibb_pat_attribute_sesso: M
    shibb_pat_attribute_emailaddress: info@comune.bugliano.pi.it
    shibb_pat_attribute_datanascita: 1/9/1976
    shibb_pat_attribute_luogonascita: Ponsacco
    shibb_pat_attribute_provincianascita: PI
    shibb_pat_attribute_telefono: 003912378945
    shibb_pat_attribute_cellulare: 333 444 666 99
    shibb_pat_attribute_indirizzoresidenza: 'Via Gramsci, 1'
    shibb_pat_attribute_capresidenza: 56056
    shibb_pat_attribute_cittaresidenza: Bugliano
    shibb_pat_attribute_provinciaresidenza: PI
    shibb_pat_attribute_statoresidenza: Italia
    shibb_pat_attribute_spidcode: 123456789
    shibb_pat_attribute_x509certificate_issuerdn: FAKE_issuerdn
    shibb_pat_attribute_x509certificate_subjectdn: FAKE_subjectdn
    shibb_pat_attribute_x509certificate_base64: "DQpSZXN1bHQgZ29lcyBoZXJlLi4uDQpCYXNlNjQNCg0KQmFzZTY0IGlzIGEgZ2VuZXJpYyB0ZXJtIGZvciBhIG51bWJlciBvZiBzaW1pbGFyIGVuY29kaW5nIHNjaGVtZXMgdGhhdCBlbmNvZGUgYmluYXJ5IGRhdGEgYnkgdHJlYXRpbmcgaXQgbnVtZXJpY2FsbHkgYW5kIHRyYW5zbGF0aW5nIGl0IGludG8gYSBiYXNlIDY0IHJlcHJlc2VudGF0aW9uLiBUaGUgQmFzZTY0IHRlcm0gb3JpZ2luYXRlcyBmcm9tIGEgc3BlY2lmaWMgTUlNRSBjb250ZW50IHRyYW5zZmVyIGVuY29kaW5nLg=="
    shibb_Shib-Session-ID: abc123abc123abc123abc123abc123abc123abc123
    shibb_Shib-Session-Index: abc123abc123abc123abc123abc123abc123abc123
    shibb_Shib-Authentication-Instant: 2000-01-01T00-00Z

x-registry-env: &#x26;registry-env
  DJANGO_DATABASE: postgres:5432:oc_registry:oc:oc
  DJANGO_SECRET_KEY: change_me
  DJANGO_SETTINGS_MODULE: application_registry.settings_production
  KAFKA_BOOTSTRAP_SERVERS: kafka:9092
  SENTRY_DSN:

configs:
  sf-config-instances:
    name: oc-instances-v1
    file: ./config/app/instances.yml
  vector-api:
    name: occ-kafka-http-v1
    file: ./config/kafka/vector.toml

services:

  # Symfony-core, esposto solo su rete interna
  app:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    stop_grace_period: 45s
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    networks:
      - oc-prod-internal
    environment:
      &#x3C;&#x3C;: *sdc-env
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health-check"]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      endpoint_mode: vip
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 2048M
        reservations:
          memory: 1024M
      labels:
        traefik.enable: 'false'

  # Frontend del symfony-core
  varnish:
    image: registry.gitlab.com/opencontent/varnish:1.2
    stop_grace_period: 45s
    networks:
      - oc-prod-public
      - oc-prod-internal
    healthcheck:
      disable: true
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      endpoint_mode: dnsrr
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 2048M
        reservations:
          memory: 1024M
      labels:
        traefik.enable: 'true'
        traefik.http.services.varnish.loadbalancer.server.port: 6081
        # disabilitato altrimenti quando fallisce backend non sfrutto grace
        #traefik.http.services.varnish.loadbalancer.healthcheck.path: /health-check

        # Security headers
        traefik.http.middlewares.security.headers.frameDeny: 'true'
        traefik.http.middlewares.security.headers.customFrameOptionsValue: 'SAMEORIGIN'
        traefik.http.middlewares.security.headers.stsSeconds: 31536000
        traefik.http.middlewares.security.headers.stsIncludeSubdomains: 'true'
        traefik.http.middlewares.security.headers.forceStsHeader: 'true'
        #traefik.http.middlewares.security.headers.contentSecurityPolicy: "default-src 'self' data:; img-src 'self' data: blob: c.bing.com www.googletagmanager.com *.openstreetmap.org c.clarity.ms cartodb-basemaps-b.global.ssl.fastly.net cartodb-basemaps-c.global.ssl.fastly.net cartodb-basemaps-a.global.ssl.fastly.nett; style-src 'self' 'unsafe-inline' static.opencityitalia.it satisfy.opencontent.it widget.freshworks.com stackpath.bootstrapcdn.com unpkg.com cdn.datatables.net printjs-4de6.kxcdn.com cdnjs.cloudflare.com fonts.googleapis.com; connect-src 'self' s3.eu-west-1.amazonaws.com api.opencityitalia.it qa-genova-id.boat.opencontent.io dev-genova-id.boat.opencontent.io mappe.genova.opencityitalia.it api.opencontent.it api.qa.stanzadelcittadino.it registry.qa.stanzadelcittadino.it satisfy.boat.opencontent.io static.opencityitalia.it www.giscom.cloud nominatim.openstreetmap.org a.clarity.ms d.clarity.ms k.clarity.ms i.clarity.ms h.clarity.ms widget.freshworks.com fiscalcode.opencontent.it form-qa.stanzadelcittadino.it form-readonly-qa.stanzadelcittadino.it cdn.datatables.net satisfy.opencityitalia.it satisfy.opencontent.it satisfy.hasura.app sdc-analytics-dev-charts-exporter.boat.opencontent.io efil-proxy-qa.boat.opencontent.io pmpay-proxy-qa.boat.opencontent.io iris-proxy-qa.boat.opencontent.io mypay-proxy-qa.boat.opencontent.io www.openstreetmap.org flyimg.opencontent.it; script-src 'self' 'unsafe-inline' 'unsafe-eval' static.opencityitalia.it cdn.announcekit.app widget.freshworks.com www.clarity.ms form-qa.stanzadelcittadino.it cdnjs.cloudflare.com cdn.form.io unpkg.com code.jquery.com www.googletagmanager.com cdn.datatables.net printjs-4de6.kxcdn.com satisfy.opencityitalia.it satisfy.opencontent.it www.recaptcha.net www.gstatic.com; object-src 'none'; font-src 'self' data: satisfy.opencontent.it stackpath.bootstrapcdn.com cdnjs.cloudflare.com unpkg.com fonts.gstatic.com; worker-src 'self'; report-uri https://csp-collector.opencontent.it/csp?env=production&#x26;mode=enforce&#x26;app=sdc-qa; child-src 'self' www.youtube-nocookie.com announcekit.app"
        traefik.http.middlewares.security.headers.contentTypeNosniff: 'true'
        traefik.http.middlewares.security.headers.permissionsPolicy: "geolocation=(self), camera=(self), microphone=(self)"
        traefik.http.middlewares.security.headers.referrerPolicy: 'strict-origin-when-cross-origin'
        traefik.http.middlewares.security.headers.browserXssFilter: 'true'

        # Cors generali
        traefik.http.routers.https.rule: 'Method(`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `PURGE`) &#x26;&#x26; Host(`app.localtest.me`)'
        traefik.http.routers.https.tls: 'true'
        traefik.http.routers.https.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.https.middlewares: 'cors, general-rl, security'

        # Cors per condivisione autenticazione
        traefik.http.routers.origin-https.rule: 'Method(`GET`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `PURGE`) &#x26;&#x26; Host(`app.localtest.me`)  &#x26;&#x26; PathPrefix(`/lang/api/session-auth`)'
        traefik.http.routers.origin-https.tls: 'true'
        traefik.http.routers.origin-https.priority: 10001
        traefik.http.routers.origin-https.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.origin-https.middlewares: 'cors-origin, general-rl, security'

        traefik.http.middlewares.cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
        traefik.http.middlewares.cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.cors.headers.addvaryheader: 'true'
        
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
        traefik.http.middlewares.cors-origin.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        traefik.http.middlewares.cors-origin.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.cors-origin.headers.addvaryheader: 'true'

    environment:
      # https://github.com/wodby/varnish#environment-variables
      VARNISH_BACKEND_GRACE  : '2880m'
      VARNISH_BACKEND_HOST: 'app'
      VARNISH_BACKEND_PORT: '80'
      VARNISH_BACKEND_PROBE: '/health-check'
      VARNISH_KEEP_ALL_PARAMS: 1
      VARNISH_CACHE_STATIC_FILES: 1
      ##VARNISH_MOBILE_SEPARATE_CASH: 1
      #VARNISH_BIG_FILES_SIZE: 10485760
      VARNISHD_MEMORY_SIZE: '1024m'
      VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT: 30s
      VARNISH_BACKEND_FIRST_BYTE_TIMEOUT: 30s
      VARNISH_STRIP_COOKIES: 'cookies_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+'
      VARNISHD_PARAM_WORKSPACE_BACKEND: '256k'
      VARNISHD_PARAM_WORKSPACE_CLIENT: '256k'
      VARNISH_SECRET: 'change_me'

  # Manager symfony, non è esposto su rete pubblica esegue migrazioni e script all'avvio
  manager:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    stop_grace_period: 45s
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    networks:
      - oc-prod-internal
    environment:
      &#x3C;&#x3C;: *sdc-env
      DB_USER: oc   # Inserire username utente manager se configurato
      DB_PASSWORD: 'oc' # Inserire password utente manager se configurato
      ENABLE_MIGRATIONS: 'true'
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      endpoint_mode: vip
      replicas: 1
      resources:
        limits:
          cpus: '1'
          memory: 1024M
        reservations:
          memory: 512M

  # Ci sono 3 valori da tenere sempre uguali: WORKER_NUM, replicas e swam_cronjob_replicas
  worker:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    networks:
      - oc-prod-internal
    stop_signal: SIGQUIT
    stop_grace_period: 20m
    command: [ "./bin/worker-daemon.sh" ]
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    volumes:
      - ./config/worker/worker-daemon.sh:/var/www/html/bin/worker-daemon.sh:ro
    healthcheck:
      disable: true
    environment:
      &#x3C;&#x3C;: *sdc-env
      SWARM_SERVICE: "worker"
      SWARM_STACK: "oc-prod"
      #DEBUG: 1
      WORKER_ID: 1
      WORKER_NUM: 1
      MAX_EXECUTIONS: 100
      MAX_ACTIONS: 50
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      endpoint_mode: dnsrr
      replicas: 1
      update_config:
        order: stop-first
      restart_policy:
        condition: none
      resources:
        limits:
          cpus: '0.5'
          memory: 2048M
        reservations:
          memory: 512M
      labels:
        swarm.cronjob.enable: "true"
        swarm.cronjob.schedule: "*/3 * * * *"
        swarm.cronjob.skip-running: "true"
        swarm.cronjob.replicas: 1

  # Espone delle api rest per la creazione di eventi su kafka
  vector:
    image: timberio/vector:0.18.1-debian
    configs:
      - source: vector-api
        target: /etc/vector/vector.toml
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:80/health" ]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    environment:
      VECTOR_LOG: debug
    networks:
      - oc-prod-internal
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: 'false'

  # Microservizio per la creazione di pdf
  gotenberg:
    image: gotenberg/gotenberg:7.9.2
    networks:
      - oc-prod-internal
      - oc-prod-public
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 1m30s
      timeout: 10s
      retries: 6
      start_period: 5s
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        prometheus.disable: 'true'
        traefik.enable: 'true'
        traefik.http.services.gotenberg.loadbalancer.server.port: 3000
        traefik.http.routers.gotenberg.rule: 'Host(`gotenberg.localtest.me`)'
        traefik.http.routers.gotenberg.tls: 'true'
        traefik.http.routers.gotenberg.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.gotenberg.middlewares: 'goodheaders'

  # Strumento per testare la posta elettronica
  mailhog:
    image: mailhog/mailhog
    networks:
      - oc-prod-internal
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.mailhog.loadbalancer.server.port: 8025
        traefik.http.routers.mailhog.rule: Host(`mail.localtest.me`)
        traefik.http.routers.mailhog.tls: 'true'
        traefik.http.routers.mailhog.middlewares: goodheaders
        traefik.http.routers.mailhog.tls.certresolver: 'myhttpchallenge'

  ##############################################################################
  # FORMS
  ##############################################################################

  # Frontend per il formserver
  form-varnish:
    image: registry.gitlab.com/opencontent/varnish:1.2
    stop_grace_period: 45s
    networks:
      - oc-prod-internal
      - oc-prod-public
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      replicas: 1
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.1'
          memory: 128M
      labels:
        prometheus.enable: 'true'
        prometheus.port: 9131
        traefik.enable: 'true'
        traefik.http.services.formvarnish.loadbalancer.server.port: 6081
        traefik.http.services.formvarnish.loadbalancer.healthcheck.path: /.vchealthz
        traefik.http.routers.formvarnish.rule: 'Method(`GET`, `HEAD`, `OPTIONS`) &#x26;&#x26; Host(`form.localtest.me`)'
        traefik.http.routers.formvarnish.tls: 'true'
        traefik.http.routers.formvarnish.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formvarnish.middlewares: 'cors'


    environment:
      # https://github.com/wodby/varnish#environment-variables
      VARNISH_BACKEND_HOST: 'form-server'
      VARNISH_BACKEND_PORT: '8000'
      VARNISH_BACKEND_GRACE: '2880m'
      VARNISH_BACKEND_PROBE: '/form/5d7aa15b18fecd734051ae7f?varnish-health'
      VARNISH_CACHE_STATIC_FILES: 1
      VARNISHD_MEMORY_SIZE: '256m'
      VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT: 10s
      VARNISH_BACKEND_FIRST_BYTE_TIMEOUT: 30s
      VARNISH_KEEP_ALL_PARAMS: 1
      VARNISH_STRIP_COOKIES: 'cookie_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+|_ga|_gid|_gat[a-zA-Z0-9-_]+'
      VARNISH_SECRET: 'nvXq3CkK'
      VARNISHD_PARAM_WORKSPACE_BACKEND: '256k'
      VARNISHD_PARAM_WORKSPACE_CLIENT: '256k'

  # Espone api rest per il crud dei form
  form-server:
    image: registry.gitlab.com/opencity-labs/area-personale/form-server:1.3.0
    networks:
      - oc-prod-public
      - oc-prod-internal
    environment:
      DB_URL: mongodb://mongo:27017/formmanager
      MAX_AGE: 5
      S_MAX_AGE: 60
    #healthcheck:
    #  test: [ "CMD", "wget", "--spider", "http://localhost:8000/form/5d7aa15b18fecd734051ae7f" ]
    #  start_period: 30s
    #  timeout: 1s
    #  retries: 1
    #  interval: 1m
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.formserver.loadbalancer.server.port: 8000

        # Espongo il formserver aperto in scrittura con oc-auth, sotto /api per il form-builder
        traefik.http.routers.formserver.rule: 'Method(`GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `PATCH`, `DELETE`) &#x26;&#x26; Host(`form-builder.localtest.me`) &#x26;&#x26; PathPrefix(`/api/`)'
        traefik.http.routers.formserver.tls: 'true'
        traefik.http.routers.formserver.tls.certresolver: 'myhttpchallenge'
        traefik.http.middlewares.strip-forms-api.stripprefix.prefixes: '/api/'
        traefik.http.routers.formserver.middlewares: 'strip-forms-api'

        # Espongo anche readonly per gli admin da backend (per vedere subito aggiornamenti)
        traefik.http.routers.formserver-ro.rule: 'Method(`GET`, `POST`, `HEAD`, `OPTIONS`) &#x26;&#x26; Host(`form-readonly.localtest.me`)'
        traefik.http.routers.formserver-ro.tls: 'true'
        traefik.http.routers.formserver-ro.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formserver-ro.middlewares: 'oc-auth, cors'

  # Microservizio per la gestione dei componenti comuni dei form (nested form)
  form-builder:
    image: registry.gitlab.com/opencity-labs/area-personale/formbuilderjs:0-5-2
    healthcheck:
      test: [ "NONE" ]
    networks:
      - oc-prod-public
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.formbuilder.loadbalancer.server.port: 80

        traefik.http.routers.formbuilder.rule: 'Method(`GET`, `HEAD`, `OPTIONS`) &#x26;&#x26; Host(`form-builder.localtest.me`)'
        traefik.http.routers.formbuilder.tls: 'true'
        traefik.http.routers.formbuilder.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.formbuilder.middlewares: 'oc-auth'

  ##############################################################################
  # FORMS
  ##############################################################################

  # Interroga la tabella PAYMENTS_STATUS  su ksqldb e richiede info ai proxy sui pagamenti pendenti
  payments-poller:
    image: registry.gitlab.com/opencity-labs/area-personale/payments-poller:1.0.2
    networks:
      - oc-prod-internal
    environment:
      KSQLDB_SERVER: http://ksqldb-server:8088
      LOOP_TIME: 900
      LOG_LEVEL: DEBUG
      HEALTHCHECK_ID: 88f0d2a5-0326-46a1-9093-42e83c3017c8
      PYTHONDEBUG: 1
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      resources:
        reservations:
          memory: 1G
        limits:
          cpus: '0.5'
          memory: 4G
    healthcheck:
      test: [ NONE ]

  # Legge gli eventi dal topic applications e crea un evento nel topic payments se necessario
  payment-dispatcher:
    image: registry.gitlab.com/opencity-labs/area-personale/payment-dispatcher:1.2.1
    networks:
      - oc-prod-internal
    environment:
      KAFKA_TOPIC_APPLICATIONS: applications
      KAFKA_TOPIC_PAYMENTS: payments
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_CONSUMER_GROUP_PREFIX: payment-dispatcher
      PROMETHEUS_JOB_NAME: payment_dispatcher
      APP_ID: payment-dispatcher:1.2.1
      KSQLDB_SERVER: http://ksqldb-server:8088
      LOG_LEVEL: INFO
    healthcheck:
      test: [ NONE ]
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      resources:
        limits:
          cpus: '0.5'
          memory: 1G

  # Gateway proxy di iris (ambiente di test)
  iris-proxy:
    image: registry.gitlab.com/opencity-labs/area-personale/iris-payment-proxy:1.3.3
    stop_grace_period: 5m
    networks:
      - oc-prod-public
    environment:
      CANCEL_PAYMENT: "true"
      KAFKA_TOPIC_NAME: payments
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_GROUP_ID: iris_payment_proxy
      IRIS_OTF_PAYMENT_URL: https://apistage.regione.toscana.it/C01/ComunicazionePosizioniDebitorieOTF/v3/IdpAllineamentoPendenzeEnteOTF
      IRIS_IUV_URL: https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneIUVService
      IRIS_OUTCOME_URL: https://apistage.regione.toscana.it/C01/InvioNotificaPagamentoEsito/v3/IdpInformativaPagamento.Esito
      IRIS_VERIFY_URL: https://apistage.regione.toscana.it/C01/VerificaStatoPagamento/v3/IdpVerificaStatoPagamento
      IRIS_NOTICE_URL: https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneAvvisiService
      EXTERNAL_API_URL: "https://iris-proxy.localtest.me"
      INTERNAL_API_URL: "http://iris-proxy.localtest.me"
      BASE_PATH_EVENT: "sdc-payments/iris-proxy/payments/"
      BASE_PATH_CONFIG: "sdc-payments/iris-proxy/tenants/"
      # STORAGE_TYPE="LOCAL|MINIO|S3"
      STORAGE_TYPE: "LOCAL"
      STORAGE_BUCKET_NAME: "payments"
      APP_ID: "iris-proxy:1.3.3"
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: 'true'
        traefik.http.services.iris-proxy-qa.loadbalancer.server.port: 8000

        traefik.http.routers.iris-proxy.entrypoints: 'websecure, web'
        traefik.http.routers.iris-proxy.rule: 'Host(`iris-proxy.localtest.me`) &#x26;&#x26; PathPrefix(`/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/{op:(docs|redoc|openapi.json|tenants|services|notify-payment)}`, `/{op:(tenants|services)}/({id:[^/]+}|schema)`) &#x26;&#x26; METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
        traefik.http.routers.iris-proxy.tls: 'true'
        traefik.http.routers.iris-proxy.tls.certresolver: 'myhttpchallenge'

        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
        # asterisco perché ognuno di essi è acceduto da tutti i domini custom che avranno gli enti
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.payment-proxy-qa-cors.headers.addvaryheader: 'true'

        traefik.http.routers.iris-proxy.middlewares: 'goodheaders, payment-proxy-qa-cors'
    volumes:
      - ./var/tenants:/app/payments/sdc-payments/iris-proxy/tenants
      - ./var/payments:/app/payments/sdc-payments/iris-proxy/payments

  # Aggiorna lo stato delle pratiche dopo che si sono verificati dei pagamenti
  krun-process-payments:
    image: registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
    configs:
      - source: sf-config-instances
        target: /var/www/html/config/instances_prod.yml
    entrypoint: /bin/krun
    stop_grace_period: 300s
    healthcheck:
      disable: true
    volumes:
      - ./config/krun/process-payment:/bin/process-payment
      - ./config/krun/krun:/bin/krun
    networks:
      - oc-prod-internal
    environment:
      &#x3C;&#x3C;: *sdc-env
      KAFKA_TOPIC: "payments"
      KAFKA_SERVER: "kafka:9092"
      KAFKA_CONSUMER_GROUP: "oc-prod-krun-process-payments"
      COMMAND: "/bin/process-payment"
      #DEBUG: 1
      VERBOSE: 1
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      resources:
        limits:
          cpus: '0.2'
          memory: 1024M
      update_config:
        parallelism: 3
        order: stop-first


  ##############################################################################
  # REGISTRY
  ##############################################################################

  # Espone le api per la configurazione dei vari sistemi di protocollazione
  registry-rest-api:
    image: registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
    networks:
      - oc-prod-public
      - oc-prod-internal
    environment:
      &#x3C;&#x3C;: *registry-env
      DJANGO_LOG_LEVEL: INFO
      REGISTRY_API_KEY: change_me
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      labels:
        traefik.enable: "true"
        traefik.http.services.registry.loadbalancer.server.port: 8000
        traefik.http.routers.registry.rule: "Host(`registry.localtest.me`)"
        traefik.http.routers.registry.tls: "true"
        traefik.http.routers.registry.tls.certresolver: "myhttpchallenge"

        traefik.http.middlewares.registry-qa-home-redir.redirectregex.regex: 'registry.localtest.me/$$'
        traefik.http.middlewares.registry-qa-home-redir.redirectregex.replacement: "registry.localtest.me/admin"

        traefik.http.middlewares.registry-redirect-tenants.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/tenants/(.*)'
        traefik.http.middlewares.registry-redirect-tenants.replacepathregex.replacement: '/api/v1/tenants/'

        traefik.http.middlewares.registry-redirect-services.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/'
        traefik.http.middlewares.registry-redirect-services.replacepathregex.replacement: '/api/v1/services/$$1/'
        traefik.http.middlewares.registry-redirect-service.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/(.+)'
        traefik.http.middlewares.registry-redirect-service.replacepathregex.replacement: '/api/v1/services/$$1/$$2/'

        traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.regex: '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/schema'
        traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.replacement: '/api/v1/services-schema/$$1'

        traefik.http.middlewares.registry-cors.headers.accesscontrolallowcredentials: 'true'
        traefik.http.middlewares.registry-cors.headers.accesscontrolallowheaders: '*'
        traefik.http.middlewares.registry-cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,OPTIONS'
        traefik.http.middlewares.registry-cors.headers.accesscontrolalloworiginlist: '*'
        traefik.http.middlewares.registry-cors.headers.accesscontrolmaxage: 100
        traefik.http.middlewares.registry-cors.headers.addvaryheader: 'true'

        traefik.http.routers.registry.middlewares: "goodheaders, registry-qa-home-redir, registry-redirect-tenants, registry-redirect-services, registry-redirect-service, registry-redirect-service-schema, registry-cors"

        prometheus.enable: "true"
        prometheus.port: 8000

  # Gestisce il sistema di retry della protocollazione
  registry-retry-scheduler:
    image: registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
    command: /app/venv/bin/python3.9 manage.py run_retry_scheduler
    networks:
      - oc-prod-internal
    environment:
      &#x3C;&#x3C;: *registry-env
      ENABLE_SENTRY_EVENT_FILTER: "true"
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      
<strong>      
</strong>  ##############################################################################
  # PDND
  ##############################################################################

  pdnd-connector:
    image: registry.gitlab.com/opencity-labs/area-personale/pdnd-connector:x.x.x
    networks:
      - oc-prod-internal
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
    environment:
      ENVIRONMENT: app_environment
      SENTRY_TOKEN: sentry DSN
      HTTP_EXTERNAL_BASEPATH: https://api.qa.stanzadelcittadino.it/pdnd
      SDC_PUBLIC_KEY_ENDPOINT: https://core/.well-known/jwks.json
      CACHE_EXPIRATION: 5m
      CACHE_EVICTION: 10m
      STORAGE_TYPE: s3
      STORAGE_BUCKET: s3_bucket
      STORAGE_BASE_PATH: pdnd
      STORAGE_LOCAL_PATH: /data/pdnd
      STORAGE_S3_KEY: S3_key
      STORAGE_S3_SECRET: s3_secret
      STORAGE_S3_REGION: eu-west-1
      STORAGE_S3_ENDPOINT:  https://s3.eu-west-1.amazonaws.com
      STORAGE_S3_SSL: "false"
    labels:
      prometheus.enable: 'true'
      prometheus.port: 8000
      traefik.enable: 'true'
      traefik.docker.network: 'oc-prod-internal'
      traefik.http.services.pdnd-connector.loadbalancer.server.port: 8000
      traefik.http.routers.pdnd-connector.entrypoints: 'websecure, web'
      traefik.http.routers.pdnd-connector.rule: 'Host(`api.qa.stanzadelcittadino.it`) &#x26;&#x26; PathPrefix(`/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}`, `/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}/({id:[^/]+})`) &#x26;&#x26; METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
      traefik.http.routers.pdnd-connector.tls: 'true'
      traefik.http.middlewares.pdnd-connector-stripper.stripprefix.prefixes: '/pdnd/'
      traefik.http.routers.pdnd-connector.tls.certresolver: 'myhttpchallenge'
      traefik.http.routers.pdnd-connector.middlewares: 'goodheaders, cors-free-all, pdnd-connector-stripper'


##############################################################################
# PROTOCOL PROXY
##############################################################################
  
  protocol-proxy-sigedo:
    image: registry.gitlab.com/opencity-labs/area-personale/protocol-proxy-sigedo:1.1.0-x86
    networks:
      - oc-prod-internal
      - oc-prod-public
    environment:
      ENVIRONMENT: PROD
      DEBUG: "false"
      SERVER_ADDRESS_PORT: https://api.opencityitalia.055055.it/registry-proxy/sigedo/v1
      CACHE_EXPIRATION: 5m
      CACHE_EVICTION: 10m
      SDC_AUTH_TOKEN_USER: user
      SDC_AUTH_TOKEN_PASSWORD: password
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_CONSUMER_GROUP: protocol-proxy-sigedo
      KAFKA_CONSUMER_TOPIC: documents
      KAFKA_PRODUCER_TOPIC: documents
      KAFKA_RETRY_TOPIC: retry_prod_queue_dispatcher
      STORAGE_ENDPOINT: https://storage.opencityitalia.055055.it
      STORAGE_TYPE: s3
      STORAGE_BUCKET: documents
      STORAGE_ACCESS_S3_KEY: user
      STORAGE_KEY_S3_ACCESS_SECRET: password
      STORAGE_S3_REGION: eu-south-1
      PREFIX_API: /registry-proxy/sigedo/v1/
    #healthcheck:
      #test: wget -O /dev/null http://0.0.0.0:8000/status || exit 1
      #interval: 5s
      #retries: 5
      #start_period: 20s
      #timeout: 10s
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      replicas: 1
      resources:
        limits:
          cpus: '0.3'
          memory: 512M
        reservations:
          memory: 64M
      labels:
        prometheus.enable: 'true'
        prometheus.port: 8000
        traefik.enable: 'true'
        traefik.docker.network: 'oc-prod-internal'
        traefik.http.services.protocol-proxy-sigedo-prod.loadbalancer.server.port: 8000
        traefik.http.services.protocol-proxy-sigedo-prod.loadbalancer.healthcheck.path: '/status'
        traefik.http.routers.protocol-proxy-sigedo-prod.entrypoints: 'websecure, web'
        traefik.http.routers.protocol-proxy-sigedo-prod.rule: 'Host(`api.opencityitalia.055055.it`) &#x26;&#x26; PathPrefix(`/registry-proxy/sigedo/v1/{op:(status|metrics|schema|tenants|services)}`, `/registry-proxy/sigedo/v1/{op:(tenants|services)}/({id:[^/]+})`) &#x26;&#x26; METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
        traefik.http.routers.protocol-proxy-sigedo-prod.tls: 'true'
        traefik.http.middlewares.protocol-proxy-sigedo-prod-stripper.stripprefix.prefixes: '/registry-proxy/sigedo/v1/'
        traefik.http.routers.protocol-proxy-sigedo-prod.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.protocol-proxy-sigedo-prod.middlewares: 'goodheaders, cors-free-all, protocol-proxy-sigedo-prod-stripper'
        traefik.http.routers.protocol-proxy-sigedo-prod-docs.entrypoints: 'websecure, web'
        traefik.http.routers.protocol-proxy-sigedo-prod-docs.rule: 'Host(`api.opencityitalia.055055.it`) &#x26;&#x26; Path(`/registry-proxy/sigedo/v1/{op:(docs|openapi.json)}`) &#x26;&#x26; METHOD(`GET`, `HEAD`, `OPTIONS`)'
        traefik.http.routers.protocol-proxy-sigedo-prod-docs.tls: 'true'
        traefik.http.routers.protocol-proxy-sigedo-prod-docs.tls.certresolver: 'myhttpchallenge'
        traefik.http.routers.protocol-proxy-sigedo-prod-docs.middlewares: 'goodheaders, cors-free-all'

  retry-orchestrator-prod:
    image: registry.gitlab.com/opencity-labs/retry-orchestrator:1.0.23-x86
    networks:
      - oc-prod-internal
    deploy:
      &#x3C;&#x3C;: *deploy-snippet
      replicas: 1
      labels:
        prometheus.enable: "true"
        prometheus.port: 8000
        com.centurylinklabs.watchtower.scope: 'autoupdate'
      resources:
        reservations:
          memory: 128M
        limits:
          cpus: '0.1'
          memory: 256M
    environment:
      KAFKA_SERVER: kafka:9092
      KAFKA_RETRY_QUEUE_TOPIC: retry_prod_queue_dispatcher
      KAFKA_DISPATCHER_POLICY: 3x1m,4x15m,3x180m,6x720m
      KAFKA_RETRY_QUEUE_PREFIX: retry_prod_queue_
      KAFKA_DEAD_LETTER_QUEUE: retry_prod_dead_letter_queue
      KAFKA_RETRY_QUEUE_CONSUMER_GROUP: retry-orchestrator-prod
      ENVIRONMENT: boat-prod
      SERVER_ADDRESS_PORT: 0.0.0.0:8000
      SENTRY_DSN: https://example.com/4505402356858880
      SERVER_DEBUG: "false"
      CACHE_EXPIRATION: 5m
      CACHE_EVICTION: 5m
    healthcheck:
      test: curl --fail http://0.0.0.0:8000/status
      interval: 30s
      retries: 2
      start_period: 10s
      timeout: 5s

</code></pre>

</details>

Prima di procedere  all’avvio dei microservizi si devono compiere alcune operazioni:

### Creazione del file delle istanze

Va creato un file `instances.yml`  sull'ambiente che ospiterà la piattaforma, questo file dovrà poi essere condiviso con i servizi che ne hanno bisogno (vedi file di esempio di distribuzione).

Il file è così formato:

```yaml
instances:
  # dominio dell'sitanza con prefisso
  stanzadelcittadino.localtest.me/comune-di-bugliano:
    # codice meccanografico dell'ente
    codice_meccanografico: c_cbug
    # identificativo dell'istanza
    identifier: comune-di-bugliano
    # lingue disponibili (it, de, en)
    app_locales: it|en
    # tipo di login (da scegliere in base al provider dell'ente)
    login_route: login_pat

```

Andrà creato un blocco istanza per ogni ente che si vorrà ospitare sulla piattaforma

### Creazione dei database

#### **Singole istanze degli enti (Symfony core)**

Il tipo di multi-tenancy implementata in OpenCity Area personale è di singolo stack applicativo con database dedicato per tenant.

Per poter effettuare un’installazione dell’infrastruttura abbiamo quindi bisogno di un singolo database per tenant con le seguenti caratteristiche.

`Postgres >= 11 con estensione postgis >= 3`

**Permessi**

Per ridurre i rischi dovuti a errori o a compromissione delle credenziali vengono usati due utenti differenti:

* un utente **oc\_manager** che viene utilizzato per creare i database ed eseguire operazione di struttura (creazione, modifica e cancellazione di tabelle, viste, colonne ecc ecc)
* un utente **oc\_user** che viene utilizzato dall’applicativo per effettuare operazioni sui dati

Attualmente nella nostra infrastruttura la creazione del database avviene con l'utente **oc\_manager**, a partire da un template preimpostato con i privilegi necessari.

\
**Creazione del template**

Come utente postgres si crea il database impostando i privilegi di default che verranno assegnati a tutti gli oggetti creati in seguito.

A postgres verranno dati privilegi ampi, mentre a **oc\_user** verranno dati privilegi minimali:

```sql
CREATE DATABASE _oc ENCODING='utf8' CONNECTION LIMIT=50;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON TYPES TO postgres WITH GRANT OPTION;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL PRIVILEGES ON TABLES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT USAGE ON TYPES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT EXECUTE ON FUNCTIONS TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT SELECT,INSERT,UPDATE,DELETE ON TABLES TO oc_user;
CREATE EXTENSION IF NOT EXISTS postgis;
```

In seguito la proprietà del database viene assegnata ad **oc\_manager**

```sql
ALTER DATABASE _oc OWNER TO oc_manager;
ALTER SCHEMA public OWNER TO oc_manager;
```

Per creare database come utente postgres gli viene dato il ruolo **oc\_manager**

```sql
GRANT oc_manager TO postgres;
```

**Creazione di un nuovo database**

Per creare il database e impostarne subito come owner l'utente corretto, si esegue come utente postgres

```sql
CREATE DATABASE oc_firenze TEMPLATE = _oc OWNER='oc_manager' ENCODING='utf8' CONNECTION LIMIT=50;Some code
```

#### **Sistema di protocollazione (registry)**

Il sistema di protocollazione ha invece bisogno di un singolo database con le seguenti caratteristiche:

`Postgres >= 11`

**Creazione di un nuovo utente**

```sql
CREATE USER registry_user WITH PASSWORD 'password';
```

Tuning di alcuni parametri di connessione (questi comandi sono opzionali ma suggeriti da django)

```sql
ALTER ROLE registry_user SET client_encoding TO 'utf8';
ALTER ROLE registry_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE registry_user SET timezone TO 'UTC';
```

**Creo il database e lo assegno all'utente**

<pre class="language-sql"><code class="lang-sql"><strong>CREATE DATABASE oc_registry ENCODING='utf8' CONNECTION LIMIT=50;
</strong>GRANT ALL PRIVILEGES ON DATABASE oc_registry TO registry_user;
</code></pre>

### Configurazione dei proxy di pagamento

Per garantire una corretta comunicazione tra l'area personale e i proxy di pagamento è necessaria configurare appropriatamente gli endpoint sul proxy di riferimento.

**Configurazione dei CORS**

Il requisito di base affinchè questi endpoint siano correttamente funzionanti è che siano correttamente racchiuse all'interno di un middleware che gestisca i CORS in maniera adeguata. In particolare questo middleware dovrà essere configurato come segue:

<table><thead><tr><th width="364">Header</th><th>Valore</th></tr></thead><tbody><tr><td><code>Access-Control-Allow-Credentials</code></td><td><code>true</code></td></tr><tr><td><code>Access-Control-Allow-Headers</code></td><td><code>Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer</code></td></tr><tr><td><code>Access-Control-Allow-Methods</code></td><td><code>GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS</code></td></tr><tr><td><code>Access-Control-Allow-Origin-List</code></td><td><code>*</code></td></tr><tr><td><code>Access-Control-Max-Age</code></td><td><code>100</code></td></tr><tr><td><code>Add-Vary-Header</code></td><td><code>true</code></td></tr></tbody></table>

Di seguito un esempio di configurazione del middleware:

```yaml
traefik.http.middlewares.cors.headers.accesscontrolallowcredentials: 'true'
traefik.http.middlewares.cors.headers.accesscontrolallowheaders: 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
traefik.http.middlewares.cors.headers.accesscontrolallowmethods: 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist: '*'
traefik.http.middlewares.cors.headers.accesscontrolmaxage: 100
traefik.http.middlewares.cors.headers.addvaryheader: 'true'
```

#### Endpoint interni (non necessita di CORS)&#x20;

L'unico endpoint interno in questo caso è quello per richiedere l'aggiornamento dello stato di un pagamento. Per garantire quindi che questo venga chiamato solo ed esclusivamente dal microservizio di polling, è necessario che questo venga configurato come endpoint raggiungibile solo internamente alla rete di docker. Di seguito un esempio:

```yaml
 networks:
 backplane-sdc:
   external: true
 internal:
 traefik-sdc:
   external: true
 swarm-backplane:
   external: true

...
 networks:
   - traefik-sdc
   - backplane-sdc
 deploy:
   <<: *deploy-snippet
   labels:
     traefik.enable: 'true'
     traefik.docker.network: 'traefik-sdc'
     traefik.http.routers.silfi-proxy-qa-internal.entrypoints: 'backplane'
     traefik.http.routers.silfi-proxy-qa-internal.rule: 'Host(`silfi-proxy-qa.boat-backplane.opencontent.io`) && PathPrefix(`/payment-proxy/silfi/update/{id:[^/]+}`) && METHOD(`GET`, `HEAD`, `OPTIONS`)'
```

**Endpoint pubblici**

Gli endpoint pubblici sono:

* Pagamento online (`/online-payment/{payment_id}`)
* Download dell'avviso cartaceo (`/offline-payment/{payment_id}`)
* Download della ricevuta telematica (`/receipt/{payment_id}`)
* Ritorno alla area personale (detta anche landing url) (`/landing/{payment_id}`)
* Form schema per la configurazione del tenant (`/tenants/schema`)
* Form schema per la configurazione del servizio (`/services/schema`)
* Documentazione Swagger delle API (`/docs`)
* Status (`/status`, facoltativo)
* Metriche di monitoraggio (`/metrics`, facoltativo)

Di seguito un esempio di configurazione degli endpoint

```yaml
traefik.http.routers.silfi-proxy-qa.entrypoints: 'web, websecure'
traefik.http.routers.silfi-proxy-qa.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/payment-proxy/silfi/{op:(docs|status|metrics)}`, `/payment-proxy/silfi/{op:(tenants|services)}/schema`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
traefik.http.routers.silfi-proxy-qa.tls: 'true'
traefik.http.routers.silfi-proxy-qa.tls.certresolver: 'mydnschallenge'
traefik.http.routers.silfi-proxy-qa.middlewares: 'goodheaders, cors'
```

**Endpoint protetti**

Gli endpoint protetti da autenticazione sono:

* Inserimento, recupero, modifica e cancellazione della configurazione del tenant (`/tenants/{id}`)
* Inserimento, recupero, modifica e cancellazione della configurazione del servizio (`/services/{id}`)

Di seguito un esempio di configurazione degli endpoint:

```yaml
# Quando pronti aggiungere il middleware 
# sdc-qa-jwt-decode
traefik.http.routers.silfi-proxy-qa-protected.entrypoints: 'web, websecure'
traefik.http.routers.silfi-proxy-qa-protected.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(tenants|services)}`, `/payment-proxy/silfi/{op:(tenants|services)}/{id:[^/]+}`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`,>
traefik.http.routers.silfi-proxy-qa-protected.tls: 'true'
traefik.http.routers.silfi-proxy-qa-protected.tls.certresolver: 'mydnschallenge'
# MIDDLEWARE ABILITATO PER CONTROLLO JWT
# traefik.http.routers.silfi-page-proxy-qa-protected.middlewares: 'sdc-qa-jwt-decode, goodheaders, cors'
traefik.http.routers.silfi-proxy-qa-protected.middlewares: 'goodheaders, cors'
```
