Keyboard shortcuts in Google Wave

I was messing around a little with figuring out some of the keyboard shortcuts in Wave. Here is what I have so far:

While editing:
Shift + Enter: Shortcut for “Done”
Control + Enter: Add comment

Control + b: Bold
Control + i: Italics
Control + u: Underline
Control + g: Poor mans CSS editor (JavaScript text input)
Control + a: Beginning of line
Control + e: End of line
Control + k: Hyperlink
Control + 1/2/3/4: Convert to h1 / h2 / h3 / h4
Control + 5: Bullets
Control + 6: Drops all formatting

While viewing:
Control + r: Reply
Enter: Same as Control + r
Control + e: Edit
Shift + Enter: Start replying at the end
Control + Space: Mark all as read

I’ll update this post if I find any more…

1 Comment

Simple HTTP POST in Java

Today I was helping a friend debug a web service they had implemented. Their side was working correctly but the developer who was trying to interface with it seemed to be running into many problems. Since they were integrating an application written in Java, I whipped up a simple test for them. All we really needed to do was to send a few variables using HTTP POST to this resource and make sure it returned exactly what we were expecting.

This uses standard libraries only, and doesn’t require anything third party. It does nothing fancy at all, just simply posts data to a URL. Hopefully you find this useful at some point.

src/postit/Main.java

/*
 * Java POST Example
 */

package postit;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Greg
 */
public class Main {

