UrlRewrite: SSL for Free Manual Verification Rule

2017 has really been the year when SSL implementation on websites has taken off. Google’s near requirement of them for SEO has forced many websites to implement some form of SSL certificate. For small personal or business, the biggest blocker for SSL hasn’t been the actual installation but the cost. Although prices have fallen, some website owners still resist forking out £30 for an SSL certificate especially if their site doesn’t actually process sensitive data.

Thankfully, SSL For Free has stepped into the fray providing free SSL certificates from the LetsEncrypt Certificate Authority.

To obtain a certificate from SSL for Free, it is necessary to verify your domain. You can do this either via FTP, DNS or manual verification. I often go the manual verification route as I find it to be faster and simpler to accomplish.

With manual verification, it necessary to download a small file to your website which SSLforFree looks for. If your site is already running in SSL and/or only accepts SSL requests, manual verification will fail as it would appear that SSLForFree only checks for the file via HTTP.

To avoid messing with IIS configuration, the simplest away around this is to use a UrlRewrite rule that acts as a pass through for verification requests:

1
2
3
4
5
6
7
<rule name="SSLForFree Pass Through" stopProcessing="true">
  <conditions logicalGrouping="MatchAll">
    <add input="{URL}" matchType="pattern" pattern="^\.well-known/acme-challenge/(.*)" ignoreCase="true" />
    <condition scope="serverVariable" index="SERVER_NAME" test="matchRegex" value="localhost|127\.0\.0\.1|::1" negate="true" />
  </conditions>
  <action type="redirect" redirectType="307" url="http://{HTTP_HOST}{HTTP_X_ORIGINAL_URL}" appendQueryString="true" />
</rule>

If you are using this rule in conjunction with an HTTP to HTTPS rule (see earlier post), it should be placed before any such rule as manual verification is done through a HTTP request.

Automated Implemention
As side note, users of the SolidCP hosting control panel – as Calzada Media are – can install LetsEncrypt certificates through the hosting control panel. Once issued, SolidCP should automatically renew the certificates every 2 months. A short step-by-step guide on how to install the certificate can be found here.

URLRewrite : HTTP to HTTPS Rule

A quick rule to use with IIS UrlRewrite to redirect all HTTP requests to their HTTPS equivalent.

1
2
3
4
5
6
7
8
<rule name="HTTP to HTTPS" stopProcessing="true">
  <match url="(.*)" ignoreCase="false" />
  <conditions>
    <add input="{HTTPS}" pattern="off" />
    <condition scope="serverVariable" index="SERVER_NAME" test="matchRegex" value="localhost|127\.0\.0\.1|::1" negate="true" />
  </conditions>
  <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Temporary" />
</rule>

This rule only does a straight redirect i.e. http://mydomain.com to https://mydomain.com

Update 28 Nov 2017
Rule updated to fix some occasional issues experienced with live sites. The revised rule has been used successfully with a number of sites for a couple of weeks now.

Uploading files in WordPress on IIS – WTF moment.

There are whole slew of blog posts and technical articles on configuring IIS and windows permissions to enable file uploads in WordPress sites. I’ve read them before and had cause to re-visit them over the last couple of days as I struggled to get uploads working on this site. Whenever I attempt to upload a file, the site would just site their until an HTTP timeout message appeared. No HTTP 500, just a PHP timeout error.

My IIS and folder permissions were correct as were all the paths in php.ini. As I was testing tweaks or configuration changes, I could see the temporary files being written in the PHP temp folder. In other words, the actual PHP upload was working. Something was blocking the copying of these files to the WordPress site. Was it WordPress itself or a Windows permission?

To compound my confusion, WordPress’ own update functionality was working without a hitch, so this meant the site had the correct write permissions at least on the wp-content folder.

After several hours of fustration I found the problem and it wasn’t with IIS, PHP or Windows. It was with a WordPress setting. I eventually wandered over to WordPress’ Settings > Media and the uploads folder path contained a reference to a W drive. I have absolutely no idea where this has come from. This site has never been on a server with a W drive. However, once set back to the default of wp-content/uploads, file uploads worked again.

WebsitePanel API: Adding a User

Following on from my initial post on how to connect to the WebsitePanel API from ASP.net, I will demonstrate how to write data to the API by creating a new WebsitePanel user account.

