|
1 | 1 | # Deployment |
2 | 2 |
|
3 | | -- [Automatic Certificate Renewal](./certificate-renewal.md) |
| 3 | +The [Deploy (Manual) workflow](../../.github/workflows/deploy-manual.yml) updates an |
| 4 | +existing installation over SSH. It does **not** provision a new server or start |
| 5 | +PostgreSQL. Follow the bootstrap instructions before using it on a fresh host. |
4 | 6 |
|
5 | | -## Deploying to production |
6 | | -`Deploy to Production` GitHub Action provides a semi-automatic way to deploy new ACC III version, implemented [here](../../.github/workflows/deploy.yml). It connects to the server via SSH and rebuilds the configured containers using `docker compose`. It is triggered on `workflow_dispatch` (i.e., manual trigger). |
| 7 | +## Prepare an Ubuntu LTS host |
7 | 8 |
|
8 | | -You can trigger the deploy workflow by going to *Actions -> Deploy to Production* and choosing which branch and configuration to use during the deployment: |
9 | | - |
| 9 | +These instructions assume an x86-64 Ubuntu LTS server with systemd, the SSH user |
| 10 | +`ubuntu`, and one ACC installation per host. Paths and container names are fixed |
| 11 | +in Compose and the workflow; selecting Development or Production does not isolate |
| 12 | +two installations on one host. ChargeFW2 currently contains x86-64-specific paths. |
10 | 13 |
|
11 | | -### Configurations |
12 | | -- `all` - rebuild everything |
13 | | -- `api` - rebuild only backend (FastAPI) |
14 | | -- `api including chargefw2` - rebuild API container and ChargeFW2 |
15 | | -- `web` - rebuild only frontend |
16 | | -- `nginx` - rebuild only nginx container |
| 14 | +1. Install Git, OpenSSH server, `ca-certificates`, `curl`, `util-linux` (for `flock`), |
| 15 | + `logrotate`, and `certbot` using Ubuntu packages. Install Docker Engine and the |
| 16 | + Compose v2/Buildx plugins using the [official Ubuntu instructions](https://docs.docker.com/engine/install/ubuntu/). |
| 17 | +2. Enable Docker, SSH, and `logrotate.timer`. Grant `ubuntu` Docker access and |
| 18 | + reconnect before testing `docker ps` and `docker compose version`. Docker access |
| 19 | + is effectively root access. The workflow also requires noninteractive `sudo` |
| 20 | + for its Git commands; verify this setup before deployment. |
| 21 | +3. Configure SSH key access for GitHub Actions. Ensure the server can fetch the |
| 22 | + repository and submodule, including when Git is invoked through `sudo`. |
| 23 | +4. Point the configured DNS names at the host. Permit SSH and TCP 80/443; do not |
| 24 | + run a host web server that occupies those ports. |
17 | 25 |
|
18 | | -### Secrets |
19 | | -Secrets are set in *Settings -> Secrets and variables -> Actions*: |
20 | | - |
| 26 | +**Network warning:** the current deployment Compose file also publishes database |
| 27 | +port 5432, API port 8000, and frontend port 3000 on all interfaces. Restrict these |
| 28 | +through the infrastructure firewall or appropriately reviewed Docker-aware rules. |
| 29 | +Do not assume UFW alone blocks Docker-published ports. |
| 30 | + |
| 31 | +Clone the repository to `/home/ubuntu/AtomicChargeCalculator`, then run from that |
| 32 | +checkout (replace the clone URL with the repository URL if creating it): |
| 33 | + |
| 34 | +```bash |
| 35 | +git submodule update --init --recursive |
| 36 | +sudo install -d -m 0755 /home/acc /var/log/acc /var/www/certbot |
| 37 | +sudo install -d -m 0755 -o root -g adm /var/log/acc/nginx |
| 38 | +sudo touch /var/log/acc/nginx/access.log /var/log/acc/nginx/error.log |
| 39 | +sudo chgrp adm /var/log/acc/nginx/access.log /var/log/acc/nginx/error.log |
| 40 | +sudo chmod 0640 /var/log/acc/nginx/access.log /var/log/acc/nginx/error.log |
| 41 | +sudo install -m 0644 -o root -g root src/deployment/nginx/logrotate /etc/logrotate.d/acc-nginx |
| 42 | +sudo systemctl enable --now logrotate.timer |
| 43 | +sudo logrotate --debug /etc/logrotate.d/acc-nginx |
| 44 | +``` |
| 45 | + |
| 46 | +Inspect existing directories before changing permissions on an established host. |
| 47 | +The logrotate debug command does not change files or test the reopen hook. |
| 48 | + |
| 49 | +## Environment and secrets |
| 50 | + |
| 51 | +Configure GitHub environments named **Development** and **Production**, with the |
| 52 | +appropriate deployment secrets: |
| 53 | + |
| 54 | +| Secret | Purpose | |
| 55 | +| --- | --- | |
| 56 | +| `SSH_HOST` | Target server | |
| 57 | +| `SSH_USERNAME` | Deployment user, normally `ubuntu` | |
| 58 | +| `SSH_PRIVATE_KEY` | Key authorized on that server | |
| 59 | +| `SSH_PORT` | Optional; defaults to 22 | |
| 60 | +| `DB_PASSWORD` | PostgreSQL password | |
| 61 | +| `OIDC_CLIENT_ID` | Registered Life Science client | |
| 62 | +| `OIDC_CLIENT_SECRET` | Life Science client secret | |
| 63 | + |
| 64 | +For manual bootstrap, securely export `DB_PASSWORD`, `OIDC_CLIENT_ID`, and |
| 65 | +`OIDC_CLIENT_SECRET` in the shell as well. Do not commit credentials or include |
| 66 | +them in shared terminal output. The workflow does not create a server `.env` file. |
| 67 | +Current workflow exports interpolate secrets into shell commands, and the database |
| 68 | +password is embedded in a connection URL. Shell-special characters and URL-reserved |
| 69 | +characters are not safely supported by that implementation; review compatibility |
| 70 | +before deployment rather than weakening credential strength. |
| 71 | + |
| 72 | +For **Development**, also export: |
| 73 | + |
| 74 | +```bash |
| 75 | +export VITE_BASE_API_URL=https://acc-dev.biodata.ceitec.cz:443/api/v1 |
| 76 | +export OIDC_BASE_URL=https://acc-dev.biodata.ceitec.cz/ |
| 77 | +export OIDC_REDIRECT_URL=https://acc-dev.biodata.ceitec.cz/api/v1/auth/callback |
| 78 | +export NGINX_CONFIG_FILE=nginx.dev.conf |
| 79 | +``` |
| 80 | + |
| 81 | +For **Production**, use: |
| 82 | + |
| 83 | +```bash |
| 84 | +export VITE_BASE_API_URL=https://acc.biodata.ceitec.cz:443/api/v1 |
| 85 | +export OIDC_BASE_URL=https://acc.biodata.ceitec.cz/ |
| 86 | +export OIDC_REDIRECT_URL=https://acc.biodata.ceitec.cz/api/v1/auth/callback |
| 87 | +export NGINX_CONFIG_FILE=nginx.prod.conf |
| 88 | +``` |
| 89 | + |
| 90 | +Register the matching callback with Life Science. The frontend API URL is baked |
| 91 | +into its image at build time. Both deployments use the database name `postgres_prod` |
| 92 | +and user `prod_user` on their respective hosts. Changing `DB_PASSWORD` does not |
| 93 | +change the password in an already initialized PostgreSQL volume. |
| 94 | + |
| 95 | +## First deployment |
| 96 | + |
| 97 | +Obtain [initial TLS certificates](./certificate-renewal.md) before starting Nginx. |
| 98 | +The production configuration needs a certificate covering both public hostnames. |
| 99 | + |
| 100 | +From the repository root, with the environment above set: |
| 101 | + |
| 102 | +```bash |
| 103 | +docker build -t chargefw2-base:acc -f ChargeFW2/Dockerfile ChargeFW2 |
| 104 | +cd src/deployment |
| 105 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml pull nginx |
| 106 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d db |
| 107 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml ps db |
| 108 | +``` |
| 109 | + |
| 110 | +Wait until PostgreSQL is healthy, then build and start the application: |
| 111 | + |
| 112 | +```bash |
| 113 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build api web |
| 114 | +docker logs --tail 100 acc-api |
| 115 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm --no-deps --pull never nginx nginx -t |
| 116 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps --pull never nginx |
| 117 | +``` |
| 118 | + |
| 119 | +Check API startup logs for successful database migrations and application startup; |
| 120 | +a running container alone does not establish that migrations succeeded. Verify |
| 121 | +HTTPS, API calls, example viewing, and login. Complete certificate renewal setup. |
| 122 | + |
| 123 | +Use **both** Compose files on hosted dev and production. Bare `docker compose` |
| 124 | +loads the local-development override. Nginx preflight needs existing certificates, |
| 125 | +host log setup, a locally available Nginx image, and running/resolvable `api` and |
| 126 | +`web` services. Consequently, selecting every GitHub deployment option on an empty |
| 127 | +host is not a substitute for this bootstrap. |
| 128 | + |
| 129 | +## Subsequent GitHub deployments |
| 130 | + |
| 131 | +Open **Actions > Deploy (Manual) > Run workflow**. Select the target environment, |
| 132 | +the workflow revision, the branch to deploy, and the required checkboxes: |
| 133 | + |
| 134 | +- **Rebuild ChargeFW2:** builds the base image; also select **Deploy API** to use it. |
| 135 | +- **Deploy API:** builds and recreates the API container. |
| 136 | +- **Deploy Web:** builds and recreates the frontend container. |
| 137 | +- **Deploy Nginx:** checks logging prerequisites and tests the candidate Nginx |
| 138 | + configuration before service updates, then recreates Nginx. |
| 139 | + |
| 140 | +All checkboxes default to false. The [deployment logic](../../.github/workflows/deploy-logic.yml) |
| 141 | +resets the server checkout to the selected branch; do not keep uncommitted work |
| 142 | +there. A host lock rejects overlapping workflow deployments. Deployments are not |
| 143 | +zero-downtime and API recreation can interrupt jobs. Use a maintenance window when |
| 144 | +necessary. Nginx uses `--pull never`; explicitly pull a chosen image before deploying |
| 145 | +an image update. Restarting a container does not apply changed Compose mounts. |
| 146 | + |
| 147 | +## Logging maintenance |
| 148 | + |
| 149 | +After deployment, verify that website requests appear as JSON in |
| 150 | +`/var/log/acc/nginx/access.log`. Check that `logrotate.timer` remains active and |
| 151 | +monitor available disk space. To test rotation, use the development server: |
| 152 | + |
| 153 | +```bash |
| 154 | +sudo logrotate --force /etc/logrotate.d/acc-nginx |
| 155 | +``` |
| 156 | + |
| 157 | +Make another request and confirm it appears in the replacement `access.log`. |
| 158 | +This exercises the log reopen hook without restarting Nginx. Normal rotation is |
| 159 | +scheduled automatically; forced rotation is only a verification step. |
| 160 | + |
| 161 | +## Persistence and retention |
| 162 | + |
| 163 | +- Nginx access/error logs live in host `/var/log/acc/nginx`; monthly rotation keeps |
| 164 | + 24 archives and compresses older files. Empty files are skipped. This is not a |
| 165 | + hard size limit; monitor disk space and copy historical logs off-server. |
| 166 | +- Nginx stdout/stderr remains available through `docker logs`, bounded by Docker's |
| 167 | + configured rotation. These diagnostics are removed with the container; |
| 168 | + persistent access/error files are separate from Docker logs. |
| 169 | +- API logs remain in host `/var/log/acc/logs.log`; the Nginx rotation rule does not |
| 170 | + rotate that file. Calculation data remains under host `/home/acc`. |
| 171 | +- PostgreSQL uses named volume `deployment_db-data` with the default project name. |
| 172 | + Never use `docker compose down -v` or remove that volume to perform an update. |
| 173 | + |
| 174 | +Host logs and volumes survive container recreation, not host loss. Arrange and test |
| 175 | +off-server backups separately. |
| 176 | + |
| 177 | +## Usage reports |
| 178 | + |
| 179 | +Generate a local report over SSH; the tool does not change the server. Examples: |
| 180 | + |
| 181 | +```bash |
| 182 | +python3 utils/usage_stats.py --server prod --year 2026 --output /tmp/acc-2026 |
| 183 | +python3 utils/usage_stats.py --server prod --database --start-date 2026-07-01 --end-date 2026-10-01 --output /tmp/acc-q3 |
| 184 | +``` |
| 185 | + |
| 186 | +Use `--year` for a calendar year, or `--start-date` and exclusive `--end-date` for a |
| 187 | +UTC interval. Add `--database` for method, molecules-per-submission, and molecule-size |
| 188 | +summaries. The report is useful for checking web traffic, calculation activity, or the |
| 189 | +mix of small and large inputs. Database dates mean record creation; method counts do |
| 190 | +not distinguish cached results from new calculations. Large-input categories are not |
| 191 | +chemical classifications, and actual computation runtimes are not recorded. |
| 192 | +Nginx results distinguish all IPs from IPs on requests not flagged by the known-bot |
| 193 | +heuristic; calculation submissions are shown separately. API logs lack user agents, |
| 194 | +so bot-excluded API counts are unavailable. |
| 195 | +New frontend requests carry a `web-app` marker; reports separate marked, unmarked, |
| 196 | +and older requests with no marker. Unmarked does not necessarily mean scripted. |
| 197 | + |
| 198 | +Reports contain `report.txt`, `report.json`, and `monthly.csv`; raw IP addresses are |
| 199 | +never included. Choose a new output directory for each run. See `--help` for offline |
| 200 | +log inputs and other options. |
0 commit comments