SuperAPI AI Directory

server-deploy

Deployment & operations

View repository

Updated 10-16 09:59

About this skill

General Server Deployment

Deploy a local project to a remote Linux server with one click. Automatically identifies the project type and adapts the corresponding build, run, and process management solution.


Step Zero: Project Identification and Information Collection

0a. Automatically Identify Project Type

Scan the project root directory and determine the type according to the following rules:

Marker File Project Type Runtime Process Management
package.json Node.js node PM2
Cargo.toml Rust Build Artifact systemd
go.mod Go Build Artifact systemd
pyproject.toml / requirements.txt / setup.py Python python3 systemd / gunicorn
Dockerfile / docker-compose.yml Docker docker docker compose
index.html(no other markers) Static Site None Hosted directly by Nginx

If multiple marker files are detected (such as package.json + Dockerfile), use AskUserQuestion to let the user choose the deployment method.

0b. Collect Information

Use AskUserQuestion to collect the following information (skip what is already in the context):

  1. SSH Connection(header: "Server")

    • Dynamically generate options based on the context (such as previously used server IPs)
    • Fallback option "Other Server"
  2. Project Path(header: "Project")

    • Automatically infer based on the current working directory
    • If uncertain, ask the user
  3. Domain(header: "Domain")

    • Existing domain (let the user enter it)
    • Access by IP only (skip domain and SSL configuration)
  4. Running Port(header: "Port")

    • Automatic detection:
      • Node.js: extract from package.json scripts or .env
      • Python: extract from main.py / app.py / manage.py
      • Rust/Go: extract from main.rs / main.go or configuration files
    • If it cannot be detected, let the user specify
    • Do not use 3000, 3001, 4000, 5000, 5173, 8000, 8080, 8888

0c. Variable Definitions

Variable Description
$SSH_TARGET SSH connection string (such as root@192.168.1.1)
$PROJECT_DIR Absolute path of the local project
$PROJECT_NAME Project name
$PROJECT_TYPE Project type:nodejs / python / rust / go / docker / static
$REMOTE_DIR Remote deployment directory (default /opt/$PROJECT_NAME)
$PORT Application running port
$DOMAIN Domain (optional)

Step One: SSH Connection and Key Configuration

1a. Check the Local SSH Public Key

cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_ed25519.pub 2>/dev/null

If there is no public key, generate one:ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519

1b. Test Passwordless Login

ssh -o ConnectTimeout=5 -o BatchMode=yes $SSH_TARGET "echo ok" 2>/dev/null

If it fails, log in with a password and add the public key:

sshpass -p '<PASSWORD>' ssh -o StrictHostKeyChecking=no $SSH_TARGET \
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '<PUBLIC_KEY>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

1c. Verify Passwordless Login

ssh -o ConnectTimeout=5 $SSH_TARGET "echo '免密登录成功' && uname -a"

Step Two: Server Environment Check

Get all information with a single command:

ssh $SSH_TARGET "echo '=== 系统 ===' && cat /etc/os-release | grep PRETTY_NAME && uname -m && echo '=== CPU ===' && nproc && echo '=== 内存 ===' && free -h && echo '=== 磁盘 ===' && df -h / && echo '=== 包管理器 ===' && which apt yum dnf pacman 2>/dev/null && echo '=== Node.js ===' && node -v 2>/dev/null || echo '未安装' && echo '=== Python ===' && python3 --version 2>/dev/null || echo '未安装' && echo '=== Rust ===' && rustc --version 2>/dev/null || echo '未安装' && echo '=== Go ===' && go version 2>/dev/null || echo '未安装' && echo '=== Docker ===' && docker --version 2>/dev/null || echo '未安装' && echo '=== Nginx ===' && nginx -v 2>&1 || echo '未安装'"

Check Points:

  • Whether disk space is sufficient
  • Memory: Node.js ≥ 512MB, Rust compilation ≥ 1GB (or local cross-compilation)
  • Architecture:x86_64 / aarch64(Rust/Go must match the compilation target)
  • Record missing components for unified installation later

Step 3: Install the runtime environment

Based on $PROJECT_TYPE , install the corresponding runtime. Only install missing components.

Node.js project

# 安装 Node.js 20(如未安装)
ssh $SSH_TARGET "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs"
# 安装 PM2
ssh $SSH_TARGET "npm install -g pm2"

