迁移旧硬盘上的Linux到新硬盘(LVM)

February 7th, 2010

买了块新的大硬盘,准备把原先硬盘上的Slackware迁移过去,并且采用LVM,xp不要了换成Win7。记录下迁移过程和遇到的一些问题及解决方法。

  • 用Slackware 13.0 dvd系统盘引导启动,并分区。Slackware 从12.0开始支持安装系统到LVM

  • Device Boot Start End Blocks Id System
    /dev/sda1 * 1 13 102400 7 HPFS/NTFS
    /dev/sda2 13 2611 20869120 7 HPFS/NTFS
    /dev/sda3 2611 3917 10485760 c W95 FAT32 (LBA)
    /dev/sda4 3917 38913 281110368+ 5 Extended
    /dev/sda5 3917 3981 519047 83 Linux
    /dev/sda6 3982 4504 4200966 82 Linux swap
    /dev/sda7 4505 38913 276390261 8e Linux LVM

    这个是我新硬盘的最终分区情况。sda1是Win7保留的100M启动分区,sda2是Win7系统盘,sda3是D盘。500M大的sda5我原本想作为/boot,不过目前我就Win7+Slackware+LILO不需要,留作未来使用吧。swap(sda6)没有用LVM,剩下的260多G(sda7)都交给LVM管理了。

  • 建立LV分区并激活

  • pvcreate /dev/sda7
    vgcreate MYVG /dev/sda7
    lvcreate -L 20G -n ROOT MYVG
    lvcreate -L 10G -n USRLOCAL MYVG
    lvcreate -L 100G -n DATA MYVG
    lvcreate -L 50G -n HOME MYVG

    vgscan --mknodes
    vgchange -ay

  • 格式化LV分区并挂载到新的root目录

  • mkreiserfs /dev/MYVG/ROOT
    mkreiserfs /dev/MYVG/USRLOCAL
    mkreiserfs /dev/MYVG/HOME

    mkdir /new_root
    mount -t reiserfs /dev/MYVG/ROOT /new_root
    mkdir -p /new_root/usr/local
    mount -t reiserfs /dev/MYVG/USRLOCAL /new_root/usr/local
    mkdir /new_root/home
    mount -t reiserfs /dev/MYVG/HOME /new_root/home

  • 挂载旧硬盘上的系统目录到旧root目录

  • mkdir /old_root
    mount -t reiserfs /dev/sda5 /old_root
    mount -t reiserfs /dev/sda7 /old_root/usr/local
    mount -t reiserfs /dev/sda8 /old_root/usr/local/opt
    mount -t reiserfs /dev/sda9 /old_root/home

    又买了个移动硬盘盒,旧硬盘就可以当移动硬盘了。

  • 迁移(复制)旧系统到新硬盘

  • cd /old_root
    /old_root/bin/tar cpBf - . --exclude=tmp/* | ( cd /new_root && /old_root/bin/tar xvpBf -)

    漫长的等待…… /dev 啥的统统要拷过来。根据参考[5],到这里接着用直接chroot后就可以mkinitrd和lilo了,但事实不是这样的,还得额外多些操作。

  • chroot到/new_root,制作initrd和LILO

  • mount --bind /proc /new_root/proc
    mount --bind /sys /new_root/sys
    mount --bind /dev /new_root/dev
    chroot /new_root
    cd /boot
    mkinitrd -c -k 2.6.29.6-smp -m reiserfs -f reiserfs -r /dev/MYVG/ROOT -L -o /boot/initrd.gz
    vim /etc/lilo.conf
    lilo -v

    三行mount –bind是必须的,slackware的README_LVM.TXT说安装程序会将/proc和/sys挂载上,但我目前不是全新安装没有使用setup安装程序,而且我运行setup企图让它帮我挂载/proc等未果 :( 所以得用–bind(见参考[6]),不过我不是很了解这个选项。不这么mount的话执行mkinitrd会出现cat /proc/partitions错误,执行lilo会出现 Fatal: raid_setup: stat(“/dev/sda”)。

    /etc/lilo.conf 也作相应的调整:

    boot="/dev/sda"
    ......
    image="/boot/vmlinuz"
    initrd="/boot/initrd.gz"
    root="/dev/MYVG/ROOT"
    label="2.6.29.6-smp"
    read-only
    # Linux bootable partition config ends
    # Windows bootable partition config begins
    other = /dev/sda1
    label="Windows7"
    table = /dev/sda
    # Windows bootable partition config ends

    Win7的引导分区是sda1,不是系统盘sda2。马上就要好了 ;-)

  • 在新系统的/dev目录下建立LVM设备文件

  • exit # 退出chroot
    umount /new_root/dev
    chroot /new_root
    vgscan --mknodes
    vgchange -ay

    如果没有这一步,重启后LILO引导菜单已经可以看见,Win7引导正常,Slackware能引导起来,但是在挂载文件系统是提示找不到 /dev/MYVG/ROOT 文件。于是我就如上这么干了,在/dev下就会有MYVG/ROOT等LV设备文件了。不过我不太明白,必须得通过vgscan –mknodes的方式来建立几个lv分区的设备文件吗?那/dev/sda文件应该是我旧系统上复制过来的,换了硬盘后能直接使用?啊呀,这方面知识太欠缺了,我自己已经没法解释了,望路过的朋友帮我解惑一下 ;-)

  • 建立fstab文件

  • /dev/sda6 swap swap defaults 0 0
    /dev/MYVG/ROOT / reiserfs defaults 1 1
    /dev/MYVG/USRLOCAL /usr/local reiserfs defaults 1 2
    /dev/MYVG/HOME /home reiserfs defaults 1 2
    /dev/sda3 /media/D vfat uid=0,umask=0022,fmask=0111,shortname=mixed,iocharset=utf8 0 0
    /dev/cdrom /mnt/cdrom auto defaults,user,ro,noexec 0 0
    devpts /dev/pts devpts gid=5,mode=620 0 0
    proc /proc proc defaults 0 0

至此我的系统迁移完毕。这一次让我发现又有很多不懂不明白啊~~~

参考:
[1] 通用线程: 学习 Linux LVM,第 1 部分
[2] 通用线程:学习 Linux LVM,第 2部分
[3] README_LVM.TXT
[4] LVM White Paper 本地PDF文件
[5] Slackware 12.1 on LVM – Installation and Migration
[6] Backup almost working, LILO hangs

承上启下

December 31st, 2009

年头那几天跟@hkai同学聊起某事件似乎才刚发生过,2009就只剩1个多小时了。有08奥运今年开的的错觉,才发现这一年太多荒唐可笑之事发生在每个人身边。我总是记不清这些事件的具体时间,因为它们太应接不暇了,因为它们仍然在继续演出着。

昨天刚结束了一种生活状态,就和当年高二升高三分班一样,还处在镇痛期。09年许多事情让我比较清醒地认识到了自己的性格弱点,不管某个结果如何,未来的一年我都将迎来全新的生活。

2010我希望自己继续善其身,也希望能为争取个人权利多做些什么;希望肉食者能够偶尔吃点蔬菜,清清自己的宿便;更希望有那么一个人与我一起共赏这纠结而又精彩美好的世界 ;-)

这是一篇比微博长,比博客短的日志~ 新年快乐 :D

养一下驴子

December 19th, 2009

用MLDonkey很久了下不热门的资源一直非常慢,以至失去用ed2k网络的兴趣了。最近才发现原来是我没有开启MLDonkey的KAD功能 -_____-!! 没有KAD之类的技术用P2P久失去了很大的乐趣。

下面记录下几点设置,我不太懂养驴子,应该还有使驴子跑得更快的设置,还请路过大大教教我 ;-)

1. MLDonkey里的enable_kademlia设为true

2. 网上看到说需要nodes.dat和contact.dat(其实这个文件不需要了,是eDonkey的Overnet网络,现在eMule的ed2k和KAD网络足矣),而且还得手工添加。其实可以通过配置MLDonkey的download.ini文件来实现自动更新nodes.dat文件。

~/.mldonkey/download.ini
web_infos = [
("server.met", 0, "http://sn.im/server.met");
("nodes.gzip", 1, "http://upd.emule-security.net/nodes.dat");
("contact.dat", 168, "http://download.overnet.org/contact.dat");
("geoip.dat", 0, "http://www.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz");
("guarding.p2p", 96, "http://www.bluetack.co.uk/config/level1.gz");
(hublist, 0, "http://dchublist.com/hublist.config.bz2");]

主要修改”server.met”和”nodes.gzip”两段,server.met用的参考[1]里提供的链接,设成0是表示只需在MLDonkey启动时载入; nodes.gzip用的参考[2]里提供的链接,设成1是表示每1小时更新一次。其他字段默认。

参考:
[1] 安全eD2k服务器列表 2009-11-28
[2] Kad节点文件 nodes.dat
[3] 终于可以在ubuntu上养驴了
[4] [问题]mldonkey可以连接kad吗?

本地机器上运行远程X程序、X环境

November 6th, 2009

X11Forwarding
X11转发,在本地机器(Linux/Windows)上运行远程服务器上的X程序

***Linux***

方法1、2是在本地Linux机器上的当前窗口上显示远程服务器上的X程序(单独窗口的形式);
方法2、3使用SSH的X11转发,因此确保远程服务器上的/etc/ssh/sshd_config的X11Forwarding为yes;
方法3、4是在本地Linux上新的控制台下的新的X server上运行远程服务器的完整X环境;
方法1、4需要设置DISPLAY环境变量;
* 所有方法在两端都不需要root权限
* 192.168.1.xx 为本地Linux的IP

方法一:
本地Linux:
$ xhost +
$ ssh user@remotehost
远程服务器:
$ export DISPLAY=192.168.1.xx:0[.0]
$ xeyes& ; nautilus&

* xhost + 允许远程X程序在当前X server上允许,详细man

方法二:
本地Linux:
$ xhost +
$ ssh -X user@remotehost
远程服务器:
$ xeyes&; konqueror&

方法三:
本地Linux:
$ X :12.0 vt12 2>&1 > /dev/null &
$ xterm -display :12.0 -e ssh -X user@remotehost &
切到Ctrl+Alt+F12,在里面的xterm里运行 xeyes 等X程序,或者gnome-session、
startkde等运行完成的X环境

方法四:
本地Linux:
$ X :11.0 vt11 2>&1 > /dev/null &
$ xterm -display :11.0 &

切到Ctrl+Alt+F11在xterm里运行:
$ xhost +

本地Linux:
$ ssh user@remotehost
$ export DISPLAY=192.168.1.xx
$ xeyes& or gnome-session& or startkde&

一般来讲用ssh -X比较方便,方法1、4只是在探讨原理,实际使用相对麻烦。
根据你的不同需求选择方法2、3,在本地只单独运行某些X程序,还是需要完整的X环境。

***Windows***

Windows下需要如下软件:
X server – 本文使用Xming
ssh client – 本本使用Putty

Putty设置:
Connect -> SSH -> X11,勾选”允许 X11 映射”,填入localhost:0.0,MIT-Magic-Cookie-1协议

Xming设置:
如果你只运行单独的X程序,选择哪种窗口模式无所谓;
如果你需要完整的X环境,请选择单窗口或者全屏模式;
一定要勾选上”No Access Control”

最后用Putty登录到远程服务器,运行xeyes等独立X程序,或者gnome-session、startkde完整X环境。

References:
[1] Linux下DISPLAY环境变量的作用
[2] 用SSH实现X11转发

Opera里域名跳转

October 31st, 2009

用twitter多了就会发现有很多短网址,而且一些常见的bit.ly、j.mp都被墙了,这非常的杯具。今天在推特上看到@feelingluck兄写的一个短域名跳转的实现,方法很好,比起我原先想的好。东西在此:http://lab.gracecode.com/free-jump.php

不过这个方法在Opera里遇到了问题。Opera对于指向localhost(127.0.0.1)的域名会直接给你空白页面,而不是像firefox那样Unable to connect。你得不到任何关于此域名页面的信息,比如上面提到方法里的location.href,也就无法通过location.href把短网址传递到@feelinglucky的网页实现跳转了。

但Opera就是Opera,她依然有方法达到此目的,只需在你的menu.ini文件的[Link Popup Menu]段加一行:

Item, free-dump=New page & Go to page, "javascript:(function(){c=document.createElement('script');c.src='http://lab.gracecode.com/free-jump.php?url='+encodeURIComponent('%l');document.body.appendChild(c);})()"

使用的时候对着短网址右击就可以了。效果如下图,而且不需要修改hosts:
free-jump-opera

shortname以及自动挂载

August 18th, 2009

一切问题来自我前些天在优盘上搞的移动版的wiki(采用DokuWiki),插上优盘后,直接编辑我的wiki。但是发现有些页面是空白,功能不正常。后来发现是因为某些文件文件名在Linux下挂载后发生了变化,比如:”JSON.php”变成了”json.php”,造成了wiki无法正常运行 -______-!!

/etc/mtab显示有一个选项是shortname=lower,于是顺藤摸瓜google,发现通过手工挂载用参数shortname=mixed就正常。Linux下mount对fat/vfat文件系统(通常就是优盘的文件系统格式)默认挂载参数shortname的值就是lower。其实这个不太好,所有有人给了patch建议改成默认为shortname=mixed,可以看如下代码,更多见LKML里的讨论

diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 8970d8c..f9af501 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -971,7 +971,7 @@  static int parse_options(char *options, int is_vfat, int silent, int *debug,
 	opts->codepage = fat_default_codepage;
 	opts->iocharset = fat_default_iocharset;
 	if (is_vfat) {
-		opts->shortname = VFAT_SFN_DISPLAY_LOWER|VFAT_SFN_CREATE_WIN95;
+		opts->shortname = VFAT_SFN_DISPLAY_WINNT|VFAT_SFN_CREATE_WIN95;
 		opts->rodir = 0;
 	} else {
 		opts->shortname = 0;

这个shortname参数都是因为有8.3文件名这种东西的存在而存在的,可以参考Wikipedia上的这篇文章
还没完,每次手工挂载太蠢了。所以我想到了HAL,当时觉得可以改变默认的mount挂载参数(但事实不是)。还找到了如下的HAL policy:

<?xml version="1.0" encoding="UTF-8"?>
 
<deviceinfo version="0.2">
  <device>
    <match key="block.is_volume" bool="true">
      <match key="volume.fsusage" string="filesystem">
        <match key="volume.fstype" string="vfat">
          <merge key="volume.policy.mount_option.shortname="
type="string">shortname=mixed</merge>
        </match>
      </match>
    </match>
  </device>
</deviceinfo>

但是没有成功,lshal里看到了 volume.policy.mount_option.shortname= = ‘mixed’ (string) 这样的信息,可是我在Konqueror里打开仍然是以lower挂载的。在cppgx大牛的提醒和google到的这篇文章中,找到了两个原因:

  1. KDE这类DE会自己管理mount参数
  2. volume.policy.mount_option.shortname 这种写法已废弃

对于第一点,你可以在插上优盘后,打开Konqueror,到Storage Media里右击你的优盘盘符,属性里有一个挂载的标签,里面就有一些自定义的参数,其中就有shortname,而且默认就是lower。因此,如果你使用KDE,那么你可以在这个标签里将shortname改成mixed就可以正常显示优盘的文件名了。不过这不是全局的,你换了个优盘后,还得改一下。这些参数保存在~/.kde/share/config/mediamanagerrc

对于第二点,难怪在最新的hal-spec里打死找不到类似的方法。

到这里我才渐渐明白HAL是无法改变mount的挂载参数的,它可以发现硬件,然后提供给上层应用关于硬盘设备的很多信息,比如什么文件系统,可用的挂载参数等。像KDE这种能否自动挂载的,一定是从HAL那儿获得信息,然后有自己的程序去最终挂载设备,然后显示给用户。我很想看看KDE到底是怎么处理这些挂载参数的,所以经过一番长时间的搜索终于找到了相关代码(从没看过KDE代码,也不懂C++,而且觉得KDE网站搜索做的也不太好):
kdebase-3.5.10/kioslave/media/mediamanager/halbackend.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
QStringList HALBackend::mountoptions(const QString &name)
{
    const Medium* medium = m_mediaList.findById(name);
    if (medium && !isInFstab(medium).isNull())
        return QStringList(); // not handled by HAL - fstab entry
 
    KConfig config("mediamanagerrc");
    config.setGroup(name);
 
    char ** array = libhal_device_get_property_strlist(m_halContext, name.latin1(), "volume.mount.valid_options", NULL);
    QMap<QString,bool> valids;
 
    for (int index = 0; array && array[index]; ++index) {
        QString t = array[index];
        if (t.endsWith("="))
            t = t.left(t.length() - 1);
        valids[t] = true;
        kdDebug() << "valid " << t << endl;
    }
    libhal_free_string_array(array);
    QStringList result;
    QString tmp;
 
    QString fstype = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.fstype");
    if (fstype.isNull())
        fstype = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.policy.mount_filesystem");
 
    QString drive_udi = libhal_device_get_property_QString(m_halContext, name.latin1(), "block.storage_device");
 
    bool removable = false;
    if ( !drive_udi.isNull() )
        removable = libhal_device_get_property_bool(m_halContext, drive_udi.latin1(), "storage.removable", NULL)
                     || libhal_device_get_property_bool(m_halContext, drive_udi.latin1(), "storage.hotpluggable", NULL);
 
    config.setGroup(drive_udi);
    bool value = config.readBoolEntry("automount", false);
    config.setGroup(name);
 
    if (libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_blank", NULL)
        || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_vcd", NULL)
        || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_svcd", NULL)
        || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_videodvd", NULL)
        || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.has_audio", NULL))
        value = false;
 
    result << QString("automount=%1").arg(value ? "true" : "false");
 
    if (valids.contains("ro"))
    {
        value = config.readBoolEntry("ro", false);
        tmp = QString("ro=%1").arg(value ? "true" : "false");
        if (fstype != "iso9660") // makes no sense
            result << tmp;
    }
 
    if (valids.contains("quiet"))
    {
        value = config.readBoolEntry("quiet", false);
        tmp = QString("quiet=%1").arg(value ? "true" : "false");
        if (fstype != "iso9660") // makes no sense
            result << tmp;
    }
 
    if (valids.contains("flush"))
    {
        value = config.readBoolEntry("flush", fstype.endsWith("fat"));
        tmp = QString("flush=%1").arg(value ? "true" : "false");
        result << tmp;
    }
 
    if (valids.contains("uid"))
    {
        value = config.readBoolEntry("uid", true);
        tmp = QString("uid=%1").arg(value ? "true" : "false");
        result << tmp;
    }
 
	if ( valids.contains("locale") )
	{
		value = config.readBoolEntry( "locale", true );
		tmp = QString( "locale=%1" ).arg( value ? "true" : "false" );
		result << tmp;
	}
 
    if (valids.contains("utf8"))
    {
        value = config.readBoolEntry("utf8", true);
        tmp = QString("utf8=%1").arg(value ? "true" : "false");
        result << tmp;
    }
 
    if (valids.contains("shortname"))
    {
        QString svalue = config.readEntry("shortname", "lower").lower();
        if (svalue == "winnt")
            result << "shortname=winnt";
        else if (svalue == "win95")
            result << "shortname=win95";
        else if (svalue == "mixed")
            result << "shortname=mixed";
        else
            result << "shortname=lower";
    }
 
    if (valids.contains("sync"))
    {
        value = config.readBoolEntry("sync", ( valids.contains("flush") && !fstype.endsWith("fat") ) && removable);
        tmp = QString("sync=%1").arg(value ? "true" : "false");
        if (fstype != "iso9660") // makes no sense
            result << tmp;
    }
 
    if (valids.contains("noatime"))
    {
        value = config.readBoolEntry("atime", !fstype.endsWith("fat"));
        tmp = QString("atime=%1").arg(value ? "true" : "false");
        if (fstype != "iso9660") // makes no sense
            result << tmp;
    }
 
    QString mount_point = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.mount_point");
    if (mount_point.isEmpty())
        mount_point = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.policy.desired_mount_point");
 
    mount_point = config.readEntry("mountpoint", mount_point);
 
    if (!mount_point.startsWith("/"))
        mount_point = "/media/" + mount_point;
 
    result << QString("mountpoint=%1").arg(mount_point);
    result << QString("filesystem=%1").arg(fstype);
 
    if (valids.contains("data"))
    {
        QString svalue = config.readEntry("journaling").lower();
        if (svalue == "ordered")
            result << "journaling=ordered";
        else if (svalue == "writeback")
            result << "journaling=writeback";
        else if (svalue == "data")
            result << "journaling=data";
        else
            result << "journaling=ordered";
    }
 
    return result;
}

第10行就是获取HAL提供的有效参数(lshal | grep valid_options);
第13到19行的for循环将各个参数放到valids数组中;
第21行的QStringList result;是所有参数/值对列表;
第92行就是和shortname有关的了。它先是读取配置文件~/.kde/share/config/mediamanagerrc中某个设备的shortname参数值,如果没有默认就是lower。看来KDE里处理shortname也是如此。

不过从上述代码来看,提供给KDE配置mount参数仍然不是很多,比如dmask、fmask等都没有。通过HAL还是可以添加所有的参数到valid_options。

* BTW,上述代码是KDE 3.5.10环境下的,KDE 4默认的shortname已经是mixed了。不过不再是mediamanager了,相关代码还没找到去看。

最后提供个方法修改mount任意挂载参数,以挂载vfat文件系统为例:

$ cat /sbin/mount.vfat
#!/bin/sh
/sbin/mount -i -t vfat -o umask=0000,fmask=0111,dmask=0000,shortname=mixed "$@"

注意,这是全局修改vfat的挂载参数。KDE最后还是调用mount挂载的设备,因此有了这个文件你就可以自定义KDE等不支持的参数了。

EOF

Test

August 6th, 2009

2009/08/06 13:44

修改Twitter Opera Widget的API地址

July 30th, 2009

看到@yegle兄把TwitterFox的API改成了第三方的twitter api proxy,想到Opera的那个Twitter Widget估计也可以改。

找到Twitter Opera Widget的wgt文件,发现它其实就是个zip,解压后把所有文件浏览了遍,基本确定是要对script/twitter-api.js这个文件动手。它原来是直接调用Twitter官方的API,现在我通通把http[s]://twitter.com的地方改成第三方API,比如http://nest.onedd.net/api。嗯,应该是这样。

可是,它竟然提示”Could not contact Twitter.”。我没想通为啥不行,直接调用nest.onedd.net的API是可以的。后来google、抓包都没啥结果。源代码也没看出啥不对劲的。然后跑到论坛的Widget,发现竟然已有前人做了此事[1],主要方法就是上面说的。不过,关键得在config.xml里加一句“nest.onedd.net”。OMG,终于可以了!

host标签是限定widget可访问的主机名。文档[2]中明确注明了这点,可我懒得去翻,结果就是为了这个弄了一个上午 =.=!! 不看文档的下场。。。

我对原来的Twitter Opera widget_7206_3.22.wgt做了修改,重新打包,想直接用的朋友点这里下载。
下面贴个diff就能明白对原来的Twitter Opera Widget做了些啥:

diff -u -r Twitter Opera widget_7206_3.22/config.xml Twitter Opera widget_7206_3.22-nest.onedd.net/config.xml
--- Twitter Opera widget_7206_3.22/config.xml	2009-06-05 11:00:34.000000000 +0800
+++ Twitter Opera widget_7206_3.22-nest.onedd.net/config.xml	2009-07-30 10:46:22.000000000 +0800
@@ -17,9 +17,10 @@
       <protocol>http</protocol>
       <host>api.twitter.com</host>
       <host>twitter.com</host>
+      <host>nest.onedd.net</host>
       <host>maps.google.com</host>
       <host>widgets.opera.com</host>
       <host>search.twitter.com</host>
     </access>
   </security>
-</widget>
\ No newline at end of file
+</widget>
diff -u -r Twitter Opera widget_7206_3.22/script/twitter-api.js Twitter Opera widget_7206_3.22-nest.onedd.net/script/twitter-api.js
--- Twitter Opera widget_7206_3.22/script/twitter-api.js	2009-06-05 11:00:34.000000000 +0800
+++ Twitter Opera widget_7206_3.22-nest.onedd.net/script/twitter-api.js	2009-07-30 10:46:50.000000000 +0800
@@ -57,7 +57,7 @@
     publicTimeline: function(callback, errorCallback) {
       makeRequest({
         method: 'GET',
-        url: 'http://twitter.com/statuses/public_timeline' + format,
+        url: 'http://nest.onedd.net/api/statuses/public_timeline' + format,
         callback: callback,
         errorCallback: errorCallback
       }, true);
@@ -69,7 +69,7 @@
 
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/statuses/friends_timeline' + format + query,
+        url: 'http://nest.onedd.net/api/statuses/friends_timeline' + format + query,
         headers: {
           'Cache-Control': 'no-cache'
         },
@@ -86,7 +86,7 @@
 
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/statuses/user_timeline' + format + query,
+        url: 'http://nest.onedd.net/api/statuses/user_timeline' + format + query,
         headers: {
           'Cache-Control': 'no-cache'
         },
@@ -101,7 +101,7 @@
     show: function(id, callback, errorCallback) {
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/statuses/show/' + id + format,
+        url: 'http://nest.onedd.net/api/statuses/show/' + id + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -115,7 +115,7 @@
 
       makeRequest({
         method: 'POST',
-        url: 'https://twitter.com/statuses/update' + format + query,
+        url: 'http://nest.onedd.net/api/statuses/update' + format + query,
         headers: {
           'Content-Type': 'application/x-www-form-urlencoded'
         },
@@ -134,7 +134,7 @@
 
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/statuses/mentions' + format + query,
+        url: 'http://nest.onedd.net/api/statuses/mentions' + format + query,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -146,7 +146,7 @@
     destroy: function(id, callback, errorCallback) {
       makeRequest({
         method: 'DELETE',
-        url: 'https://twitter.com/statuses/destroy/' + id + format,
+        url: 'http://nest.onedd.net/api/statuses/destroy/' + id + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -160,7 +160,7 @@
     friends: function(callback, errorCallback) {
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/statuses/followers' + format,
+        url: 'http://nest.onedd.net/api/statuses/followers' + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -174,7 +174,7 @@
     directTimeline: function(callback, errorCallback) {
       makeRequest({
         method: 'GET',
-        url: 'https://twitter.com/direct_messages' + format,
+        url: 'http://nest.onedd.net/api/direct_messages' + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -188,7 +188,7 @@
     verifyCredentials: function(username, password, callback, errorCallback) {
       makeRequest({
         method: 'HEAD',
-        url: 'https://twitter.com/account/verify_credentials' + format,
+        url: 'http://nest.onedd.net/api/account/verify_credentials' + format,
         username: username,
         password: password,
         callback: callback,
@@ -200,7 +200,7 @@
     endSession: function(callback) {
       makeRequest({
         method: 'POST',
-        url: 'http://twitter.com/account/end_session',
+        url: 'http://nest.onedd.net/api/account/end_session',
         callback: callback,
         errorCallback: callback
       });
@@ -212,7 +212,7 @@
     createFriendship: function(id, callback, errorCallback) {
       makeRequest({
         method: 'POST',
-        url: 'https://twitter.com/friendships/create/' + id + format,
+        url: 'http://nest.onedd.net/api/friendships/create/' + id + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -224,7 +224,7 @@
     destroyFriendship: function(id, callback, errorCallback) {
       makeRequest({
         method: 'DELETE',
-        url: 'https://twitter.com/friendships/destroy/' + id + format,
+        url: 'http://nest.onedd.net/api/friendships/destroy/' + id + format,
         username: this.username,
         password: this.password,
         callback: callback,
@@ -236,7 +236,7 @@
     friendshipExists: function(id, callback, errorCallback) {
       makeRequest({
         method: 'POST',
-        url: 'https://twitter.com/friendships/exists/' + id + format,
+        url: 'http://nest.onedd.net/api/friendships/exists/' + id + format,
         username: this.username,
         password: this.password,
         callback: callback,

[1] Opera Twitter Widget 复活手记 – http://bbs.operachina.com/viewtopic.php?f=38&t=49093
[2] Opera Widget 规范 1.0 第三版 – http://kb.operachina.com/node/98#xml_security

我的常用Twitter应用列表

July 28th, 2009

伟大的国家都是相似的,流氓的政府各有各的无耻行径。

下面列出了我常用的Twitter应用服务,做个备份,也方便路人 ;-)

浏览器网页类:

桌面客户端:

手机端:

  • Opera Mini – Opera公司荣誉产品,直接访问twitter.com即可
  • Twibble – 速度很快,功能强大,不过看不了@自己和D自己的推

Email方式:

  • TwitterMail – 非常赞!可以发推、图片到推特,@你自己邮件提醒(非即时),获得朋友的timelines,发送大于140字的推,定时发送,推特挂了也不要紧,会帮你暂存,等好了再发上去。

另外,还有SOCIALSCOPE,Gravity等据说异常强大的应用,可惜我还没用过。

Twitter API Proxy:

BTW,有了推特wiki我更懒的更新这个博客了 :(

乌龙

May 14th, 2009

上周末发了篇博文,希望有朋友能看到并帮我测试一下。结果至今无一个留言 :( 今天越发不对劲了,从别的超链接到我这个网站统统显示403,被禁止访问!而通过手工在浏览器的地址栏输入一摸一样的地址却能正常访问。。。我把这个疑问发到了twitter上,很快 @rainux 提醒我是不是限制了HTTP_REFERER。我立马登录空间,查看.htaccess的内容,确实有两条HTTP_REFERER的规则在里面。不过我对这个HTTP_REFERER很陌生,为了确定是否是它引起的,我先后把我自己添加的规则和HTTP_REFERER注释掉。结果正如 @rainux 所说。

RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?vvoody.org/?.*$ [NC]
RewriteRule \.*$ - [F,NC]

G了一下,明白了HTTP_REFERER干嘛的。上面的两条规则意思是HTTP_REFERER不为空或者不是从vvoody.org链接来的统统禁止访问。因为从浏览器输入地址时HTTP_REFERER为空,因此不会被禁止访问。

唉,这个乌龙好啊!

参考:
http://ted.is-programmer.com/posts/4460.html
http://zh.wikipedia.org/wiki/HTTP_referrer
http://www.fwolf.com/blog/post/320