    /**
     * Pretend you're a script...
     */
    public static void main(String[] args) {
        final String server = "somewhere.ontheinter.net";

        URL url = null;
        try {
            url = new URL("http://" + server + "/we-expect-post/data");
        } catch (MalformedURLException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        HttpURLConnection urlConn = null;
        try {
            // URL connection channel.
            urlConn = (HttpURLConnection) url.openConnection();
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Let the run-time system (RTS) know that we want input.
        urlConn.setDoInput (true);

        // Let the RTS know that we want to do output.
        urlConn.setDoOutput (true);

        // No caching, we want the real thing.
        urlConn.setUseCaches (false);

        try {
            urlConn.setRequestMethod("POST");
        } catch (ProtocolException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        try {
            urlConn.connect();
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        DataOutputStream output = null;
        DataInputStream input = null;

        try {
            output = new DataOutputStream(urlConn.getOutputStream());
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Specify the content type if needed.
        //urlConn.setRequestProperty("Content-Type",
        //  "application/x-www-form-urlencoded");

        // Construct the POST data.
        String content =
          "name=" + URLEncoder.encode("Greg") +
          "&email=" + URLEncoder.encode("greg at code dot geek dot sh");

        // Send the request data.
        try {
            output.writeBytes(content);
            output.flush();
            output.close();
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Get response data.
        String str = null;
        try {
            input = new DataInputStream (urlConn.getInputStream());
            while (null != ((str = input.readLine()))) {
                System.out.println(str);
            }
            input.close ();
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

, ,

6 Comments

Using a ZFS filesystem with Time Machine

This simple how-to explains how to get your Time Machine backups working with a ZFS filesystem. This allows you to use the features of ZFS filesystems for your Time Machine backups.

Please note this is for Mac OS X – Snow Leopard.

1) Enable unsupported network volumes on your Mac by opening a Terminal and pasting this:

greg@macbook:~ %> defaults write com.apple.systempreferences TMShowUnsupportedNetworkVolumes 1

2) Create a new ZFS filesystem and enable CIFS access to it:

greg@opensolaris:~ %> zfs create tank/userbackups
greg@opensolaris:~ %> zfs set sharesmb=on tank/userbackups
greg@opensolaris:~ %> zfs set sharesmb=name=userbackups tank/userbackups
greg@opensolaris:~ %> zfs set aclmode=passthrough tank/userbackups
greg@opensolaris:~ %> zfs set aclinherit=passthrough tank/userbackups

You will probably want to setup the correct permissions on your new share, more details in [this post].

3) Make sure you can mount this share and write to it from your Mac.

4) Create the correct disk image:

greg@macbook:~ %> /bin/bash
greg@macbook:~ %> cd /Volumes/userbackups
greg@macbook:~ %> SYSNAME=`scutil --get ComputerName`
greg@macbook:~ %> hdiutil create -size 600G -fs HFS+J \
> -volname 'Time Machine Backups' -type SPARSEBUNDLE "${SYSNAME}.sparsebundle"
greg@macbook:~ %> UUID=`system_profiler | grep 'Hardware UUID' | awk '{print $3}'`
greg@macbook:~ %> cat << EOF > "${SYSNAME}.sparsebundle"/com.apple.TimeMachine.MachineID.plist
> <?xml version="1.0" encoding="UTF-8"?>
> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
> <plist version="1.0">
> <dict>
>         <key>com.apple.backupd.HostUUID</key>
>         <string>$UUID</string>
> </dict>
> </plist>
> EOF
greg@macbook:~ %>

5) and finally, open up Time Machine. You should now see your network share as an option. Choose it, configure any excludes you want and kick off your first backup!

I’ll post a little later on restoring these backups using one of these methods:

  • Restore from Time Machine by using the boot disk
  • or by doing a standard install then using the Migration Assistant.

Good luck!

, ,

2 Comments

New addition to the family

So after much anticipation, many tireless weeks of spec checking and a whole bunch of money I was really glad to be on the receiving end of a brand spanking new 17″ MacBook Pro!

Although it has been less than 72 hours this is (so far) truly the most amazing piece of computer equipment I have ever owned, honestly worth every single cent! The initial setup was completely flawless and only took a few minutes. The graphics, resolution and performance are exceptional, in fact I haven’t even had to switch over from the standard “on-board” video card to the higher end controller yet, despite putting the GPU through a thorough beating, testing out the Quake 4 demo :P

The specs in this machine are as follows:

  • Intel(R) Core(TM)2 Duo CPU     T9600  @ 2.80GHz
  • 4GB RAM
  • 500GB 5400rpm hard disk
  • 17″ – 1920 x 1200 native resolution

I plan to upgrade the memory to 8GB and the drive to the 7200rpm model sometime in the future.

The packaging ... Please excuse the terrible quality of my 300 year old camera

The packaging ... please excuse the terrible quality of my 300 year old camera

Here are a few more (slightly better quality) pics:

The desk

My desk

A little closer

A little closer

Yo dawg, we herd yo like laptop pics...

Yo dawg, we herd yo like laptop pics...

A little closer, this should look familiar...

This should look familiar...

Another one

That trackpad really is awesome!

The rest of the network.

The rest of the network.

From the left, OpenBSD 4.5 gateway, proxy and firewall, ADSL router and OpenWRT wireless router on top of the 3com gigabit switch. The main workhorse (storage, virtual machines and build zones) is underneath that with a quad core processor, 4GB of RAM and just over 2TB of disk space in 2 ZFS pools (3 x 500GB and 3 x 640GB) running OpenSolaris and finally 2 x 2kVA UPSs.

5 Comments

Quick Solaris zone stats

Add this:

alias zonestat="prstat -vZ 1 1 | grep -A50 '^ZONEID'"

to your ~/.profile and you should see something like this when running it:

root@tank:~# zonestat
ZONEID    NPROC  SWAP   RSS MEMORY      TIME  CPU ZONE
     0       58 1198M 1206M    30%  16:15:40 1.7% global
     6       25  172M  175M   4.4%   0:03:14 0.0% cl-build
     2       27   48M   31M   0.8%   0:00:47 0.0% mirror
Total: 110 processes, 534 lwps, load averages: 0.09, 0.08, 0.07

,

No Comments

The basics of ZFS ACLs

This post was mostly inspired by reading this post in trying to get my head around the ZFS ACL and permission system.

Basically I have a pool set out as follows:

tank
tank/media
tank/zones

tank/media is served via CIFS and NFS to multiple clients on my network, each with their own unix account on the OpenSolaris server. tank/zones is used for extra zones running on the host.

Everything was working great until I found that files and directories created by clients ended up looking like this:

----------  1 greg staff 734310400 2009-07-18 19:10 foo.txt
d---------  2 greg staff        19 2008-12-06 14:10 Bar

This sure didn’t go down well when other users needed to access those files or directories.

So in following the above mentioned post I did this:

 # zfs set aclinherit=passthrough tank/media
 # zfs set aclmode=passthrough tank/media
 # /bin/chmod 0774 /tank/media
 # /bin/chmod -R A- /tank/media
 # /bin/chmod -R A=owner@:full_set:fd:allow /tank/media
 # /bin/chmod -R A+group@:full_set:fd:allow /tank/media
 # /bin/chmod -R A+everyone@:read_set:fd:allow /tank/media

A better description of what the flags / syntax mean can be found here and here

A simple breakdown:

  • First off, we tell ZFS that all files or directories must inherit all acls / permissions from their parent.
  • We use /bin/chmod as the chmod in the default path is the GNU chmod which does not understand ZFS acls.
  • The second chmod “A-” will remove all acls currently set on the object.
  • We then set the owners permission to the “full_set”, thus giving the owner all possible permissions.
  • We do the same for the group.
  • Finally, we give everyone else read access.

, ,

2 Comments

Simple OpenVPN Server Statistics

Ever wondered what the status of your OpenVPN server is, or wanted some simple stats ?

Here is a really simple script to give you some info. The output looks like this:

greg@leonis:~$ vpnstatus
Common Name   Virtual Address    Real Address          Sent      Received             Connected Since
greg.vpn      10.8.142.6         196.9.23.59        1.11 MB     282.50 KB    Thu Jul 16 09:07:15 2009

Firstly, ensure your server is actually saving the stats somewhere (/etc/openvpn/openvpn.status in my example):

greg@leonis:~$ grep status /etc/openvpn/server.conf
status openvpn.status

Once that is happening, drop this code into a file and make it executable.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

STATUS = "/etc/openvpn/openvpn.status"

status_file = open(STATUS, 'r')
stats = status_file.readlines()
status_file.close()

hosts = []

headers = {
    'cn':    'Common Name',
    'virt':  'Virtual Address',
    'real':  'Real Address',
    'sent':  'Sent',
    'recv':  'Received',
    'since': 'Connected Since'
}

sizes = [
    (1<<50L, 'PB'),
    (1<<40L, 'TB'),
    (1<<30L, 'GB'),
    (1<<20L, 'MB'),
    (1<<10L, 'KB'),
    (1,       'B')
]

def byte2str(size):
    for f, suf in sizes:
        if size >= f:
            break

    return "%.2f %s" % (size / float(f), suf)

for line in stats:
    cols = line.split(',')

    if len(cols) == 5 and not line.startswith('Common Name'):
        host  = {}
        host['cn']    = cols[0]
        host['real']  = cols[1].split(':')[0]
        host['recv']  = byte2str(int(cols[2]))
        host['sent']  = byte2str(int(cols[3]))
        host['since'] = cols[4].strip()
        hosts.append(host)

    if len(cols) == 4 and not line.startswith('Virtual Address'):
        for h in hosts:
            if h['cn'] == cols[1]:
                h['virt'] = cols[0]

fmt = "%(cn)-25s %(virt)-18s %(real)-15s %(sent)13s %(recv)13s %(since)25s"
print fmt % headers
print "\n".join([fmt % h for h in hosts])

,

No Comments

Run something as another user

Here is a simple way to run something on UNIX / Linux as another user, without having to resort to weird sudo incantations. The Makefile is left as an exercise for the reader.

This has only been tested on FreeBSD, Debian Linux and OpenSolaris so far.

Compile with: cc -o setuid setuid.c

#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h> 

int main(int argc, const char **argv) {

    /* Check command line */
    if (argc < 3) {
        fprintf(stderr, "Usage: %s user cmd\n", argv[0]);
        return 1;
    }

    struct passwd *pw;
    pw = getpwnam(argv[1]);

    if (pw==NULL) {
        fprintf(stderr, "User not found\n");
        return 1;
    }

    if (initgroups(argv[1], pw->pw_gid)==-1) {
        perror("initgroups");
        return 1;
    }

    if (setregid(pw->pw_gid, pw->pw_gid)==0 &&
        setreuid(pw->pw_uid, pw->pw_uid)==0) {
        argv += 2;
        execvp(argv[0], argv);
        perror("exec");
        return 1;
    }

    perror("setre[gu]id");
    return 1;

}

, ,

No Comments

Hello world!

So I finally decided to join the world of blogging. Let’s hope I have something interesting to say…

No Comments