(define page `((p "Tekuti means \"I'm telling you\" in Oshiwambo. It is weblog software written in Scheme, using Git as its persistent store.") (h3 "Why") (p "Tekuti hacks the web as it is meant to be hacked.") (p (i "Tekuti uses tags, not categories.") " Put however many labels you like on your posts, the more the merrier: with each tag you add meaning to your tag cloud, related posts, and related tags.") (p (i "Tekuti uses Git as its backing store.") " The data model scales, and you get hot backups and rollback for free.") (p (i "All operations on the data store are revertable.") " Try it: make a post, add some comments, revert some comments, revert the reversions...") (p (i "Tekuti is secure.") " There is one entry point, and the entirety of the program is written in the functional style. It does not perform any destructive modifications to data. It is very easy to audit its code.") (p (i "Tekuti respects the meaning of the web's GET and POST operators.") " Clicking on a link will not modify the data store.") (p (i "There is a strict separation in types between XML and strings.") "You won't see malformed XHTML come out of Tekuti, much less an XSS vulnerability.") (p (i "Output transformation and templating") " is easy and built-in to the language via the powerful pre-post-order operator.") (p "And, most importantly, it's written in Scheme, so it's fun to hack!") (h3 "How") (h4 "Installing prerequisites") (ol (li (p "Build and install Guile 1.9.14 or newer." " (Or if you are reading this before 1.9.14 is out, build Guile from git.)")) (li (p "Install git. Any modern git will work."))) (h4 "Get and build tekuti") (pre "git clone git://gitorious.org/tekuti/tekuti.git") (pre "cd tekuti; autoreconf -vif; ./configure && make") (h4 "Run it!") (pre "./env src/tekuti") (p "Now navigate to " (a (@ (href "http://localhost:8080/")) "http://localhost:8080/") " in your browser. Does it work? Sweet!") (p "To add posts, visit the admin interface at e.g. " (tt "http://localhost/blog/admin") ". The default user is admin and the password is admin.") (h3 "Serving tekuti behind apache") (p "I hear the kids are all running nginx or mongrel2 or whatever, " "but I haven't caught up yet. So " (tt "a2enmod") " mod_proxy, mod_rewrite," "and mod_proxy_http, then add this to an appropriate " (tt ".htaccess") " file:") (pre "RewriteEngine On RewriteBase / RewriteRule ^archives/?(.*)$ http://localhost:8080/archives/$1 [P,QSA] RewriteRule ^feed(/.*)?$ http://localhost:8080/feed/$1 [QSA,P] RewriteRule ^search/?$ http://localhost:8080/search [QSA,P] RewriteRule ^tags/?(.*)?$ http://localhost:8080/tags/$1 [QSA,P] RewriteRule ^debug/*$ http://localhost:8080/debug [QSA,P] RewriteRule ^admin/?(.*)$ http://localhost:8080/admin/$1 [QSA,NE,P]") (p "You might need to set this somewhere else in your config:") (pre "AllowEncodedSlashes On") (p "In any case, you'll have to restart apache.") (h3 "Migrating from wordpress") (p "Tekuti includes a script to import posts from wordpress into a suitably-laid-out directory. Run it as:") (pre "python ./wordpress-to-dir.py HOST USER PASSWORD DBNAME") (p "Then in the directory that the script makes, run:") (pre "git init git add . git commit -m 'initial import from wordpress'") (p "Then replace ~/blog.git with the " (tt ".git") " subdirectory.") (p "Tekuti's URL structure is almost the same as Wordpress' so the only other thing you might want to do would be to redirect category links to tag links, and the rss2 feed to the atom feed:") (pre "RewriteRule ^archives/category/(.*)$ /tags/$1 [R=permanent] RewriteRule ^feed/rss2$ /feed/atom [R=permanent]") (p "Tekuti will emit valid XML. It will complain if the wordpress posts are not valid. Common errors would be using
instead of
, and ampersands in URLs. Running xmllint on all files named \"content\" in the directory might be useful.") (p "The XML code doesn't understand all character entities, either -- see config.scm's *character-entities* to add more.") (h3 "Hacking tekuti") (p "Run Tekuti with the " (tt "--listen") " option. Then telnet to port 37146 on localhost, and voila, a REPL!") (p "For me, the best way to hack in the repl is to enter \"inside\" other modules. For example, to change the admin password, I would:") (pre " ;; enter in the already-defined (tekuti config) module (define-module (tekuti config)) ;; replace the *admin-pass* definition (define *admin-pass* \"foo\")") (p "Of course the best way is to do all this from within Emacs. Just install " (a (@ (href "http://www.nongnu.org/geiser/")) "Geiser") ", then M-x geiser-connect to the running REPL. Open tekuti/config.scm in Emacs, change the admin password, and press C-x C-e after the closing parenthesis. Then go to your admin page and see if the password changed. Nifty, eh?") (h3 "Deploying") (p "For deployment-specific variables like the CSS files and the paths and such, you can run tekuti with the -c argument and pass it a config file, which will be evaluated inside the (tekuti config) module. For example, one of mine looks like this:") (pre ";; -*- scheme -*- (set! *navbar-links* '((\"about\" . \"/about/\") (\"software\" . \"/software/\") (\"writings\" . \"/writings/\") (\"photos\" . \"/photos/\"))) (set! *navbar-infix* \" | \")") (p "I suggest taking the CSS file from my blog and customizing it for your usage. Please make it look different. If you come up with something that I should include as a default, I'd be interested in seeing it.") (p "I deploy tekuti in a screen session. Run " (tt "screen") ", then run tekuti. Then press C-a d to detach from the session. Then when you come back to your server you run " (tt "screen -r") " to jump back into that tekuti session.") (h3 "Feedback") (p "Send me an email at " (code "wingo at pobox.com") "."))) ;; Now output the HTML (use-modules (sxml simple) (sxml transform)) (load "../../template.scm") (define xhtml-doctype (string-append "\n")) (define (make-index) (display xhtml-doctype) (sxml->xml (templatize page "tekuti" "projects" "../../")))