Skip to content

Commit af10ba9

Browse files
authored
Merge pull request #55 from sb-ncbr/dev
Update deployment
2 parents 74b1c28 + 3e46203 commit af10ba9

18 files changed

Lines changed: 967 additions & 217 deletions

‎.github/workflows/deploy-logic.yml‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ jobs:
3434
deploy:
3535
name: Deploy Logic
3636
runs-on: ubuntu-latest
37+
concurrency:
38+
group: deploy-${{ inputs.target_environment }}
39+
cancel-in-progress: false
3740
environment:
3841
name: ${{ inputs.target_environment }}
3942

@@ -43,15 +46,24 @@ jobs:
4346

4447
- name: Deploy via SSH
4548
uses: appleboy/ssh-action@v1
49+
env:
50+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
51+
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
52+
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
4653
with:
4754
host: ${{ secrets.SSH_HOST }}
4855
username: ${{ secrets.SSH_USERNAME }}
4956
key: ${{ secrets.SSH_PRIVATE_KEY }}
5057
port: ${{ secrets.SSH_PORT || 22 }}
5158
command_timeout: 60m
59+
envs: DB_PASSWORD,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET
5260
script: |
5361
set -e
5462
63+
# Serialize checkout changes and container/log migration on this host.
64+
exec 9>"$HOME/.acc-deploy.lock"
65+
flock -n 9 || { echo "Another ACC deployment is running."; exit 1; }
66+
5567
BRANCH_NAME="${{ inputs.branch_name }}"
5668
5769
PROJECT_ROOT="/home/ubuntu/AtomicChargeCalculator"
@@ -61,10 +73,6 @@ jobs:
6173
echo "Deployment Branch: [$BRANCH_NAME]"
6274
6375
echo "Setting up environment variables ..."
64-
export DB_PASSWORD=${{ secrets.DB_PASSWORD }}
65-
66-
export OIDC_CLIENT_ID=${{ secrets.OIDC_CLIENT_ID }}
67-
export OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }}
6876
6977
if [ "${{ inputs.target_environment }}" = "Production" ]; then
7078
export VITE_BASE_API_URL="https://acc.biodata.ceitec.cz:443/api/v1"
@@ -89,13 +97,23 @@ jobs:
8997
fi
9098
sudo git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
9199
sudo git reset --hard "origin/$BRANCH_NAME"
92-
sudo git submodule update --recursive
100+
sudo git submodule update --init --recursive
93101
94102
cd $DEPLOY_DIR
95103
echo "Changed directory to $(pwd)"
96104
97105
COMPOSE_FILES="-f docker-compose.yml -f docker-compose.prod.yml"
98106
107+
if [ "${{ inputs.deploy_nginx }}" == "true" ]; then
108+
# One-time host setup and legacy log backup must precede migration.
109+
if [ ! -d /var/log/acc/nginx ] || [ ! -f /etc/logrotate.d/acc-nginx ]; then
110+
echo "Prepare /var/log/acc/nginx and install nginx/logrotate on the host before deploying."
111+
exit 1
112+
fi
113+
systemctl is-active --quiet logrotate.timer
114+
docker compose $COMPOSE_FILES run --rm --no-deps --pull always nginx nginx -t
115+
fi
116+
99117
if [ "${{ inputs.deploy_chargefw2 }}" == "true" ]; then
100118
echo "Deploying ChargeFW2 Base..."
101119
docker build -t chargefw2-base:acc -f $CHARGEFW2_DIR/Dockerfile $CHARGEFW2_DIR --no-cache
@@ -114,7 +132,7 @@ jobs:
114132
115133
if [ "${{ inputs.deploy_nginx }}" == "true" ]; then
116134
echo "Deploying Nginx..."
117-
docker compose $COMPOSE_FILES up -d --no-deps --build --force-recreate nginx
135+
docker compose $COMPOSE_FILES up -d --no-deps --pull never --force-recreate nginx
118136
fi
119137
120138
echo "Pruning unused images..."

‎.github/workflows/frontend.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ jobs:
1919
uses: actions/checkout@v5
2020

2121
- name: Setup Node.js
22-
uses: actions/setup-node@v4
22+
uses: actions/setup-node@v7
2323
with:
2424
node-version: '26.10.0'
2525

2626
- name: Setup pnpm
27-
uses: pnpm/action-setup@v4
27+
uses: pnpm/action-setup@v6
2828
with:
2929
version: 12.8.1
3030

‎.gitmodules‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[submodule "ChargeFW2"]
22
path = ChargeFW2
3-
url = https://github.com/sb-ncbr/ChargeFW2.git
3+
url = git@github.com:sb-ncbr/ChargeFW2.git

‎ChargeFW2‎

‎docs/deployment/README.md‎

Lines changed: 194 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,200 @@
11
# Deployment
22

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.
46

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
78

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-
![deploy trigger](./images/deploy-trigger.png)
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.
1013

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.
1725

18-
### Secrets
19-
Secrets are set in *Settings -> Secrets and variables -> Actions*:
20-
![secrets](./images/secrets.png)
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

Comments
 (0)