# Deploying NACEF on kaissflow.com

Target: OVH VPS-4 (8 vCore, 24 GB, 200 GB) at `141.94.33.236`, Ubuntu.

## What ends up where

| Hostname                  | Serves                        | Behind nginx |
|---------------------------|-------------------------------|--------------|
| `<tenant>.kaissflow.com`  | tenant POS, db = first label  | `127.0.0.1:8069` |
| `admin.kaissflow.com`     | operator control plane, `masterdb` | `127.0.0.1:8070` |
| `jenkins.kaissflow.com`   | Jenkins                       | `127.0.0.1:8080` |
| `git.kaissflow.com`       | GitLab (SSH on `:2224`)       | `127.0.0.1:8929` |
| `kaissflow.com`           | 301 to `admin.`               | — |

Nothing but nginx and sshd listens on a public interface.

## DNS

Already correct in the OVH zone: `*` A → `141.94.33.236` and `@` A →
`141.94.33.236`. The wildcard is what makes new tenants resolve the moment
they are provisioned, with no DNS change per tenant.

## Certificate

The wildcard is not optional: tenant subdomains are created at runtime, and
HTTP-01 cannot validate a name that does not exist yet. That forces DNS-01,
which for OVH means an API credential.

Create one at https://api.ovh.com/createToken/ with these rights on
`/domain/zone/kaissflow.com/*`: `GET`, `POST`, `PUT`, `DELETE`.

```bash
cat > /etc/letsencrypt/ovh.ini <<'EOF'
dns_ovh_endpoint = ovh-eu
dns_ovh_application_key = ...
dns_ovh_application_secret = ...
dns_ovh_consumer_key = ...
EOF
chmod 600 /etc/letsencrypt/ovh.ini

certbot certonly --dns-ovh --dns-ovh-credentials /etc/letsencrypt/ovh.ini \
  --dns-ovh-propagation-seconds 60 \
  -d kaissflow.com -d '*.kaissflow.com' \
  -m khabir.mohamed12@gmail.com --agree-tos --no-eff-email
```

Renewal is the packaged `certbot.timer`; the deploy hook installed by
`bootstrap-server.sh` reloads nginx.

## Bring-up order

```bash
# on the server, as root
bash bootstrap-server.sh

# Jenkins clones /opt/nacef/app itself on its first successful build (from its
# own workspace, so it needs no git credentials there). It only needs to own
# the parent directory:
chown -R jenkins:jenkins /opt/nacef

# Run one build now. Static checks, tests, and Publish code will pass; Deploy
# will stop at the missing deploy/.env, which is the point: it creates the
# checkout you are about to configure.

cp /opt/nacef/app/deploy/.env.example /opt/nacef/app/deploy/.env
chmod 600 /opt/nacef/app/deploy/.env
chown jenkins:jenkins /opt/nacef/app/deploy/.env
$EDITOR /opt/nacef/app/deploy/.env         # DB_PASSWORD at minimum

# cert first, then nginx will start
certbot certonly ...                       # as above
nginx -t && systemctl restart nginx

cd /opt/nacef/app
docker compose -f deploy/docker-compose.yml up -d
deploy/scripts/create-template.sh          # masterdb + nacef_tpl
```

`deploy/.env` is gitignored and `rsync`/`git clean` never touch it, so it
survives every subsequent deploy.

If Jenkins runs as a host service rather than the container in
`ci-compose.yml`, it needs the Docker CLI and membership in the `docker`
group:

```bash
apt-get install -y docker-ce-cli docker-compose-plugin
usermod -aG docker jenkins
systemctl restart jenkins      # group changes need a fresh process
sudo -u jenkins docker ps      # must succeed
```

Then set the tenant base domain so the credentials wizard hands out real URLs
instead of `localhost:8069`:

```
admin.kaissflow.com > Settings > Technical > System Parameters
  nacef.tenant_base_domain = kaissflow.com
  nacef.tenant_template    = nacef_tpl
```

`_tenant_login_url()` builds `http://<db>.<domain>/`; if you want the links to
be https, that is a one-word change in `nacef_tenant.py:159`.

## Wiring the pipeline

Repo: `https://gitlab.com/mohamedkhabir/nacef.git`.

1. New Item → Pipeline → "Pipeline script from SCM" → Git → the URL above,
   branch `*/master`, script path `Jenkinsfile`. Name the job `nacef` so the
   webhook URL below matches.
2. Add the repo credential (deploy token or personal access token) in Jenkins
   and select it on the job. Only the job needs it; `/opt/nacef/app` is seeded
   from the workspace, so it never talks to gitlab.com.
3. GitLab → project → Settings → Webhooks → URL
   `https://jenkins.kaissflow.com/project/nacef`, trigger: push events. Paste
   the secret token from the job's GitLab trigger section.

After that: `git push origin master` → Jenkins builds → Build Now is only
needed for a manual redeploy.

Required plugins: `workflow-aggregator`, `git`, `gitlab-plugin`,
`timestamper`, `ansicolor`. The first three are functional; the last two are
referenced by `options`/`triggers` and the Jenkinsfile will not parse without
them.

## What a build does

`Jenkinsfile` in the repo root:

1. **Static checks** — `compileall`, XML well-formedness, manifests parse.
2. **Tests** — every module installed into a throwaway Postgres with
   `--test-enable`. Odoo exits non-zero on failure, which fails the build
   before anything on the live box is touched.
3. **Publish code** — `/opt/nacef/app` is checked out to the exact commit that
   was tested, not to the branch tip.
4. **Deploy** — `deploy/scripts/deploy.sh`: pg_dump every database + the
   filestore, stop the two web containers, run `odoo -u` per database with
   only the nacef modules that database actually has installed, restart, smoke
   check.
5. **Verify** — curl through nginx over TLS.

There is downtime during step 4. Odoo cannot apply a schema update to a
database it is concurrently serving, so this is inherent, not a shortcut. It
scales with tenant count; budget roughly a minute per tenant.

## Rollback

`deploy/scripts/rollback.sh [stamp]` restores the dumps, the filestore, and
the commit. Re-checking-out old code alone would not help: an `odoo -u` has
already migrated the schema forward.

Set the `AUTO_ROLLBACK` build parameter to have a failed deploy do this
by itself.

## Backups

`deploy.sh` writes a pre-deploy backup to `$BACKUP_DIR/<stamp>/`. That is a
deploy safety net, not a backup policy — it only runs when you deploy, and it
sits on the same disk. For fiscal archives (NACEF inalterability) add a
scheduled off-box copy:

```
0 2 * * * /opt/nacef/app/deploy/scripts/deploy.sh --db __backup_only__ ...
```

or simpler, a nightly `pg_dumpall` piped to OVH object storage. Not written
here because it needs a destination you have to choose.

## Resource budget

GitLab is the expensive tenant on this box: ~4 GB resident with the settings
in `ci-compose.yml` (registry, Prometheus, and runners all off). Jenkins is
capped at 2 GB heap, Odoo at 4+3 workers with a 2.5 GB hard limit each. That
leaves headroom in 24 GB, plus the 8 GB swap file `bootstrap-server.sh`
creates. If it gets tight, moving GitLab to gitlab.com and keeping only
Jenkins locally frees the most.
