#!/usr/bin/env ruby require 'net/http' require 'json' require 'uri' require 'yaml' require 'optparse' # Configuration mapping ENV_CONFIG = { 'stg' => { url: "https://git.staging.jamkazam.com/api/v1", token_var: "GITEA_TOKEN_STG" }, 'prd' => { url: "https://git.jamkazam.com/api/v1", token_var: "GITEA_TOKEN_PRD" } } options = { env: 'stg' } OptionParser.new do |opts| opts.banner = "Usage: ruby sync-gitea-repos.rb [options] " opts.on("-e", "--env ENV", "Environment (stg or prd, default stg)") { |v| options[:env] = v } end.parse! if ARGV.length < 2 puts "āŒ Error: Missing Bitbucket credentials." puts "Usage: ruby sync-gitea-repos.rb --env [stg|prd] " exit 1 end BB_USER = ARGV[0] BB_PASS = ARGV[1] # Select config config = ENV_CONFIG[options[:env]] if config.nil? puts "āŒ Error: Invalid environment '#{options[:env]}'. Use 'stg' or 'prd'." exit 1 end # Resolve Token GITEA_TOKEN = ENV[config[:token_var]] || ENV['GITEA_TOKEN'] GITEA_URL = config[:url] GITEA_OWNER = "seth" MANIFEST_FILE = File.expand_path('gitea-repos.yaml', __dir__) if GITEA_TOKEN.nil? || GITEA_TOKEN.empty? puts "āŒ Error: API Token environment variable '#{config[:token_var]}' is not set." exit 1 end def gitea_request(method, path, payload = nil) uri = URI.parse("#{GITEA_URL}#{path}") header = { 'Content-Type' => 'application/json', 'Authorization' => "token #{GITEA_TOKEN}" } http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.read_timeout = 300 case method when :get then req = Net::HTTP::Get.new(uri.request_uri, header) when :post then req = Net::HTTP::Post.new(uri.request_uri, header); req.body = payload.to_json if payload when :delete then req = Net::HTTP::Delete.new(uri.request_uri, header) end http.request(req) end puts "šŸš€ Reconciling Gitea mirrors for #{options[:env].upcase}..." puts "šŸ”— API: #{GITEA_URL}" # 1. Load Manifest manifest = YAML.load_file(MANIFEST_FILE) desired_repos = manifest['repositories'] desired_names = desired_repos.map { |r| r['name'] } # 2. Get Current State from Gitea puts "šŸ” Fetching current repositories..." resp = gitea_request(:get, "/users/#{GITEA_OWNER}/repos") if resp.code != "200" puts "āŒ Failed to fetch repos: #{resp.code} #{resp.body}" exit 1 end current_repos = JSON.parse(resp.body) current_names = current_repos.map { |r| r['name'] } # 3. Reconcile: Delete repos not in manifest repos_to_delete = current_names - desired_names repos_to_delete.each do |name| puts "šŸ—‘ļø Deleting repository '#{name}' (not in manifest)..." gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}") end # 4. Reconcile: Create/Update mirrors desired_repos.each do |repo| name = repo['name'] url = repo['url'] if current_names.include?(name) repo_info = current_repos.find { |r| r['name'] == name } if repo_info['empty'] puts "āš ļø Mirror '#{name}' exists but is EMPTY. Recreating..." gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}") else puts "āœ… Mirror '#{name}' already exists. Skipping." next end end puts "šŸš€ Creating mirror for '#{name}'..." payload = { "clone_addr" => url, "auth_username" => BB_USER, "auth_password" => BB_PASS, "repo_name" => name, "mirror" => true, "mirror_interval" => "10m", "service" => "bitbucket", "repo_owner" => GITEA_OWNER, "lfs" => true, "releases" => true } res = gitea_request(:post, "/repos/migrate", payload) if res.code == "201" puts "✨ Successfully created mirror for '#{name}'." else puts "āŒ Failed to create mirror for '#{name}': #{res.code} #{res.body}" end end puts "\nšŸŽÆ Reconcile complete for #{options[:env].upcase}."