As before, my example is a simple ASP.Net Webform. For details of prerequisites and the development environment I am using, please read the first post.

HTML:

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
119
120
121
122
123
124
125
126
<fieldset>
        <legend>Create User</legend>
        <p>Fields in <b>bold</b> are required.</p>
        <table>
            <tr>
                <td>Owner:</td>
                <td><asp:DropDownList ID="ddlOwner" runat="server" /></td>
            </tr>
            <tr>
                <td>Load Error:</td>
                <td><asp:Literal ID="ltlLoadError" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Send Email Alert</b></td>
                <td><asp:CheckBox ID="chkSendEmail" runat="server" /></td>
            </tr>
            <tr>
                <td colspan="2"><hr /></td>
            </tr>
            <tr>
                <td><b>Role</b></td>
                <td>
                    <asp:DropDownList ID="ddlRole" runat="server">
                        <asp:ListItem Text="Administrator" Value="1" />
                        <asp:ListItem Text="Reseller" Value="2" />
                        <asp:ListItem Text="User" Value="3" />
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><b>Status</b></td>
                <td>
                    <asp:DropDownList ID="ddlStatus" runat="server">
                        <asp:ListItem Text="Active" Value="1" />
                        <asp:ListItem Text="Suspended" Value="2" />
                        <asp:ListItem Text="Cancelled" Value="3" />
                        <asp:ListItem Text="Pending" Value="4" />
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><b>Is Demo</b></td>
                <td><asp:CheckBox ID="chkIsDemo" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Username</b></td>
                <td><asp:TextBox ID="txtUsername" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Password</b></td>
                <td><asp:TextBox ID="txtPassword" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>First name</b></td>
                <td><asp:TextBox ID="txtFirstname" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Last Name</b></td>
                <td><asp:TextBox ID="txtLastname" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Email</b></td>
                <td><asp:TextBox ID="txtEmail" Width="400" runat="server" /></td>
            </tr>
            <!-- Optional Parameters -->
            <tr>
                <td>Company Name</td>
                <td><asp:TextBox ID="txtCompanyName" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Address</td>
                <td><asp:TextBox ID="txtAddress" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>City</td>
                <td><asp:TextBox ID="txtCity" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>State</td>
                <td><asp:TextBox ID="txtState" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Zip/Postal Code</td>
                <td><asp:TextBox ID="txtPostcode" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Primary Phone</td>
                <td><asp:TextBox ID="txtPrimaryPhone" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Secondary Phone</td>
                <td><asp:TextBox ID="txtSecondaryPhone" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Fax</td>
                <td><asp:TextBox ID="txtFax" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Instant Messenger</td>
                <td><asp:TextBox ID="txtInstantMessenger" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>SecondaryEmail</td>
                <td><asp:TextBox ID="txtSecondaryEmail" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>HTML Email</b></td>
                <td><asp:CheckBox ID="chkHtmlEmail" runat="server" /></td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td><asp:Button ID="btnCreateUser" Text="Create User" runat="server" /></td>                
            </tr>
            <tr>
                <td colspan="2"><hr/></td>
            </tr>
            <tr>
                <td>Result:</td>
                <td><asp:Literal ID="ltlResult" runat="server" /></td>
            </tr>
            <tr>
                <td>Error:</td>
                <td><asp:Literal ID="ltlError" runat="server" /></td>
            </tr>
        </table>        
    </fieldset>

Code Behind:

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
119
120
121
122
123
Imports System.Web.Services
Imports Microsoft.Web.Services3
Imports WebsitePanel.EnterpriseServer

Partial Class CreateUser
    Inherits System.Web.UI.Page

