howto: use google voice on dialup

Here at the Hollow in what can euphemistically be called a "cabin", there is no cell service, and I have not signed up for long distance, as all the phone is normally used for is to call a local dialup internet number.

It would be nice to be able to occasionally make outgoing long distance calls in this situation though. With some difficulty, Google voice can be used in this situation.

The difficulty is because my google voice number is itself a long distance call from here, so I can't just call it up and use it as intended. And google voice's web interface was not designed with dialup in mind; it assumes you have a phone line that's not carrying your internet connection.

verifying the phone

This is the tricky part. Google voice wants to call you back on your phone and have you verify a number. To start this process, you need to use the internet. It's impossible to hang up from the net before they call back.

Since it's a one-off, I didn't try to automate this, and just had someone do the internet side of the verification, call me with the number, and then trigger google voice to call me back.

(Google voice's forums are full of similar stories.)

making outgoing long distance calls

Here's the fun part. You'll need a shell account somewhere else, where you can download and install pygooglevoice.

Then create a ~/.gvoice file, filling in your login info and your phone number.



Now to make a call, you can just run something like this on the shell account:

sleep 2m; ./gvoice --batch call XXX-XXX-XXXX

The sleep gives you time to free up the phone line. Google voice then calls you and connects you with the number you requested.

xmonad layouts for netbooks

How a window manager with tiling layouts makes a 1024x600 resolution livable.

I spend a lot of time in front of a screen, and for the past year I have used only my netbook. No desktops, no laptops. Just a cheap $200 computer that is nearly a kid's toy. The most challenging thing about this is dealing with the 1024x600 screen resolution. When I moved to this screen from something with many more pixels, it at first seemed very cramped.

After a year, I think that the most important part of coping with the small screen is the flexability and customizability of layouts provided by the Xmonad window manager. In this post I will explain a few layouts I have developed for fitting specific tasks onto the netbook screen.

(I assume that you know maybe 0.1% of Haskell (about what I do), and can read Haskell code without having a brain aneurysm.)

web browsing

It's important that the web browser have as few toolbars and other cruft as possible, as horizontal space is especially at a premium. I've configured both Epiphany and Firefox to put everything in one tool bar. But now I use Chromium, which comes pre-configured that way.

What the netbook is designed for. You just want a web browser, taking up the full screen, and with its own tabs. So far, so easy: that's Xmonad's Full layout.

But, you sometimes want to see two websites side-by-side. This layout accomplishes that, allowing the sizes to be adjusted as needed. It also uses Xmonad's Magnifier to zoom the smaller window when it's focused, which is useful if you briefly need to see more of a web site.

mySplit = magnifiercz' 1.4 $ Tall nmaster delta ratio
        -- The default number of windows in the master pane
        nmaster = 1
        -- Percent of screen to increment by when resizing panes
        delta   = 3/100
        -- Default proportion of screen occupied by master pane
        ratio   = 60/100

screenshot     screenshot

web development

If you're doing web development, you'll want probably one big web browser window, but also with a nice wide terminal on the same screen, in order to see web server logs. A simple way to do this is to mirror Xmonad's Tall layout by 90 degrees.

myWide = Mirror $ Tall nmaster delta ratio
        -- The default number of windows in the master pane
        nmaster = 1
        -- Percent of screen to increment by when resizing panes
        delta   = 3/100
        -- Default proportion of screen occupied by master pane
        ratio   = 80/100

The myWide layout uses the full screen width for a single terminal, and splits the width when there are more. This is sufficient for viewing logs and doing minor things at the shell prompt, in between testing the result in the web browser. Of course a terminal can be temporarily moved to the master area by pressing mod-return, if you need it to be larger.

screenshot     screenshot


Tips on configuring Pidgin for a netbook: Set it up to use vertical tabs to save horizontal space. Configure the input area to only 1 line tall, and turn off as much other cruft as you can. The menu bars, sadly, cannot be disabled, nor can the excessively large borders. In my screenshots, you can see that stuff wasting space that could be used to show four more lines of text.

For chat, you want to put the buddy list on the side of the screen, and use the rest for conversation windows. Xmonad's IM module takes care of allocating a sidebar on the screen for the buddy window. The rest of the screen can be occupied by any layout you choose.

I like to have the conversation windows be as wide as possible, and typically only want to see two conversations at a time. But sometimes I might have a dozen visible. A good layout to cope with those needs is Grid.

myChat' l = withIM size roster l
        -- Ratio of screen roster will occupy
        size = 1%5
        -- Match roster window
        roster = Title "Buddy List"
myChat = myChat' Grid

The withIM layout puts the buddy list on the left; I prefer it on the right for some reason, so I tweaked my layout to do that. All it took was using the reflectHoriz layout modifier to get a mirror image of the layout. Then I reflect the inner layout back to its normal orientation. Being able to throw in a few function calls and mutate a layout like that is where Xmonad shines.

myChat' l = reflectHoriz $ withIM size roster $
            reflectHoriz $ l

screenshot     screenshot


Ever notice that tail -f wastes the last line of the terminal? On a netbook this matters. shorttail lets the last line be used.

I have a dedidated workspace that I use to tail logs, and as a place to send long-running tasks (such as compiles). The layout for this needs to keep windows wide, to see whole logged lines, but they need only be five or so lines tall. And it's convenient to have one bigger window with the small ones below. Xmonad has a perfect layout for this, called Dishes, because it's sorta like a stack of plates.

myDish = limitWindows 5 $ Dishes nmaster ratio
        -- The default number of windows in the master pane
        nmaster = 1
        -- Default proportion of screen occupied by other panes
        ratio = 1/5

screenshot     screenshot

In the second screenshot above, I have 6 windows open, but only 5 are visible. The limitWindows 5 accomplishes this. It's handy mostly because gnome-terminal has a bad habit of crashing when resized to 0x0. (That's gotta be a bug in something!)

