Toadhttpd

Etoyoc.com
Login

Examination of the configuration for Etoyoc.com

The webserver http://www.etoyoc.com is the personal web server of Sean Woods. It manages several virtual hosts (www.etoyoc.com, www.gbeefabrics.com) as well as a mix of dynamically generated content and static files. This page is an explaination of how the site is configured to run toadhttpd in the hopes that the example may answer one or more questions you may have about your own setup.

Overview

To keep things ... sane... Sean has broken his configuration file up into several chunks. config.tcl is just the launching point to get to those files.

Files on his linode are hosted from /opt/www. Under that directory is:

htdocs/The doc_root for the default host (http://www.etoyoc.com)
sites/gbeefabrics The configuration file and static content for http://www.gbeefabrics.com
sites/fossilThe configuration file and static content for http://fossil.etoyoc.com
sites/yodaThe configuration file and static content Sean's personal website http://www.etoyoc.com/yoda
sites/pffThe configuration file, static content, customizations, etc for an online volunteer registration database the Sean runs for the Philadelphia Folk Festivals' Camping Committee. (http://pff.etoyoc.com)

To make things extra interesting, Sean builds all of his content at home, and synchronizes the file systems on linode via rsync. So there are several points in the configuration where an alternate URL is provided if the request was for localhost, and not a public domain name.

config.tcl

Sean's config.tcl is less a configuration file than a launching point to load other configuration files:
###
# The server's configuration can be modified with calls to "my config set"
###
my config set doc_ttl 900

###
# Package require the stock plugins we need
###
package require toadhttpd::sqlite
package require toadhttpd::bouncer
package require toadhttpd::clique
package require toadhttpd::fossil
package require toadhttpd::trivia
package require toadhttpd::bootstrap
package require toadhttpd::nadger

###
# Some plugins inject behavior into the server object
# do that process
###
my plugin bouncer
my plugin sqlite
my plugin clique
my plugin bootstrap
my plugin nadger

# Remember this location
set srvhere [file dirname [file normalize [info script]]]

###
# Some plugins require additional site specific configuration
# information
###
my config set notifier_sender     {The Blue Fairy}
my config set notifier_originator REDACTED_EMAIL_ADDRESS
my config set notifier_ports        REDACTED_SMTP_PORTS
my config set notifier_server       REDACTED_SMTP_SERVER
my config set notifier_username  REDACTED_SMTP_USER
my config set notifier_password  REDACTED_SMTP_PASSWORD

namespace eval ::etoyoc {}
###
# Load long-form customizations from the libtml directory
###
foreach file [glob -nocomplain [file join $srvhere libtml *.tcl]] {
  try {
    source $file
  } on error {err errdat} {
    puts "File: $file"
    puts "Error: $err"
    puts "Trace: [dict get $errdat -errorinfo]"
  }
}
###
# Go out to each site and load any customizations they need
###
foreach site [glob -nocomplain [file join $srvhere sites *]] {
  foreach file [glob -nocomplain [file join $site libtml *.tcl]] {
    try {
      source $file
    } on error {err errdat} {
      puts "File: $file"
      puts "Error: $err"
      puts "Trace: [dict get $errdat -errorinfo]"
    }
  }
}
###
# Send requests for popular script attack vectors to
# the black hole implemented in the bouncer plugin
###
my uri add % {
  /ccvv
  /admin%
  /test/wp-admin%
  /wp-login.php%
  /CGI/Execute /PhpMyAdmin% %.php
  /manager/html
  /wls-wsat% /.DS_Store% /.git%  /.hg%  /.idea%  /.ssh%
  /.well-known%  %phpunit%  /sftp_config.%
} {
  mixin toadhttpd::content.honeypot
}

###
#  Load the site specific URL rules
###
foreach site [glob -nocomplain [file join $srvhere sites *]] {
  if {![file exists [file join $site config.tcl]]} continue
  try {
    set sitename [file tail $site]
    source [file join $site config.tcl]
  } on error {err errdat} {
    puts "Site: $site"
    puts "Error: $err"
    puts "Trace: [dict get $errdat -errorinfo]"
  }
}

sites/fossil/config.tcl

###
# Sites can load their own packages
###
package require toadhttpd::fossil
set here [file dirname [info script]]

if {[file exists /opt/fossil]} {
  ###
  # On the server we only deliver fossil repos in the /opt/fossil directory
  ###
  foreach file [glob -nocomplain /opt/fossil/*.fossil] {
    set proj [file rootname [file tail $file]]
    ###
    # Add to the global robots.txt file that we REALLY do not want to see robots hitting
    # any of the following URIs that fossil hosts
    ###
    foreach area {login leaves files brlist taglist reportlist setup test tkthistory help vdiff} {
      my robots.txt add /fossil/$proj/$area
    }
  }
  my uri add fossil.etoyoc.com {/fossil/ /fossil/index%} {
    mixin httpd::content.redirect LOCATION http://fossil.etoyoc.com/fossil
  }
  my uri add fossil.etoyoc.com /fossil {
    mixin toadhttpd::content.fossil_root
    fossil_root /opt/fossil
    prefix fossil
  }
  my uri add fossil.etoyoc.com /fossil/% {
    mixin toadhttpd::content.fossil_node_scgi
    prefix fossil
    fossil_root /opt/fossil
  }
} else {
  ###
  # On the dev machine, offer up any fossil repository
  # that can be located by "fossil list"
  ##
  my uri add localhost {/fossil /fossil/ /fossil/index%} {
    mixin toadhttpd::content.fossil_root
    prefix fossil
  }
  my uri add localhost /fossil/% {
    TTL 0
    mixin toadhttpd::content.fossil_node_scgi
    prefix fossil
  }
}

sites/gbeefabrics/libtml/gbee.tcl

This file add some customized behaviors
namespace eval ::gbee {}
tool::define ::gbee::style {
  superclass ::etoyoc::style

  method html_css {} {
    upvar 1 is_localhost is_localhost
    if {$is_localhost} {
      set site_root /gbee
      set site_rel {}
      set stylesheet /gbee/style.css
    } else {
      set site_root {}
      set site_rel {}
      set stylesheet /style.css
    }
    append result "<link rel=\"stylesheet\" href=\"${stylesheet}\">"
    append result \n [uplevel 1 {subst {<style media="screen" type="text/css">
body {
	background:  url(${site_root}/images/bee-fleece.jpg) repeat;
	font-family: serif;
	color:#000066;
	font-size: 12pt;
}
.container {
    background-color: white;
}
.normal {
    background-color: white;
}
</style>}}]

  }
  method html_header {title args} {
    set result {}
    uplevel 1 {
set is_localhost [expr {[lindex [split [my http_info getnull HTTP_HOST] :] 0] eq "localhost"}]
    }
    upvar 1 is_localhost is_localhost
    if {$is_localhost} {
      set site_root /gbee
      set site_rel {}
      set stylesheet /gbee/style.css
    } else {
      set site_root {}
      set site_rel {}
      set stylesheet /style.css
    }
    append result "<HTML><HEAD>"
    if {$title ne {}} {
      append result "<TITLE>$title</TITLE>"
    }
    append result [my html_css]
    append result "</HEAD><BODY><!-- Gbee header -->"
    append result \n {<div id="top-menu">}
    if {[dict exists $args banner]} {
      append result "<img src=\"[dict get $args banner]\">"
    } else {
      append result "<img src=\"${site_root}/images/gbee-logo.jpg\">"
    }
    if {[dict exists $args sideimg]} {
      append result "\n<div name=\"sideimg\"><img align=right src=\"[dict get $args sideimg]\" width=25%></div>"
    }
    append result {<div id="content">}
    return $result
  }
  method html_footer_content {args} {
     return [subst {<font size="-1">All content copyright [clock format [clock seconds] -format %Y], GBee Fabrics | <tt>email: <a href="gbee_fabrics@etoyoc.com">gbee_fabrics@etoyoc.com</a> </tt></b>}]
  }
}

sites/gbeefabrics/config.tcl

set here [file dirname [file normalize [info script]]]

###
# Gbeefabrics uses a custom content generator to deliver
# news and events
###
my uri add www.gbeefabrics.com {/news% /events%} {
  CONTENT_TTL 86400
  mixinmap {reply ::etoyoc::content.news style ::gbee::style}
  prefix /news
  path [file join $here htdocs news]
}
###
# All other traffic for gbee fabrics is static content
# served from sites/gbeefabrics/htdocs
# Note: We use an alternate style plugin
###
my uri add www.gbeefabrics.com % {
  TTL 86400
  mixinmap {reply httpd::content.file style ::gbee::style}
  path [file join $here htdocs]
}
###
# The website has had a few different names that
# have been printed to buisness cards, redirect them
# to the canonical location
###
my uri add gbee%.etoyoc.com % {
  mixin httpd::content.redirect
  LOCATION http://www.gbeefabrics.com
}

###
# On the dev box, I just need some rules to deliver the website 
# to http://localhost/gbee
###
my uri add localhost {/gbee/news% /gbee/events%} {
  TTL 0
  mixinmap {reply ::etoyoc::content.news style ::gbee::style}
  prefix /gbee/news
  path [file join $here htdocs news]
}
my uri add localhost /gbee% {
  CONTENT_TTL 0
  mixinmap {reply httpd::content.file style ::gbee::style}
  path [file join $here htdocs]
  prefix /gbee
}