Saturday, August 4, 2012

Password-Free SSH on OS X Mountain Lion

I had set up my home network with password-free ssh access between various Mac computers. But when I recently upgraded to OS X Mountain Lion, this stopped working.

Turns out /etc/sshd_config is modified during Mountain Lion installation. The following line gets unchecked:

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile      .ssh/authorized_keys


Once I had renamed ~/.ssh/authorized_keys2 to ~/.ssh/authorized_keys on my Mountain Lion computers, everything worked fine again.

Friday, July 6, 2012

Send Emails via Gmail from Terminal in Mac OS X Server

It took me a while to figure this out, so I'm hoping it might save somebody else the trouble of scouring the web for the relevant info. You can send emails from the Terminal in Mac OS X with the following commands:
 mail recipient@somewhere.com
 <enter>
 Subject: Hi there!
 <enter>
 This is the body of the email.
 This is another line in the email body.
 <ctrl + d>
Note: <enter> means hit the enter key, and <ctrl + d> means type 'd' while holding the control key.

However, this won't actually send an email unless you have a mail server running. As far as I can tell, you have two options for setting up a mail server - either churn out a series of incomprehensible commands in a Terminal window, or use Mac OS X Server's Server Admin utility to configure the Mail Service using a (somewhat) friendly GUI.

I went for the latter option - I just wanted to get this up and running using my gmail account with the minimum of fuss. Unfortunately, it didn't work right off the bat - having configured the gmail smtp server along with my username and password, I was seeing frustrating "Timed out" errors in the logs and my test mails weren't getting sent. Turns out some ISPs block port 25 - so I needed to specify port 587 in the hostname. Then I was getting some even more meaningless errors along the lines of "Must issue a STARTTLS command first". I mean what is that? Thankfully, google to the rescue again - this great blog had all the answers.

Step-By-Step Instructions

1. Launch the Server Admin utility, located in Applications/Server

2. In the Settings tab, fill in the settings as per the screenshot below. The Domain name and Host name are ignored, so just fill in anything. The key settings are highlighted in red:

Note: the gmail account you specify in the "Authenticate to relay with user name:" box will be used as the From Address for all emails you send from the command line.