Notice that the screenshots above also have Procmeter in a sidebar on the right. I (ab)used the withIM layout to do that:

myLogs' l = reflectHoriz $ withIM procmeterSize procmeter $
              reflectHoriz $ l
        -- Ratio of screen procmeter will occupy
        procmeterSize = 1%7
        -- Match procmeter
        procmeter = ClassName "ProcMeter3"
myLogs = myLogs' myDish


Everything above was easy. Doing coding (or writing) on a small screen is where it gets hard. When I'm coding I want to have one window that is exactly 80 columns wide, and as tall as possible, where I do the main development. Then I need a minimum of two other windows also visible: one action window for running tests and the like, and one to view references (emails, man pages, other files, etc). I may need to view any of these windows fullscreen at any time, of course.

As an example of the three window rule: While I'm writing this post, I have a reference window open with my .xmonad/xmonad.hs, and an action window open where I am managing screenshots.

Often, deep into something, I will accumulate many other reference and action windows. But three seems to be the magic, minimum number for me; use of screen or anything else doesn't reduce it; if I can't see all three at once, I will waste time and concentration flipping back and forth.

The Xmonad layout I've developed to handle this is based on the handy FixedColumn layout, which automatically keeps the master window 80 columns wide. I modify this by adding the Magnifier, so that my action and reference windows "pop out" over the master window when focused.

myCode = limitWindows 3 $ magnifiercz' 1.4 $ FixedColumn 1 20 80 10

The 1.4 was carefully tuned, to magnify a window to be big enough to be usable for editing, reading, or command-line, while not obscuring too much of the other two windows. And again I use limitWindows, to only show three windows, in order to keep them all as big as possible. Other windows are hidden offscreen, but I can rotate to them with alt-tab as needed.

screenshot     screenshot

reading ebooks

When I'm reading an ebook, I want to have the reader in the middle of the screen. But a widescreen netbook like mine is too wide to comfortably read text all the way across, so it's nice to have margins. (In the margins, I put terminals, or notes.)

The ThreeColumns module has a layout that does just that.

myBook = ThreeColMid nmaster delta ratio
        nmaster = 1
        -- Percent of screen to increment by when resizing panes
        delta   = 3/100
        -- Default proportion of screen occupied by master pane
        ratio   = 2/3


putting it all together

I join all my layouts together, so Xmonad will allow switching between them. These three combinations all have the same layouts, only ordered differently, so that different workspaces can have different default layouts.

codeFirst  = myCode  ||| myWide  ||| myGrid  ||| myDish
dishFirst  = myDish  ||| myCode  ||| myWide  ||| myGrid
gridFirst  = myGrid  ||| myDish  ||| myCode  ||| myWide

Xmonad layouts can be decorated with things like title bars. To save horizontal space, I leave off the title bars on layouts where they are not needed to disambiguate windows. Two helper functions can be applied to add or remove titles (and also cause the layout to avoid the Gnome panel.)

