commit ce9b28917bfa7b86a54eb939c17cb9e719575ba8 Author: Dada Date: Sun Mar 23 18:31:05 2025 -0400 Initial commit of Forgejo configuration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..949ca01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Terraform files +terraform/.terraform/ +terraform/terraform.tfstate +terraform/terraform.tfstate.backup +terraform/.terraform.lock.hcl +terraform/crash.log + +# Ansible files +ansible/*.retry + +# SSH keys +*.pem +*.key +id_rsa* + +# OS files +.DS_Store +Thumbs.db + +# Editor files +.vscode/ +.idea/ +*.swp +*~ + +# Logs +*.log diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..87ac23c --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -0,0 +1,147 @@ +# Forgejo Deployment Guide + +This guide provides detailed step-by-step instructions for deploying the Forgejo git repository on a GCP VM with SSL. + +## Prerequisites + +1. Google Cloud Platform account with project ID: `homelab-454516` +2. Terraform installed locally +3. Ansible installed locally +4. SSH key pair at `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub` +5. DuckDNS account with subdomain: `bootknockery.duckdns.org` + +## Deployment Steps + +### 1. Infrastructure Provisioning with Terraform + +```bash +# Navigate to the terraform directory +cd terraform + +# Initialize Terraform +terraform init + +# Apply the Terraform configuration +terraform apply +``` + +This will create a GCP VM in the `us-central1-a` zone with the following specifications: +- Machine type: e2-medium +- Disk size: 50GB +- Operating system: Debian + +### 2. Update DuckDNS + +Ensure your DuckDNS subdomain (`bootknockery.duckdns.org`) points to the VM's external IP address. + +### 3. Deploy Forgejo with Ansible + +```bash +# Navigate to the ansible directory +cd ../ansible + +# Run the Ansible playbook +ansible-playbook -i inventory.yml forgejo.yml +``` + +This will: +- Install Docker and Docker Compose +- Deploy Forgejo in a Docker container +- Configure Nginx as a reverse proxy +- Set up SSL with Let's Encrypt +- Configure SQLite with WAL mode for better performance + +### 4. Verify the Deployment + +1. Access Forgejo via HTTPS: + ``` + https://bootknockery.duckdns.org + ``` + +2. Check that SSL is properly configured: + ```bash + ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo certbot certificates" + ``` + +3. Verify that SQLite WAL mode is active: + ```bash + ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo ls -la /opt/forgejo/data/gitea/gitea.db*" + ``` + You should see additional files like `gitea.db-wal` and `gitea.db-shm`. + +## Post-Deployment Configuration + +### Initial Forgejo Setup + +When you first access Forgejo, you'll need to: + +1. Set up the admin account +2. Configure the database settings (SQLite is already configured) +3. Set up repository settings + +### SSH Access for Git Operations + +To use Git over SSH: + +```bash +# Add to ~/.ssh/config +Host bootknockery.duckdns.org + Port 222 + User git + IdentityFile ~/.ssh/id_rsa +``` + +Then clone repositories using: +```bash +git clone git@bootknockery.duckdns.org:username/repository.git +``` + +## Maintenance Tasks + +### SSL Certificate Renewal + +SSL certificates are automatically renewed weekly. To manually renew: + +```bash +ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo /etc/cron.weekly/certbot-renew" +``` + +### Database Backup + +To back up the Forgejo database: + +```bash +ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo cp /opt/forgejo/data/gitea/gitea.db ~/backup_gitea.db" +scp -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS:~/backup_gitea.db ./ +``` + +### Updating Forgejo + +To update Forgejo to a newer version: + +1. Edit `ansible/templates/docker-compose.yml.j2` to update the image version +2. Run the Ansible playbook again: + ```bash + cd ansible + ansible-playbook -i inventory.yml forgejo.yml + ``` + +## Troubleshooting + +### Common Issues + +1. **Cannot access Forgejo via HTTPS** + - Check Nginx status: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo systemctl status nginx"` + - Verify SSL certificates: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo ls -la /etc/letsencrypt/live/bootknockery.duckdns.org/"` + +2. **Forgejo container not running** + - Check Docker status: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo docker ps"` + - View Forgejo logs: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo docker logs forgejo"` + +3. **SSL certificate issues** + - Check certificate logs: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo tail -f /var/log/letsencrypt/letsencrypt.log"` + - Force renewal: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo /etc/cron.weekly/certbot-renew"` + +4. **Performance issues** + - Verify WAL mode: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo ls -la /opt/forgejo/data/gitea/gitea.db*"` + - Check system resources: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "top"` diff --git a/README.md b/README.md new file mode 100644 index 0000000..63f19e2 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# GCP Data Engineering VM with Forgejo + +This project sets up a reusable Google Cloud Platform (GCP) virtual machine for data engineering and machine learning projects, with Forgejo as the first installed module. + +## Technologies Used + +- **Terraform**: For infrastructure provisioning and state management +- **Cloud-init**: For initial VM setup and configuration with Debian +- **Ansible**: For configuration management and application deployment +- **Docker & Docker Compose**: For containerized applications +- **Forgejo**: Open source git repository that can be accessed through a DuckDNS subdomain + +## Project Structure + +``` +. +├── ansible/ # Ansible playbooks and configurations +│ ├── forgejo.yml # Forgejo installation playbook +│ ├── inventory.yml # Ansible inventory file +│ └── templates/ # Jinja2 templates for configurations +│ ├── app.ini.j2 # Forgejo app configuration +│ ├── docker-compose.yml.j2 # Docker Compose template +│ ├── nginx.conf.j2 # Nginx configuration with SSL support +│ └── certbot-renew.j2 # SSL certificate renewal script +├── cloud-init/ # Cloud-init scripts +│ └── cloud-init.sh # Initial VM setup script +├── terraform/ # Terraform configurations +│ ├── main.tf # Main Terraform configuration +│ ├── variables.tf # Variable definitions +│ └── terraform.tfvars.example # Example variable values +├── deploy.sh # Deployment automation script +└── README.md # This file +``` + +## Prerequisites + +1. Google Cloud Platform account with a project +2. Terraform installed locally +3. Ansible installed locally +4. SSH key pair for VM access +5. DuckDNS subdomain for Forgejo access + +## Setup Instructions + +### 1. Configure Terraform Variables + +Copy the example variables file and edit it with your GCP project details: + +```bash +cp terraform/terraform.tfvars.example terraform/terraform.tfvars +``` + +Edit `terraform/terraform.tfvars` with your GCP project ID, preferred region/zone, and SSH key path. + +### 2. Configure Ansible Inventory + +Edit `ansible/inventory.yml` to set your DuckDNS subdomain and admin email for Let's Encrypt SSL certificates. + +### 3. Run the Deployment Script + +```bash +./deploy.sh +``` + +This script will: +- Initialize and apply Terraform configuration to create the VM +- Update the Ansible inventory with the VM's IP address +- Wait for the VM to be ready +- Run the Ansible playbook to install and configure Forgejo + +## Accessing Forgejo + +Once deployment is complete, Forgejo will be accessible at: + +``` +https://your-duckdns-subdomain.duckdns.org +``` + +The deployment automatically configures: +- HTTPS with Let's Encrypt SSL certificates +- Automatic HTTP to HTTPS redirection +- Weekly SSL certificate renewal +- SQLite with WAL mode for improved performance + +SSH access to the Forgejo Git server will be available on port 222: + +```bash +ssh -p 222 git@your-duckdns-subdomain.duckdns.org +``` + +## Performance Optimizations + +### SQLite WAL Mode + +The deployment configures SQLite to use Write-Ahead Logging (WAL) mode for better performance. This provides: +- Improved write performance with concurrent operations +- Better read concurrency (readers don't block writers) +- Reduced disk I/O +- Improved durability and crash recovery + +To modify this setting, edit the `ansible/templates/app.ini.j2` file: + +```ini +[database] +SQLITE_JOURNAL_MODE = WAL +``` + +## Customization + +- **Machine Type**: Edit `terraform/terraform.tfvars` to change the VM size +- **Forgejo Configuration**: Modify `ansible/templates/app.ini.j2` to customize Forgejo settings +- **Additional Applications**: Add new Ansible playbooks in the `ansible` directory + +## Maintenance + +- **Terraform State**: The Terraform state is stored locally in `terraform/terraform.tfstate` +- **VM Updates**: SSH into the VM and run standard Debian update commands +- **Forgejo Updates**: Update the Docker image version in `ansible/templates/docker-compose.yml.j2` +- **SSL Certificate Renewal**: Certificates are automatically renewed weekly via a cron job +- **Database Backups**: To back up the Forgejo database, copy `/opt/forgejo/data/gitea/gitea.db*` from the VM +- **Monitoring**: Check the status of services with: + ```bash + ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo systemctl status nginx" + ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo docker ps" + ``` + +## Troubleshooting + +- **SSH Access**: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS` +- **Logs**: + - Forgejo logs: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo docker logs forgejo"` + - Nginx logs: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo tail -f /var/log/nginx/error.log"` + - SSL certificate logs: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo tail -f /var/log/letsencrypt/letsencrypt.log"` +- **Connectivity**: Ensure GCP firewall rules allow traffic on ports 80, 443, 22, 3000, and 222 +- **SSL Issues**: + - Check certificate status: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo certbot certificates"` + - Force certificate renewal: `ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo /etc/cron.weekly/certbot-renew"` +- **Database Performance**: If experiencing slowdowns, check SQLite WAL mode is working: + ```bash + ssh -i ~/.ssh/id_rsa debian@VM_IP_ADDRESS "sudo ls -la /opt/forgejo/data/gitea/gitea.db*" + ``` + You should see additional files like `gitea.db-wal` and `gitea.db-shm` if WAL mode is active. diff --git a/ansible/forgejo.yml b/ansible/forgejo.yml new file mode 100644 index 0000000..ec82689 --- /dev/null +++ b/ansible/forgejo.yml @@ -0,0 +1,124 @@ +--- +- name: Install and configure Forgejo + hosts: all + become: true + vars: + forgejo_domain: "{{ duckdns_subdomain }}.duckdns.org" + forgejo_data_dir: /opt/forgejo + forgejo_user_uid: 1000 + forgejo_user_gid: 1000 + forgejo_http_port: 3000 + forgejo_ssh_port: 222 + + tasks: + - name: Ensure Forgejo directory exists + file: + path: "{{ forgejo_data_dir }}" + state: directory + owner: "{{ forgejo_user_uid }}" + group: "{{ forgejo_user_gid }}" + mode: '0755' + + - name: Create Forgejo docker-compose.yml + template: + src: templates/docker-compose.yml.j2 + dest: "{{ forgejo_data_dir }}/docker-compose.yml" + owner: "{{ forgejo_user_uid }}" + group: "{{ forgejo_user_gid }}" + mode: '0644' + + - name: Create app.ini configuration file + template: + src: templates/app.ini.j2 + dest: "{{ forgejo_data_dir }}/app.ini" + owner: "{{ forgejo_user_uid }}" + group: "{{ forgejo_user_gid }}" + mode: '0644' + + - name: Start Forgejo with Docker Compose + community.docker.docker_compose_v2: + project_src: "{{ forgejo_data_dir }}" + state: present + become: true + become_user: debian + + - name: Install Nginx + apt: + name: nginx + state: present + update_cache: yes + + - name: Configure Nginx for Forgejo + template: + src: templates/nginx.conf.j2 + dest: /etc/nginx/sites-available/forgejo + owner: root + group: root + mode: '0644' + + - name: Enable Nginx site + file: + src: /etc/nginx/sites-available/forgejo + dest: /etc/nginx/sites-enabled/forgejo + state: link + + - name: Remove default Nginx site + file: + path: /etc/nginx/sites-enabled/default + state: absent + + - name: Restart Nginx + service: + name: nginx + state: restarted + enabled: yes + + - name: Pull Certbot Docker image + community.docker.docker_image: + name: certbot/certbot + source: pull + when: admin_email is defined + + - name: Stop Nginx before obtaining SSL certificate + service: + name: nginx + state: stopped + when: admin_email is defined + + - name: Obtain SSL certificate with Certbot Docker + block: + - name: Run Certbot Docker to obtain SSL certificate + command: > + docker run --rm -p 80:80 -p 443:443 + -v /etc/letsencrypt:/etc/letsencrypt + -v /var/lib/letsencrypt:/var/lib/letsencrypt + certbot/certbot certonly --standalone + -d {{ forgejo_domain }} --non-interactive --agree-tos + -m {{ admin_email }} + args: + creates: /etc/letsencrypt/live/{{ forgejo_domain }}/fullchain.pem + register: certbot_output + failed_when: certbot_output.rc != 0 and certbot_output.stderr is not search("already exists") + - name: Handle certbot errors + debug: + msg: "Certbot error: {{ certbot_output.stderr }}" + when: certbot_output is failed + when: admin_email is defined + ignore_errors: yes + + - name: Start Nginx after obtaining SSL certificate + service: + name: nginx + state: started + when: admin_email is defined + + - name: Set up SSL certificate renewal + block: + - name: Create SSL certificate renewal script + template: + src: templates/certbot-renew.j2 + dest: /etc/cron.weekly/certbot-renew + owner: root + group: root + mode: '0755' + when: admin_email is defined diff --git a/ansible/inventory.yml b/ansible/inventory.yml new file mode 100644 index 0000000..3d8724d --- /dev/null +++ b/ansible/inventory.yml @@ -0,0 +1,8 @@ +all: + hosts: + data_engineering_vm: + ansible_host: "34.67.170.119" + ansible_user: debian + ansible_ssh_private_key_file: "~/.ssh/id_rsa" + duckdns_subdomain: "bootknockery" # Replace with your actual DuckDNS subdomain + admin_email: "jaypeedaylee@gmail.com" # Replace with your email for Let's Encrypt diff --git a/ansible/templates/app.ini.j2 b/ansible/templates/app.ini.j2 new file mode 100644 index 0000000..d296dbe --- /dev/null +++ b/ansible/templates/app.ini.j2 @@ -0,0 +1,77 @@ +APP_NAME = Forgejo +RUN_USER = git +RUN_MODE = prod + +[repository] +ROOT = /data/git/repositories + +[repository.local] +LOCAL_COPY_PATH = /data/gitea/tmp/local-repo + +[repository.upload] +TEMP_PATH = /data/gitea/uploads + +[server] +APP_DATA_PATH = /data/gitea +DOMAIN = {{ forgejo_domain }} +SSH_DOMAIN = {{ forgejo_domain }} +HTTP_PORT = 3000 +ROOT_URL = https://{{ forgejo_domain }}/ +DISABLE_SSH = false +SSH_PORT = {{ forgejo_ssh_port }} +SSH_LISTEN_PORT = 22 +LFS_START_SERVER = true +LFS_CONTENT_PATH = /data/git/lfs +LFS_JWT_SECRET = +OFFLINE_MODE = false + +[database] +PATH = /data/gitea/gitea.db +DB_TYPE = sqlite3 +SQLITE_JOURNAL_MODE = WAL + +[indexer] +ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve + +[session] +PROVIDER_CONFIG = /data/gitea/sessions +PROVIDER = file + +[picture] +AVATAR_UPLOAD_PATH = /data/gitea/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars + +[attachment] +PATH = /data/gitea/attachments + +[log] +MODE = console +LEVEL = info +ROOT_PATH = /data/gitea/log + +[security] +INSTALL_LOCK = true +SECRET_KEY = +INTERNAL_TOKEN = + +[service] +DISABLE_REGISTRATION = false +REQUIRE_SIGNIN_VIEW = false +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +ALLOW_ONLY_EXTERNAL_REGISTRATION = false +ENABLE_CAPTCHA = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = noreply.example.org + +[oauth2] +JWT_SECRET = + +[mailer] +ENABLED = false + +[openid] +ENABLE_OPENID_SIGNIN = true +ENABLE_OPENID_SIGNUP = true diff --git a/ansible/templates/certbot-renew.j2 b/ansible/templates/certbot-renew.j2 new file mode 100644 index 0000000..e0b0770 --- /dev/null +++ b/ansible/templates/certbot-renew.j2 @@ -0,0 +1,37 @@ +#!/bin/bash +# Certbot renewal script for Forgejo SSL certificates +# Automatically created by Ansible + +# Set up logging +LOG_FILE="/var/log/certbot-renewal.log" +echo "$(date): Starting certificate renewal" >> $LOG_FILE + +# Stop Nginx to free up port 80 +echo "$(date): Stopping Nginx" >> $LOG_FILE +systemctl stop nginx +if [ $? -ne 0 ]; then + echo "$(date): Failed to stop Nginx" >> $LOG_FILE + exit 1 +fi + +# Run Certbot renewal +echo "$(date): Running Certbot renewal" >> $LOG_FILE +docker run --rm -p 80:80 -p 443:443 \ + -v /etc/letsencrypt:/etc/letsencrypt \ + -v /var/lib/letsencrypt:/var/lib/letsencrypt \ + certbot/certbot renew --standalone --non-interactive + +if [ $? -ne 0 ]; then + echo "$(date): Certbot renewal failed" >> $LOG_FILE + # Even if renewal fails, we should restart Nginx +fi + +# Start Nginx again +echo "$(date): Starting Nginx" >> $LOG_FILE +systemctl start nginx +if [ $? -ne 0 ]; then + echo "$(date): Failed to start Nginx" >> $LOG_FILE + exit 1 +fi + +echo "$(date): Certificate renewal completed successfully" >> $LOG_FILE diff --git a/ansible/templates/docker-compose.yml.j2 b/ansible/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..3e3acf2 --- /dev/null +++ b/ansible/templates/docker-compose.yml.j2 @@ -0,0 +1,21 @@ +networks: + forgejo: + external: false + +services: + server: + image: codeberg.org/forgejo/forgejo:10 + container_name: forgejo + environment: + - USER_UID={{ forgejo_user_uid }} + - USER_GID={{ forgejo_user_gid }} + restart: always + networks: + - forgejo + volumes: + - {{ forgejo_data_dir }}/data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - '{{ forgejo_http_port }}:3000' + - '{{ forgejo_ssh_port }}:22' diff --git a/ansible/templates/nginx-forgejo.conf.j2 b/ansible/templates/nginx-forgejo.conf.j2 new file mode 100644 index 0000000..3a64301 --- /dev/null +++ b/ansible/templates/nginx-forgejo.conf.j2 @@ -0,0 +1,12 @@ +server { + listen 80; + server_name {{ forgejo_domain }}; + + location / { + proxy_pass http://localhost:{{ forgejo_http_port }}/; + 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; + } +} diff --git a/ansible/templates/nginx.conf.j2 b/ansible/templates/nginx.conf.j2 new file mode 100644 index 0000000..e386655 --- /dev/null +++ b/ansible/templates/nginx.conf.j2 @@ -0,0 +1,32 @@ +server { + listen 80; + server_name {{ forgejo_domain }}; + + # Redirect all HTTP requests to HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name {{ forgejo_domain }}; + + ssl_certificate /etc/letsencrypt/live/{{ forgejo_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ forgejo_domain }}/privkey.pem; + + # SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + location / { + proxy_pass http://localhost:{{ forgejo_http_port }}/; + 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; + } +} diff --git a/cloud-init/cloud-init.sh b/cloud-init/cloud-init.sh new file mode 100644 index 0000000..6745c4b --- /dev/null +++ b/cloud-init/cloud-init.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e + +# Update and install dependencies +apt-get update +apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + python3 \ + python3-pip \ + git \ + unzip + +# Install Docker +curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +apt-get update +apt-get install -y docker-ce docker-ce-cli containerd.io + +# Install Docker Compose +curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose + +# Add debian user to docker group +usermod -aG docker debian + +# Install Ansible +pip3 install ansible + +# Create directory for Ansible playbooks +mkdir -p /opt/ansible +chown -R debian:debian /opt/ansible + +# Create directory for Forgejo +mkdir -p /opt/forgejo +chown -R debian:debian /opt/forgejo + +# Signal cloud-init completion +touch /var/lib/cloud/instance/boot-finished diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..10fcc5e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Starting deployment of GCP Data Engineering VM with Forgejo...${NC}" + +# Check if terraform is installed +if ! command -v terraform &> /dev/null; then + echo -e "${RED}Terraform is not installed. Please install it first.${NC}" + exit 1 +fi + +# Check if ansible is installed +if ! command -v ansible &> /dev/null; then + echo -e "${RED}Ansible is not installed. Please install it first.${NC}" + exit 1 +fi + +# Check if terraform.tfvars exists +if [ ! -f terraform/terraform.tfvars ]; then + echo -e "${YELLOW}terraform.tfvars not found. Creating from example file...${NC}" + cp terraform/terraform.tfvars.example terraform/terraform.tfvars + echo -e "${RED}Please edit terraform/terraform.tfvars with your GCP project details before continuing.${NC}" + exit 1 +fi + +# Initialize Terraform +echo -e "${GREEN}Initializing Terraform...${NC}" +cd terraform +terraform init + +# Apply Terraform configuration +echo -e "${GREEN}Applying Terraform configuration...${NC}" +terraform apply -auto-approve + +# Get the VM IP address +VM_IP=$(terraform output -raw instance_ip) +echo -e "${GREEN}VM created with IP: ${VM_IP}${NC}" +cd .. + +# Update Ansible inventory with VM IP +echo -e "${GREEN}Updating Ansible inventory with VM IP...${NC}" +sed -i "s/ansible_host: \"{{ vm_ip_address }}\"/ansible_host: \"${VM_IP}\"/g" ansible/inventory.yml + +# Wait for VM to be ready +echo -e "${YELLOW}Waiting for VM to be ready (60 seconds)...${NC}" +sleep 60 + +# Run Ansible playbook +echo -e "${GREEN}Running Ansible playbook to configure Forgejo...${NC}" +cd ansible +ansible-playbook -i inventory.yml forgejo.yml + +echo -e "${GREEN}Deployment completed successfully!${NC}" +echo -e "${YELLOW}Forgejo should be accessible at https://your-duckdns-subdomain.duckdns.org${NC}" +echo -e "${YELLOW}Please allow a few minutes for DNS propagation and SSL certificate setup.${NC}" diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..f0c2944 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,66 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + } + backend "local" { + path = "terraform.tfstate" + } +} + +provider "google" { + project = var.project_id + region = var.region + zone = var.zone +} + +resource "google_compute_instance" "data_engineering_vm" { + name = var.instance_name + machine_type = var.machine_type + zone = var.zone + + boot_disk { + initialize_params { + image = "debian-cloud/debian-11" + size = var.disk_size_gb + } + } + + network_interface { + network = "default" + access_config { + // Ephemeral public IP + } + } + + metadata = { + ssh-keys = "${var.ssh_username}:${file(var.ssh_pub_key_path)}" + } + + metadata_startup_script = file("${path.module}/../cloud-init/cloud-init.sh") + + tags = ["http-server", "https-server", "ssh"] + + service_account { + scopes = ["cloud-platform"] + } +} + +resource "google_compute_firewall" "forgejo" { + name = "allow-forgejo" + network = "default" + + allow { + protocol = "tcp" + ports = ["22", "80", "443", "3000", "222"] + } + + source_ranges = ["0.0.0.0/0"] + target_tags = ["http-server", "https-server", "ssh"] +} + +output "instance_ip" { + value = google_compute_instance.data_engineering_vm.network_interface[0].access_config[0].nat_ip +} diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 0000000..476dfa1 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,8 @@ +project_id = "homelab-454516" +region = "us-central1" +zone = "us-central1-a" +instance_name = "data-engineering-vm" +machine_type = "e2-medium" +disk_size_gb = 50 +ssh_username = "debian" +ssh_pub_key_path = "~/.ssh/id_rsa.pub" diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..bf14c6c --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,8 @@ +project_id = "your-gcp-project-id" +region = "us-central1" +zone = "us-central1-a" +instance_name = "data-engineering-vm" +machine_type = "e2-medium" +disk_size_gb = 50 +ssh_username = "debian" +ssh_pub_key_path = "~/.ssh/id_rsa.pub" diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..c5b7a63 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,46 @@ +variable "project_id" { + description = "The GCP project ID" + type = string +} + +variable "region" { + description = "The GCP region" + type = string + default = "us-central1" +} + +variable "zone" { + description = "The GCP zone" + type = string + default = "us-central1-a" +} + +variable "instance_name" { + description = "Name of the VM instance" + type = string + default = "data-engineering-vm" +} + +variable "machine_type" { + description = "The machine type for the VM" + type = string + default = "e2-medium" +} + +variable "disk_size_gb" { + description = "Boot disk size in GB" + type = number + default = 50 +} + +variable "ssh_username" { + description = "SSH username for the VM" + type = string + default = "debian" +} + +variable "ssh_pub_key_path" { + description = "Path to the SSH public key file" + type = string + default = "~/.ssh/id_rsa.pub" +}