#Region "Events & Initial Loading"

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then PopulateUsersDdl()
    End Sub

    Private Sub PopulateUsersDdl()
        ' Note: Uses userId of 1 and returns all users

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = INSERT_WEBSITEPANEL_ENTERPRISESERVER_URL
        config.Username = INSERT_WEBSITEPANEL_ENTERPRISESERVER_USERNAME
        config.Password = INSERT_WEBSITEPANEL_ENTERPRISESERVER_PASSWORD

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Perform lookup
        Try
            Dim lst As WebsitePanel.EnterpriseServer.UserInfo() = users.GetUsers(1, True)
            ddlOwner.DataValueField = "UserId"
            ddlOwner.DataTextField = "Username"
            ddlOwner.DataSource = lst
            ddlOwner.DataBind()
            lst = Nothing
        Catch ex As Exception
            ' If error, output and hide create button
            ltlLoadError.Text = ex.Message
            btnCreateUser.Visible = False
        End Try

        ' Clean up
        ' - little bit of paranoia
        users.Dispose()
        users = Nothing
        config = Nothing
    End Sub

#End Region


#Region "User Creation"

    Protected Sub btnCreateUser_Click(sender As Object, e As EventArgs) Handles btnCreateUser.Click
        ' Reset Output
        ltlError.Text = ""
        ltlResult.Text = ""

        ' Get details
        Dim user As New WebsitePanel.EnterpriseServer.UserInfo
        user.OwnerId = ddlOwner.SelectedValue

        ' Mandatory Parameters
        user.RoleId = ddlRole.SelectedValue
        user.StatusId = ddlStatus.SelectedValue
        user.IsDemo = chkIsDemo.Checked
        user.Username = txtUsername.Text
        user.Password = txtPassword.Text
        user.FirstName = txtFirstname.Text
        user.LastName = txtLastname.Text
        user.Email = txtEmail.Text

        ' Optional Parameters
        user.CompanyName = txtCompanyName.Text
        user.Address = txtAddress.Text
        user.City = txtCity.Text
        user.State = txtState.Text
        user.Zip = txtPostcode.Text
        user.PrimaryPhone = txtPrimaryPhone.Text
        user.SecondaryPhone = txtSecondaryPhone.Text
        user.Fax = txtFax.Text
        user.InstantMessenger = txtInstantMessenger.Text
        user.HtmlMail = chkHtmlEmail.Checked
        user.SecondaryEmail = txtSecondaryEmail.Text

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = ConfigurationManager.AppSettings("WebsitePanelServerUrl")
        config.Username = ConfigurationManager.AppSettings("WebsitePanelUsername")
        config.Password = ConfigurationManager.AppSettings("WebsitePanelPassword")

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Attempt to save
        Try
            Dim result As Int32 = users.AddUser(user, chkSendEmail.Checked)

            If (result > 0) Then
                ltlResult.Text = String.Format("New UserId: {0}", result)
            Else
                Select Case result
                    Case -2
                        ltlResult.Text = "User creation failed:  Invalid OwnerId specified"
                    Case -100
                        ltlResult.Text = "User creation failed:  Username already exists"
                    Case -111
                        ltlResult.Text = "User creation failed:  Username not specified"
                    Case Else
                        ltlResult.Text = String.Format("User creation failed:  Result code of {0} returned", result)
                End Select
            End If
        Catch ex As Exception
            ltlError.Text = ex.Message
        End Try

        users = Nothing
        user = Nothing
    End Sub

#End Region

End Class

If successful, the value of result will be a positive integer which is the ID of the new user account. If a negative value is returned and error has occurred and the value is the error code. The error codes I have discovered so far are:

  • -2 = Invalid OwnerId specified
  • -100 = Username already exists
  • -111 = Username not specified

Testing Notes

  • Although the API documentation states that there are 10 required parameters, I have successfully created a new user by just specifying valid values for OwnerId, RoleId, StatusId and Username
  • An XML exception will occur if non-recognised values are specified for RoleId and StatusId

WebsitePanel API: Connecting from ASP.Net

This is the first in a series of posts explaining how to manipulate WebsitePanel using its’ Integration API from an ASP.Net website.

At Calzada Media, we’ve used WebsitePanel as our hosting control panel for many years now.  With only a few hiccups, it has done exactly what it says on the tin.  What it doesn’t do is in anyway integrate with any of the other systems on our website like billing.  Apart from the slight operational headache, this really doesn’t make for a great customer experience.  The eventual aim is to produce a helper library that will bridge this gap.

Sandbox (aka Bash’n’Crash) installation of WebsitePanel

You will need access to a WebsitePanel instance.  Unless you are supremely brave (and I’m not!) you should not use a production instance of WebsitePanel, but a bash’n’crash or sandbox installation.

