Posted by dan on Oct 14, 2009 in
Articles,
General,
Nerdy,
Programming,
essays
(Cross-posted from the Aegir group on Drupal.org. Aegir is a brilliant new framework for managing web sites built in Drupal – upgrading, migrating, enabling, disabling and so, SO much more.)
Hi all,
I just wanted to share my experience migrating an existing site to Aegir, in the hope that others will find it illuminating and it will help them avoid some of the pitfalls I encountered.
Firstly, I made sure my site was completely checked into Subversion, and then checked it out on the Aegir host in the platforms directory as “mysite-1.0″. I had decided after reading the available documentation that the best way to bring an existing site into Aegir is to import the whole Drupal distro as a platform and then migrate the “default” site into another existing platform (the latter step isn’t really necessary, but I wanted to avoid having dozens of platforms for all my existing sites – defeats the point of Aegir somewhat).
Secondly, and before I imported the site, I renamed the “sites/default” directory to “sites/mysite.client2.gravityrail.net”. Aegir ignores the “default” site. I then created a symlink, “sites/mysite.com”, because that’s how Aegir does it.
Third, I imported my database. Of course.
Now it was time to import the platform in Aegir. I crossed my fingers, toes and eyes (then uncrossed my eyes because I needed them to see the screen… and my fingers because I needed to type… so at this point only my toes were ensuring good luck came my way, and clearly toes aren’t enough because…)
BAM! It worked!
Wha..?
Awesome!
Time to import the site. Breathe. Breathe. It can’t be this easy, can it?
It wasn’t.
Okay, here I’ve hit my first problem. I wanted the site to have a generated alias to mysite.com, but I think I either forgot or didn’t see the entry box because this wasn’t baked into the generated Apache config in config/vhost.d, and my site (I later found out) wouldn’t appear when the DNS got changed. Whoops!
Lesson 1: Make sure you enter your domain aliases when you import the site in Aegir.
Secondly, the import task failed because of a missing file on a mysqldump, “/dev/fd/3″. Turns out on Debian Lenny you need to make sure you’ve got udev installed so the smart mysql script can create temporary devices containing mysql credentials.
Lesson 2: On Debian, make sure you have udev installed.
But my script was halting at another point now. It was complaining that I wasn’t using a password for MySQL! That’s weird… checked my settings.php, all seems well. Turns out the issue is that my password started with a “#” symbol. Turns out one of our components here doesn’t like certain punctuation in passwords. Similar problems will occur if your password contains } or ) or various other symbols that could be interpreted as PHP/shell/MySQL delimiters of various kinds.
Lesson 3: Don’t include punctuation in your passwords, for now. Hopefully this will be fixed shortly.
Ran it again. Got a bunch of permissions errors on files, typical stuff and I fixed it.
So, finally I got the migration script to run but, critically, by this point I was running it from the command line so I could see this debug output. This has ramifications later.
When the migration script completed, there were a lot of errors to do with missing packages. See, I’d assumed that drush or provision or one of the other smart tools underlying this system would detect the modules in the source platform and, if they were missing in the target platform, it would automatically download them.
Whoops, nope, turns out that’s not the case, for a whole bunch of really good reasons.
Lesson 4: Make sure you have all the required packages installed on your target platform before you migrate.
At this point I decided to flex my Unix command-line muscles and concocted a simple command line to port and enable the packages. As we’ll find out, I’m not as smart as I thought I was.
cp -R source-platform/sites/all/modules target-platform/sites/all
ls target-platform/sites/all/modules | xargs -y enable
See, this assumes there is just one module per directory name, and that the name of the module is the name of the directory. As y’all in Drupal-land know, this just ain’t so. Chalk one up for Java-guy-learning-Drupal.
So, thinking I was done and readying my martini, monocle, slippers, and wallet for the inevitable glory and prestige, I pointed my browser at the site: mysite.clientX.gravityrail.net.
[GARBAGE FILLS THE SCREEN]
I dropped the martini, the monocle fell out, and my crossed-toes drew blood inside my tartan slippers as I surveyed the damage. Missing images, ill-rendered Javascript, formatting completely gone, fonts in TIMES NEW BLOODY ROMAN. This was no good, no good at all. I was building a professional site, not MySpace. Back to the typing-board.
Okay, time for a few more lessons:
Lesson 5: Copy the themes, not just the modules. I had forgotten that my site’s theme was a Zen subtheme, and you can’t have a subtheme without the super-theme.
Lesson 6: As above, enable all the modules you need. Though this shouldn’t be necessary if the modules are actually present in the target platform when you migrate – Aegir was clearly making a good-faith effort to enable everything when I ran the migrate script.
Lesson 7: If you don’t migrate using the web interface, then your site will be copied as the Aegir user, not as www-data. Then you’ll find that suddenly imagecache can’t create thumbnails of your images, for example. At the very least, do a “chgrp -R www-data sites/mysite.com/files”. Hopefully this will be enough.
Lesson 8: There are plenty of Drupal modules that hate having their site renamed. ImageAPI / ImageCache is one of them. Moving from “default” to “mysite.com” meant that I had to go into the database (shudder) and do an “UPDATE files SET filepath = REPLACE(filepath,”sites/default”,”sites/mysite.com”);” so imagecache could find the files. This is a design flaw in the way that Drupal or this module handles files, and multi-site should be fixed to handle it. I strongly believe no files should have their paths stored relative to the system root in a multi-site framework, for obvious reasons.
So, after fixing permissions, filepaths, enabling required modules, testing and re-testing, I had my site imported and migrated onto Pressflow 6.14.56 and working well.
Then my client changed their DNS, and the site didn’t appear, and they got angry and changed their DNS back. Because on my final successful migration, Aegir rewrote my apache configuration without the Site Alias of mysite.com.
We re-launch today. Second time lucky. Aegir still has some rough edges for newbies, but I feel really excited that I finally have a consistent way of managing all my sites. Migrating your existing sites into Aegir is totally worth it, and I hope that my lessons above help a few people to get into this wonderful new world.
Posted by dan on Oct 5, 2009 in
Nerdy,
Programming
Based on notes from a recent BarCamp, here’s my one-minute introduction to Git, a distributed revision control system (a system for managing collaborative changes to files, usually used for software development). This is probably only useful to someone who’s already a programmer and already trying to use git, as a kind of cheat-sheet.
git notes
Links:
Step 1: Create a directory with files in it. Done? Good. Let’s proceed. “cd” into the directory and…
$ git init <- initialise repo
$ git status <- tells you which files are / are-not in version control
$ git add ... <- add files to repo, do it for each "meaningful change"
$ git commit <- commit new files to repo
$ git commit -am "Feature 1" <- commit all changed files to repo, -m "message" sets message without loading $EDITOR
$ git log <- shows recent changes
$ git config user.email "goldsounds@gmail.com" --global <- configure email notifications
$ git branch feature1 <- create a "branch" for feature1. You should branch for each feature
$ git branch <- list branches. "*" indicates current branch
$ git checkout feature1 <- switch to branch "feature1"
$ gitk --all <- horrific-looking but useful GUI
$ git merge feature1 <- merge changes on feature1 into current branch(example assumes you're on a different branch)
If you get a merge conflict, it will look like this:
Auto-merged file1
CONFLICT (content): Merge conflict in file1
Automatic merge failed; fix conflicts and then commit the result.
Hope this helps someone else as much as it helped me…
Posted by dan on Aug 11, 2009 in
Nerdy,
Programming
So, I lost my iPhone a while ago and that was annoying.
Then my annoyance doubled when I realised that my Google contacts were no longer being synced to my OS X Address Book. It turns out that Apple will only let you synchonise Google contacts when you have an iPhone connected to the system.
This would have to be the most brain-damaged mis-feature I’ve seen in years. It’s not like my laptop has to access Google through the iPhone. It’s just a completely arbitrary co-dependence of two otherwise utterly unrelated features of the computer.
Luckily there’s a workaround for those who no longer have an iPhone but enjoyed the Google Contact syncing. It’s a short script, so only really useful to those willing to get their hands dirty in the shell.
#!/bin/sh
echo "Syncing Contacts with Google"
/System/Library/PrivateFrameworks/GoogleContactSync.framework/Versions/A/Resources/gconsync --sync com.google.ContactSync
echo "Sync Complete"
There – done! I suppose you could turn it into a launchd script if you’re really keen, but I’m happy to just run it by hand from time-to-time. Eat it, Apple!
Tags: google, iphone, osx
Posted by dan on Aug 7, 2009 in
Comedy,
Programming
Last week on Melbourne’s 3RRR, Georgia Webster, Andrew Fish, Keren Flavell and myself discussed Windows 7, TinyXP, iPhone security, defamation, video games, the seeming success of the Australian ‘net filtering trial and my experiences at the Open Video Conference.
It was a really fun experience and reminded me how much I miss radio. There’s something very liberating about such a simple live medium, and you don’t have to worry about whether you’ve got food on your shirt. Radio is kind of a nerd’s dream, because it’s closer to “pure thought” than all the live visual media.
You can download the MP3 here.
Posted by dan on Dec 12, 2008 in
Programming
Ok, so I’ve been playing with Brix, a CMS toolkit from the creators of Apache Wicket that uses Apache Jackrabbit as its content repository.
And it rules.
Jackrabbit (a JCR implementation, i.e. a hierarchical repository for all sorts of content) is maturing fast, with 1.5.0 hot off the presses. It’s performance is really quite good in my simple tests (most page loads creating just one highly optimised database hit).
Wicket is also about to take a great leap, thanks to 1.4’s generics helping everyone keep their code type-safe.
Brix’s own architecture is fascinating. It currently has a very simple GUI, but don’t let that fool you – there’s a lot of hidden power underneath, and the code is very clean and well-factored (if under-commented in parts…)
The combo is a winner, so I’m actually switching the bulk of my own CMS over to Brix as of today. Lots of work, but I get the feeling it’ll all be worthwhile.
Tags: brix wicket apache jackrabbit
Posted by dan on Oct 23, 2008 in
Articles,
Programming
In my previous post, I showed how you can use Wicket’s HTML rendering engine to render HTML emails by faking a request/response cycle.
In this post, I’ll show you how to use an IVisitor to change image and anchor URLs to be absolute instead of relative. This is absolutely essential in order to make your HTML email work – otherwise all your images can’t be found, and your links point to your own mail server.
The trick is to use Wicket’s IVisitor to add a TransformerBehaviour to all the Images and Links that uses a regex to transform the URL after render but before the page is returned.
The code for the IVisitor is below:
private final class RelativeToAbsoluteUrlVisitor implements IVisitor {
private final String requestPath;
private Pattern urlPattern;
private Class<? extends Component> componentClass;
private RelativeToAbsoluteUrlVisitor(String requestPath, Class<? extends Component> componentClass, String attributeName) {
this.requestPath = requestPath;
this.componentClass = componentClass;
urlPattern = Pattern.compile(attributeName+"=\"(.*?)\"");
}
public Object component(Component component) {
//if this component is of the specified class, update the URL attribute to be absolute instead of relative
if(componentClass.isInstance(component)) {
component.add(new AbstractTransformerBehavior() {
@Override
public CharSequence transform(Component component,
CharSequence output) throws Exception {
log.warn("Transforming component output: "+output);
Matcher m = urlPattern.matcher(output);
if(m.find()){
String attributeValue = m.group(1);
int start = m.start(1);
int end = m.end(1);
//convert relative to absolute URL
String absolutePath = RequestUtils.toAbsolutePath(requestPath, attributeValue);
log.warn("Got absolute path '"+absolutePath+"' from relative path '"+attributeValue+"'");
//construct a new string with the absolute URL
String strOutput = String.valueOf(output);
String finalOutput = strOutput.substring(0, start)+absolutePath+strOutput.substring(end);
log.warn("Returning updated component: '"+finalOutput+"'");
return finalOutput;
}
return output;
}});
}
return IVisitor.CONTINUE_TRAVERSAL;
}
}
Then we override the onBeforeRender() routine to traverse the component hierarchy and add this behaviour to the appropriate elements. Note that I haven’t shown how you get the current absolute request URL, as in my system this is proprietary. There’s plenty of example code floating around on how to do that, anyway.
protected void onBeforeRender() {
super.onBeforeRender();
final String requestPath = MyCustomWebRequestCycle.get().getCurrentUrlAsString();
IVisitor imageVisitor = new RelativeToAbsoluteUrlVisitor(requestPath, Image.class, "src");
IVisitor anchorVisitor = new RelativeToAbsoluteUrlVisitor(requestPath, Link.class, "href");
visitChildren(Image.class, imageVisitor);
visitChildren(Link.class, anchorVisitor);
}
So there you have it! All the bits and pieces to create HTML email with Wicket. There’s one more catch though: You have to generate these emails in the same process as the Wicket Application. Calling Application.get() outside of the main process results in an error. In my system, I get around this by generating the HTML email source every time the user saves my Newsletter bean, which means that when it’s finally sent (in the background), it just sends the pre-generated HTML. Easy!
Tags: apache, email, html, java, marketing, render, wicket
Posted by dan on Oct 21, 2008 in
Articles,
Programming
Something that’s very desirable to do in Apache Wicket is create HTML emails using Wicket’s brilliant component-oriented markup.
I’ve been working on this problem on and off for ages — it’s tricky because of teh way that markup rendering is so deeply tied to the requestcycle, which in turn is deeply dependent on the httpservletrequest — with good reason, too. That’s where Wicket gets its autoconfiguring magic from!
So in order to use Wicket to create HTML emails, we need to fake the request/response cycle. I wrote this convenient method that renders a bookmarkable page (pageclass + pageparameters) to a string:
protected String renderPage(Class<? extends Page> pageClass, PageParameters pageParameters) {
//get the servlet context
WebApplication application = (WebApplication) WebApplication.get();
ServletContext context = application.getServletContext();
//fake a request/response cycle
MockHttpSession servletSession = new MockHttpSession(context);
servletSession.setTemporary(true);
MockHttpServletRequest servletRequest = new MockHttpServletRequest(
application, servletSession, context);
MockHttpServletResponse servletResponse = new MockHttpServletResponse(
servletRequest);
//initialize request and response
servletRequest.initialize();
servletResponse.initialize();
WebRequest webRequest = new WebRequest(servletRequest);
BufferedWebResponse webResponse = new BufferedWebResponse(servletResponse);
webResponse.setAjax(true);
WebRequestCycle requestCycle = new WebRequestCycle(
application, webRequest, webResponse);
requestCycle.setRequestTarget(new BookmarkablePageRequestTarget(pageClass, pageParameters));
try {
requestCycle.request();
log.warn("Response after request: "+webResponse.toString());
if (requestCycle.wasHandled() == false) {
requestCycle.setRequestTarget(new WebErrorCodeResponseTarget(
HttpServletResponse.SC_NOT_FOUND));
}
requestCycle.detach();
} finally {
requestCycle.getResponse().close();
}
return webResponse.toString();
}
One other thing that’s desirable to do is change all relative links in the email to absolute URLs — something that Wicket makes super-easy, if you know how. That will be the subject of my next post.
Tags: apache, email, html, java, Programming, wicket
Posted by dan on Sep 23, 2008 in
Articles,
General,
Programming
Today I had a small revelation.
I was wracking my brains trying to figure out the SMS messaging provider to use to send myself service outage notifications for my clients’ web sites. Given that I have just a handful of clients so far, it makes no sense to use a provider that requires a minimum monthly or yearly spend.
Ideally of course, I’d like to spend nothing at all, and in exasperation I finally threw my hands in the air (they’re detachable) and whined: “Google sends SMS’s for free – why is it so hard for everyone else?”
(answer: not everyone has billions of dollars)
And then came the revelation: Why not create a command-line tool that uses Google’s Calendar API to create events 6 minutes in the future that have an SMS notification set for 5 minutes prior to launch? That way, within a minute you get a notification sent to your phone for free within 1 minute. Sweet!
So, here’s the code (it’s in Java… sorry)
/**
* Simple command-line notification command that uses Google Calendar ATOM API to create
* a single event 6 minutes in the future with a 5 minute SMS reminder
*
* @author Daniel Walmsley
*
*/
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.List;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarFeed;
import com.google.gdata.data.extensions.Reminder;
import com.google.gdata.data.extensions.When;
import com.google.gdata.data.extensions.Reminder.Method;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
/**
* This is a test template
*/
public class GCalNotifier {
public static void main(String[] args) {
/**
* Command line args:
*
* username
* password
* calendar name (e.g. "Notifications")
* TimeZone offset (in hours)
* event start offset (in minutes)
* event end offset (in minutes)
* title
* description
*/
try {
// Create a new Calendar service
CalendarService myService = new CalendarService("GCal Event Notifier");
myService.setUserCredentials(args[0], args[1]);
String calendarName = args[2];
Long tzOffset = new Double(Double.parseDouble(args[3])).longValue() * 60 * 60 * 1000;
Long startOffset = new Integer(Integer.parseInt(args[4])).longValue() * 60 * 1000;
Long endOffset = new Integer(Integer.parseInt(args[5])).longValue() * 60 * 1000;
String title = args[6];
String description = args[7];
// Get a list of all entries
URL metafeedUrl = new URL(
"http://www.google.com/calendar/feeds/default/allcalendars/full");
System.out.println("Getting Calendar entries...\n");
CalendarFeed resultFeed = myService.getFeed(metafeedUrl,
CalendarFeed.class);
List entries = resultFeed.getEntries();
for (int i = 0; i < entries.size(); i++) {
CalendarEntry entry = entries.get(i);
String currCalendarName = entry.getTitle().getPlainText();
System.out.println("\t" + currCalendarName);
if (currCalendarName.equals(calendarName)) {
sendDowntimeAlert(myService, entry,
title, description, startOffset, endOffset, tzOffset);
}
}
System.out.println("\nTotal Entries: " + entries.size());
} catch (AuthenticationException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendDowntimeAlert(CalendarService myService,
CalendarEntry entry, String title, String description, Long startOffset, Long endOffset, Long tzOffset) throws IOException,
ServiceException {
String postUrlString = entry.getLink("alternate", "application/atom+xml").getHref();
URL postUrl = new URL(postUrlString);//was: "http://www.google.com/calendar/feeds/jo@gmail.com/private/full"
CalendarEventEntry myEntry = new CalendarEventEntry();
myEntry.setTitle(new PlainTextConstruct(title));
myEntry.setContent(new PlainTextConstruct(description));
Date now = new Date();
Date startDate = new Date(now.getTime()+startOffset);
Date endDate = new Date(now.getTime()+endOffset);
DateTime startTime = new DateTime(startDate.getTime()+tzOffset);
DateTime endTime = new DateTime(endDate.getTime()+tzOffset);
When eventTimes = new When();
eventTimes.setStartTime(startTime);
eventTimes.setEndTime(endTime);
myEntry.addTime(eventTimes);
// Send the request and receive the response:
CalendarEventEntry insertedEntry = myService.insert(postUrl, myEntry);
System.err.println("Got response for: "+insertedEntry.getTitle().getPlainText());
for(When when : insertedEntry.getTimes()) {
System.err.println("When: "+when.getStartTime()+" to "+when.getEndTime());
}
//5 minute reminder
Reminder reminder = new Reminder();
reminder.setMinutes(5);
reminder.setMethod(Method.SMS);
insertedEntry.getReminder().add(reminder);
insertedEntry.update();
}
}
Don’t forget, you’ll need to download the Google Data APIs and put their JARs in your classpath before this will work!
Personally I use this with Nagios. I always use the same args for the calendar offsets, so I’ve encapsulated most of my settings (except title and body) in a script.
#!/bin/sh
export SCRIPTDIR=/opt/calAlert
export USERNAME=username@gmail.com
export PW=mySecurePassword
export CAL=Notifications
export TZOFFSET=10
export STARTOFFSET=7
export ENDOFFSET=12
export TITLE=$1
export BODY=$2
#export CURRDIR=`pwd`
export CLASSPATH="${SCRIPTDIR}/calAlert.jar"
#assumes GData libs are in "libs" subdirectory of SCRIPTDIR
for jarfile in $(ls "${SCRIPTDIR}/lib")
do
CLASSPATH="${CLASSPATH}:${SCRIPTDIR}/lib/${jarfile}"
echo lib/${jarfile}
done
echo "CLASSPATH=${CLASSPATH}"
export CLASSPATH
java GCalNotifier ${USERNAME} ${PW} ${CAL} ${TZOFFSET} ${STARTOFFSET} ${ENDOFFSET} "${TITLE}" "${BODY}"
Tags: sms java google calendar api atom app service monitoring
Posted by dan on May 26, 2008 in
Comedy,
General,
Programming
Ok, so a while ago I set up a Google Alert to inform me of happenings related to myself, and periodically it tells me someone on the web called “Dan Walmsley” (typically an Irish F1 Engineer, but occasionally me) has been written about.
Today, however, the results were far more disturbing.
Dan twinned the Steward way in back circuitously 2002 for example a musician in re the non-philharmonic restlessness. “Ace did exclamation in order to the administration ‘oh, ourselves sense, Themselves do up have it gently too’,” me recalled as to his untimely days. “Nonetheless top brass didn’t factually accredit oneself.”
First of all, I think I’ve been misquoted.
Second of all, who does this article benefit?
Thirdly, the post categories are: “african american, audism, feel good tips, republicans, yasin”. Why?
Fourthly, it’s clearly a translation of this article. But a translation into what? Half-English gibberish?
Welcome, ladies and gentlemen, to hard evidence that synonym-generation does not lead to 100% readability during summary generation. This reminds me of my lost year at NICTA.
Posted by dan on Jan 20, 2008 in
Programming
Was up until 3am last night banging my head against another frustrating go-nowhere issue deploying Wicket on Debian Etch’s default Tomcat5.5.
Apparently the latest version (5.5.20-2etch1) has additional security headaches features which prevent wicket from functioning properly out-of-the-box:
- First of all, there’s still an (as-yet-unsolved) mystery around why I couldn’t get Wicket to start up as a filter. Just the mysterious “ERROR: filterStart” which makes me want to feed Tomcat to angry lions. Worked around it by using Wicket in Servlet mode instead.
- Tomcat’s juli.jar can’t access WEB-INF/classes/logging.properties. Fixed (in sledgehammer-like way) by adding “permission java.security.AllPermission;” to /etc/tomcat5.5/policy.d/03catalina.policy, in the Juli section.
- Tomcat security prevents webapps from accessing all sorts of features and methods by default, including wicket.properties, methods inside shipped jars, etc. Not being a Tomcat expert, and trusting the innate security of the server and millions of lines of third party code (i.e. I’m an idiot) I again just popped a java.security.AllPermission; in appropriate spots in /etc/tomcat5.5/policy.d/04webapps.policy. Let the flames commence!
If Tomcat was a little more helpful in its error messages, this would never have been so painful. Jetty has always run my Wicket apps without complaint (though I’ve never tried the official Debian Jetty packages – maybe they’re crippleware secure too?).
The only reason I use Tomcat at all is the remote management and deployment features, which are well-supported by Cargo. Now that these issues are out of the way (mostly) I can take another few steps towards my dream of a seamless, fire-and-forget, auto-deploying, smoke-tested, pluggable and modular web app deployment system.
Oh, and have I mentioned recently how much I LOVE IInitializer?
Bless you, Wicket. Bless you.
Tags: apache wicket tomcat maven debian deployment provisioni