Some checks failed
Deploy to VPS / deploy (push) Has been cancelled
- 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`.
434 lines
16 KiB
Markdown
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.
|