My approach was to create a new Windows 2008 R2 virtual machine on my local Hyper-V server.  This VM was then configured as a broad representation of one of Calzada’s hosting servers (IIS + MySQL + WebsitePanel).  WebsitePanel was configured with the Portal listening on port 9001 and the EnterpriseServer on 9002.  Custom rules were added to the Windows Firewall to allow remote access on both of these ports.

If you don’t want to go down the VM route, you can install WebsitePanel on your PC if it is capable of running IIS  – typically Professional, Enterprise and Ultimate editions of Windows.

Required DLL files

Once you’ve installed WebsitePanel, you will need to copy three DLL files from c:\WebsitePanel\Portal\Bin into the bin directory of your ASP.net website:

  • Microsoft.Web.Service3.dll
  • WebsitePanel.EnterpriseServer.Base.dll
  • WebsitePanel.EnterpriseServer.Client.dll

Note: Although WebsitePanel uses Web Service Enhancements v3, you don’t need to install it on your development machine.  Just including the DLL should be sufficient.

Connecting to the WebsitePanel API and getting a list of users

Connecting to the WebsitePanel is actually fairly easy.  As WebsitePanel uses WSE3.0, not WCF, there is no need to create service references within Visual Studio – everything can be done programmatically.

1. In Visual Studio, create a new Web Form and paste the following code between the form tags

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
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="GetUsers.aspx.vb" Inherits="GetUsers" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Get Users</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Users</h1>    
        <table>
            <tr>
                <td><asp:Button ID="btnReloadList" Text="Refresh" runat="server" /></td>
                <td>&nbsp;</td>
            </tr>
            <tr>
                <td>Result:</td>
                <td><asp:Literal ID="ltlResult" runat="server" /></td>
            </tr>
            <tr>
                <td>Error:</td>
                <td><asp:Literal ID="ltlError" runat="server" /></td>
            </tr>
        </table>

    </div>
    </form>
</body>
</html>

The code behind should be

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
Imports System.Web.Services
Imports Microsoft.Web.Services3
Imports WebsitePanel.EnterpriseServer

Partial Class GetUsers
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        LoadUsersList()
    End Sub

    Protected Sub btnReloadList_Click(sender As Object, e As EventArgs) Handles btnReloadList.Click
        LoadUsersList()
    End Sub

    Private Sub LoadUsersList()
        ' Assume User ID 1

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = INSERT_WEBSITEPANEL_ENTERPRISESERVER_URL
        config.Username = INSERT_WEBSITEPANEL_SERVERADMIN_USERNAME
        config.Password = INSERT_WEBSITEPANEL_SERVERADMIN_PASSWORD

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Perform lookup
        Try
            Dim lst As WebsitePanel.EnterpriseServer.UserInfo() = users.GetUsers(1, True)
            gvUsers.DataSource = lst
            gvUsers.DataBind()
        Catch ex As Exception
            ltlError.Text = ex.Message
        End Try

        ' Clean up
        ' - little bit of paranoia
        users.Dispose()
        users = Nothing
        config = Nothing
    End Sub

End Class

Note: If an exception occurs, the error message will be written to the ltlError usercontrol.

Notes & Comments

The username and password is sent in the clear (via the SOAP header). Whilst this is not necessarily an issue for development work, it is of considerable importance when considering API usage in a production environment. If you are going to access the API remotely, you should consider additional measures (SSL, Firewall rules etc) to protect the crucial serveradmin account details.

Unlike my experience with WCF web services, I have not had to make any alterations or tweaks to the web.config to cater for SOAP message sizes etc. The calls just seem to work.

The Website API Documentation is somewhat sparse. I got the above to work through a combination of trawling through forums and experimentation in Visual Studio. There are a whole range of API calls that are simply not documented – like Hosting Plans – that I will be covering in future posts.

Configuring the holding or parking page in WebsitePanel

Configuring the default holding page in WebsitePanel can be a little time-consuming, primarily because I can never remember where the configuration options are.  To change the holding page, or Parking Page in WSP parlance, do the following:

  1. Login to WebsitePanel
  2. Click on the Policies link at the bottom right of the page.
  3. Click on WEB Policy link
  4. Under the section Parking Page enter the code for your holding page
  5. .Click on the Save button at the bottom of page once done.

 

 

