# Dyolink

Monorepo: **NestJS** backend (`backend/`), **Next.js** frontend (`frontend/`), **Docker** stack under `infrastructure/`..

Local development: see **`backend/README.md`** and **`frontend/README.md`**.

**Cursor AI:** project conventions for agents are in [`AGENTS.md`](AGENTS.md), [`.cursor/rules/`](.cursor/rules/), and [`.cursor/skills/`](.cursor/skills/).

---

## Production deploy (Docker Hub + HTTPS + Let's Encrypt)

**Full step-by-step guide:** [`infrastructure/DEPLOY.md`](infrastructure/DEPLOY.md)

Minimal server setup: install Docker, create `.env` + `secrets/`, `docker login`, run one script.

| On server (once) | In repo / Docker |
|------------------|------------------|
| DNS A record → server IP | `docker-compose.prod.yml`, nginx, certbot |
| `docker login` (private Hub) | Build & push images from dev machine |
| `secrets/database.env`, `secrets/backend.env` | Examples: `database.prod.env.example`, `backend.prod.env.example` |
| `infrastructure/.env` (`DOMAIN`, `LETSENCRYPT_EMAIL`) | `deploy.prod.env.example` |

**Dev machine** — build frontend with the public domain baked in, push to Docker Hub:

```bash
./infrastructure/scripts/build-and-push-prod.sh wixur.ir latest
```

**Server** — from `infrastructure/`:

```bash
cp deploy.prod.env.example .env          # edit DOMAIN, paths
mkdir -p ../secrets && cp database.prod.env.example ../secrets/database.env
cp backend.prod.env.example ../secrets/backend.env   # set passwords + FRONTEND_URL
docker login
chmod +x scripts/*.sh
./scripts/deploy-prod.sh
```

SSL is issued automatically via **Certbot** (`scripts/init-letsencrypt.sh`). Nginx config is generated from `DOMAIN` in `.env`. When you move to another domain (e.g. `dyolink.com`), update `.env` + `backend.env`, re-run `init-letsencrypt.sh`, and **rebuild the frontend image** with the new URL.

---

## Deploy on your own server (Docker + Gitea)

High level: **build container images → push to a registry → server pulls images and runs Compose**. Optionally **Gitea Actions** automates that on every merge to `main` / `master`.

### 1. One-time server preparation

1. Install **Docker** and **Docker Compose** on the server.
2. Run **Gitea** with the **container registry** enabled (same host/port you use for `docker login`, e.g. `178.131.50.201:3000`).
3. Copy the repo (or deploy only `infrastructure/` + secrets). You need at least:

   - `infrastructure/docker-compose.registry.yml`
   - `infrastructure/nginx/` configs referenced by that compose file
   - `infrastructure/database/init.sql` if used by your Postgres service

4. **Secrets on the server** (never commit real values):

   - Copy `infrastructure/database.staging.env.example` → **`database.staging.env`** (Postgres user/password/db).
   - Copy `infrastructure/backend.staging.env.example` → **`backend.staging.env`** (e.g. `DATABASE_URL`, JWT, pointing at the compose Postgres service name).
   - Put both files in one directory on the server, e.g. `/opt/dyolink/secrets/`.

5. **Registry login from the server** (same credentials you use for `docker push`):

   ```bash
   docker login <registry-host>:<port> -u <user>
   ```

   For HTTP registries, Docker may require **`insecure-registries`** on the daemon.

### 2. Manual deploy (build images elsewhere, run on server)

On your **dev machine** (after successful local builds):

```powershell
$REG = "<registry-host>:<port>"
$OWN = "<registry-owner>"
$TAG = "manual"

docker build -t "${REG}/${OWN}/dyolink-backend:${TAG}" -t "${REG}/${OWN}/dyolink-backend:latest" ./backend

docker build `
  --build-arg NEXT_PUBLIC_API_URL="http://<your-public-ip>:<nginx-port>/api" `
  --build-arg NEXT_PUBLIC_APP_URL="http://<your-public-ip>:<nginx-port>" `
  --build-arg NEXT_PUBLIC_APP_NAME="Dyolink" `
  -t "${REG}/${OWN}/dyolink-frontend:${TAG}" `
  -t "${REG}/${OWN}/dyolink-frontend:latest" `
  ./frontend

docker push "${REG}/${OWN}/dyolink-backend:${TAG}"
docker push "${REG}/${OWN}/dyolink-backend:latest"
docker push "${REG}/${OWN}/dyolink-frontend:${TAG}"
docker push "${REG}/${OWN}/dyolink-frontend:latest"
```

On the **server**, from `infrastructure/`:

1. Create **`deploy.registry.env`** (see `infrastructure/deploy.registry.env.example`):

   - `REGISTRY_PREFIX=<host>:<port>/<owner>` (no `http://`, no trailing slash)
   - `IMAGE_TAG=latest` or the tag you pushed
   - `STAGING_HTTP_PORT=<host port>` (e.g. `8088` — browser uses `http://<ip>:8088`)

2. Set **`DEPLOY_SECRETS_DIR`** to the absolute path of the folder containing `database.staging.env` and `backend.staging.env` (you can export it in the shell or add it to `deploy.registry.env` if your Compose setup expects it).

3. Pull and start:

   ```bash
   docker compose -f docker-compose.registry.yml --env-file deploy.registry.env pull backend frontend
   docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d
   ```

The backend container runs **`prisma migrate deploy`** on startup (via entrypoint) when `NODE_ENV=production`, so schema updates apply after you deploy a new image that includes new migrations.

### 3. Automatic deploy (Gitea Actions)

Workflow file: **`.gitea/workflows/registry-build-deploy.yml`**.

**Requirements:**

- **Gitea Actions** enabled for the repository.
- A **self-hosted runner** (with Docker) registered to Gitea — the workflow uses `runs-on: self-hosted`.
- **Windows runners:** the workflow uses **PowerShell** (not Bash). Gitea’s runner was failing with `execvpe(/bin/bash) failed` when Bash was routed through WSL without a real `/bin/bash`. If your runner is **Linux**, switch `.gitea/workflows/registry-build-deploy.yml` to `defaults.run.shell: bash` and use Bash syntax instead.
- **Repository → Actions → Variables** (examples):

  - `REGISTRY_HOST` — e.g. `178.131.50.201:3000`
  - `REGISTRY_OWNER` — image namespace (same as Docker image path after the host), e.g. `admin`
  - `PUBLIC_BASE_URL` — URL users open in the browser, e.g. `http://178.131.50.201:8088` (no trailing slash)
  - `DEPLOY_SECRETS_DIR` — **absolute path on the runner machine** to the folder containing `database.staging.env` and `backend.staging.env`
  - Optional: `STAGING_HTTP_PORT` (defaults to `8088`)

- **Repository → Actions → Secrets:**

  - `REGISTRY_USERNAME`
  - `REGISTRY_PASSWORD` — access token with package read/write (or equivalent)

**Trigger:** push to **`main`** or **`master`**, or run the workflow manually (**workflow_dispatch**).

The pipeline clones from your Gitea instance, builds and pushes backend/frontend images, then on the runner runs **`docker compose pull`** and **`up -d`** using `infrastructure/docker-compose.registry.yml`.

---

## Related paths

| Path | Role |
|------|------|
| `backend/Dockerfile` | API image |
| `frontend/Dockerfile` | Web image |
| `infrastructure/docker-compose.registry.yml` | Pull-only staging stack (registry images + nginx + postgres) |
| `infrastructure/deploy.registry.env.example` | Template for `deploy.registry.env` |