3. Add the following lines to /etc/postfix/main.cf (don't ask me why):
 smtp_tls_security_level = may
 smtp_sasl_security_options = noanonymous

That's it - you can now send mails from a Terminal window!

Update: Mountain Lion
30 Aug 2012

After upgrading to Mountain Lion and installing OS X Server, my mail stopped working. I had to make the following change to /Library/Server/Mail/Config/postfix/main.cf to get it working again:
# Change this line:
#smtp_sasl_password_maps = hash:/etc/postfix/sasl/passwd
# To this:
smtp_sasl_password_maps = hash:/Library/Server/Mail/Config/postfix/sasl/passwd

Hope it helps someone!

Update: Mountain Lion #2
23 Oct 2012

I've just had to reinstall OS X after a hard disk failure, and found that the above changes weren't sufficient to get command-line email working again. For Mountain Lion, after installing and starting OS X Server, and configuring the Mail service in the Server application to use the gmail SMTP relay, I had to do only the following on the command-line:
sudo vi /Library/Server/Mail/Config/postfix/main.cf
I changed this line:
# By default is 'no' so change to 'yes'
smtpd_use_tls = yes
And added the following two lines at the end:
# By default not included in the config, so add these lines
smtp_tls_security_level = may
smtp_sasl_security_options = noanonymous
After that, I stopped and started Mail using the Server application, and command-line email now worked correctly.

Friday, December 30, 2011

PostgreSQL Mac Preferences

Unlike many coders, I'm not overly fond of the command line. Yes it's quick, but only provided you can remember the commands!. Too often, I find myself hunting through obscure documentation (and StackOverflow...) to find the right command, even for relatively simple things.

I much prefer to use a GUI (ideally with keystrokes for invoking its frequently-used functions). Not only does it free me from the burden of committing pointlessly complex keywords and arguments to memory - it just feels nicer!

So it was when I made the switch from MySQL to PostgreSQL (due to an instinctive mistrust of the former's new corporate owners) that I was disappointed to find that PostgreSQL relies on the command line in a major way. A simple task like starting & stopping the server is far from a no-brainer - you have to find the relevant script, set the environment variables just so, and even switch to the right user (with some installations). Compare this to MySQL, which has a swish System Preferences screen for starting, stopping and enabling auto-startup.

Well, today I am pleased to announce the release of PostgreSQL Preferences for Mac OS X. This borrows heavily from its MySQL counterpart in its visual styling, and also owes a large debt of gratitude to the excellent work of John T Wang.




Please see installation instructions below, and be sure to check out the User Guide - although given the above rant, I'm hoping it's so intuitive to use that you won't need to look at this much!

So now you can happily avoid struggling with mundane activities like starting / stopping servers - and concentrate on the more important business of designing great websites!

Install
  1. Download latest version here
  2. Unzip
  3. Double-click or drag to System Preferences
Uninstall
  1. Right-click PostgreSQL icon in System Preferences, and choose Remove - or drag to trash
  2. Delete the following files if they exist:
/Library/LaunchAgents/com.hkwebentrepreneurs.postgresql.plist* ~/Library/LaunchAgents/com.hkwebentrepreneurs.postgresql.plist*

Further Information
Update 10-March 2012: New version 1.0.1 released, which fixes a few bugs raised by users. Please let us know if you have any further problems.

Saturday, August 6, 2011

GWT DecoratorLayoutPanel

I've been experimenting with GWT and GWT Designer lately. I get the feeling that a version or two down the road, these are going to give me a massive productivity boost. Right now, I still find myself stuck trying to figure out why things aren't working as I expect.

For example the new GWT 2.0 Layout framework does not sit well with the pre-2.0 widgets such as DecoratorPanel. Try adding a LayoutPanel to a DecoratorPanel - it won't work, you'll just see nothing inside the DecoratorPanel.

To get round this, I've implemented a DecoratorLayoutPanel, which subclasses LayoutPanel and so can contain any Layout-compatible widgets. I've added the source code below - feel free to copy/reuse/extend as you like, and I'd welcome any feedback!

import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * A {@link LayoutPanel} that wraps its contents in stylized boxes, which can be
 * used to add rounded corners to a {@link Widget}.
 * </p>
 * <p>
 * This class aims to be a drop-in replacement for
 * {@link com.google.gwt.user.client.ui.DecoratorPanel}.
 * However, it uses a {@link LayoutPanel} to layout the 9-box, whereas
 * {@link com.google.gwt.user.client.ui.DecoratorPanel} uses a DOM table.
 * The benefit is that this panel can contain other {@link LayoutPanel} widgets, which
 * {@link com.google.gwt.user.client.ui.DecoratorPanel} cannot do successfully.
 * </p>
 * <p>
 * All of the same class names used in {@link com.google.gwt.user.client.ui.DecoratorPanel}
 * are used here, e.g. .gwt-DecoratorPanel, .topCenter, .bottomRightInner
 * </p>
 * <p>
 * Important differences to {@link com.google.gwt.user.client.ui.DecoratorPanel}:
 * <ul>
 * <li>.middleCenter should not be given height and width of 100%, as this will cause
 * unexpected behaviour.</li>
 * <li>The size of the corners is set in the constructor, but note that this can be
 * overridden in CSS.</li>
 * </ul> 
 * </p>
 */
public class DecoratorLayoutPanel extends LayoutPanel implements AcceptsOneWidget {
    /**
     * The default style name.
     */
    private static final String DEFAULT_STYLENAME = "gwt-DecoratorPanel";
    
    /**
     * Wrapper for middle center widget.
     */
    private LayoutPanel container;

    /**
     * Middle center widget
     */
    private Widget widget;
    
    /**
     * Create new instance using default size of corners.
     */
    public DecoratorLayoutPanel() {
        this(5, Unit.PX);
    }
    
    /**
     * Create new instance using specified size of corners.
     * 
     * @param size Height and width of corners
     * @param unit Unit to use for size 
     */
    public DecoratorLayoutPanel(double size, Unit unit) {
        this.setStyleName(DEFAULT_STYLENAME);
        
        /* Container for center widget */
        this.container = new LayoutPanel();            
        
        /* All inner widgets */
        Widget topLeftInner = new HTML(" ");
        Widget topCenterInner = new HTML(" ");
        Widget topRightInner = new HTML(" ");
        Widget middleLeftInner = new HTML(" ");
        Widget middleCenterInner = this.container;
        Widget middleRightInner = new HTML(" ");
        Widget bottomLeftInner = new HTML(" ");
        Widget bottomCenterInner = new HTML(" ");
        Widget bottomRightInner = new HTML(" ");
        
        /* Add to LayoutPanel */
        super.add(topLeftInner);
        super.add(topCenterInner);
        super.add(topRightInner);
        super.add(middleLeftInner);
        super.add(middleCenterInner);
        super.add(middleRightInner);
        super.add(bottomLeftInner);
        super.add(bottomCenterInner);
        super.add(bottomRightInner);
        
        /* Set default positioning - can override using CSS */
        this.setWidgetLeftWidth(topLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(topCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(topRightInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topLeftInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topCenterInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topRightInner, 0, unit, size, unit);
        
        this.setWidgetLeftWidth(middleLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(middleCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(middleRightInner, 0, unit, size, unit);
        this.setWidgetTopBottom(middleLeftInner, size, unit, size, unit);
        this.setWidgetTopBottom(middleCenterInner, size, unit, size, unit);
        this.setWidgetTopBottom(middleRightInner, size, unit, size, unit);
        
        this.setWidgetLeftWidth(bottomLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(bottomCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(bottomRightInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomLeftInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomCenterInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomRightInner, 0, unit, size, unit);
        
        /* Set CSS Styles */
        topLeftInner.setStyleName("topLeftInner");
        topCenterInner.setStyleName("topCenterInner");
        topRightInner.setStyleName("topRightInner");
        middleLeftInner.setStyleName("middleLeftInner");
        middleCenterInner.setStyleName("middleCenterInner");
        middleRightInner.setStyleName("middleRightInner");
        bottomLeftInner.setStyleName("bottomLeftInner");
        bottomCenterInner.setStyleName("bottomCenterInner");
        bottomRightInner.setStyleName("bottomRightInner");        
        this.getWidgetContainerElement(topLeftInner).setClassName("topLeft");
        this.getWidgetContainerElement(topCenterInner).setClassName("topCenter");
        this.getWidgetContainerElement(topRightInner).setClassName("topRight");
        this.getWidgetContainerElement(middleLeftInner).setClassName("middleLeft");
        this.getWidgetContainerElement(middleCenterInner).setClassName("middleCenter");
        this.getWidgetContainerElement(middleRightInner).setClassName("middleRight");
        this.getWidgetContainerElement(bottomLeftInner).setClassName("bottomLeft");
        this.getWidgetContainerElement(bottomCenterInner).setClassName("bottomCenter");
        this.getWidgetContainerElement(bottomRightInner).setClassName("bottomRight");
    }
    
    /**
     * Set the center middle widget.
     * 
     * @param widget The widget to add
     */
    @Override
    public void add(Widget widget) {
        setOneWidget(widget);
    }
    
    /**
     * Overloaded version for IsWidget.
     * 
     * @see #add(Widget)
     */
    public void add(IsWidget w) {
        setWidget(w);
    }
    
    /**
     * Set the center middle widget.
     * 
     * @param w The widget to add
     */
    @Override
    public void setWidget(IsWidget w) {
        setOneWidget(asWidgetOrNull(w));
    }
    
    private void setOneWidget(Widget w) {
        // validate
        if (w == widget) {
            return;
        }
        
        // remove the old widget
        if (widget != null) {
            this.container.remove(widget);
        }
        
        // logical attach
        widget = w;

        if (w != null) {
            this.container.add(w);
        }            
    }
}

Thursday, August 19, 2010

HK Web 2.0

Also found another Facebook group for entrepreneurs - HK Web 2.0. This one seems to be even bigger, although again there was a lot of activity a couple of years ago, and not much since.

Saturday, August 14, 2010

Welcome!

This blog is for Web Entrepreneurs based in Hong Kong.

There's also a group on Facebook: Web Entrepreneurs in Hong Kong. Has quite a few members (70 at the moment), but not a lot of recent activity...