withTitles l = noFrillsDeco shrinkText myTheme (desktopLayoutModifiers l)
noTitles l = desktopLayoutModifiers l

I have semi-dedicated workspaces; one for each of the above activities. To assign a layout to a workspace, I use the PerWorkspace module.

Notice that the "web" workspace has only two available layouts. Meanwhile, the "book" workspace always uses the specialized myBook layout; I don't use that layout elsewhere.

perWS = onWorkspace "logs" (withTitles $ myLogs' dishFirst) $
        onWorkspace "web"  (noTitles   $ (mySplit ||| myWide)) $
        onWorkspace "chat" (noTitles   $ myChat' gridFirst) $
        onWorkspace "book" (noTitles   $ myBook) $
                           (withTitles $ codeFirst)

Finally, I allow toggling between the currently selected layout and fullscreen mode, and use smartBorders to avoid displaying borders when there is only one window onscreen.

myLayout = smartBorders $ toggleLayouts Full perWS

Xmonad layout optimised for the small screen of a netbook: Done! (For now...)

  screenshots   screenshots   screenshots   screenshots   screenshots  

PS: Thanks to #xmonad regulars for always having the answer I need up their sleeves.

Debian Installer USB partitioning

The Debian Installer has a USB boot image that is supposed to be written raw to the USB stick. Problem is that means the image has a fixed size, 256 MB, and all the other gigabytes on your typical USB stick are unusable after writing this image to it. It's a bit of a cheat for me to blog about this, since I'm slighly responsible for the thing that I'm working around. But still.

The real problem is not the fixed size of the image, but that it is written to the USB stick as a "super floppy", without any partition table. If there were a partition table, you could make a second partition in the rest of the space of the USB stick, and use it to store files, etc. So, let's set that up. It's not really hard:

  1. Use your favorite partition tool to partition the USB stick. Make a 256 MB DOS partition, that will be used to hold the installer, and set it bootable. Make a second, or more partitions for your own use.
  2. Unplug and replug the USB stick so the device files show up, and write the installer image to the first partition (/dev/sda1 in this example). zcat boot.img >/dev/sda1
  3. Make the USB stick bootable by installing the mbr package, and running install-mbr /dev/sda
  4. Copy a Debian ISO image to the USB stick somewhere. You can put it on the first partition, as ususal, or if you prefer, can put it on one of the later, larger partitions. That's useful if you want to use a larger ISO than the netinst. The installer should find it anywhere.
debian desktop via vnc on the palm pre

On my Palm Pre, I'm running a PalmOS emulator, with a VNC client in it, connecting to a VNC server on the same phone, in order to get to an xterm. About the most roundabout way to get a prompt on a phone one can imagine, and also very wasteful of screen space because of that PalmOS emulator. I hope better methods become available.

  1. Install Classic. (This will only work for a week w/o being bought, BTW.)
  2. Login as root to Debian chroot on the Pre.
  3. Download PalmVNC: cd /media/internal/ClassicApps; wget
  4. apt-get install tightvncserver
  5. su to your non-root account on the Pre.
  6. Create a VNC password file containing an empty password by running: mkdir -p ~/.vnc; echo | vncpasswd -f > ~/.vnc/passwd; chmod 600 ~/.vnc/passwd
  7. Run tightvncserver -interface -geometry 320x320
    (It only listens to localhost, so the empty password is ok (ish). The geometry is all that is visible on the Pre without scrolling.)
  8. Start Classic (or restart it).
  9. In Classic, select PalmVNC and tell it to connect to localhost, and change the display from 0 to 1. It will prompt for a password, just hit Ok for a blank one.
  10. Optionally, make VNC auto-start on boot, add to rc.local something like this: su joey -c 'cd; tightvncserver -interface -geometry 320x320'


  • Need way to send arrow keys!
  • xmonad configuration

Update: Here's another screenshot, of FBReader on the Pre. One of the missing apps on the Pre is a good offline e-book reader with no vendor lock-in. FBReader is what I use on my laptop, so it's great to have it on my phone. Especially since I have room for my entire e-book library on the Pre's generous flash drive.

Previously: Palm Pre, ssh to Debian chroot on Palm Pre

ssh to debian chroot on palm pre

Following setting up a Debian chroot on the Pre, I wanted to be able to ssh into the chroot. This sets that up, including starting Debian's ssh on boot.

Actually, I went a step further and hooked Debian's rc3.d init scripts into the Pre's webOS boot process, so all services installed in Debian will start on boot. So as a bonus, cron, at, etc will also work.

Since the Pre's EVDO IP address changes all the time, dynamic DNS is needed if you want to ssh in over EVDO. I used ez-ipupdate to handle that. (If you didn't want to ssh via EVDO, you could omit that part, and add "-i eth0" to both the firewall rules below.)

  1. Use novacom and the cable to get a root shell, and chroot /media/debian
  2. Install ssh: apt-get install openssh-server openssh-client
  3. Hook Debian init scripts into webOS startup:
     cat > /media/pre-root/etc/event.d/debian_start <<EOF
     # start Debian services after webOS boot is complete to avoid delaying that
     start on stopped finish
         echo "Starting Debian services..."
         chroot /media/debian /etc/init.d/rc 2
     end script
  4. Edit /etc/rc.local and add these commands to it:
     # Add firewall rule to allow SSH access on port 22,
     # both wifi and EVDO.
     iptables -D INPUT -p tcp --dport 22 -j ACCEPT || true
     iptables -I INPUT -p tcp --dport 22 -j ACCEPT
  5. If you want to easily be able to determine the phone's address while it's on wifi, you can use mDNS (hostname.local): apt-get install avahi-daemon
  6. Install ez-ipupdate: apt-get ez-ipupdate
  7. Follow the prompts to configure ez-ipupdate to use dyndns or your preferred dyanmic DNS service. When it asks if ppp is used, say yes.
  8. Edit /etc/ez-ipupdate/default.conf and make sure the interface is set to 'ppp0' (See Debian bug #536512 for why you need to do this currently.)
  9. Hook ez-ipupdate into webOS's ip-up script, so it is run whenever the address changes:
     echo '#!/bin/sh' > $HOOK
     echo '/usr/sbin/chroot /media/debian /etc/ppp/ip-up.d/ez-ipupdate >/dev/null 2>&1 || true' >> $HOOK
     chmod a+x $HOOK
  10. Reboot the Pre. Now is a good time to learn that Orange+Sym+R on the keypad is equivilant to ctrl-alt-del. :-) The ssh daemon should be started automatically.


  • Enable avahi for mdns for wifi.
  • Cleanly stop Debian services on shutdown. Problem with doing this is that I can't just run '/etc/init.d/rc 0', because that will kill every process in webOS too, and hang the phone. It'd need a very custom runlevel to be set up in Debian.
palm pre


I got a Palm Pre because I want a Linux smartphone that is not locked down (like the G1), on which I can play with Debian and even my own kernel. But I also want one that seems likely to be widely viable going forward, and there I have my doubts about OpenMoko, sadly.

(Or, I got a Palm Pre because I dreamt last night that I got one, and decided my subconcious must have made a decision after dithering for years. And because Madduck rules.)

Debian chroot on Palm Pre

This is a quick installation of a Debian chroot on the Palm Pre phone. Similar methods are explained at the Pre Dev Wiki. But they have you download a prebuilt Debian image and modify the Palm webOS system to install ssh; I preferred to use debootstrap and keep webOS changes to a minimum.

Also, since the Pre uses LVM, its media partition can be resized on the fly. That allows for a larger Debian filesystem, and I think is a nicer method than the loopback filesystem approach documented elsewhere, but do read the warnings about Palm, LVM, and upgrades here.

On the Pre:

  1. Enable dev mode by opening the Pre's Launcher and typing the following into it: upupdowndownleftrightleftrightbastart
  2. Plug the Pre into your laptop, tell it to charge via USB, while doing the following on the laptop.

On a Linux laptop:

  1. Run debootstrap to build an armel chroot: sudo debootstrap --foreign --arch=armel sid /tmp/debian
  2. Download novacom, cd to it, and build it: sudo apt-get install libusb-dev; make
  3. Transfer the armel chroot from the laptop to the Pre: (cd /tmp; sudo tar czv debian) | sudo ./novacom put file:///tmp/debian.tar.gz
  4. Run sudo ./novacom to get root shell on the Pre.
  5. In that shell, use LVM to resize the existing /media/internal volume down to 1 GB, and create a new 6 GB one for Debian:
     cp -a /media/internal /opt
     umount /media/internal
     lvresize -L 1G /dev/mapper/store-media
     mkdosfs -F 32 /dev/mapper/store-media
     mount /media/internal
     mv /opt/internal/ /opt/internal/. /media/internal
     rmdir /opt/internal
     lvcreate -l 100%FREE -n debian store
     mkfs.ext3 /dev/store/debian
     mkdir /media/debian
     echo "/dev/mapper/store-debian /media/debian auto noatime 0 0" >> /etc/fstab
     mount /media/debian
  6. Then in the Pre's root shell, you can unpack Debian, set up some bind mounts to allow accessing the Pre's filesystems from inside Debian, and chroot in to finish debootstrap:
     cd /media
     tar zxvf /tmp/debian.tar.gz
     mkdir /media/debian/media/pre-root /media/debian/media/internal
     echo "/ /media/debian/media/pre-root bind defaults,bind 0 0" >> /etc/fstab
     echo "/var /media/debian/media/pre-root/var bind defaults,bind 0 0" >> /etc/fstab
     echo "/var/log /media/debian/media/pre-root/var/log bind defaults,bind 0 0" >> /etc/fstab
     echo "/tmp /media/debian/tmp bind defaults,bind 0 0" >> /etc/fstab
     echo "/proc /media/debian/proc bind defaults,bind 0 0" >> /etc/fstab
     echo "/dev /media/debian/dev bind defaults,bind 0 0" >> /etc/fstab
     echo "/dev/pts /media/debian/dev/pts bind defaults,bind 0 0" >> /etc/fstab
     echo "/sys /media/debian/sys bind defaults,bind 0 0" >> /etc/fstab
     echo "/media/internal /media/debian/media/internal bind defaults,bind 0 0" >> /etc/fstab
     mount -a
     chroot debian
     /debootstrap/debootstrap --second-stage
     ln -sf /proc/mounts /etc/mtab
     ln -sf /media/pre-root/etc/resolv.conf /etc/resolv.conf
     apt-get clean
  7. Now customise the Debian chroot as usual.

How this could be made easier:

  • Make a debian package of novacom.
  • Installation script? debcoexist? d-i?


  • Install openssh in Debian and configure it to start at boot.
  • Set up VNC, install VNC app in webOS to allow accessing a Debian desktop.
editing code_swarms with kino

Last time I created a code_swarm video, I forgot to document the whole video editing process. Let's try to record the key stuff this time.

  1. Run code_swarm (see its docs). Note that its output is best if run on a display without subpixel rendering -- so desktop, not laptop.
  2. Now to convert the frames into a dv video. (There is a small quality loss here.) ffmpeg -f image2 -r 24 -i ./frames/code_swarm-%05d.png -s ntsc out.dv
  3. Import video into kino.
  4. Edit video in kino.
  5. Sound. Needs to be in wav format. Use effects, and Dub sound in to video.
  6. Final encoding. Kino's exporters are not very reliable; I have it export to a raw dv, and then use xvidenc (packaged in debian-multimedia) for the encoding.
passing mp3s from firefox to mpd

I have finally figured out how to make clicking on a mp3 link in firefox cause the url to be instantly passed to mpd and played. If this seems like it should be easy, think again..

Firefox makes it hard, because while it lets you configure an program to run for a mp3, if the mp3 is not streamed, it will first download the whole mp3, and then run your program, passing it the local file. That's not what we want: It's slow, and it's much easier to queue an url in mpd than a new file.

The trick to making it work is an addon called MediaPlayerConnectivity. Confusingly, this addon sometimes refers to itself as "MPC" -- not to be confused with "mpc(1)". Warning: This addon is a ugly mess that wants to do a lot of other stuff than play mp3s, so make sure to disable all that.

MediaPlayerConnectivity's preferences allows you to specify a command to run for mp3 files (but annoyingly, not ogg vorbis). This command will be passed the url to the mp3. So you need to write a little script that runs mpc add and starts the queued url playing.

My take on that is mpqueue, which uses the mpinsert command from mpdtoys to add the url just after the currently playing song in the playlist. Then it uses mpc next to skip to it. This way, after whatever you clicked on finishes playing, mpd continues with whatever was next in your playlist, and the music never stops.

BTW, since I use a mpd proxy this is all done on whatever remote mpd the proxy is currently set to use, rather than my laptop. Clicking on a link and having the sound system in the next room switch to playing it is pretty nice. :)

