18 KiB
🚀 .NET Application Deployment Generator
Purpose: Generate complete deployment configuration for a new .NET application on your Forgejo CI/CD infrastructure.
Infrastructure:
- Server: 192.168.1.43 (Ubuntu 24.04)
- Forgejo: 192.168.1.63:3000
- Deploy User:
deploy - Reverse Proxy: Caddy
- Process Manager: systemd
- Deployment Method: self-contained .NET publish + rsync + SSH
PART 1: INFORMATION COLLECTION
Ask the user the following questions and collect their answers:
Application Configuration
-
Application Name (e.g., "BlogApp", "ShopAPI", "DashboardApp")
- Answer:
_______
- Answer:
-
.NET Version (e.g., "9.0.x", "10.0.x")
- Answer:
_______
- Answer:
-
Executable Name (defaults to Application Name if blank)
- Answer:
_______(or blank to use app name)
- Answer:
Environment Setup
-
Deploy TEST environment? (yes/no)
- Answer:
_______
- Answer:
-
Deploy PROD environment? (yes/no)
- Answer:
_______
- Answer:
Network Configuration (TEST)
Skip if TEST not enabled
6a. TEST Port Number (e.g., 5002, 5010, 5020)
- Answer:
_______
6b. TEST Domain (e.g., "test-blog.jarjarbinks", "staging-shop.jarjarbinks")
- Answer:
_______
Network Configuration (PROD)
Skip if PROD not enabled
7a. PROD Port Number (e.g., 5003, 5011, 5021)
- Answer:
_______
7b. PROD Domain (e.g., "blog.jarjarbinks", "shop.jarjarbinks")
- Answer:
_______
Runner Configuration
-
Forgejo Runner Setup (existing/new)
- Answer:
_______
- Answer:
-
Runner Name (if new runner selected, e.g., "blogapp-runner", "shop-runner")
- Answer:
_______(skip if using existing)
- Answer:
PART 2: VARIABLE MAPPING
Based on user answers, create these variables:
| Variable | Derivation | Example |
|---|---|---|
{{APP_NAME}} |
Q1 answer | BlogApp |
{{APP_NAME_LOWER}} |
Q1 lowercase | blogapp |
{{DOTNET_VERSION}} |
Q2 answer | 9.0.x |
{{EXECUTABLE_NAME}} |
Q3 answer or Q1 | BlogApp |
{{INCLUDE_TEST}} |
Q4 = "yes" | true |
{{INCLUDE_PROD}} |
Q5 = "yes" | true |
{{TEST_PORT}} |
Q6a answer | 5002 |
{{TEST_DOMAIN}} |
Q6b answer | test-blog.jarjarbinks |
{{TEST_FOLDER}} |
/opt/{{APP_NAME_LOWER}}-test/app |
/opt/blogapp-test/app |
{{TEST_SERVICE}} |
{{APP_NAME_LOWER}}-test |
blogapp-test |
{{PROD_PORT}} |
Q7a answer | 5003 |
{{PROD_DOMAIN}} |
Q7b answer | blog.jarjarbinks |
{{PROD_FOLDER}} |
/opt/{{APP_NAME_LOWER}}/app |
/opt/blogapp/app |
{{PROD_SERVICE}} |
{{APP_NAME_LOWER}} |
blogapp |
{{CREATE_RUNNER}} |
Q8 = "new" | true |
{{RUNNER_NAME}} |
Q9 answer | blogapp-runner |
PART 3: OUTPUT FOLDER STRUCTURE
Create this folder structure with generated files:
deployment-configs-{{APP_NAME_LOWER}}/
├── README.md
├── .forgejo/
│ └── workflows/
│ └── cicd.yml
├── systemd/
│ ├── {{APP_NAME_LOWER}}-test.service # if INCLUDE_TEST
│ └── {{APP_NAME_LOWER}}.service # if INCLUDE_PROD
├── caddy/
│ └── {{APP_NAME_LOWER}}-config.txt
├── scripts/
│ ├── setup-server.sh
│ └── setup-runner.sh # if CREATE_RUNNER
└── secrets-checklist.md
PART 4: FILE TEMPLATES
Template 1: .forgejo/workflows/cicd.yml
name: {{APP_NAME}}-cicd
on:
push:
branches: [ "main" ]
workflow_dispatch: {}
jobs:
{{#if INCLUDE_TEST}}
build_test_deploy_test:
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "{{DOTNET_VERSION}}"
- name: Test
run: dotnet test -c Release
- name: Publish (self-contained linux-x64)
run: dotnet publish -c Release -r linux-x64 --self-contained true -o out
- name: Install deploy tools
run: |
apt-get update
apt-get install -y rsync openssh-client
- name: Deploy TEST
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
set -e
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
rsync -az --delete out/ "$DEPLOY_USER@$DEPLOY_HOST:{{TEST_FOLDER}}/"
ssh "$DEPLOY_USER@$DEPLOY_HOST" "chmod +x {{TEST_FOLDER}}/{{EXECUTABLE_NAME}} && sudo systemctl restart {{TEST_SERVICE}}"
{{/if}}
{{#if INCLUDE_PROD}}
deploy_prod_manual:
if: ${{ github.event_name == 'workflow_dispatch' }}
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "{{DOTNET_VERSION}}"
- name: Publish (self-contained linux-x64)
run: dotnet publish -c Release -r linux-x64 --self-contained true -o out
- name: Install deploy tools
run: |
apt-get update
apt-get install -y rsync openssh-client
- name: Deploy PROD
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
set -e
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
rsync -az --delete out/ "$DEPLOY_USER@$DEPLOY_HOST:{{PROD_FOLDER}}/"
ssh "$DEPLOY_USER@$DEPLOY_HOST" "chmod +x {{PROD_FOLDER}}/{{EXECUTABLE_NAME}} && sudo systemctl restart {{PROD_SERVICE}}"
{{/if}}
Generation Rule:
- Remove
{{#if INCLUDE_TEST}}...{{/if}}block if TEST not enabled - Remove
{{#if INCLUDE_PROD}}...{{/if}}block if PROD not enabled - Substitute all
{{VARIABLES}}with actual values
Template 2: systemd/{{APP_NAME_LOWER}}-test.service
Generate only if INCLUDE_TEST = true
[Unit]
Description={{APP_NAME}} TEST Environment
After=network.target
[Service]
Type=notify
WorkingDirectory={{TEST_FOLDER}}
ExecStart={{TEST_FOLDER}}/{{EXECUTABLE_NAME}}
Environment=ASPNETCORE_URLS=http://127.0.0.1:{{TEST_PORT}}
Environment=ASPNETCORE_ENVIRONMENT=Staging
User=minapp
Group=minapp
Restart=always
RestartSec=10
SyslogIdentifier={{TEST_SERVICE}}
[Install]
WantedBy=multi-user.target
Template 3: systemd/{{APP_NAME_LOWER}}.service
Generate only if INCLUDE_PROD = true
[Unit]
Description={{APP_NAME}} PRODUCTION Environment
After=network.target
[Service]
Type=notify
WorkingDirectory={{PROD_FOLDER}}
ExecStart={{PROD_FOLDER}}/{{EXECUTABLE_NAME}}
Environment=ASPNETCORE_URLS=http://127.0.0.1:{{PROD_PORT}}
Environment=ASPNETCORE_ENVIRONMENT=Production
User=minapp
Group=minapp
Restart=always
RestartSec=10
SyslogIdentifier={{PROD_SERVICE}}
[Install]
WantedBy=multi-user.target
Template 4: caddy/{{APP_NAME_LOWER}}-config.txt
{{#if INCLUDE_TEST}}
# TEST Environment
http://{{TEST_DOMAIN}} {
reverse_proxy 127.0.0.1:{{TEST_PORT}}
}
{{/if}}
{{#if INCLUDE_PROD}}
# PRODUCTION Environment
http://{{PROD_DOMAIN}} {
reverse_proxy 127.0.0.1:{{PROD_PORT}}
}
{{/if}}
Note: This content must be manually added to /etc/caddy/Caddyfile on the server.
Template 5: scripts/setup-server.sh
#!/bin/bash
# Setup script for {{APP_NAME}} deployment
# Run this on the webserver (192.168.1.43)
set -e
echo "==================================="
echo "Setting up {{APP_NAME}} on server"
echo "==================================="
{{#if INCLUDE_TEST}}
# Create TEST directories
echo "Creating TEST environment directories..."
sudo mkdir -p {{TEST_FOLDER}}
sudo chown -R minapp:minapp /opt/{{APP_NAME_LOWER}}-test
# Install TEST systemd service
echo "Installing TEST systemd service..."
sudo cp systemd/{{APP_NAME_LOWER}}-test.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable {{TEST_SERVICE}}
echo "✓ TEST service installed: {{TEST_SERVICE}}"
{{/if}}
{{#if INCLUDE_PROD}}
# Create PROD directories
echo "Creating PROD environment directories..."
sudo mkdir -p {{PROD_FOLDER}}
sudo chown -R minapp:minapp /opt/{{APP_NAME_LOWER}}
# Install PROD systemd service
echo "Installing PROD systemd service..."
sudo cp systemd/{{APP_NAME_LOWER}}.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable {{PROD_SERVICE}}
echo "✓ PROD service installed: {{PROD_SERVICE}}"
{{/if}}
# Update Caddy configuration
echo ""
echo "==================================="
echo "MANUAL STEP REQUIRED:"
echo "==================================="
echo "Add the following to /etc/caddy/Caddyfile:"
echo ""
cat caddy/{{APP_NAME_LOWER}}-config.txt
echo ""
echo "Then run: sudo systemctl reload caddy"
echo ""
echo "==================================="
echo "Setup complete!"
echo "==================================="
{{#if INCLUDE_TEST}}
echo "TEST service: {{TEST_SERVICE}}"
echo "TEST folder: {{TEST_FOLDER}}"
echo "TEST URL: http://{{TEST_DOMAIN}}"
{{/if}}
{{#if INCLUDE_PROD}}
echo "PROD service: {{PROD_SERVICE}}"
echo "PROD folder: {{PROD_FOLDER}}"
echo "PROD URL: http://{{PROD_DOMAIN}}"
{{/if}}
echo "==================================="
Template 6: scripts/setup-runner.sh
Generate only if CREATE_RUNNER = true
#!/bin/bash
# Setup Forgejo Runner for {{APP_NAME}}
# Run this on the Forgejo server (192.168.1.63)
set -e
RUNNER_NAME="{{RUNNER_NAME}}"
FORGEJO_URL="http://192.168.1.63:3000"
echo "==================================="
echo "Setting up Forgejo Runner: $RUNNER_NAME"
echo "==================================="
# Create runner directory
mkdir -p ~/forgejo-runners/$RUNNER_NAME
cd ~/forgejo-runners/$RUNNER_NAME
# Create docker-compose.yml
cat > docker-compose.yml <<'EOF'
version: '3'
services:
runner:
image: code.forgejo.org/forgejo/runner:latest
container_name: {{RUNNER_NAME}}
restart: unless-stopped
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
EOF
echo "✓ docker-compose.yml created"
# Start the runner
echo "Starting runner container..."
docker-compose up -d
echo "Waiting for runner to start..."
sleep 5
# Register runner
echo ""
echo "==================================="
echo "MANUAL STEP REQUIRED:"
echo "==================================="
echo "1. Go to Forgejo → Repository Settings → Actions → Runners"
echo "2. Click 'Create new Runner'"
echo "3. Copy the registration token"
echo "4. Run the following command:"
echo ""
echo "docker exec -it {{RUNNER_NAME}} forgejo-runner register \\"
echo " --instance $FORGEJO_URL \\"
echo " --token YOUR_REGISTRATION_TOKEN \\"
echo " --name {{RUNNER_NAME}} \\"
echo " --labels docker:docker://node:20-bookworm"
echo ""
echo "==================================="
Template 7: README.md
# {{APP_NAME}} - Deployment Configuration
**Generated:** $(date)
## Overview
This folder contains all configuration files needed to deploy **{{APP_NAME}}** to your infrastructure:
- **Server:** 192.168.1.43 (Ubuntu 24.04)
- **Forgejo:** 192.168.1.63:3000
- **.NET Version:** {{DOTNET_VERSION}}
## Environments
{{#if INCLUDE_TEST}}
### TEST Environment
- **Port:** {{TEST_PORT}}
- **Domain:** http://{{TEST_DOMAIN}}
- **Folder:** {{TEST_FOLDER}}
- **Service:** {{TEST_SERVICE}}
- **Deploy:** Automatic on `git push` to main
{{/if}}
{{#if INCLUDE_PROD}}
### PROD Environment
- **Port:** {{PROD_PORT}}
- **Domain:** http://{{PROD_DOMAIN}}
- **Folder:** {{PROD_FOLDER}}
- **Service:** {{PROD_SERVICE}}
- **Deploy:** Manual via workflow_dispatch
{{/if}}
## Setup Instructions
### 1. Configure Forgejo Secrets
Go to your repository in Forgejo → Settings → Actions → Secrets
Required secrets (should already exist):
- `DEPLOY_HOST` = `192.168.1.43`
- `DEPLOY_USER` = `deploy`
- `DEPLOY_SSH_KEY` = SSH private key for deploy user
### 2. Copy Workflow to Repository
```bash
# In your repository
mkdir -p .forgejo/workflows
cp .forgejo/workflows/cicd.yml YOUR_REPO/.forgejo/workflows/
cd YOUR_REPO
git add .forgejo/workflows/cicd.yml
git commit -m "Add CI/CD workflow"
git push
3. Setup Server
# Copy files to server
scp -r systemd caddy scripts deploy@192.168.1.43:~/{{APP_NAME_LOWER}}-setup/
# SSH to server
ssh deploy@192.168.1.43
# Run setup script
cd ~/{{APP_NAME_LOWER}}-setup
chmod +x scripts/setup-server.sh
./scripts/setup-server.sh
# Manually add Caddy configuration (as instructed by script)
sudo nano /etc/caddy/Caddyfile
# Add content from caddy/{{APP_NAME_LOWER}}-config.txt
sudo systemctl reload caddy
{{#if CREATE_RUNNER}}
4. Setup Forgejo Runner (Optional)
# Copy script to Forgejo server
scp scripts/setup-runner.sh forgejo@192.168.1.63:~/
# SSH to Forgejo server
ssh forgejo@192.168.1.63
# Run setup script
chmod +x setup-runner.sh
./setup-runner.sh
# Follow instructions to register runner
{{/if}}
Deployment
{{#if INCLUDE_TEST}}
Deploy to TEST
git push origin main
Check deployment:
# On server
sudo systemctl status {{TEST_SERVICE}}
sudo journalctl -u {{TEST_SERVICE}} -n 50
# Test locally
curl http://127.0.0.1:{{TEST_PORT}}
# Test via Caddy
curl http://{{TEST_DOMAIN}}
{{/if}}
{{#if INCLUDE_PROD}}
Deploy to PROD
- Go to Forgejo → Repository → Actions
- Select the workflow
- Click Run workflow
- Confirm deployment
Check deployment:
# On server
sudo systemctl status {{PROD_SERVICE}}
sudo journalctl -u {{PROD_SERVICE}} -n 50
# Test locally
curl http://127.0.0.1:{{PROD_PORT}}
# Test via Caddy
curl http://{{PROD_DOMAIN}}
{{/if}}
Troubleshooting
Service Issues
{{#if INCLUDE_TEST}}
# TEST service
sudo systemctl status {{TEST_SERVICE}}
sudo journalctl -u {{TEST_SERVICE}} -n 100 -f
{{/if}}
{{#if INCLUDE_PROD}}
# PROD service
sudo systemctl status {{PROD_SERVICE}}
sudo journalctl -u {{PROD_SERVICE}} -n 100 -f
{{/if}}
Check Ports
ss -lntp | grep {{TEST_PORT}}
ss -lntp | grep {{PROD_PORT}}
Restart Services
{{#if INCLUDE_TEST}}
sudo systemctl restart {{TEST_SERVICE}}
{{/if}}
{{#if INCLUDE_PROD}}
sudo systemctl restart {{PROD_SERVICE}}
{{/if}}
sudo systemctl reload caddy
File Structure
{{TEST_FOLDER}}/ # TEST deployment
{{PROD_FOLDER}}/ # PROD deployment
/etc/systemd/system/{{TEST_SERVICE}}.service
/etc/systemd/system/{{PROD_SERVICE}}.service
/etc/caddy/Caddyfile # Contains reverse proxy config
---
### Template 8: secrets-checklist.md
```markdown
# Forgejo Secrets Checklist
Repository: **YOUR_REPO_NAME**
Go to: Forgejo → Repository → Settings → Actions → Secrets
## Required Secrets
| Secret Name | Value | Status |
|-------------|-------|--------|
| `DEPLOY_HOST` | `192.168.1.43` | ☐ |
| `DEPLOY_USER` | `deploy` | ☐ |
| `DEPLOY_SSH_KEY` | SSH private key (ED25519) | ☐ |
## Verify Secrets
These secrets should already exist from previous deployments. Verify they are configured:
```bash
# Test SSH connection
ssh deploy@192.168.1.43 "echo Connection successful"
If Secrets Missing
Generate SSH key pair:
ssh-keygen -t ed25519 -C "deploy@forgejo-ci"
Add public key to server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@192.168.1.43
Add private key to Forgejo secrets:
cat ~/.ssh/id_ed25519
# Copy output and paste in Forgejo secret
---
## PART 5: GENERATION INSTRUCTIONS
### Step 1: Create Output Folder
mkdir deployment-configs-{{APP_NAME_LOWER}} cd deployment-configs-{{APP_NAME_LOWER}}
### Step 2: Apply Variable Substitutions
For each template:
1. Replace all `{{VARIABLE}}` with actual values from mapping table
2. Process conditional blocks:
- Remove `{{#if CONDITION}}...{{/if}}` wrapper and keep content if condition is true
- Remove entire `{{#if CONDITION}}...{{/if}}` block if condition is false
3. Save to appropriate file in folder structure
### Step 3: Generate Files
Create each file based on conditions:
| File | Condition | Template |
|------|-----------|----------|
| `.forgejo/workflows/cicd.yml` | Always | Template 1 |
| `systemd/{{APP_NAME_LOWER}}-test.service` | INCLUDE_TEST | Template 2 |
| `systemd/{{APP_NAME_LOWER}}.service` | INCLUDE_PROD | Template 3 |
| `caddy/{{APP_NAME_LOWER}}-config.txt` | Always | Template 4 |
| `scripts/setup-server.sh` | Always | Template 5 |
| `scripts/setup-runner.sh` | CREATE_RUNNER | Template 6 |
| `README.md` | Always | Template 7 |
| `secrets-checklist.md` | Always | Template 8 |
### Step 4: Set Execute Permissions
```bash
chmod +x scripts/*.sh
Step 5: Create Archive (Optional)
tar -czf deployment-configs-{{APP_NAME_LOWER}}.tar.gz deployment-configs-{{APP_NAME_LOWER}}/
PART 6: EXAMPLE OUTPUT
For user answers:
- App Name: BlogApp
- .NET Version: 9.0.x
- Executable: BlogApp
- TEST: yes, port 5002, domain test-blog.jarjarbinks
- PROD: yes, port 5003, domain blog.jarjarbinks
- Runner: existing
Generated folder structure:
deployment-configs-blogapp/
├── README.md
├── .forgejo/
│ └── workflows/
│ └── cicd.yml
├── systemd/
│ ├── blogapp-test.service
│ └── blogapp.service
├── caddy/
│ └── blogapp-config.txt
├── scripts/
│ └── setup-server.sh
└── secrets-checklist.md
USAGE
- Copy this prompt to your AI assistant
- Answer all questions in PART 1
- AI will generate all files with substituted values
- Review generated files
- Follow README.md instructions to deploy
Version: 1.0
Compatible with: .NET 9+, Ubuntu 24.04, Forgejo Actions
Author: Janus