tags:

views:

152

answers:

9

I have created a custom, specialized CMS with a number of clients on it, each with their own domain name, website, admin area and database, but all residing on the same server.

Whenever I get a new client, I simply copy all of the code over, change 3 lines in a config file, and then all of their styles/data is taken out of the database or from uploads they post to the server from their own admin area.

Sounds great so far right?

Well when I recently decided to update all of the clients sites, this became a major pain. Obviously i had to change the code on each install. For a major update this is fine, but for frequent tweaks or the like, duplicating the effort of uploading becomes very annoying....I hope to someday have dozens or hundreds of clients, so the code will eventually have to be centralized so that changing it in one place updates it everywhere...how does one do that?

+14  A: 

A easy solution would be to put the source code in a subdirectory, except the files, which should be altered for each client (for example, the config file).

You can then put this source code directory somewhere out and just create symlinks to it.

For example, your directory structure might look like:

/var/www/src/index.php
/var/www/src/more_source.php
/var/www/clients/client_a/settings.php
/var/www/clients/client_a/src -> ../../src/
/var/www/clients/client_b/settings.php
/var/www/clients/client_b/src -> ../../src/

If you choose this structure, the only thing you would need to change, would be the include for settings.php (e.g. from require "settings.php" to require "../settings.php").

ChrisM
this sounds like a promising and simple solution...if only i knew what symlinks were :) to clarify...i will research it. i'm not a trained programmer and ONLY know web languanges: php,js,mysql,html,flash. no sysadmin stuff at all.
Phil
A symlink is a symbolic link (http://en.wikipedia.org/wiki/Symbolic_link), which just tells the server that it should look for the information somewhere else. If you are using Linux/UNIX on the server, you can set the symlink with ln -s [source] [target].
ChrisM
+1  A: 

I'd split into the library (your working code) and the config (the three lines for each user, probably database connections and the like).

Split the library out to a universal location eg /home/library (in *nix) and allow read attribute to the other user accounts.

Now for each site there's a config file which first includes the library and second sets the site-specific config. Updating the library updates all versions. A word of caution though - you'll want to have some unit tests in place such that an update of the library doesn't break anyone of the single sites.

eg config: (one per site)

<?php
require_once('/home/library/include.php');
//Line 1 config
//Line 2 config
//Line 3 config
?>

If your library of code is also used to render the content (index.php etc) then you have two options: (1) create symlinks between each site's web folders and the library source, or (2) separate your code into core functionality and rendering code.

A symlink creates a system link between two points on the drive (it's like a pointer). A properly created link allows you to access a folder by a different name so you can create /home/user1/library, /home/user2/library /home/user3/library, and they all point and contain the content of /home/library (they're not copies of the data).

I'd be included to go with #2 although it'll take more work up front. Your library does the work, but the site rendering is performed by the the second piece of code which is supplied on a per-site bases (likely including the afore mentioned config), this allows more flexibility on a site by site basis (even if you don't foresee yourself needing this yet). As a side effect you're on the way to a multi-tiered application.

Rudu
+4  A: 

Personally, I have a client table on the database that holds the current version number of the code that they're running. When a user logs in, it sets a cookie called CodeVersion indicating the code version for that client (e.g. "v5-09-00"). In the Apache .htaccess, I have:

RewriteEngine on

RewriteCond %{HTTP_COOKIE} CodeVersion=([^;]+) [NC]
RewriteRule ^(.*)$ http://v%1.%{HTTP_HOST}/${escape:$1} [R=302,P,L]

and within the local hosts file, I have:

127.0.0.1    myservername       myservername.myserverdomain.com

127.0.0.1    v5-08-00.myservername  v5-08-00.myservername.myserverdomain.com
127.0.0.1    v5-09-00.myservername  v5-09-00.myservername.myserverdomain.com
127.0.0.1    v5-10-00.myservername  v5-10-00.myservername.myserverdomain.com

which handles local redirection within Apache to an entry in vhosts

The vhosts file sets the environment for each version of the code:

<VirtualHost *:80>
    ServerAdmin [email protected]
    DocumentRoot /usr/local/apache/htdocs_5-09-00
    ServerName v5-09-00.myservername.myserverdomain.com
    ServerAlias v5-09-00.myservername
    ErrorLog logs/myservername-error_log_5-09-00
    CustomLog logs/myservername-access_log_5-09-00 common
    php_value include_path ".:/php/includes:/usr/local/include/5-09-00/API:/usr/local/include/5-09-00/library:/usr/local/include/5-09-00/businesslogic:/usr/local/include/5-09-00/configuration:/usr/local/include/5-09-00/library/PHPExcel/Classes"
</VirtualHost>

<Directory "/usr/local/apache/htdocs_5-09-00">
    Options Indexes FollowSymLinks ExecCGI
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

Within each set of directories, I can have symbolic links pointing to common code, and actual php/ini files for client-specific code or configuration.

Mark Baker
+1 I like this kung-fu, great idea.
Rudu
doesn't this create problems if the user fakes the cookie? I'm guessing there is some kind of security issue here unless you're extremely careful
rmeador
There are additional checks within the code, so a faked cookie will delete that cookie and log the user out.
Mark Baker
+3  A: 

Just to let you know I've had this problem before. It's easy to get caught by some phantom .htaccess irregularities or open exploits up to other clients if you decide to configure through this.

I recommend keeping everything separate between clients.

Therefore, give them their own FTP, database and codebase. Don't pile it all into one folder and think configuration is going to help. It seems sexy to do it that way, but when you have one little problem then you have the same problem on all client sites, but if you have one problem on a customer and everything is kept separate, then the other customers don't feel it necessarily.

You can run your main codebase on a separate test server for doing updates and then simply patch your customer FTP files and database SQL.

But Keep it simple and separate!

Geekster
interesting viewpoint...what if the clients have no FTP or database access? they access the server solely through their own admin area in their own directory.
Phil
@Phil - I agree. I have also strayed down the path of sexy config files and when the point comes that one client want something which requires more than just a few settings (and it will come) it can get very messy. What I realised I was looking for was actually a method of deploying an updated codebase to a number of sites with built in unit tests. Check out my similar question here http://stackoverflow.com/questions/3386529/deploy-to-multiple-instances
Macros
Sorry, meant @Geekster I agree. Trying to maintain a central codebase singlehanded can get very messy
Macros
i understand where you guys are coming from...i have had a few specific coding requests from clients and 'we don't do that' isn't a popular reply....my thought process is that we don't make tweaks but we will make improvements which everyone else can optionally include or not on their sites. maybe i'm being short-sighted tho...thanks for feedback
Phil
@Phil: Don't let your customers manage your business. Just be straight with them and tell them that they have results they want and you can achieve the results but they will have to trust you on how the results are achieved. Most customers know very little about what they SHOULD be doing, so they come up with ideas and they want to propagate their ideas for the sole reason that it is their ideas. There are more bugs and broken-by-design problems as a result of us placating them. Just tell them how it's gonna be and if they don't like it -- fire em.
Geekster
One bad customer can cost YOU more profit than ten good customers can bring in.
Geekster
+1  A: 

An alternative approach is using stubs. Simply move the shared code, like a library into a shared directory. Then for each individual site, create hollow .php scripts which just include the shared code:

  // file: index.php
  include(".../shared/index.php");

You'll had to repeat that for each application entry point. So you have almost empty scripts in each site, and all changes in the shared codebase automatically apply to all setups. Just let the config.php differ between installations.

For static files (images, css, etc.) I'd also use a symlink approach or a RewriteRule.

mario
A: 

Firstly, you need to be careful of the pitfalls of sharing code between sites. Updating many sites at once multiplies the danger of your updates causing a problem -- if the shared code has a bug, it's better to find out on one site than on a hundred at once!

A deployment management solution may be better than directly sharing the code. (if you're using Drupal, for example, you could use Aegir to manage all your site deployments and updates)

That said, it can be helpful to share code in some cases. I did something like this at a previous job, where they wanted several sites using a single shared code base. What we did was to put the shared libraries into a folder on the server, and each site was then given a symbolic link to that folder, so it treated it as a local folder. Quite easy to do, and quite easy to manage.

Spudley
thanks for that viewpoint. I am using plain old PHP and mysql built from scratch. can you suggest a deployment management solution for that?
Phil
You might want to migrate to a version control and rollout system. This makes semi-automated updates feasible. Yet you can check per system, if something goes wrong.
mario
Agreed with @mario -- A version control system (SVN, Git, etc) will make things a lot easier as you can grab any given version from the repository at any time, so it's easy to roll-back a change if something goes wrong. I'm so used to using version control I forgot to even mention it; I'd consider it a vital tool for any software developer, but particularly one where you need to manage multiple installations.
Spudley
A: 

When I do something like this, I have each client's folder on the web server be an SVN working copy (I use SVN as my version control system, obviously). Then when I've made some updates, tested, and tagged the release, I just switch the client folder to the new release. SVN automatically pulls down all the changes for me. There is one caveat: make sure you set up your web server to not allow serving of the contents of the .svn folders in each directory (or the equivalent for your VCS of choice, if there is one). I believe I first read of this technique in the official SVN docs, so I think it's sanctioned by their developers, but I am unable to find a reference to it in the docs right now.

rmeador
A: 

How about something that uses the same code, but each has own mysql.
Like, http://mycms.com/myuser/ brings the prefix myuser_ to the tables, thus, allowing 1 src structure, and multiple site. You will need .htaccess though

InsanityNet
A: 

If you place your code into version control, then each client website can be a separate checkout. You can check in your changes, and roll them into each client (or test site) by doing a version control update (or using export) at the root. Then you can update and test each client one at a time to make sure the patch applied properly.

SorcyCat