Previously: My mpd setup

git as an alternative to unison

I've used unison for a long while for keeping things like my music in sync between machines. But it's never felt entirely safe, or right. (Or fast!) Using a VCS would be better, but would consume a lot more space.

Well, space still matters on laptops, with their smallish SSDs, but I have terabytes of disk on my file servers, so VCS space overhead there is no longer of much concern for files smaller than videos. So, here's a way I've been experimenting with to get rid of unison in this situation.

  • Set up some sort of networked filesystem connection to the file server. I hate to admit I'm still using NFS.

  • Log into the file server, init a git repo, and check all your music (or whatever) into it.

  • When checking out on each client, use git clone --shared. This avoids including any objects in the client's local .git directory.

    git clone --shared /mnt/fileserver/stuff.git stuff
  • Now you can just use git as usual, to add/remove stuff, commit, update, etc.


  • git add is not very fast. Reading, checksumming, and writing out gig after gig of data can be slow. Think hours. Maybe days. (OTOH, I ran that on an Thecus.)
  • Overall, I'm happy with the speed, after the initial setup. Git pushes data around faster than unison, despite not really being intended to be used this way.
  • Note that use of git clone --shared, and read the caveats about this mode in git-clone(1).
  • git repack is not recommended on clients because it would read and write the whole git repo over NFS.
  • Make sure your NFS server has large file support. (The userspace one doesn't; kernel one does.) You don't just need it for enormous pack files. The failure mode I saw was git failing in amusing ways that involved creating empty files.
  • Git doesn't deal very well with a bit flipping somewhere in the middle of a 32 gigabyte pack file. And since this method avoids duplicating the data in .git, the clones are not available as backups if something goes wrong. So if regenerating your entire repo doesn't appeal, keep a backup of it.

(Thanks to Ted T'so for the hint about using --shared, which makes this work significantly better, and simpler.)

locking down ssh authorized keys

The way .ssh/authorized_keys is typically used is not secure. Because using it securely is hard, and dumping in passwordless ssh keys is easy. I spent about 5 hours today locking down my authorized_keys.


The only easy case is a rsync of a single directory. Follow these good instructions.

If you need to rsync multiple separate directories, it's easy to find several documents involving a Do not use, it is insecure -- it allows rsync to be run with any parameters. Including parameters that allow the remote system to rsync in a new ~/.ssh/authorized_keys. Oops. (You can probably also trick into running other arbitrary commands.) To be secure, you have to check the rsync parameters against some form of whitelist.


Locking down git should be easy. Use git-shell.

Unless you also want to be able to log in to an interactive shell on the same account. To do that, you'll need to generate a separate ssh key for git. Both git servers I use are named git.*, so I set it up like this in ~/.ssh/config on the client:

Host git.*
    IdentityFile ~/.ssh/id_rsa.git

Then on the server, I added the key to authorized_keys, prefixed with this:

command="perl -e 'exec qw(git-shell -c), $ENV{SSH_ORIGINAL_COMMAND}'"

(I also tried the simpler command="git-shell -c $SSH_ORIGINAL_COMMAND"; but it didn't work with alioth's old version of openssh, and I didn't want to worry about exposing SSH_ORIGINAL_COMMAND to the shell.)

Before arriving at that, I took a detour to using gitosis. It can make it impressively easy to manage locked down git over ssh, but you have to fit how it does things. Including repository layout, and a dedicated account. So I spent several hours changing my git repo layout. In the end, I decided gitosis was too complicated and limiting for what I needed, but I do recommend checking it out.


Like git, this is fairly easy, since svn provides svnserve. The svnbook documents how to use it in .authroized_keys. Basically, just use

command="svnserve -t"

A similar approach as used for git can be used here if you want to have a dedicated ssh key that causes svnserve to be run.

d-i daily builds

d-i has a d-i-unpack-helper that can be put in authorized_keys.


Can probably be locked down similarly to rsync, but I haven't tried yet.


The only way seems to not use duplicity over ssh at all, and instead use a different transport.

too hard

What if you want to allow locked down git, and a rsync, and maybe unison too, all to the same account? You'll end up with some nasty mismash of all of the above, and there are plenty more things using ssh as a transport that need other techniques to lock them down.

Until this becomes easier, a majority will just dump passwordless ssh keys in ~/.ssh/authorized_keys, creating exploitable trust paths that don't need to exist.