From 2d00e454c94ac1a650697bcd180c3f8f50116d7e Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Tue, 14 Apr 2026 21:10:43 +0800 Subject: [PATCH] feat(nginx): add nginx + functionality to toggle between letsencrypt, custom-ssl and http-only modes in .env file --- README.md | 254 ++++++++++++++++++++++------------- docker-compose.yml | 39 +++++- run.sh | 10 +- scripts/setup-letsencrypt.sh | 32 +++++ scripts/setup-ssl.sh | 168 ++++++++++++++++++----- 5 files changed, 365 insertions(+), 138 deletions(-) create mode 100644 scripts/setup-letsencrypt.sh diff --git a/README.md b/README.md index da01eb1..c186f28 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,225 @@ # Gitea Self‑Hosted Deployment -This repository provides a **simple automated deployment for a self‑hosted Gitea server** using Docker Compose. +A **simple, automated way to deploy your own Git server** using [Gitea](https://gitea.io) and Docker Compose. -It is designed to make deploying a production‑ready Git server easy with: +This project sets up everything you need for a production‑ready Git hosting platform: -- Docker Compose deployment -- PostgreSQL database -- Optional SMTP email -- Optional HTTPS support -- Automated bootstrap script -- Persistent data storage +- One‑command deployment via a bootstrap script +- PostgreSQL database (included) +- Nginx reverse proxy for HTTP / HTTPS +- Three SSL modes: **none**, **Let's Encrypt**, or **custom certificate** +- Optional SMTP email notifications +- Persistent data storage with easy backups -# Requirements +## Requirements -Minimum recommended: +You'll need a Linux server (Ubuntu or Debian recommended) with the following: -- Linux server (Ubuntu / Debian) -- Docker -- Docker Compose -- 2 CPU cores -- 2‑4GB RAM -- 10GB disk space +| Requirement | Minimum | +|------------------|------------------| +| Docker | 20.10+ | +| Docker Compose | v2+ | +| CPU | 2 cores | +| RAM | 2–4 GB | +| Disk | 10 GB free | -# Quick Start +## Quick Start -Clone the repository: -``` +### 1. Clone the repository + +```bash git clone https://git.amiirkhl.ir/interanet/gitea-deployment.git cd gitea-deployment ``` -Run the bootstrap script: -``` +### 2. Run the bootstrap script + +```bash chmod +x run.sh -sudo ./run.sh +./run.sh ``` -On first run the script will: -- Create `.env` from `.env.sample` -- Ask you to configure settings +The first time you run this, it will create a `.env` file from the included template and ask you to configure it. -Edit `.env`: -``` +### 3. Edit your configuration + +Open `.env` in any text editor: + +```bash nano .env ``` -Set at minimum: -``` -GITEA_EXTERNAL_URL -GITEA_ROOT_USER -GITEA_ROOT_PASSWORD -GITEA_ROOT_EMAIL +At a minimum, set these values: + +```env +GITEA_EXTERNAL_URL=http://YOUR_SERVER_IP # or https://your-domain.com +GITEA_DOMAIN=YOUR_SERVER_IP # your domain or IP +GITEA_ROOT_USER=admin +GITEA_ROOT_PASSWORD=SomeStrongPassword +GITEA_ROOT_EMAIL=you@example.com ``` -Then run again: -``` -sudo ./run.sh +### 4. Run again + +```bash +./run.sh ``` +That's it — Gitea will be up and running. -# Access Gitea -After deployment: +## Accessing Gitea + +Once deployed, open your browser and go to: + ``` http://YOUR_SERVER_IP ``` -or +or, if you configured HTTPS: + ``` https://your-domain.com ``` +Log in with the admin credentials you set in `.env`. -# Data Persistence -All persistent data is stored in: -``` -./gitea-data +## SSL / HTTPS Setup + +This project uses an **Nginx reverse proxy** in front of Gitea to handle HTTPS. You control the behavior with a single variable in `.env`: + +```env +SSL_MODE=none # Options: none | letsencrypt | custom ``` -Structure: +### Option 1: No HTTPS (`none`) + +This is the default. Nginx listens on port 80 and proxies traffic to Gitea over plain HTTP. + +```env +SSL_MODE=none +GITEA_EXTERNAL_URL=http://your-domain.com +``` + +No extra configuration needed. + +--- + +### Option 2: Let's Encrypt (`letsencrypt`) + +Automatically provisions a free TLS certificate from Let's Encrypt. HTTP traffic on port 80 is redirected to HTTPS on port 443. + +```env +SSL_MODE=letsencrypt +GITEA_EXTERNAL_URL=https://your-domain.com +GITEA_DOMAIN=your-domain.com +LETSENCRYPT_EMAIL=you@example.com +``` + +**Prerequisites:** +- Your domain must point to your server's public IP (A record in DNS) +- Ports 80 and 443 must be open and reachable from the internet + +The bootstrap script handles everything else — it starts Nginx, runs Certbot for the ACME challenge, and reloads Nginx with the new certificate. + +**To renew the certificate later:** + +```bash +./scripts/setup-letsencrypt.sh +``` + +You can automate this with a weekly cron job: + +```bash +0 3 * * 1 cd /path/to/gitea-deployment && ./scripts/setup-letsencrypt.sh +``` + +--- + +### Option 3: Custom Certificate (`custom`) + +Use your own certificate files (purchased, Cloudflare origin, self‑signed, etc.). HTTP traffic is redirected to HTTPS. + +```env +SSL_MODE=custom +GITEA_EXTERNAL_URL=https://your-domain.com +GITEA_DOMAIN=your-domain.com +SSL_CERT_PATH=/path/to/your/fullchain.pem +SSL_KEY_PATH=/path/to/your/privkey.pem +``` + +The script copies your cert and key into `./nginx/ssl/` and configures Nginx to use them. + +**To update your certificate later without restarting everything:** + +```bash +cp /path/to/new/fullchain.pem ./nginx/ssl/cert.pem +cp /path/to/new/privkey.pem ./nginx/ssl/key.pem +chmod 600 ./nginx/ssl/key.pem +docker exec gitea-nginx nginx -s reload +``` + +## Data Persistence + +All persistent data lives in the `./gitea-data` directory: + + ``` gitea-data/ -├─ gitea/ -└─ postgres/ +├── gitea/ # repositories, config, attachments +└── postgres/ # database files ``` -Back up this directory to preserve: - -- repositories -- database -- attachments -- configuration +**To back up your instance**, just copy this directory somewhere safe. It contains everything you need to restore later. -# Enabling HTTPS +## Managing the Server -Set in `.env`: -``` -GITEA_EXTERNAL_URL=https://git.example.com -SSL_CERT_PATH=/etc/letsencrypt/live/git.example.com/fullchain.pem -SSL_KEY_PATH=/etc/letsencrypt/live/git.example.com/privkey.pem -``` +Here are the most common commands you'll use: -During deployment the `setup-ssl.sh` script will copy certificates to: -``` -/data/https/cert.pem -/data/https/key.pem -``` - -Gitea will automatically use them for HTTPS. - - -# Managing the Server - -View logs: -``` +```bash +# View live logs docker compose logs -f -``` -Stop services: -``` +# Stop all services docker compose down -``` -Restart: -``` +# Restart services docker compose restart -``` -Update Gitea: -``` +# Update Gitea to the latest version docker compose pull docker compose up -d ``` +## Repository Structure -# Repository Structure ``` -gitea-deployment -├─ docker-compose.yml -├─ run.sh -├─ .env.sample -├─ README.md -└─ scripts - ├─ setup-swap.sh - └─ setup-ssl.sh +gitea-deployment/ +├── docker-compose.yml # Defines all services (Gitea, PostgreSQL, Nginx, Certbot) +├── run.sh # Main bootstrap script +├── .env.sample # Configuration template +├── README.md +├── nginx/ # Generated at runtime (gitignored) +│ ├── conf.d/ # Nginx site config +│ └── ssl/ # Custom SSL certs (if applicable) +└── scripts/ + ├── setup-swap.sh # Configures swap space if needed + ├── setup-ssl.sh # Generates Nginx config based on SSL_MODE + └── setup-letsencrypt.sh # Provisions / renews Let's Encrypt certs ``` +## Notes -# Notes - -- The first time Gitea starts it will initialize the database automatically. -- Admin credentials are configured through `.env`. -- SMTP is optional but recommended for production. +- On first start, Gitea automatically initializes the database — no manual setup required. +- Admin credentials come from your `.env` file and are created during the bootstrap. +- SMTP is optional but recommended for production use (password resets, notifications). +- The `nginx/` directory is generated by the scripts and should not be committed to git. -# License +## License MIT diff --git a/docker-compose.yml b/docker-compose.yml index 572e128..c141820 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,6 @@ services: restart: always depends_on: - db - environment: USER_UID: 1000 USER_GID: 1000 @@ -37,7 +36,7 @@ services: GITEA__database__USER: ${GITEA_DB_USER} GITEA__database__PASSWD: ${GITEA_DB_PASSWORD} - GITEA__security__INSTALL_LOCK: true + GITEA__security__INSTALL_LOCK: "true" GITEA__mailer__ENABLED: ${SMTP_ENABLE} GITEA__mailer__HOST: ${SMTP_HOST}:${SMTP_PORT} @@ -45,17 +44,43 @@ services: GITEA__mailer__PASSWD: ${SMTP_PASS} GITEA__mailer__FROM: ${SMTP_FROM} GITEA__mailer__SKIP_VERIFY: ${SMTP_SKIP_VERIFY} - volumes: - gitea_data:/data - + expose: + - "3000" ports: - - "${HTTP_PORT}:3000" - "${SSH_PORT}:22" - networks: - gitea_net + nginx: + image: nginx:alpine + container_name: gitea-nginx + restart: always + depends_on: + - gitea + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + - certbot_webroot:/var/www/certbot:ro + - certbot_certs:/etc/letsencrypt:ro + networks: + - gitea_net + + # Only used when SSL_MODE=letsencrypt; harmless otherwise + certbot: + image: certbot/certbot + container_name: gitea-certbot + volumes: + - certbot_webroot:/var/www/certbot + - certbot_certs:/etc/letsencrypt + entrypoint: "/bin/true" + profiles: + - letsencrypt + networks: gitea_net: driver: bridge @@ -63,3 +88,5 @@ networks: volumes: gitea_data: gitea_postgres_data: + certbot_webroot: + certbot_certs: diff --git a/run.sh b/run.sh index 40ffc6f..42a213e 100644 --- a/run.sh +++ b/run.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -e GREEN='\033[0;32m' @@ -42,7 +41,7 @@ echo "[STEP] Checking swap configuration..." ./scripts/setup-swap.sh || true echo "" -echo "[STEP] Preparing SSL certificates (if HTTPS enabled)..." +echo "[STEP] Preparing SSL / Nginx configuration..." ./scripts/setup-ssl.sh || true echo "" @@ -62,6 +61,13 @@ do sleep 5 done +# If letsencrypt mode, provision certs now that Nginx is running +if [[ "$SSL_MODE" == "letsencrypt" ]]; then + echo "" + echo "[STEP] Provisioning Let's Encrypt certificate..." + ./scripts/setup-letsencrypt.sh +fi + echo "[STEP] Creating admin user..." docker exec -u git gitea-server gitea admin user create \ --username "$GITEA_ROOT_USER" \ diff --git a/scripts/setup-letsencrypt.sh b/scripts/setup-letsencrypt.sh new file mode 100644 index 0000000..a685317 --- /dev/null +++ b/scripts/setup-letsencrypt.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +source .env + +if [[ "$SSL_MODE" != "letsencrypt" ]]; then + exit 0 +fi + +GREEN='\033[0;32m' +NC='\033[0m' + +echo "[LE] Requesting certificate for ${GITEA_DOMAIN}..." + +docker compose --profile letsencrypt run --rm certbot certonly \ + --webroot \ + --webroot-path /var/www/certbot \ + -d "$GITEA_DOMAIN" \ + --email "$LETSENCRYPT_EMAIL" \ + --agree-tos \ + --no-eff-email \ + --force-renewal + +echo -e "${GREEN}[LE] Certificate obtained. Regenerating Nginx config...${NC}" + +# Re-run setup-ssl to write the HTTPS config now that certs exist +./scripts/setup-ssl.sh + +echo "[LE] Reloading Nginx..." +docker exec gitea-nginx nginx -s reload + +echo -e "${GREEN}[LE] Done. HTTPS is active.${NC}" diff --git a/scripts/setup-ssl.sh b/scripts/setup-ssl.sh index ecb339b..64176f5 100644 --- a/scripts/setup-ssl.sh +++ b/scripts/setup-ssl.sh @@ -1,47 +1,147 @@ #!/usr/bin/env bash - set -e +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + if [ ! -f ".env" ]; then - echo "[ERROR] .env file not found. Skipping SSL setup." + echo -e "${RED}[ERROR] .env file not found. Skipping SSL setup.${NC}" exit 1 fi source .env -if [[ ! "$GITEA_EXTERNAL_URL" == https://* ]]; then - echo "[INFO] HTTPS not enabled in GITEA_EXTERNAL_URL. Skipping SSL setup." - exit 0 -fi +SSL_MODE="${SSL_MODE:-none}" +NGINX_CONF_DIR="./nginx/conf.d" +NGINX_SSL_DIR="./nginx/ssl" -if [[ -z "$SSL_CERT_PATH" || -z "$SSL_KEY_PATH" ]]; then - echo "[INFO] SSL_CERT_PATH or SSL_KEY_PATH not set. Skipping SSL copy." - exit 0 -fi +mkdir -p "$NGINX_CONF_DIR" "$NGINX_SSL_DIR" -if [ ! -f "$SSL_CERT_PATH" ]; then - echo "[ERROR] Certificate file not found: $SSL_CERT_PATH" +# ── Helper: write HTTP-only config ── +write_http_conf() { + cat > "$NGINX_CONF_DIR/gitea.conf" <<'NGINX' +server { + listen 80; + server_name _; + + location / { + proxy_pass http://gitea:3000; + 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; + client_max_body_size 512M; + } +} +NGINX +} + +# ── Helper: write HTTPS config (works for both letsencrypt & custom) ── +write_https_conf() { + local cert_path="$1" + local key_path="$2" + + cat > "$NGINX_CONF_DIR/gitea.conf" </dev/null 2>&1 && \ + docker run --rm -v gitea-deployment_certbot_certs:/etc/letsencrypt alpine \ + test -f "/etc/letsencrypt/live/${GITEA_DOMAIN}/fullchain.pem" 2>/dev/null; then + echo "[SSL] Existing Let's Encrypt certs found. Writing HTTPS config." + write_https_conf "$CERT" "$KEY" + else + echo "[SSL] No certs yet. Writing temporary HTTP config for ACME challenge." + write_http_conf + fi + ;; + + custom) + echo -e "${GREEN}[SSL] Mode: custom${NC}" + + if [[ -z "$SSL_CERT_PATH" || -z "$SSL_KEY_PATH" ]]; then + echo -e "${RED}[ERROR] SSL_CERT_PATH and SSL_KEY_PATH are required for custom mode.${NC}" + exit 1 + fi + if [[ ! -f "$SSL_CERT_PATH" ]]; then + echo -e "${RED}[ERROR] Certificate not found: $SSL_CERT_PATH${NC}" + exit 1 + fi + if [[ ! -f "$SSL_KEY_PATH" ]]; then + echo -e "${RED}[ERROR] Key not found: $SSL_KEY_PATH${NC}" + exit 1 + fi + + cp "$SSL_CERT_PATH" "$NGINX_SSL_DIR/cert.pem" + cp "$SSL_KEY_PATH" "$NGINX_SSL_DIR/key.pem" + chmod 600 "$NGINX_SSL_DIR/key.pem" + + write_https_conf "/etc/nginx/ssl/cert.pem" "/etc/nginx/ssl/key.pem" + echo -e "${GREEN}[SSL] Custom certificates copied to $NGINX_SSL_DIR${NC}" + ;; + + *) + echo -e "${RED}[ERROR] Unknown SSL_MODE: $SSL_MODE (expected: none, letsencrypt, custom)${NC}" exit 1 -fi + ;; +esac -if [ ! -f "$SSL_KEY_PATH" ]; then - echo "[ERROR] Key file not found: $SSL_KEY_PATH" - exit 1 -fi - -echo "[INFO] Preparing Gitea SSL directory..." - -mkdir -p ${GITEA_DATA_PATH}/gitea/https - -echo "[INFO] Copying SSL certificates..." - -cp "$SSL_CERT_PATH" ${GITEA_DATA_PATH}/gitea/https/cert.pem -cp "$SSL_KEY_PATH" ${GITEA_DATA_PATH}/gitea/https/key.pem - -chmod 600 ${GITEA_DATA_PATH}/gitea/https/key.pem - -echo "[SUCCESS] SSL certificates copied." - -echo "Gitea will load them from:" -echo " /data/https/cert.pem" -echo " /data/https/key.pem" +echo -e "${GREEN}[SSL] Nginx config written to $NGINX_CONF_DIR/gitea.conf${NC}"