Icinga missing check_nrpe on Ubuntu

Now that I’ve got Icinga up and running, I was looking to expand its’ reach by using NRPE to monitor some additional items, most noticeably Windows Event Log entries.

However, I could not get any NRPE commands to work.  The reason, the check_nrpe plugin was missing from /usr/lib/nagios/plugins or anywhere else on the server.

It would seem that the check_nrpe plugin is no longer included within the standard Nagios Plugins, but it may be installed separately through apt-get:

apt-get install nagios-nrpe-plugin

However, this also command will prompt you install the additional packages of nagios3, nagios3-cgi, nagios3-common and nagios3-core. As I’m using Icinga, this is the last things I want or need to install.

The solution, is to run the apt-get command with –use-no-install-recommends parameter:

apt-get install nagios-nrpe-plugin --use-no-install-recommends

Worked like a treat.

 

– very few of the

Monitoring MySQL with Zabbix using the appaloosa-zabbix-templates

The standard MySQL Templates for Zabbix are a little basic and I admit I never really got them to work properly.  Thankfully, the Appaloosa Zabbix Templates are available that are based on the excellent Percona Monitoring Plugins.

As always, this is how I got this to work.  If you have any suggestions or improvements, get in touch.

Initial Work

On Ubuntu, some additional Perl modules are required:

$ apt-get install libxml-simple-perl libdatetime-perl

You also need to create some directories

$ mkdir /usr/local/zabbix
$ mkdir /usr/local/zabbix/plugins
$ mkdir /usr/local/zabbix/agent.d
Generating the Template

Download the source from Google Project Hosting

$ wget http://appaloosa-zabbix-templates.googlecode.com/files/appaloosa-zabbix-templates-0.0.1.tgz

Extract the source by running

$ tar -zxvf appaloosa-zabbix-templates-0.0.1.tgz

Open the extracted folder and generate the template XML

$ cd appaloosa-zabbix-templates-0.0.1
$ perl tools/gen_template.pl defs/mysql.pl mysql.xml

Copy the generated file, mysql.xml, to the /usr/local/zabbix/plugins directory

$ cp mysql.xml /usr/local/zabbix/plugins
Creating the Config File

Now create the agent’s config file by editing conf/mysql_agentd.conf using the sed command.

$ sed -e 's|$ZABBIX_AGENT_PATH|/usr/local/zabbix/plugins|' conf/mysql_agentd.conf > mysql.conf

Copy the new config file to the /usr/local/zabbix/config.d directory

$ cp mysql.conf /usr/local/zabbix/config.d

Open the Zabbix Agent configuration file

$ nano /usr/local/etc/zabbix_agentd.conf

Add the following line

Include=/usr/local/zabbix/agent.d
Restart the Zabbix Agent
$ service zabbix-agent restart
Creating a MySQL User

Connect to mysql using the command: (you will be prompted for the root’s password)

$ mysql --u root -p mysql

Create the new user

mysql> CREATE USER 'zabbixmonitor'@'localhost' IDENTIFIED BY 'password';

Grant privileges to the user

mysql> GRANT SELECT, SUPER, PROCESS ON *.* TO 'zabbixmonitor'@'localhost';

Reload all privileges:

mysql> FLUSH PRIVILEGES;

Exit mysql

mysql> quit

Alternatively, you can use a UI tool like Webmin if it is installed on your server.

Configuring the PHP Script

The template requires a PHP file from the original Cacti template.  Download and extract the latest version of the Cacti Templates using

$ wget http://mysql-cacti-templates.googlecode.com/files/better-cacti-templates-1.1.8.tar.gz
$ tar -zxvf better-cacti-templates-1.1.8.tar.gz

Copy the file scripts/ss_get_mysql_stats.php to /usr/local/zabbix/plugins

$ cp better-cacti-templates-1.1.8/scripts/ss_get_mysql_stats.php /usr/local/zabbix/plugins

Open ss_get_mysql_stats.php for editing

$ nano /usr/local/zabbix/plugins/ss_get_mysql_stats.php

Change the values of $mysql_user and $mysql_pass to that of the mysql user created above

