1. The Whole Damn Thing
article.tgz holds this article plus all the scripts, templates, CSS stuff, etc.
2. Introduction
I've been using Firefox as a file-browser for years. The default file display provided by Apache is OK, but it's not great.
I tried some tweaks but they weren't much of an improvement, and I had a specific itch to scratch: rapidly searching or reading dated files like firewall- or system-logs.
For example, I rotate my system logs at midnight by creating hard-links from /var/log files to files under a dated directory. On 15 Apr, the tree looked like this:
FILETREE HARDLINKED TO /var +--log | +--2026 | | +--0415 | | | +--cron /var/log/cron | | | +--daemon /var/log/daemon | | | +--debug.log ... | | | +--local0log | | | +--local1log | | | +--local2log | | | +--local3log | | | +--local4log | | | +--local5log | | | +--local6log | | | +--local7log | | | +--maillog | | | +--ntplog | | | +--secure | | | +--syslog /var/log/syslog
I want a calendar on the right half of the screen. Clicking 4/15/2026 gives me something like this on the left half, where the filenames are URLs pointing to the associated files:
561422 15-Apr-2026 23:59:57 cron
101 15-Apr-2026 00:01:00 daemon
37 15-Apr-2026 00:00:01 debug.log
37 15-Apr-2026 00:00:01 local0log
37 15-Apr-2026 00:00:01 local1log
37 15-Apr-2026 00:00:01 local2log
37 15-Apr-2026 00:00:01 local3log
37 15-Apr-2026 00:00:01 local4log
37 15-Apr-2026 00:00:01 local5log
285736 15-Apr-2026 23:36:18 local6log
194 15-Apr-2026 00:02:00 local7log
4663 15-Apr-2026 05:38:37 maillog
140 15-Apr-2026 06:22:28 ntplog
37 15-Apr-2026 00:00:01 secure
41485 15-Apr-2026 23:59:57 syslog
Permissions, etc. are dealt with elsewhere.
3. Apache configuration
My httpd.conf:
# DirectoryIndex: sets the file that Apache will serve if a directory
# is requested. Basics first...
<IfModule dir_module>
DirectoryIndex index.htm index.html index.xml index.txt
</IfModule>
# ...then append index.php to DirectoryIndex...
<IfDefine php_module.c>
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
<IfModule dir_module>
DirectoryIndex index.php
</IfModule>
</IfDefine>
# ...and finally append autoindex -- must come last.
# Sat, 14 Nov 2020 03:53:51 -0500
# Disable if you want to autogenerate fancy indexes.
<IfModule dir_module>
DirectoryIndex /cgi-bin/autoindex
</IfModule>
4. Web calendars
Dealing with dates is a PITA, but web access takes it to an entirely new level:
- You finally write a calendar that doesn't require you to scroll all over East Jesus, it's actually readable, and it works on more than one browser.
- You want to change one itty-bitty thing, and you end up going down a rabbit hole that makes you redo the whole page.
- My fix was to get a basic table web-page that works for one month, and embed as many months as I like in a larger table.
- If I were to do it over again, I'd use something like grid or flexbox -- I didn't put this on my webpage right away because I tried to do just that and ended up going down a rabbit hole of "improvements".
5. Avoid NIH: use cal to generate months
cal does everything I need for a readable month. All I had to do was add URLs for each day. cal output for Oct 2015 looks like this:
October 2015
Su Mo Tu We Th Fr Sa
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
5.1. Script: mon2pre
mon2pre reads cal output and writes a table.
cal 10 2015 | mon2pre | tidy > sample-oct.htm
The tidied-up HTML looks like this. I frequently use days as folders, so the CSS "class=e" is for an empty day, and "class=m" is for a date that should be marked as (say) containing files.
<!DOCTYPE html>
<!-- Generator: mon2pre -->
<html>
<head>
<title>October 2015</title>
<style>
a { text-decoration: none; }
a:link { color: blue; }
a:visited { color: red; }
a:hover { background-color: black; color: white; }
</style>
</head>
<body>
<pre>
Oct
Su Mo Tu We Th Fr Sa
<a class="e"
href="TOP/2015/1001"> 1</a><a class="e"
href="TOP/2015/1002"> 2</a><a class="e"
href="TOP/2015/1003"> 3</a>
<a class="e"
href="TOP/2015/1004"> 4</a><a class="e"
href="TOP/2015/1005"> 5</a><a class="e"
href="TOP/2015/1006"> 6</a><a class="e"
href="TOP/2015/1007"> 7</a><a class="e"
href="TOP/2015/1008"> 8</a><a class="e"
href="TOP/2015/1009"> 9</a><a class="e"
href="TOP/2015/1010"> 10</a>
<a class="e"
href="TOP/2015/1011"> 11</a><a class="e"
href="TOP/2015/1012"> 12</a><a class="e"
href="TOP/2015/1013"> 13</a><a class="e"
href="TOP/2015/1014"> 14</a><a class="e"
href="TOP/2015/1015"> 15</a><a class="e"
href="TOP/2015/1016"> 16</a><a class="e"
href="TOP/2015/1017"> 17</a>
<a class="e"
href="TOP/2015/1018"> 18</a><a class="e"
href="TOP/2015/1019"> 19</a><a class="e"
href="TOP/2015/1020"> 20</a><a class="e"
href="TOP/2015/1021"> 21</a><a class="e"
href="TOP/2015/1022"> 22</a><a class="e"
href="TOP/2015/1023"> 23</a><a class="e"
href="TOP/2015/1024"> 24</a>
<a class="e"
href="TOP/2015/1025"> 25</a><a class="e"
href="TOP/2015/1026"> 26</a><a class="e"
href="TOP/2015/1027"> 27</a><a class="e"
href="TOP/2015/1028"> 28</a><a class="e"
href="TOP/2015/1029"> 29</a><a class="e"
href="TOP/2015/1030"> 30</a><a class="e"
href="TOP/2015/1031"> 31</a>
</pre>
</body>
</html>
oct.htm has the HTML, and oct.jpg has a screenshot. The script has an option -b (body only) in case I want to use the output as part of a larger page that already has CSS and a container for multiple months.
It's intended to be a template; replace "TOP" with the top-level directory you want to display.
5.2. Script: mkmonths
mkmonths runs mon2pre for several years. I stopped at 2038 since that's going to be our next Unix clean-up-the-code year:
#!/bin/ksh
# generate plain/html calendars
export PATH=/usr/local/bin:/bin:/usr/bin
for year in $(seq 1990 2038); do
for mon in $(seq -w 1 12); do
echo ${year}-${mon}
cal $mon $year | ./mon2pre -b > templates/${year}/${mon}.tpl
done
done
6. Generated templates
Unless someone redoes the Gregorian calendar, these templates should do the trick. The templates directory for 2025 looks like this:
autoindex +--templates | +--2025 | | +--01.tpl | | +--02.tpl | | +--03.tpl | | +--04.tpl | | +--05.tpl | | +--06.tpl | | +--07.tpl | | +--08.tpl | | +--09.tpl | | +--10.tpl | | +--11.tpl | | +--12.tpl | +--2025.htm | +--2025.tpl
The January 2025 template (01.tpl) is the calendar body contained within the <pre> tags:
Jan
Su Mo Tu We Th Fr Sa
<a class="e"
href="TOP/2025/0101"> 1</a><a class="e"
href="TOP/2025/0102"> 2</a><a class="e"
href="TOP/2025/0103"> 3</a><a class="e"
href="TOP/2025/0104"> 4</a>
<a class="e"
href="TOP/2025/0105"> 5</a><a class="e"
href="TOP/2025/0106"> 6</a><a class="e"
href="TOP/2025/0107"> 7</a><a class="e"
href="TOP/2025/0108"> 8</a><a class="e"
href="TOP/2025/0109"> 9</a><a class="e"
href="TOP/2025/0110"> 10</a><a class="e"
href="TOP/2025/0111"> 11</a>
<a class="e"
href="TOP/2025/0112"> 12</a><a class="e"
href="TOP/2025/0113"> 13</a><a class="e"
href="TOP/2025/0114"> 14</a><a class="e"
href="TOP/2025/0115"> 15</a><a class="e"
href="TOP/2025/0116"> 16</a><a class="e"
href="TOP/2025/0117"> 17</a><a class="e"
href="TOP/2025/0118"> 18</a>
<a class="e"
href="TOP/2025/0119"> 19</a><a class="e"
href="TOP/2025/0120"> 20</a><a class="e"
href="TOP/2025/0121"> 21</a><a class="e"
href="TOP/2025/0122"> 22</a><a class="e"
href="TOP/2025/0123"> 23</a><a class="e"
href="TOP/2025/0124"> 24</a><a class="e"
href="TOP/2025/0125"> 25</a>
<a class="e"
href="TOP/2025/0126"> 26</a><a class="e"
href="TOP/2025/0127"> 27</a><a class="e"
href="TOP/2025/0128"> 28</a><a class="e"
href="TOP/2025/0129"> 29</a><a class="e"
href="TOP/2025/0130"> 30</a><a class="e"
href="TOP/2025/0131"> 31</a>
I needed a quick way to include month tables in a certain order, and a shell script plus M4 is about as easy as it gets. The 2025.tpl file holds M4 commands to create a 4-row/3-col calendar for 2025, with TOP replaced by /home/notebook:
define(`TOP',`/home/notebook')dnl sinclude(`header')dnl <table><thead><tr><th></th><th>2025</th><th></th></tr></thead><tbody> <!-- row 1/4 --> <tr> <td valign="top"><pre class="calendar">sinclude(`2025/01.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/02.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/03.tpl')</pre></td> </tr> <!-- row 2/4 --> <tr> <td valign="top"><pre class="calendar">sinclude(`2025/04.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/05.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/06.tpl')</pre></td> </tr> <!-- row 3/4 --> <tr> <td valign="top"><pre class="calendar">sinclude(`2025/07.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/08.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/09.tpl')</pre></td> </tr> <!-- row 4/4 --> <tr> <td valign="top"><pre class="calendar">sinclude(`2025/10.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/11.tpl')</pre></td> <td valign="top"><pre class="calendar">sinclude(`2025/12.tpl')</pre></td> </tr> </tbody></table> sinclude(`footer')dnl
The header and footer include files hold everything else needed to create a page table.
This covers the right side of the screen, leaving the left side free for displaying directories and files.
7. File listing
I wanted something like a basic Apache file list with directories first, some whitespace, and then plain files (ignoring dotfiles and other cruft). It should include filetype icons plus basic metadata like filesize and modification time.
A CGI script can handle this without excessive overhead, since I'm using pre-generated date templates for everything except the file list. I have old machines and I'm not patient; when my response time gets annoying, I'll trade up to whatever the cool kids are using.
One caveat: NO Java or Javascript. I consider those tools of Satan.
8. Security notes
- If the directory isn't available under your webserver via an alias or the moral equivalent, you won't even get a file listing.
- If the webserver doesn't have permission to read any of the files you're trying to display, this will fail miserably.
- This can use symbolic links from my web DOCUMENT_ROOT to directories outside the stuff usually seen by Apache; I'd recommend staying in your webserver directory tree or NOT using this on any system you don't control.
9. Script: autoindex
This is a top-level driver that finds the type and location of the directory we're displaying. It's different for (say) a syslog or firewall-log directory vs. my home directory. It looks for a file called .autoindex holding one line -- the script that does the real work of listing files on one side and a calendar on the other.
10. Script: dirindex
This is where the real work happens. It's a perl CGI script which uses the DOCUMENT_ROOT and REQUEST_URI environment variables to figure out what directory you want to display and where it actually resides on the filesystem. Here are the relevant environment variables when viewing the URL /home/notebook/2026/0417/:
DOCUMENT_ROOT = /doc/html/htdocs REQUEST_METHOD = GET REQUEST_SCHEME = http REQUEST_URI = /home/notebook/2026/0417/
My DOCUMENT_ROOT has a symbolic link to my home directory, so I can append REQUEST_URI to DOCUMENT_ROOT and get a legitimate top-level directory for the directory I want to display. The file $HOME/notebook/2026/0417/hiding-datacenter-emissions will be shown on the page as /home/notebook/2026/0417/hiding-datacenter-emissions.
Basic idea:
cd to today's directory from $REQUEST_URI and open it Find any subdirectories Find any other files that aren't directories
The screen looks like this, with icons shown in square brackets:
Directories: [DIR] 18 2026-04-17 22:13:02 Maildir [DIR] 6 2026-04-17 08:10:10 movies [DIR] 9 2026-04-17 08:03:03 rss [DIR] 26 2026-04-18 00:15:06 stats [DIR] 65 2026-04-20 08:05:28 townhall Files: [TXT] 316709 2026-04-17 08:05:28 aier.xml [TXT] 4770 2026-04-17 00:20:00 appointments.txt [TXT] 13800 2026-04-17 23:59:00 browser-history.furbag [TXT] 141977 2026-04-17 08:05:26 fifth-domain.xml [TXT] 7304 2026-04-17 03:57:20 hiding-datacenter-emissions [TXT] 176832 2026-04-17 08:05:27 nextgov.xml [TXT] 283340 2026-04-17 08:05:28 quillette.xml [TXT] 1940 2026-04-17 02:44:17 reddit-bash-scripting [TXT] 30424 2026-04-17 08:05:30 risks.xml
Here's a screenshot.
You can see a list of directories at the top of the calendar display on the right. I didn't see a good reason to regenerate something on the fly that rarely if ever changes, so dirindex looks for those directories in the file $HOME/notebook/.dirlist:
<a href="/home/notebook/1990/0101">1990</a> <a href="/home/notebook/1991/0101">1991</a> ... <a href="/home/notebook/2009/0101">2009</a> <a href="/home/notebook/2010/0101">2010</a><br> <a href="/home/notebook/2011/0101">2011</a> ... <a href="/home/notebook/2025/0101">2025</a> <a href="/home/notebook/2026/0101">2026</a>
I've included a reasonable set of icons in the ficons directory (expected to be under DOCUMENT_ROOT) which are assigned to files based on the extension:
'.[1-9]' => 'file-man.png', '.aiff' => 'file-sound.png', '.doc' => 'x-office-document.png', '.exe' => 'application-x-executable.png', ... '.wav' => 'file-sound.png', '.wmv' => 'video-x-generic.png', '.xhtml' => 'text-html.png',
11. The final display
I generate as much content as possible in advance, so the CGI script runs quickly. Yearly calendars are copied from the templates directory to my notebook directory:
+--home | +--vogelke | | +--notebook | | | +--2000 | | | | +--.calendar <== copy of templates/2000.htm | | | +--2001 | | | | +--.calendar <== copy of templates/2001.htm ... | | | +--2025 | | | | +--.calendar <== copy of templates/2025.htm | | | +--2026 | | | | +--.calendar <== copy of templates/2026.htm ...
Here's a complete sample page showing the calendar for 4 May 2026.
12. Stylesheet: 2col.css
I derived this from an article by Matthew James Taylor: https://matthewjamestaylor.com/2-column-layouts
His articles are excellent if you want responsive multi-column pages that work.
13. Installation
- Any files or directories you want to display must be readable by your webserver. This can be risky, which is why I copy things like /var/log or firewall-log files elsewhere for display.
- Tell your webserver to use /cgi-bin/autoindex as the default for generating a directory index. The Apache configuration section shows how to do that for Apache.
- Put dirindex and autoindex scripts in your cgi-bin directory.
- Copy templates for each year (YYYY.htm) wherever you can find them. Those templates contain hard-wired paths (in my case, /home/notebook/2026 for this year) that match aliases provided by Apache, like /home pointing to my home directory.
- Copy the stylesheet (2col.css) wherever your CSS files live. The dirindex script assumes they live in a /style directory under your document root.
- Scripts will look for yearly calendars named .calendar under the YYYY directories.
- I included two additional scripts: slindex is for displaying copies of syslog files (/var/log) since I rotate them daily, and mfindex displays information about files that have been modified or added on a given day.
14. Feedback
Feel free to send comments.
Generated from article.t2t by
txt2tags
$Revision: 1.8 $
$Date: 2026-05-05 01:16:21-04 $
$UUID: 1b845f31-a658-4a5d-a871-9aa9573b52cd $