Published by Rolf on 06 Sep 2010

Startup errors with Plesk 9.5 on CentOS 5

When ordering a new VPS box from my hosting provider, I was pleased to see they had upgraded to Plesk 9.5. However, I was disappointed to find out it wasn’t running automatically on https://MYIPADDRESS:8443/ (which obviously makes it impossible to log in and configure the first time). So I rolled up my sleeves, SSH’ed in and got to work.

The first hint of a problem was the Apache error I got when I tried to bounce plesk:

[root@tisailing ~]# /usr/local/psa/admin/bin/websrvmng -a
/usr/sbin/httpd: error while loading shared libraries: libldap-2.3.so.0: cannot open shared object file: No such file or directory
websrvmng: get_apache_path: '/usr/sbin/httpd -V' failed
websrvmng: get_apache_path: '/usr/sbin/httpd -V' failed

This was odd, but easy enough to fix:

# yum update
# service psa restart

Then at the link you get:

ERROR: PleskFatalException StatInfo->getProductVersion failed: file_get_contents() failed: mktime() [function.mktime]: It is not safe to rely on the system's timezone settings. Please use the date.timezone setting, the TZ environment variable or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/Los_Angeles' for 'PDT/-7.0/DST' instead 0: common_func.php3:146 psaerror(string 'StatInfo->getProductVersion failed: file_get_contents() failed: mktime() [function.mktime]: It is not safe to rely on the system's timezone settings. Please use the date.timezone setting, the TZ environment variable or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/Los_Angeles' for 'PDT/-7.0/DST' instead') 1: common_func.php3:66 getProductLoginVersion() 2: SP.php:101 ssoGetSpName() 3: SP.php:393 SsoSpConfig->load() 4: SP.php:215 SsoSpConfig->__construct() 5: SP.php:223 SsoSpConfig::getInstance() 6: SP.php:447 SsoSpConfig() 7: RelyingParty.php:19 isSsoEnabled() 8: auth.php3:185

Uh, OK. A bit of Googling yields this blog posting, so add the proper OS and Plesk version strings to /usr/local/psa/version and restart Plesk via service psa restart.

Then the Plesk login screen at least comes up. I ran into a number of non-blocking error messages (all related to the DNS configuration) as I created my administrator account so we’ll see if any of those come back to bite me later, but so far it seems to be behaving normally.

One thing I did notice, was that /etc/httpd/conf.d/php.conf was not properly set up to handle PHP scripts. If you get a “download file” dialog, then change

<IfModule mime_module>
    AddType application/x-httpd-php .php
    AddType text/html .php
    AddHandler application/x-httpd-php php
</IfModule>

to

<IfModule mod_php>
AddType application/x-httpd-php .php
AddHandler application/x-httpd-php .php
AddHandler application/php5-script .php
</IfModule>

Published by Rolf on 04 Sep 2010

A simpler NamespaceFile engine for CakePHP 1.2

Using Cake, once you start working with a large number of data cache files, you see the limitation of storing them all in a single directory (which is the default FileEngine behavior). In my case I have a hierarchical Tree behavior on a model and rather than traverse the tree to find its path each time (which incurs a bunch of DB queries), I simply cache the array of its “branch” to save the work. But this means I have a cache file for each node in the tree, and rapidly that gets to be a big number of files in app/tmp.

Frank de Graaf wrote an excellent drop-in upgrade of FileEngine that lets you name the cache keys as namespaces, which get stored in different directories. So you can, for example, delete a group of keys at once by deleting their directory. He doesn’t recommend it for replacing your default FileEngine cache, but for specialized instances (like mine) it works great. One change I did make to his class was to make it extend FileEngine rather than reimplement it like he does – this results in a very compact class that seems to work just as well, as far as I can tell.

To use it, I chose to activate it at runtime, just where I needed it, then switch back to the default. E.g.,

