Migrate users script

Download, Add a comment, Back to main page
[2004-03-06 11:56 CST] Maqi A note on JIT: JIT uses jabberd14/WPJabber's XDB facilities which jabberd2 does not provide. JIT runs within WPJabber (and uses its xdb_file component). WPJabber in turn connects to jabberd2 (as an external legacy component). No need to migrate anything.
[2004-03-15 16:35 CST] Trejkaz Xaoza (email) Don't bet on it.

When JIT connects to Jabberd 1.4 using Jabberd 1.4's XDB support, it puts the spool in a certain format.

When JIT connects to Jabberd 2.0 the spool is in a completely different format, adding new subdirectories all over the place, presumably as some form of hashing.

So how is no migration required?
[2004-03-16 08:20 CST] Maqi If you look at JIT's docs, you see that the scenario 1 you described (JIT runs in WPJabber, WPJabber connects to jabberd14 and uses its XDB services) is *not* covered. You can of course configure your setup this way and it works, however, the standard way is to let WPJabber's xdb_file handle XDB (which is unrelated to the main jabberd's XDB then).

I don't know what you mean with scenario 2. Jabberd2 does not handle XDB at all AFAIK so letting JIT connect to jabberd2 without configuring the WPJabber JIT runs in to handle XDB will fail. If you configure WPJabber to handle XDB, nothing changes from the jabberd14 setup though.

And, just for the record, jabberd 1.4.2 and 1.4.3 use a "flat" directory structure for XDB. jabberd14 (CVS) and WPJabber both use subdirectories (see jabberd14 CVS commit logs for details) as this improves speed a lot with many users and "old" filesystems such as ext2 without htree extensions (handling large directories is very slow then).
[2004-03-19 12:06 CST] Maqi As I reread your post, I think I understand. You seem to refer to WPJabber's (new) spool format, this is not really related to jabberd2. If a jabberd1.4.2/1.4.3 spool directory (for JIT) shall be handled by WPJabber's XDB component, you need to convert it before. There's a tool included in WPJabber to do this. jabberd14 (CVS) which uses the same spool format as WPJabber even migrates transparently and automatically. The whole topic is bit confusing I fear ;-).
[2004-04-08 13:47 CDT] Sean (email) fyi, this script doesn't work with Ruby 1.6.4. Using the 1.8.1 works fine, however. Cool script!
[2004-04-10 01:37 CDT] Sean (email) err, there's a problem I didn't notice earlier. Running the script yields this:

/usr/local/ruby-1.8.1/lib/ruby/site_ruby/1.8/rexml/parsers/baseparser.rb:291:in `pull': Missing end tag for 'query' (got "xdb") (REXML::ParseException) Line: 6 Position: 2795 Last 80 unconsumed characters: from /usr/local/ruby-1.8.1/lib/ruby/site_ruby/1.8/rexml/document.rb:180:in `build' from /usr/local/ruby-1.8.1/lib/ruby/site_ruby/1.8/rexml/document.rb:44:in `initialize' from /home/sean/migrate_tx:66:in `new' from /home/sean/migrate_tx:66 from /home/sean/migrate_tx:53:in `foreach' from /home/sean/migrate_tx:53

I'm not versed in ruby...a little help?
[2004-12-19 16:24 CST] trejkaz (email) Just so people know, my contact JID is soon changing: trejkaz@jabber.zim.net.au


#!/usr/bin/ruby
#
# Migration script for jabberd1.4 to jabberd2.
#
# Copyright (c) 2004  Trejkaz Xaoza
#
# This Ruby script converts a jabberd1.4 spool directory into a Mysql script for jabberd2.
# The reason it doesn't directly use Mysql to insert the data is so that the result can be
# confirmed to work before the script is run.  Additionally, I have no access to Ruby on the
# server my Jabber service runs on.  The reason it doesn't use Perl is I was dissatisfied
# with Perl's support for XML namespaces.
#
# What it does do:
#   * Converts all possible data from a single spool directory into a Mysql script.
#   * Yes, that includes offline messages and VCard, neither of which jabberd2's tools/migrate.pl can do.
#   * Makes your users happier because you don't wipe their offline messages.  Can't stress this enough. ;-)
#
# What doesn't it do:
#   * It doesn't work with Postgresql or db4 storage types.  In the case of Postgresql this might
#     not be too hard to fix.  In fact the quoting character is probably the only difference between
#     Mysql and Postgresql for this purpose.
#   * It doesn't update the database directly.
#   * It doesn't migrate the transports.  Note that JIT, since it uses a different jabberd and more
#     importantly a different xdb_file module, has a spool format which is different to all the other
#     components' spools, and incompatible with the JIT spool saved by jabberd1.4.  This means you still
#     have to migrate the JIT spool separately.  It would be nice if someone would contribute a script
#     to do this actually.
#   * It doesn't cook toast, etc.
#
# Contact details:
#     Email: trejkaz@xaoza.net
#     Jabber: trejkaz@jabber.xaoza.net
#
# Revision history:
#     0.3 - Check for null passwords and insert empty strings in this case.
#     0.2 - Fixed authreg realm values.  Make entries into the active table to prevent some login failures.
#     0.1 - Original version
#

require "rexml/document"
require "mysql"

if not ARGV[0] then
    $stderr.puts("ERROR: You must specify the realm (server id) to use.")
    Kernel.exit(1)
end

realm = ARGV[0]
if not File.stat(realm).directory? then
    $stderr.puts("ERROR: You must be in the spool directory.")
    Kernel.exit(1)
end

Dir.foreach(realm) do |filename|
    username = filename.clone;
    username.gsub!(/\.xml$/, '')
    owner = username + "@" + realm
    filename = realm + "/" + filename

    if not File.stat(filename).directory? then
        $stderr.puts "Processing #{owner}..."

        puts "INSERT INTO authreg (username, realm) VALUES ('#{Mysql.escape_string(username)}', '#{Mysql.escape_string(realm)}');"
        puts "INSERT INTO active (`collection-owner`) VALUES ('#{Mysql.escape_string(owner)}');"
    
        file = File.new(filename)
        doc = REXML::Document.new(file)
        doc.root.elements.each do |element|
            qname = element.name
            if element.namespace != nil then
                qname = element.namespace + " " + qname
            end
        
            case qname
            when "jabber:iq:auth:0k zerok"
                puts "UPDATE authreg SET hash = '#{element.elements["hash"].text}', token = '#{element.elements["token"].text}', " +
                     "sequence = #{element.elements["sequence"].text} WHERE username = '#{Mysql.escape_string(username)}' AND realm = '#{Mysql.escape_string(realm)}';"
                  
            when "jabber:iq:auth password"
                puts "UPDATE authreg SET `password` = '#{Mysql.escape_string(element.text.nil? ? '' : element.text)}' " +
                     "WHERE username = '#{Mysql.escape_string(username)}' AND realm = '#{Mysql.escape_string(realm)}';"

            when "jabber:iq:last query"
                puts "INSERT INTO logout (`collection-owner`, time) VALUES ('#{Mysql.escape_string(owner)}', #{element.attributes["last"]});"

            when "jabber:iq:roster query"
                element.elements.each("item") do |item|
                    item_subscription = item.attributes["subscription"]
                    if (item_subscription == "to" || item_subscription == "both") then
                        item_to = 1
                    else
                        item_to = 0
                    end
                    if (item_subscription == "from" || item_subscription == "both") then
                        item_from = 1
                    else
                        item_from = 0
                    end
                    if (item.attributes["ask"] == "subscribe") then
                        item_ask = 1
                    else
                        # Note: item_ask = 2 isn't possible since jabberd 1.4 doesn't store pending unsubscribe state.
                        item_ask = 0
                    end
                    
                    item_jid = item.attributes["jid"]
                    item_name = item.attributes["name"]
                
                    if item_name then
                        puts "INSERT INTO `roster-items` (`collection-owner`, jid, name, `to`, `from`, ask) " +
                             "VALUES ('#{Mysql.escape_string(owner)}', '#{Mysql.escape_string(item_jid)}', '#{Mysql.escape_string(item_name)}', " +
                             "#{item_to.to_s}, #{item_from.to_s}, #{item_ask.to_s});"
                    else
                        puts "INSERT INTO `roster-items` (`collection-owner`, jid, `to`, `from`, ask) " +
                             "VALUES ('#{Mysql.escape_string(owner)}', '#{Mysql.escape_string(item_jid)}', " +
                             "#{item_to.to_s}, #{item_from.to_s}, #{item_ask.to_s});"
                    end
            
                    item.elements.each("group") do |group|
                        if (group.text) then
                            puts "INSERT INTO `roster-groups` (`collection-owner`, jid, `group`) " +
                                 "VALUES ('#{Mysql.escape_string(owner)}', '#{Mysql.escape_string(item_jid)}', '#{Mysql.escape_string(group.text)}');";
                        end
                    end
                end

            when "jabber:x:offline foo"
                element.elements.each("message") do |message|
                    puts "INSERT INTO queue (`collection-owner`, `xml`) VALUES ('#{Mysql.escape_string(owner)}', '#{Mysql.escape_string(message.to_s)}');"
                end

            when "vcard-temp vCard", "vcard-temp vcard" # typo
                puts "INSERT INTO vcard (`collection-owner`) VALUES ('#{Mysql.escape_string(owner)}');"

                def vcard_iter
                    yield "fn", "FN"
                    yield "nickname", "NICKNAME"
                    yield "url", "URL"
                    yield "tel", "TEL/NUMBER"
                    yield "email", "EMAIL[USERID]/USERID"
                    yield "title", "TITLE"
                    yield "role", "ROLE"
                    yield "bday", "BDAY"
                    yield "desc", "DESC"
                    yield "n-given", "N/GIVEN"
                    yield "n-family", "N/FAMILY"
                    yield "adr-street", "ADR/STREET"
                    yield "adr-extadd", "ADR/EXTADD"
                    yield "adr-locality", "ADR/LOCALITY"
                    yield "adr-region", "ADR/REGION"
                    yield "adr-pcode", "ADR/PCODE"
                    yield "adr-country", "ADR/COUNTRY"
                    yield "org-orgname", "ORG/ORGNAME"
                    yield "org-orgunit", "ORG/ORGUNIT"
                end

                vcard_iter { |vcard_table_field, vcard_xpath|
                    vcard_field = element.elements[vcard_xpath]
                    if (vcard_field and vcard_field.text) then
                        puts "UPDATE vcard SET `#{vcard_table_field}` = '#{Mysql.escape_string(vcard_field.text)}' " +
                             "WHERE `collection-owner` = '#{Mysql.escape_string(owner)}';"
                    end
                }

            else
                if element.attributes["j_private_flag"] == "1" then
                    puts "INSERT INTO private (`collection-owner`, ns, xml) VALUES ('#{Mysql.escape_string(owner)}', '#{element.namespace}', '#{Mysql.escape_string(element.to_s)}');"
                else
                    # We ignore these because non-private arbitrary storage is out of the question.
                end
            end
        end
    end
end

$stderr.puts "Completed processing for #{realm}!"