rewrite URL shortener in Rust

This commit is contained in:
2026-07-10 04:46:34 +00:00
parent a250ce4c1d
commit a6e8be7553
16 changed files with 2906 additions and 1102 deletions
+140
View File
@@ -0,0 +1,140 @@
# Production migration and rollback
This runbook moves the titan deployment from the Python source tree at
`/root/repo/urlshortener` to the published `sodium/ushort` image. The final
server-side application directory is `/root/compose/ushort`; no source checkout
is required on titan.
Do not cut over until the exact image tag has been built for both `linux/amd64`
and `linux/arm64`, pushed, and recorded by digest.
## 1. Prepare without affecting production
Create this layout on titan:
```text
/root/compose/ushort/
├── docker-compose.yml
├── config.toml
└── data/
```
Copy `deploy/docker-compose.yml` as the Compose file. Convert the live JSON
settings to TOML using `config.example.toml` as the reference, with these
cutover-specific rules:
- keep the live `base_url`, limits, retention, and rate-limit values;
- set `db_path = "data/urlshort.db"`;
- set `production = false` so the binary serves its embedded frontend; and
- generate a new API key, because the old key existed in the legacy Git
history.
Keep `config.toml` owned by `1001:1001` with mode `0400`, matching the
container identity. Do not copy it into Git or a container image.
Validate the deployment file before stopping anything:
```bash
cd /root/compose/ushort
chown 1001:1001 config.toml
chmod 0400 config.toml
docker-compose config
docker pull sodium/ushort:0.1.0
```
## 2. Take a consistent database copy
The final copy must be made while the Python service is stopped so no committed
row is missed and no SQLite journal is in flight:
```bash
cd /root/repo/urlshortener
docker-compose stop urlshort
cp -a data/urlshort.db /root/compose/ushort/data/urlshort.db
cp -a data/urlshort.db /root/compose/ushort/data/urlshort.db.pre-rust
chown -R 1001:1001 /root/compose/ushort/data
chmod 0750 /root/compose/ushort/data
chmod 0640 /root/compose/ushort/data/urlshort.db*
```
Do not delete or alter the original database during the initial soak period.
## 3. Start and validate ushort locally
```bash
cd /root/compose/ushort
docker-compose up -d
docker-compose ps
docker-compose logs --tail=100 ushort
curl -i http://127.0.0.1:18082/s/api/health
curl -I http://127.0.0.1:18082/s/
```
Expected results are HTTP 200 health JSON and HTTP 200 HTML. Confirm the
container does not restart and the database remains writable.
## 4. Switch nginx
Replace the old `include /root/repo/urlshortener/nginx.conf;` in the `xcel.me`
vhost with the location blocks from the new `nginx.conf`. Those blocks proxy
the frontend as well as API/redirect traffic to `127.0.0.1:18082`; they do not
refer to a source or static-file directory.
Test before reload:
```bash
docker exec nginx nginx -t
docker exec nginx nginx -s reload
```
Then validate through the public endpoint:
```bash
curl -i https://xcel.me/s/api/health
curl -I https://xcel.me/s/
curl -I https://xcel.me/s/static/app.js
```
Check that `/s/` is `no-store`, static assets are `no-cache`, an existing short
code still returns the original 302 target, and its visit count increments once.
## 5. Soak and clean up
During the soak period, monitor:
```bash
cd /root/compose/ushort
docker-compose ps
docker-compose logs --tail=200 ushort
```
After the rollback window closes:
1. retain a protected database backup;
2. remove the obsolete `/root/repo/urlshortener` source tree;
3. remove its `/srv/urlshortener` bind mount from the global nginx Compose
file; and
4. recreate nginx and re-run `nginx -t`.
At that point, application source exists only in Gitea, while titan contains
only the Compose file, TOML config, and data under `/root/compose/ushort`.
## Rollback
If validation fails before public traffic is enabled:
```bash
cd /root/compose/ushort
docker-compose down
cd /root/repo/urlshortener
docker-compose start urlshort
```
Restore the old nginx include and reload nginx.
If public writes occurred after cutover, stop ushort first and copy its current
database back to the legacy data path before starting Python; otherwise those
new short URLs would be lost. The schema is deliberately identical, so no
reverse schema migration is required.