diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100644 index 0000000..4b4b1ef --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Runs on `git push`. If a tag ref is among what's being pushed, build locally +# and deploy to the server. Normal branch pushes are untouched. +set -euo pipefail + +deploy=0 +tag="" +while read -r local_ref _local_sha _remote_ref _remote_sha; do + case "$local_ref" in + refs/tags/*) + deploy=1 + tag="${local_ref#refs/tags/}" + ;; + esac +done + +if [ "$deploy" = "1" ]; then + echo "→ Tag '$tag' is being pushed — building locally and deploying to the server…" + exec "$(git rev-parse --show-toplevel)/scripts/deploy.sh" +fi + +exit 0 diff --git a/.gitignore b/.gitignore index 9a0092d..ae95c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# local deploy secrets +.env.deploy diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..9f04f94 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,60 @@ +# Deployment + +The site is a **static export**. It is built **on your machine** and the result +(`out/`) is copied to the server — nothing is built on the server. + +- Server: `185.226.116.88`, user `ubuntu` +- Target dir: `/var/www/aramland-admin` (its contents are fully replaced each deploy) + +## Deploy by pushing a git tag (automatic) + +A versioned git hook ([`.githooks/pre-push`](.githooks/pre-push)) runs the local +build + upload whenever you push a **tag**: + +```bash +git tag v1.0.0 +git push origin v1.0.0 # ← hook builds locally, then uploads to the server +``` + +Normal branch pushes are unaffected — only tag pushes deploy. + +> The hook path is set via `git config core.hooksPath .githooks` (already configured +> in this clone). On a fresh clone, run that once. + +## Deploy manually (no tag) + +```bash +npm run deploy # = bash scripts/deploy.sh : build locally + upload +``` + +## The password + +Both paths use [`scripts/deploy.sh`](scripts/deploy.sh), which streams `out/` over a +single SSH connection. By default **SSH asks for the server password once** per deploy. + +To make it **unattended** (no prompt), provide the password without committing it: + +1. Create `.env.deploy` in the project root (already gitignored): + ```bash + DEPLOY_PASSWORD=your-server-password + ``` +2. Install `sshpass` (the only piece that can feed a password to SSH non-interactively): + - Linux/WSL: `sudo apt-get install -y sshpass` + - macOS: `brew install hudochenkov/sshpass/sshpass` + - Windows Git Bash has no sshpass — either deploy from WSL, or just answer the one + password prompt. + +Override the host/user/path too if needed (env or `.env.deploy`): +`DEPLOY_HOST`, `DEPLOY_USER`, `DEPLOY_PATH`. + +> **More robust option:** set up an SSH **key** (`ssh-copy-id ubuntu@185.226.116.88`). +> Then deploys are unattended on any OS with no password or sshpass at all. + +## Notes / troubleshooting + +- **Permission denied** writing the target: the `ubuntu` user must own it → + on the server run `sudo chown -R ubuntu:ubuntu /var/www/aramland-admin`. +- **Web server:** point nginx's site root at `/var/www/aramland-admin`. The export uses + `trailingSlash: true`, so clean URLs resolve to `…/index.html`. +- The deploy replaces the directory **contents** (including dotfiles) in place; it does + not touch nginx config or sibling folders. diff --git a/app/dashboard/media/page.tsx b/app/dashboard/media/page.tsx index 26c5840..f08c2ce 100644 --- a/app/dashboard/media/page.tsx +++ b/app/dashboard/media/page.tsx @@ -10,6 +10,7 @@ import { ConfirmDialog, Field, Input, + Textarea, Select, Switch, Modal, @@ -339,7 +340,7 @@ export default function MediaPage() { - setCaption(e.target.value)} placeholder="توضیح کوتاه" diff --git a/package.json b/package.json index a39c31d..ab303b6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "deploy": "bash scripts/deploy.sh" }, "dependencies": { "next": "16.2.7", diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..3a56be6 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Build the static site locally, then replace the server directory with it. +# +# - Builds on THIS machine (npm run build -> ./out). +# - Streams out/ over a single SSH connection (tar pipe) and swaps the +# contents of the target dir on the server. No rsync/sshpass required. +# +# Password handling: +# - If `sshpass` is installed AND DEPLOY_PASSWORD is set (env or .env.deploy), +# the deploy is fully unattended. +# - Otherwise SSH prompts for the password once (interactive). +# +# Config (override via env or .env.deploy): +set -euo pipefail +cd "$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(dirname "$0")")" + +# Optional local secrets file (gitignored). +[ -f .env.deploy ] && . ./.env.deploy + +DEPLOY_HOST="${DEPLOY_HOST:-185.226.116.88}" +DEPLOY_USER="${DEPLOY_USER:-ubuntu}" +DEPLOY_PATH="${DEPLOY_PATH:-/var/www/aramland-admin}" +DEPLOY_PASSWORD="${DEPLOY_PASSWORD:-}" + +echo "▸ Building locally…" +npm run build + +if [ ! -d out ]; then + echo "✗ Build did not produce ./out" >&2 + exit 1 +fi + +# Pick the SSH command: unattended with sshpass, else interactive prompt. +ssh_cmd=(ssh -o StrictHostKeyChecking=no "$DEPLOY_USER@$DEPLOY_HOST") +if command -v sshpass >/dev/null 2>&1 && [ -n "$DEPLOY_PASSWORD" ]; then + ssh_cmd=(sshpass -p "$DEPLOY_PASSWORD" "${ssh_cmd[@]}") +else + echo "ℹ sshpass/password not available — SSH will prompt for the password." +fi + +echo "▸ Uploading to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH …" +# Replace the directory contents (incl. dotfiles) then extract the new build. +tar -C out -czf - . | "${ssh_cmd[@]}" \ + "set -e; mkdir -p '$DEPLOY_PATH'; find '$DEPLOY_PATH' -mindepth 1 -delete; tar -C '$DEPLOY_PATH' -xzf -" + +echo "✓ Deployed to $DEPLOY_HOST:$DEPLOY_PATH"