Python project

# 安装 Python3 + pip + venv(如未安装)
ssh $SSH_TARGET "apt-get install -y python3 python3-pip python3-venv"

Rust project

Two options, choose based on server resources:

Option A: Compile on the server (memory ≥ 1GB)

ssh $SSH_TARGET "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source ~/.cargo/env"

Option B: Local cross-compilation (recommended for low-spec servers)

# 本地编译(需安装对应 target)
rustup target add x86_64-unknown-linux-gnu  # 或 aarch64-unknown-linux-gnu
cargo build --release --target x86_64-unknown-linux-gnu
# 仅上传编译产物

Go project

Also two options:

Option A: Compile on the server

ssh $SSH_TARGET "wget -q https://go.dev/dl/go1.22.0.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz && echo 'export PATH=\$PATH:/usr/local/go/bin' >> ~/.bashrc"

Option B: Local cross-compilation (recommended)

GOOS=linux GOARCH=amd64 go build -o $PROJECT_NAME .
# 仅上传二进制文件

Docker project

# 安装 Docker(如未安装)
ssh $SSH_TARGET "curl -fsSL https://get.docker.com | sh && systemctl enable docker && systemctl start docker"

Nginx (required when there is a domain name)

ssh $SSH_TARGET "apt-get install -y nginx && systemctl enable nginx && systemctl start nginx"

Step 4: Upload the project

4a. Exclusions (by project type)

Project type Excluded directories/files
Node.js node_modules/, .next/, dist/, .git/
Python __pycache__/, .venv/, venv/, *.pyc, .git/
Rust target/, .git/(Option B only uploads target/release/$BINARY)
Go vendor/(if any), .git/(Option B only uploads the binary)
Docker .git/(Dockerfile needs to be uploaded)
Static site .git/, node_modules/

4b. rsync upload

Note: SSH may disconnect during large file transfers. Use keepalive parameters:

rsync -avz --timeout=300 \
  -e "ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10" \
  --exclude='.git' --exclude='<TYPE_SPECIFIC_EXCLUDES>' \
  $PROJECT_DIR/ $SSH_TARGET:$REMOTE_DIR/

If the file is very large (>500MB): upload the code first, then upload the large file separately.

If SSH frequently disconnects: adjust the server SSH configuration:

ssh $SSH_TARGET "sed -i 's/#ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config && sed -i 's/#ClientAliveCountMax.*/ClientAliveCountMax 120/' /etc/ssh/sshd_config && systemctl reload ssh || systemctl reload sshd"

4c. Install dependencies + build

Run in the background with nohupto prevent interruption from disconnection:

Project type Command
Node.js nohup bash -c 'npm install && npm run build' > /tmp/build.log 2>&1 &
Python nohup bash -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements.txt' > /tmp/build.log 2>&1 &
Rust (Option A) nohup bash -c 'source ~/.cargo/env && cargo build --release' > /tmp/build.log 2>&1 &
Go (Option A) nohup bash -c 'go build -o $PROJECT_NAME .' > /tmp/build.log 2>&1 &
Docker nohup bash -c 'docker compose build' > /tmp/build.log 2>&1 &
Static site No build required

Wait and then check the build result:ssh $SSH_TARGET "tail -20 /tmp/build.log"


Step 5: Start the application and process management

Choose the corresponding process management solution based on the project type.

Node.js → PM2

ssh $SSH_TARGET "cd $REMOTE_DIR && pm2 start npm --name $PROJECT_NAME -- start && pm2 startup && pm2 save"

Verify:ssh $SSH_TARGET "pm2 list && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"

Python → systemd

ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/.venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port $PORT
# 或 gunicorn: ExecStart=$REMOTE_DIR/.venv/bin/gunicorn -w 4 -b 0.0.0.0:$PORT main:app
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"

Note: adjust ExecStart according to the framework:

  • FastAPI/Starlette: uvicorn main:app
  • Flask: gunicorn -w 4 main:app
  • Django: gunicorn -w 4 project.wsgi:application
  • Pure script: python main.py

Verify:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"

Rust / Go → systemd

ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/$BINARY_NAME
Restart=on-failure
RestartSec=5
Environment=PORT=$PORT

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"

