Files
biztaghavisite/nodecloud-server-context.md
Ali Taghavi 29b70c6865
Some checks failed
Deploy to VPS / deploy (push) Has been cancelled
feat: add admin API routes for tags, timeline events, uploads, and uses management
- Implemented GET and POST endpoints for managing tags in `src/app/api/admin/tags/route.ts`.
- Created PUT and DELETE endpoints for timeline events in `src/app/api/admin/timeline/[id]/route.ts`.
- Added GET and POST endpoints for timeline management in `src/app/api/admin/timeline/route.ts`.
- Developed file upload functionality with validation in `src/app/api/admin/upload/route.ts`.
- Introduced PUT and DELETE endpoints for managing uses items in `src/app/api/admin/uses/[id]/route.ts`.
- Added GET and POST endpoints for uses management in `src/app/api/admin/uses/route.ts`.

feat: enhance admin UI components for better user experience

- Created `AdminSidebar` component for navigation in `src/components/admin/AdminSidebar.tsx`.
- Developed `ImageUpload` component for handling image uploads in `src/components/admin/ImageUpload.tsx`.
- Implemented `RichTextEditor` component for rich text editing in `src/components/admin/RichTextEditor.tsx`.
- Added `TagsInput` component for managing tags in `src/components/admin/TagsInput.tsx`.
- Created `TiptapRenderer` component for rendering HTML content in `src/components/writing/TiptapRenderer.tsx`.

feat: establish database interaction layer with Prisma

- Added database connection and session management in `src/lib/db.ts` and `src/lib/auth.ts`.
- Implemented CRUD operations for posts, products, projects, settings, timeline events, and uses items in respective files under `src/lib/db/`.
- Introduced utility functions for formatting dates and slug generation in `src/lib/types.ts`.
2026-05-03 14:07:58 +03:30

434 lines
16 KiB
Markdown