$mysql_user = 'zabbixmonitor';
$mysql_pass = '12345678';

Save and close the file

Import the Template

If you generated the mysql.xml on the Zabbix server, you need to download it to your client computer.

Once you have the file, log into Zabbix and go to Configuration > Templates

Click on the Import button in the top right

Click on Choose File to select mysql.xml and then click on Import

If successful, you should see the message Template Imported

Adding the template to a host

In Zabbix, go to Configuration > Hosts

Click on the host to add the template

Select the Templates tab

Click on Add and select template_mysql from the list

Click on Select and then Save

Troubleshooting

If you don’t get any data, check the Zabbix agent’s log.  The logfile’s path is defined in the agent’s configuration file, but the default are:

  • Linux: /tmp/zabbix_agent.log
  • Windows: c:\zabbix\zabbix_agentd.log

Don’t forget, if you make any changes to the configuration file, you will need to restart the agent for them to take effect.

References

Appaloosa Installation Notes: http://code.google.com/p/appaloosa-zabbix-templates/wiki/Installation

Buxxnt: http://buzznt.blogspot.co.uk/2011/09/install-zabbix-mysql-plugin-imported.html

 

WebsitePanel and MySQL 5.5 Configuration Error. Again.

A couple of months ago I wrote about an error we encountered with WebsitePanel and MySQL 5.5.  Essentially, this boiled down to WebsitePanel only recognising specific versions of the MySQL .Net Connector.  The solution was actually very simple: tweak some entries in the web.config files.

Since then, we’ve upgraded WebsitePanel from 1.2.1 to version 2 (highly recommended) without any problems.  A couple of weeks ago, we upgraded the MySQL .Net connector for a new project and promptly got the following error message:

System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---&gt; System.IO.FileNotFoundException: Could not load file or assembly 'MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' or one of its dependencies. The system cannot find the file specified.

As before, this problem is very easy to fix.  Simply change the following entries in the web.config in both WebsitePanel’s server and portal components (the default paths are c:\websitepanel\server andc:\websitepanel\portal respectively).  If these entries don’t exist, create them.

<runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
        <dependentAssembly> 
            <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" /> 
            <bindingRedirect oldVersion="0.0.0.0-6.3.7.0" newVersion="6.5.4.0" /> 
        </dependentAssembly> 
    </assemblyBinding> 
</runtime>

To

<runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
        <dependentAssembly> 
            <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" /> 
            <bindingRedirect oldVersion="0.0.0.0-6.5.4.0" newVersion="6.6.4.0" />
        </dependentAssembly> 
    </assemblyBinding> 
</runtime>

The only line that changes begins with bindingRedirect and you need to change the value of newVersion to that of the version of the MySQL .Net Connector you have installed.  In this case, 6.6.4.0.

The mistake here – and it was my mistake – was assuming that a known issue with WebsitePanel had been addressed in the latest version.  Needless to say, there are now specific notes regarding upgrading the MySQL .Net Connector.

On a side note: I’ve been developing .Net websites using MySQL for well over 8 years now.  I’ve very rarely run into this problem and it nearly has always occurred when I have deployed a website that utilises a much newer version of the connector than is installed on the server.  One thing that I never done is to explicitly define a version of the .Net Connector.  Whilst this may be considered not to be best practice in some quarters, it does go some where to accommodating differing installed versions of the connector, especially on shared hosting providers (i.e. servers outside of my direct control).

The return of LeafURL

Nearly a year ago I wrote a URL Shortening service called LeafURL.  This was part intellectual exercise and part fun – part of me just wanted to build something other than a “standard” website.  As with most spur of the moment ideas, it took longer than expected to complete.  I did include some extra bells and whistles including graphical reports using the Microsoft Chart Controls.

A few weeks ago, I decided to give LeafURL an annual service and to improve its’ overall integration with the Calzada Media website.  A few scratchings of the head later punctuated with quite a few what on earth did I do here? and version 2 of LeafURL has appeared.  With version 2, LeafURL is now powered by the UrlShortener module for Camino CMS, Calzada Media’s own content management system.  Outwardly, there are few visual changes, but by using the latest version of Camino, LeafURL is now more stable and robust than before.

The newly refurbished LeafURL is now available and is free to use by anyone.