Rust binary path:$REMOTE_DIR/target/release/$PROJECT_NAME(Option A) or $REMOTE_DIR/$PROJECT_NAME(Option B) Go binary path:$REMOTE_DIR/$PROJECT_NAME

Verify:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"

Docker → docker compose

ssh $SSH_TARGET "cd $REMOTE_DIR && docker compose up -d"

Configure startup on boot: the Docker service starts with systemd by default; for docker compose, use restart: unless-stopped .

Verify:ssh $SSH_TARGET "docker compose ps && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"

Static site → hosted directly by Nginx

No process management required; Nginx points directly to the static file directory (see Step 6).


Step 6: Nginx reverse proxy

If the user chooses "access by IP only", skip this step and the subsequent SSL steps.

6a. Dynamic application (HTTP reverse proxy)

ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
    listen 80;
    server_name $DOMAIN www.$DOMAIN;

    client_max_body_size 50M;

    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\";
    }
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"

6b. Static site (direct hosting)

ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
    listen 80;
    server_name $DOMAIN www.$DOMAIN;

    root $REMOTE_DIR;
    index index.html;

    location / {
        try_files \$uri \$uri/ /index.html;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control \"public, immutable\";
    }
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"

6c. HTTPS version (when there is an SSL certificate)

Based on the corresponding server block:

  • Add to the port 80 server return 301 https://$host$request_uri;
  • Add a new port 443 server block, add ssl_certificate and security headers
server {
    listen 443 ssl;
    server_name $DOMAIN www.$DOMAIN;

    ssl_certificate /etc/ssl/cloudflare/fullchain.pem;
    ssl_certificate_key /etc/ssl/cloudflare/key.pem;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # ... location 块同上 ...
}

6d. Nginx security hardening

ssh $SSH_TARGET "sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf && nginx -t && systemctl reload nginx"

Step 7: DNS and SSL configuration

Only execute when the user has a domain name.

7a. DNS configuration guide

Tell the user to add DNS records in the domain registrar's backend:

Record type Name Content
A @ <服务器IP>
A www <服务器IP>

If using Cloudflare, it is recommended to enable the orange cloud (proxy mode).

7b. SSL Option Selection

Use AskUserQuestion to ask:

  1. Cloudflare Origin Certificate (recommended) — DNS hosted on Cloudflare, certificate valid for 15 years
  2. Let's Encrypt — Free public certificate, requires periodic renewal
  3. Do not configure SSL for now

Cloudflare Origin Certificate process:

  1. Tell the user what to do: Cloudflare → SSL/TLS → Origin Server → Create Certificate

    • Ensure the hostname includes $DOMAIN and *.$DOMAIN
    • Private key type: RSA, Validity: 15 years
  2. After the user provides the certificate and private key, write them to the server and concatenate the certificate chain:

ssh $SSH_TARGET "mkdir -p /etc/ssl/cloudflare"
# 写入 cert.pem 和 key.pem(chmod 600)
# 下载 Origin CA 根证书并拼接
ssh $SSH_TARGET "curl -s -o /etc/ssl/cloudflare/origin-ca-rsa-root.pem https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem && cat /etc/ssl/cloudflare/cert.pem /etc/ssl/cloudflare/origin-ca-rsa-root.pem > /etc/ssl/cloudflare/fullchain.pem"
  1. The certificate must be verified:
ssh $SSH_TARGET "openssl x509 -in /etc/ssl/cloudflare/cert.pem -noout -text | grep -E 'DNS:|Subject:|Issuer:|Extended'"
  • DNS: includes the domain
  • Extended Key Usage includes TLS Web Server Authentication
  • If missing, tell the user to recreate it
  1. Cloudflare SSL mode: first set Full (Strict), if a 526 error occurs, downgrade to Full

Let's Encrypt process:

ssh $SSH_TARGET "apt-get install -y certbot python3-certbot-nginx && certbot --nginx -d $DOMAIN -d www.$DOMAIN --non-interactive --agree-tos --email <EMAIL>"

You need to temporarily disable the Cloudflare proxy (orange → gray), and you can re-enable it after completion.


Extended Reference

Detailed material starting at ## 第八步:安全加固 has been moved to reference/extended.md to keep this skill concise. Load that reference when the task requires the moved examples, command catalogs, checklists, platform details, or implementation templates.