# NODE-Cloud Server — Master Context for AI Assistants
> Last updated: 2026-04-27
> Owner: Ali Taghavi (@biztaghavi), CEO of NODE-Group (Novin Ofogh Dadeh Etemad)
> Purpose: Paste this into any new Claude/AI chat when working on this server or deploying new apps.
---
## 1. Server Overview
| Field | Value |
|---|---|
| Hostname | `srv9588446167` |
| IP | `193.105.234.35` |
| OS | Ubuntu 24.04.4 LTS |
| Kernel | 6.8.0-107-generic |
| CPU | 4 cores (KVM/QEMU) |
| RAM | 3.8 GB |
| Disk | 145 GB (29 GB used, 116 GB free) |
| Swap | None |
| Virtualization | KVM |
| SSH | Port 22, root access |
| Docker | 28.2.2 with Compose 2.37.1 |
| Nginx | 1.24.0 |
| Certbot | 2.9.0 with auto-renewal timer active |
---
## 2. Iran-Specific Constraints (CRITICAL — Read Before Any Deployment)
This server is hosted in Iran. Several international services are blocked or unreliable:
### 2.1 Docker Images
- **Docker Hub is blocked.** All images must use the ArvanCloud mirror: `docker.arvancloud.ir/library/`
- Examples: `docker.arvancloud.ir/library/node:20-alpine`, `docker.arvancloud.ir/library/mariadb:11`, `docker.arvancloud.ir/library/redis:7-alpine`
- Docker is configured with mirror: `https://docker-mirror.liara.ir/`
### 2.2 npm/Node.js Packages
- **npmjs.org is intermittently blocked.** Use Liara's npm mirror: `https://package-mirror.liara.ir/repository/npm/`
- **npm v10 has a known bug** ("Exit handler never called") — it silently fails and doesn't install packages. Use **pnpm** instead.
- **pnpm installation:** Use `corepack enable && corepack prepare pnpm@9 --activate` with `ENV COREPACK_NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/`
- **Liara mirror rate-limits aggressively** — use `--network-concurrency 1` to avoid 429 errors. If rate-limited, wait 10-30 minutes and retry.
- **Fallback strategy for large projects:** Install `node_modules` on a Mac/dev machine with unrestricted internet, then `scp` the tarball to the server. When doing this:
- Install with `npm install --platform=linux --arch=x64 --libc=musl` to get Linux Alpine binaries
- Also manually install native musl binaries: `@next/swc-linux-x64-musl`, `lightningcss-linux-x64-musl`, `@tailwindcss/oxide-linux-x64-musl`
- Match SWC version to the exact Next.js version (check `package.json`)
- Set `ENV NEXT_SWC_DOWNLOAD_DISABLED=1` in Dockerfile to prevent Next.js from trying to download SWC
### 2.3 Alpine Linux Packages
- **`apk add` is blocked** — `dl-cdn.alpinelinux.org` is unreachable. Never use `apk add` in Dockerfiles. The base `node:20-alpine` image already has what's needed.
### 2.4 Google Services
- **Google Fonts are blocked at build time.** If a project uses `next/font/google`, it must be replaced with `next/font/local` or `@fontsource/*` local packages.
- `fonts.googleapis.com` is unreachable during Docker builds.
### 2.5 Prisma ORM
- **`binaries.prisma.sh` is blocked.** Prisma cannot download its engine binaries during build.
- **Solution:** Pre-download Prisma engine binaries on a machine with internet access, commit them to the repo in `prisma-binaries/`, and set these env vars in the Dockerfile:
```dockerfile
ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-linux-musl-openssl-3.0.x.so.node
ENV PRISMA_SCHEMA_ENGINE_BINARY=/app/prisma-binaries/schema-engine-linux-musl-openssl-3.0.x
ENV PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1
ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1
```
- Both `libquery_engine` and `schema-engine` binaries are needed for `linux-musl-openssl-3.0.x` (Alpine).
- Run `prisma generate` in the Dockerfile after copying binaries — it uses local files, no network needed.
- For migrations, apply SQL files directly: `docker exec -i <mysql_container> mariadb -u user -ppassword dbname < migration.sql`
### 2.6 MySQL
- **MySQL 8.0+ Docker images crash** on this server's CPU (no x86-64-v2 support). Use **MariaDB 11** instead — fully MySQL-compatible.
- MariaDB healthcheck: `["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]`
### 2.7 SSL / Let's Encrypt
- **Let's Encrypt cannot reach this server directly** for domains behind ArvanCloud CDN. Certbot fails with timeouts.
- **For domains behind ArvanCloud CDN:** Use ArvanCloud's built-in SSL (set mode to "Full" or "Flexible"). No certbot needed.
- **For domains pointing directly to the server IP:** Certbot works fine (e.g., pouyatebparto.ir, nodegroup.ir, hooranhr.ir).
- A **self-signed certificate** exists at `/etc/ssl/certs/nodecloud-selfsigned.crt` and `/etc/ssl/private/nodecloud-selfsigned.key` — used for domains behind ArvanCloud where ArvanCloud handles public SSL.
- **IMPORTANT:** Any new site behind ArvanCloud CDN MUST listen on port 443 with the self-signed cert in Nginx. Otherwise Nginx falls back to the first server block that has SSL (which is git.nodecloud.ir/Gitea) and serves wrong content.
---
## 3. Hosted Applications
### 3.1 Gitea (Self-hosted Git)
| Field | Value |
|---|---|
| Domain | `git.nodecloud.ir` |
| Container | `gitea` + `gitea-db` |
| Port | `127.0.0.1:3000` |
| Stack | Gitea 1.25.5 + PostgreSQL 14 |
| Compose | `/srv/nodecloud/gitea/docker-compose.yml` |
| SSL | Self-signed cert (ArvanCloud CDN handles public SSL) |
### 3.2 Pouya Teb Parto (Medical Services)
| Field | Value |
|---|---|
| Domain | `pouyatebparto.ir` |
| Container | `pouya_teb_app` + `pouya_teb_mysql` + `pouya_teb_phpmyadmin` |
| Port | `127.0.0.1:3001` |
| Stack | Next.js 16 + MariaDB 11 + phpMyAdmin |
| Compose | `/srv/nodecloud/apps/pouya-teb-parto/docker-compose.prod.yml` |
| Git | `http://git.nodecloud.ir/pouyadolat/Pouya-teb-parto.git` |
| SSL | Let's Encrypt (certbot) |
| phpMyAdmin | `127.0.0.1:8080` (SSH tunnel only) |
### 3.3 NODE-Group Website
| Field | Value |
|---|---|
| Domain | `nodegroup.ir` |
| Container | `nodegroup_web` |
| Port | `127.0.0.1:3002` |
| Stack | Static Next.js (exported to HTML, served by Nginx in container) |
| Compose | `/srv/nodecloud/apps/nodegroup/docker-compose.prod.yml` |
| Git | `http://git.nodecloud.ir/nodegroup/nodegroupirwebsite.git` |
| SSL | Let's Encrypt (certbot) |
### 3.4 Hooran HR (HR Gamification Platform)
| Field | Value |
|---|---|
| Domain | `hooranhr.ir`, `www.hooranhr.ir`, `hooranhr.com`, `www.hooranhr.com` |
| Container | `hooranhr_app` + `hooranhr_mysql` |
| Port | `127.0.0.1:3003` |
| Stack | Next.js 16 + MariaDB 11 + DISC assessment |
| Compose | `/srv/nodecloud/apps/hooranhr/docker-compose.prod.yml` |
| Git | `http://git.nodecloud.ir/nodegroup/hooranhrir.git` |
| SSL | Let's Encrypt (certbot, covers all 4 domains) |
### 3.5 Khanehban (Building Management SaaS)
| Field | Value |
|---|---|
| Domain | `khanehbaan.ir` |
| Container | `khanehbaan-app-1` + `khanehbaan-mysql-1` + `khanehbaan-redis-1` |
| Port | `127.0.0.1:3008` |
| Stack | Next.js 16 + MariaDB 11 + Redis 7 + Prisma ORM |
| Compose | `/srv/nodecloud/apps/khanehbaan/docker-compose.prod.yml` |
| Git | `http://git.nodecloud.ir/nodegroup/khanehbaan-ir.git` |
| SSL | ArvanCloud CDN (self-signed cert on server, ArvanCloud "Full" mode) |
| S3 Storage | Liara S3 (`storage.c2.liara.space`, bucket: `buildingmanagement`) — remains on Liara |
| Special | Prisma with pre-downloaded binaries, node_modules transferred from Mac |
### 3.6 Portainer (Docker Management UI)
| Field | Value |
|---|---|
| Domain | `manage.nodecloud.ir` |
| Container | `portainer` |
| Port | `127.0.0.1:9000` |
| Stack | Portainer CE LTS |
| Compose | `/srv/nodecloud/apps/portainer/docker-compose.yml` |
| SSL | Self-signed cert (ArvanCloud CDN handles public SSL) |
### 3.7 Nodecloud.ir (Gateway/Placeholder)
| Field | Value |
|---|---|
| Domain | `nodecloud.ir`, `www.nodecloud.ir` |
| Type | Static text response ("nodecloud main gateway is up") |
| Port | 80 only (no SSL, no proxy) |
---
## 4. Port Allocation Map
| Port | Service | Notes |
|---|---|---|
| 3000 | Gitea | Reserved |
| 3001 | Pouya Teb Parto | Next.js app |
| 3002 | NODE-Group website | Static Nginx |
| 3003 | Hooran HR | Next.js app |
| 3004-3007 | Available | — |
| 3008 | Khanehban | Next.js + Prisma |
| 3009+ | Available | — |
| 8080 | phpMyAdmin (Pouya Teb) | SSH tunnel only |
| 9000 | Portainer | Docker management UI |
**Next available port: 3004**
---
## 5. Architecture Pattern for New Deployments
Every app follows this pattern:
```
Internet → ArvanCloud CDN (SSL) → Server:80/443 → Nginx → 127.0.0.1:PORT → Docker Container
```
### 5.1 Directory Structure
```
/srv/nodecloud/
├── gitea/ # Gitea (separate, not under apps/)
│ └── docker-compose.yml
└── apps/
├── pouya-teb-parto/ # Each app gets its own directory
│ ├── docker-compose.prod.yml
│ ├── Dockerfile
│ ├── .env
│ └── ... (source code)
├── nodegroup/
├── hooranhr/
├── khanehbaan/
└── portainer/
```
### 5.2 Standard Dockerfile Template (Next.js + pnpm)
```dockerfile
FROM docker.arvancloud.ir/library/node:20-alpine AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
ENV PATH="/app/node_modules/.bin:$PATH"
ENV COREPACK_NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/
RUN corepack enable && corepack prepare pnpm@9 --activate
COPY package.json ./
RUN pnpm config set registry https://package-mirror.liara.ir/repository/npm/ && \
pnpm install --no-frozen-lockfile --network-concurrency 1
COPY . .
RUN next build
FROM docker.arvancloud.ir/library/node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
```
> **Note:** `next.config` must have `output: "standalone"` for this to work.
### 5.3 Standard docker-compose.prod.yml Template
```yaml
services:
app:
build: .
container_name: APPNAME_app
restart: unless-stopped
ports:
- "127.0.0.1:PORT:3000"
env_file: .env
environment:
NODE_ENV: production
# Only if app needs a database:
mysql:
image: docker.arvancloud.ir/library/mariadb:11
container_name: APPNAME_mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 20
# Only if app needs Redis:
redis:
image: docker.arvancloud.ir/library/redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql_data:
redis_data:
```
### 5.4 Nginx Config Template
**For domains with direct DNS (certbot possible):**
```nginx
server {
listen 80;
listen [::]:80;
server_name DOMAIN.ir www.DOMAIN.ir;
client_max_body_size 20M;
location / {
proxy_pass http://127.0.0.1:PORT;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60;
proxy_send_timeout 60;
}
}
```
Then: `certbot --nginx -d DOMAIN.ir -d www.DOMAIN.ir`
**For domains behind ArvanCloud CDN (MUST include SSL block):**
```nginx
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name DOMAIN.ir www.DOMAIN.ir;
ssl_certificate /etc/ssl/certs/nodecloud-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nodecloud-selfsigned.key;
client_max_body_size 20M;
location / {
proxy_pass http://127.0.0.1:PORT;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60;
proxy_send_timeout 60;
}
}
```
In ArvanCloud: SSL mode = "Full", origin = `193.105.234.35`, purge cache after setup.
---
## 6. Deployment Checklist for New Sites
1. **Clone repo** to `/srv/nodecloud/apps/APPNAME/`
2. **Fix Dockerfile** for Iran: replace `node:20-alpine` with `docker.arvancloud.ir/library/node:20-alpine`, remove any `apk add`, use pnpm with Liara mirror
3. **Fix Google Fonts** if present: grep for `next/font/google` and replace
4. **Create `.env`** from `.env.example` with real credentials
5. **Use MariaDB 11** instead of MySQL 8.x
6. **Set port** in `docker-compose.prod.yml` to next available (currently 3004+)
7. **Build:** `docker compose -f docker-compose.prod.yml build --no-cache`
8. **Launch:** `docker compose -f docker-compose.prod.yml up -d`
9. **Verify:** `curl -s http://127.0.0.1:PORT | head -5`
10. **Run migrations** if needed (apply SQL directly to MariaDB container)
11. **Create Nginx config** in `/etc/nginx/sites-available/`, symlink to `sites-enabled/`
12. **SSL:** Either certbot (direct DNS) or self-signed + ArvanCloud CDN
13. **Test:** `curl -I https://DOMAIN`
---
## 7. Common Operations
### Redeploy an app after code changes
```bash
cd /srv/nodecloud/apps/APPNAME
git pull
# Re-fix Dockerfile if git pull overwrote it
docker compose -f docker-compose.prod.yml build --no-cache
docker compose -f docker-compose.prod.yml up -d
```
### View all container status
```bash
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
```
### View resource usage
```bash
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
```
### View logs for an app
```bash
cd /srv/nodecloud/apps/APPNAME
docker compose -f docker-compose.prod.yml logs -f app
```
### Restart an app
```bash
cd /srv/nodecloud/apps/APPNAME
docker compose -f docker-compose.prod.yml restart
```
### Access phpMyAdmin (Pouya Teb) via SSH tunnel
From your Mac:
```bash
ssh -L 8080:127.0.0.1:8080 root@193.105.234.35
```
Then open `http://localhost:8080`
### Full server health check
```bash
echo "=== CONTAINERS ===" && docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" && echo "" && echo "=== RESOURCES ===" && docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
```
---
## 8. Firewall (UFW)
Only three ports are open:
- **22** (SSH)
- **80** (HTTP)
- **443** (HTTPS)
All Docker container ports are bound to `127.0.0.1` — not publicly accessible. Nginx is the only public-facing service.
---
## 9. Known Issues & Gotchas
1. **git pull overwrites Dockerfile** — After pulling, always re-apply Iran-specific fixes (image mirrors, apk removal, pnpm config). Consider committing a `Dockerfile.prod` to the repo instead.
2. **No swap configured** — With 3.8 GB RAM and ~1.8 GB used, there's ~2 GB headroom. Adding more apps may require adding swap or upgrading RAM.
3. **DNS resolver on server is slow** — `dig` commands sometimes time out. This doesn't affect app operation, only manual DNS lookups from the server.
4. **ArvanCloud CDN caching** — After any Nginx config change, always purge ArvanCloud cache and test in incognito.
5. **Self-signed cert is shared** — `git.nodecloud.ir`, `manage.nodecloud.ir`, and `khanehbaan.ir` all use the same self-signed cert. This is fine because ArvanCloud handles public SSL.
6. **Certbot auto-renewal** is active but will only renew certs for domains with direct DNS (not behind ArvanCloud CDN).
7. **No automated backups** — Docker volumes for databases are not backed up. Consider setting up periodic `mysqldump` cron jobs.
8. **Portainer times out** if admin account isn't created quickly after restart. Run `docker restart portainer` and immediately access the UI.