...
	function function_that_needs_this_cache_engine()
	{
		// Use NamespaceFile cache engine, only for this
		App::import('Vendor', 'NamespaceFile'); 
		Cache::config('app', array( 
			'engine' => 'NamespaceFile', 
			'duration'=> '+1 year', 
			'prefix' => 'data.' 
			)); 
 
		Cache::write('namespace.cachename', 'cache_value', "+1 year");
                echo Cache::read('namespace.cachename');
 
		// revert to the normal cache engine
		Cache::config('default', array(
			'engine' => 'File', 
			'path'	=>	ROOT . DS . APP_DIR .  DS . 'tmp'
			));
	}

The class is here; put it in app/vendors/namespace_file.php:

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
<?php 
/** 
 * Partial reimplementation of Cake's original FileEngine. This 
 * engine supports namespaces similar to: 
 * 
 * Cache::write('app.pictures.recent', $recentPictures, 'app'); 
 * Cache::write('app.pictures.top', $topPictures, 'app'); 
 * 
 * This namespacing allows deletion of parent namespaces like: 
 * 
 * Cache::delete('app.pictures', 'app'); 
 * 
 * The settings for this cache should be 
 * defined at the bottom of /config/bootstrap.php like the following: 
 * 
 * App::import('Vendor', 'NamespaceFile'); 
 * Cache::config('app', array('engine' => 'NamespaceFile', 'duration'=> '+1 hour', 'prefix' => 'cake.'));
 * 
 * Place this file in APP/vendors/. 
 * 
 * WARNING: DO NOT USE AS YOUR APP'S DEFAULT CACHE! 
 * 
 * @author Frank de Graaf (Phally) 
 * @link http://www.frankdegraaf.net 
 * @license MIT license 
 * 
 * // Modified 9/4/10 by echothis - Changed to extend FileEngine, resulting in a much simpler class
 */ 
class NamespaceFileEngine extends FileEngine { 
 
	/** 
	 * Write data for key into cache 
	 * 
	 * @param string $key Identifier for the data 
	 * @param mixed $data Data to be cached 
	 * @param mixed $duration How long to cache the data, in seconds 
	 * @return boolean True if the data was succesfully cached, false on failure 
	 * @access public 
	 */ 
	function write($key, &$data, $duration) { 
		$writable =& $this->__setKey($key); 
		if (!is_a($writable, 'File') || !$this->__init || $data === '') { 
			return false; 
		} 
 
		if (!$writable->exists()) { 
			$writable->Folder->create($this->__getFolderPath($key)); 
			$writable->create(); 
		} 
		parent::write($key, &$data, $duration);
	}
	/** 
	 * Get absolute file for a given key 
	 * 
	 * @param string $key The key 
	 * @return mixed Absolute cache file for the given key or false if erroneous 
	 * @access private 
	 */ 
	function __getPath($key) { 
		return $this->settings['path'] . str_replace('.', DS, $key); 
	} 
 
	/** 
	 * Get absolute folder for a given key 
	 * 
	 * @param string $key The key 
	 * @return mixed Absolute cache file for the given key or false if erroneous 
	 * @access private 
	 */     
	function __getFolderPath($key) { 
		return $this->__getPath(substr($key, 0, strrpos($key, '.') + 1)); 
	} 
 
	/** 
	 * Return the class (File/Folder) for a key. 
	 * 
	 * @param string $key The key 
	 * @return mixed File or Folder class for a key. 
	 * @access private 
	 */     
	function __setKey($key) { 
		return $this->__setPath($this->__getPath($key)); 
	} 
 
	/** 
	 * Return the class (File/Folder) for a path. 
	 * 
	 * @param string $path The path 
	 * @return mixed File or Folder class for a path. 
	 * @access private 
	 */         
	function __setPath($path) { 
		$this->__File->Folder->cd($path); 
		if (is_dir($path)) { 
			$object  = &$this->__File->Folder; 
			return $object; 
		} 
 
		$this->__File->path = $path; 
		$object  = &$this->__File; 
		return $object; 
	} 
 
