#!/usr/bin/env ruby # For some reasons sqlite gem fails to work together with DRB server # DRB server drops connection with unclear stack trace # This file contains operations that are performed on the trac.db # # debian: apt-get install libzip-ruby1.8 # freebsd: portmanager archivers/ruby-zip require 'rubygems' require 'sqlite3' require "yaml" require 'logger' require "zlib" require 'zip/zip' require File.dirname(__FILE__) + "/minitar" require 'fileutils' class TracDbUtil attr_accessor :trac_path attr_accessor :zip_path include FileUtils::Verbose include Archive::Tar # command def init_db sqlite_db do |db| $log.info "set permissions" reset_permissions db $log.info "set enums" set_trac_enum db, 'priority', %w{highest high normal low lowest} set_trac_enum db, 'severity', %w{blocker critical major normal minor trivial} end end # command options def reset_perms sqlite_db do |db| reset_permissions db end end def reset_permissions db $log.info "delete old perms" db.execute "DELETE FROM permission WHERE username='anonymous' AND action IN ('WIKI_CREATE', 'WIKI_MODIFY', 'TICKET_CREATE', 'TICKET_MODIF')" $log.info "add @editors" add_permissions db, '@editors', %w{WIKI_CREATE WIKI_MODIFY TICKET_CREATE TICKET_MODIFY} $log.info "add @managers" add_permissions db, '@managers', %w{TRAC_ADMIN WIKI_ADMIN} end def add_permissions db, user, actions actions.each do |act| begin db.execute("INSERT INTO permission(username, action) VALUES(?,?)", user, act) rescue SQLite3::SQLException => ignore_exception end end end def set_trac_enum db, enum, values db.execute("DELETE FROM enum WHERE type=?", enum) values.each_with_index do |v, i| db.execute("INSERT INTO enum(type, name, value) VALUES(?, ?, ?)", enum, v, i + 1) end end # CREATE TABLE milestone ( # name text PRIMARY KEY, # due integer, # completed integer, # description text # ); # command def export_milestones rows = [] sqlite_db do |db| # it is Sqlite3 object, pack it as ruby db.execute("select name,due,completed,description from milestone") do |row| rows << { :name => row[0], :due => row[1], :completed => row[2], :description => row[3] } end end $log.debug(rows.inspect) puts YAML::dump(rows) end # command def import_milestones mstones = YAML::load(STDIN.read) $log.debug(mstones.inspect) sqlite_db do |db| mstones.each do |m| if db.execute("select * from milestone where name=?", m[:name]).size == 0 db.execute("insert into milestone(name,due,completed,description) values(?,?,?,?)", m[:name], m[:due], m[:completed] ? 1 : 0, m[:description]) end end end end def sqlite_db(&block) db = nil begin db = SQLite3::Database.new(trac_db) begin yield db rescue => e $log.info e.backtrace.join("\n") end ensure db.close if db end end def trac_db "%s/db/trac.db" % trac_path end # == Trac Archive Import # * create tmp trac dir # * copy conf from original trac # * copy db from arhive and convert it to sqlite3 if necessary # * copy attachments from archive # * run trac-admin upgrade on new trac.db # * run trac-admin resync # * move current trac to backup dir # * move tmp trac dir to current trac # TODO copy some trac props from archive trac.ini like default milestone, custom fields def import return unless test(?d, trac_path) self.trac_path = trac_path.gsub(/\/+$/, '') old_trac_path = trac_path trac_name = trac_path.split("/").last self.trac_path += "-#{Time.now.to_i}" begin %w{conf log db }.map { |f| mkdir_p File.join(trac_path, f)} cp "#{old_trac_path}/conf/trac.ini", "#{trac_path}/conf" if zip_path =~ /\.zip$/i zip_extract zip_path elsif zip_path =~ /\.tar\.gz$/i tar_gz_extract zip_path else raise "Invalid archive extension" end # Convert db if necessary convert_db = false File.open(trac_db) do |file| convert_db = file.read(16) !~ /^SQLite format 3/ end if convert_db mv trac_db, "#{trac_path}/db/trac.v2.db" sh "sqlite #{trac_path}/db/trac.v2.db .dump | sqlite3 #{trac_db}" rm "#{trac_path}/db/trac.v2.db" end # Upgrade to newer version sh "trac-admin #{trac_path} upgrade" # Resync sh "trac-admin #{trac_path} resync" if !($config['backup_dir'] && test(?w, $config['backup_dir'])) $config['backup_dir'] = '/tmp' end backup_path = File.join($config['backup_dir'], "#{trac_name}-#{Time.now.to_i}") mv old_trac_path, backup_path mv trac_path, old_trac_path ensure rm_rf trac_path if test(?d, trac_path) end end def zip_extract(zip_path) Zip::ZipInputStream::open(zip_path) { |io| while (entry = io.get_next_entry) next if entry.symlink? file_path = extract_file?(entry.name) entry.extract file_path if file_path end } end def extract_file?(name) parts = name.split '/' parts.shift # remove parent dir if parts[0] == "attachments" || parts[0] == "VERSION" || (parts[0] == "db" && parts[1] == "trac.db") file_path = parts.unshift(trac_path).join('/') mkdir_p File.dirname(file_path) return file_path end nil end def tar_gz_extract(zip_path) tgz = Zlib::GzipReader.new(File.open(zip_path)) Minitar::Input.open(tgz) do |io| io.each do |entry| next if entry.directory? file_path = extract_file?(entry.full_name) io.extract_entry(File.dirname(file_path), entry) if file_path end end end def sh(cmd) puts cmd system(cmd) end end # trac_db_utils.rb # options: # -init_db init trac db # -import_milestones # -export_milestones # -reset_perms # -import zip_file_path if ARGV.size < 2 or ARGV.size > 3 puts "Usage: ruby trac_db_utils.rb trac_path command [zip_file_path]" puts "zip_file_path: You must give this argument with -import command" exit end $log = Logger.new "#{File.dirname(__FILE__)}/log/control.log" obj = TracDbUtil.new obj.trac_path = ARGV[0] obj.zip_path = ARGV[2] method = ARGV[1].gsub(/^-/, '') if method == "import" and ARGV[2].nil? puts "Usage: ruby trac_db_utils.rb trac_path option [zip_file_path]" puts "You must give zip_file_path when you are using -import" exit end $config = { } config = File.dirname(__FILE__) + '/control.yml' if test(?f, config) $config = YAML::load(File.open(config)) end class Logger def puts(s) info s end def write(s) info s end end $stderr = $log obj.send(method)