	/** 
	 * Override the parents key method, so the keys don't get transformed 
	 * 
	 * @param string $key The key 
	 * @return mixed The key if one is passed else return false 
	 * @access public 
	 */ 
	function key($key = null) { 
		if (empty($key)) { 
			return false; 
		} 
		return $key; 
	} 
} 
?>

Published by Rolf on 10 Aug 2010

Google Chrome doesn’t like to stream

Often if you have a long-loading page, you’ll want to stream the content as it arrives. Firefox does a great job of aggressively rendering content, but other browsers like Google Chrome and IE don’t.

A typical workaround for this is to first “wake up” the browser, then periodically flush the output buffer – this has been known for awhile. What’s less known, is that the textual content you’re sending actually matters as well (at least to Chrome and thus likely Safari). E.g., a standard workaround is this:

// start output buffer
if (ob_get_level() == 0) ob_start();
for($i=0;$i<70;$i++)
{
    echo "printing...<br />";
    print str_pad('',4096)."\n";
 
    ob_flush();
    flush();
    usleep(30000);
}

But what’s interesting is that this slight modification breaks it on Chrome:

// start output buffer
if (ob_get_level() == 0) ob_start();
for($i=0;$i<70;$i++)
{
    echo "printing...\n";
    print str_pad('',4096)."\n";
 
    ob_flush();
    flush();
    usleep(30000);
}

Apparently Google engineers think <br>’s are important, but newline characters, not so much.

Published by Rolf on 03 Aug 2010

What to do if your ISP decides to block outgoing SSH requests

I run several websites on a leased VPS box and use WinSCP to SSH in. Suddenly a few days ago I got locked out, and got only “Connection Timeouts” when I tried to log in.

  1. First I figured this was due to my Norton Internet Security firewall suddenly getting confused. But disabling it, and the Windows Firewall, had no effect.
  2. Then I tried resetting my router and flushing my DNS cache, which I knew was a long shot but worth a try. Still no luck.
  3. I tried SSH’ing in from another box on the internet and sure enough, got in right away (as did a friend of mine elsewhere and the VPS support people). So this told me that Comcast (my ISP) had decided that traffic shaping was in my best interest (or at least theirs), and was thus blocking port 22 traffic to this particular box. I should note that this server has no objectionable content on it (it’s actually for a few nonprofits I work with), I don’t transfer large files back and forth, or anything like that. And also, this is an outgoing connection, not an incoming one, so it’s 100% within the Comcast terms of use anyway.

This in mind, I logged into the server (via the other internet box) and set up sshd to run on a second, higher-number port by editing /etc/ssh/sshd_config:

...
Port 22
Port [HIGH_NUMBER]
...

Then I bounced the sshd service and was able to log in on the new port.

This of course begs the question of what exactly Comcast is doing snooping on my network traffic and blocking connections without warning. If I have a spare few hours to sit on the phone with their tech support, I intend to find out.

Published by Rolf on 15 Jul 2010

How to restore a SQL Server database from a backup file

Recently I volunteered to port a website from ColdFusion/SQL Server to PHP and MySql. But rather than getting the database snapshot as a SQL script, which would have been easy to migrate, I was given the database in the form of a SQL Server backup, which has two parts: an LDF log fie and an MDF data file.

Now not being particularly skilled in SQL Server, I had to hunt around for what to do with this. It turned out to be ultimately pretty easy. Just install SQL Server 2008 via the Web Installer, reboot, and then launch the administration interface and run this SQL statement to import the backup file as a database:

RESTORE DATABASE mydb FROM disk = 'C:\\path\\to\\mydb.bak'
WITH REPLACE, MOVE 'mydb_Dat' TO 'C:\\Program Files\\Microsoft SQL Server\\MSSQL10.SQLEXPRESS\MSSQL\\DATA\\mydbdat.mdf',
MOVE 'mydb_log' TO 'C:\\Program Files\\Microsoft SQL Server\\MSSQL10.SQLEXPRESS\\MSSQL\\DATA\\mydblog.ldf'

Then choose “Script to File” to output the database to an ordinary SQL script, which then can be imported into MySQL.

« Prev - Next »