Monthly Archive for Dezember, 2007

Sessions aufräumen – Stale Sessions Clean Up

Wenn man den ganz normalen Session Handler von Rails benutzt, dann werden die Sessions unter tmp/sessions/ abgelegt. Rails räumt dieses Session Verzeichnis aber nicht selber auf. Das kann zu einem Problem werden, wenn man plötzlich tausende von Sessionfiles in diesem Verzeichnis findet. Erst einmal wird die Application dadurch langsamer und zum Zweiten kann es auch das File System des Servers belasten, wenn der nämlich plötzlich keine Nodes mehr machen kann.

Eine einfach Lösung für dieses Problem ist ein Cron Job der das Verzeichnis regelmäßig aufräumt und alle alten (stale) Sessions löscht.

Hierfür habe ich ein Shell Script geschrieben, was ich in den Script Ordner meines Rails Projektes abgelegt habe. So sieht es aus:
script/remove_stale_sessions.sh

#!/bin/sh
find ../tmp/sessions/ruby_sess.* -mtime +1 -print | xargs rm -rf

Das Script löscht alle Sessions die seit 1 Tag nicht mehr benutzt wurden. Jetzt fehlt noch der Cron Job, der das Script alle paar Minuten aufruft. Ich hab ihn mal auf alle 10 Minuten gestellt. Mit crontab -e ruft man den Cron Job Manager auf. Jetzt i drücken, damit man in den INSERT Mode kommt und in die letzte Zeile folgenden Code schreiben:

*/10 * * * * sh /pfad/zur/app/script/remove_stale_sessions.sh

esc drücken um den INSERT Mode zu verlassen und :wq zum speichern und schon sollte der neue Cron installiert sein.

Update:
Ach so… vielleicht sollte man dafür gleich nen Deployment Task oder so was schreiben… irgendwas, damit man halt nicht bei jedem Projekt dran denken muss… wenn jemand Vorschläge hat…

Localization Plugin: Improved generate_l10n_file

Das Localization Plugin hat eine Methode generate_l10n_file um die zu lokalisierenden Strings auszulesen. Leider macht sie dies nicht besonders gut.

Auf die Schnelle habe ich sie etwas abgeändert und siehe da:

def self.generate_l10n_file
  "Localization.define('de') do |l|" < <
  Dir.glob("#{RAILS_ROOT}/app/**/*.*").collect do |f| 
    ["# #{f}"] << File.read(f).scan(/(<%=_ |_\()[\"\'](.*?)[\"\']/)
  end.uniq.flatten.collect do |g|
    g.starts_with?('#') ? "" : "  l.store '#{g}', ''"
  end.uniq.sort.join("\n").gsub("  l.store '_(', ''",'').gsub("  l.store '<%=_ ', ''",'') << "\nend"
end

Jetzt werden sämtliche Controller ausgelesen und es werden deutlich mehr Strings gefunden (nämlich auch die wo nicht %= davor steht).

UPDATE: Zu “#{RAILS_ROOT}/app/**/*.*” müsste man noch “#{RAILS_ROOT}/app/views/**/*.*” irgendwie hinzunehmen.

Hier noch ein rake task dafür:

namespace [:l10n] do
    desc 'Verbose localization file (experimental)'
    task :generate => :environment do
      puts
      puts 'Creating localization file...'
      puts '----------- SNIP -----------'
      puts Localization.generate_l10n_file
      puts '----------- SNAP -----------'
      puts
      puts 'Finished.'
      puts
    end
end

Capistrano 2 hinter den Vorhang gucken

Auf Railstrac habe ich diese wunderbare Datei gefunden, die mir endlich verrät, was genau hinter den cap tasks steckt:
http://dev.rubyonrails.org/browser/tools/capistrano/lib/capistrano/recipes/deploy.rb

“Capistrano überschreibt meine Dateien immer!”

Wer seit Neuestem mit Capistrano seine rails app deployed, der kam vielleicht auch schon dahinter, dass dann alle Dateien weg sind, die man in ein bestimmtes Verzeichnis geschrieben oder hochgeladen hat.

“meinprojekt” ist im Folgenden das Verzeichnis auf dem Server, wohin deployed wurde. Standardmäßig ist das glaube ich /u/apps/meinprojekt.

Also zum Beispiel meinprojekt/public/uploads wird nach dem deploy einfach gelöscht und alle Dateien sind weg (nicht wirklich weg, man muss nur im Verzeichnis meinprojekt/releases danach suchen ;).

Um das zu verhindern (hier gefunden) muss man im meinprojekt/shared Verzeichnis auf dem Server wo man es deployed hat die Verzeichnisse erstellen, die solche wichtigen Sachen enthalten.

Also z. B. mkdir meinprojekt/shared/public/uploads. Anschließend passt man seine deploy.rb wie folgt an:

task :after_update_code, :roles => :app do
run “ln -nfs #{deploy_to}/shared/public/uploads #{release_path}/public/uploads”
end

Dadurch wird nachdem der Code deployed (also hochgeladen) wurde, ein Hardlink von dem deployten uploads-Verzeichnis auf /u/apps/meinprojekt/shared/public/uploads gemacht. Also sozusagen ein weiterverweisen auf das shared Verzeichnis.

Immer wenn jetzt jemand was hochläd, wird es in shared gespeichert, anstatt in current. Beim nächsten Deploy wird der Link wieder auf shared gesetzt und keine Daten gehen verloren.

Hier mal ein Beispiel für meine Gallery:

namespace :deploy do
[task :start...]
after “deploy:update_code”, :link_to_shared
end

# Create hard links to data in shared
desc “Link in critical data”
task :link_to_shared do
run “ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml”
run “ln -nfs #{deploy_to}/shared/backup #{release_path}/backup”
run “ln -nfs #{deploy_to}/shared/private #{release_path}/private”
run “ln -nfs #{deploy_to}/shared/public/images/gallery #{release_path}/public/images/gallery”
run “ln -nfs #{deploy_to}/shared/public/images/pending #{release_path}/public/images/pending”
end

Super. Danke Capistrano 2, dass du das kannst :)

apache und mongrel_balancer errors

Hi,

wenn du sowas hast:

client denied by server configuration: proxy:balancer://mongrel_cluster/

dann musst du in deine httpd.conf das hier reinschreiben, (weil in der /etc/apache2/mods-available/proxy.conf alles auf “deny from all” steht):

Order allow,deny
Allow from all

und wenn du danach das hier hast:

proxy: No protocol handler was valid for the URL /. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.

dann musst du den mod_proxy_http hinzufügen:

ln /etc/apache2/mods-available/proxy_http.load /etc/apache2/mods-enabled/proxy_http.load
/etc/init.d/apache2 restart

Hat mich ‘ne Stunde gekostet ;)

subversion auf debian aufgesetzt

Hi, endlich geschafft. SVN läuft und es war (im Rückblick gesehen) gar nicht so schwer. Dieser Artikel hat mir gut weitergeholfen. Nach der Installation von subversion mit aptitude hat folgende Einrichtung auf einem unserer debian server funktioniert:

Verzeichnis für Repositories auf dem Server erstellen:

mkdir /var/svn

Jetzt wird aus diesem Verzeichnis ein subversion repository gemacht:

svnadmin create /var/svn

Da der Server bisher nur über einen root Zugang verfügte, erstmal Benutzer anlegen, die subversion später nutzen werden:

useradd future
useradd manuel

Diese bekommen dann noch schöne Passwörter verpasst. Bei Eingabe wird ein gewünschtes Passwort für den jeweiligen user abgefragt.

passwd future
passwd manuel

Jetzt noch die home-verzeichnisse anlegen:

mkdir /home/future
mkdir /home/manuel
chown future:users /home/future
chown manuel:users /home/manuel

Damit wir uns später Arbeit sparen, legen wir eine Gruppe an, und setzen die neuen User in diese Gruppe. So ist es später leichter, Rechte für die Nutzung von SVN zu vergeben.

adduser future subversion
adduser manuel subversion

Jetzt bekommt die Gruppe (und somit future und manuel) Zugriff auf das vorhin erstellte svn repository. Der -R Parameter steht für rekursive, also alle Unterverzeichnisse inbegriffen (auf o. a. Artikel gibt es etwas mehr Details hierzu).

chgrp -R subversion /var/svn
chmod -R o-rwx /var/svn
chmod -R g+rw /var/svn
chmod g+s /var/svn/db

Jetzt wird es nochmal etwas tricky. Wenn oben genannte User Dateien erstellen und so weiter, dann werden diesen Dateien bestimmte Rechte gegeben. Nun wollen wir aber sicher stellen, dass niemand aus Versehen nur Rechte für sich selbst einräumt.

Dafür verwenden wir den UNIX Befehl umask. Wenn wir “umask 002″ aufrufen, heißt dass, dass von jetzt an bei Dateierstellung volle Rechte für die eigene Gruppe und lese und execute Rechte für alle andere gesetzt werden soll.

Da nicht jeder ständig diesen Befehl eingeben wird und möchte, “automatisieren” wir das ganz schlau. Nutzt jemand unser SVN repository, wird die Datei “svnserve” aufgerufen. Wir wechseln in das Verzeichnis, wo diese Datei sich befindet (“which svnserve” verrät uns wo das ist) und bennennen die Datei um! und zwar nehmen wir das letzte “e” weg:

cd /usr/bin
mv svnserve svnserv

Anschließend legen wir eine bash-Datei an, die genau so heißt wie unser svnserve vorher. In diese Datei schreiben wir folgendes:
Inhalt von /usr/bin/svnserve

#!/bin/bash
umask 002
/usr/bin/svnserv $*

Das bewirkt, dass jedes Mal wenn jemand svnserve (mit oder ohne Parameter) startet, er unser kleines Progrämmchen startet, dadurch automatisch “umask 002″ ausführt und dann erst das original svnserv. Schlau oder? (Den Trick habe ich übrigens hier gefunden.)

Jetzt befassen wir uns mit der Datei /var/svn/conf/svnserve.conf. Es handelt sich um die Konfiguration unseres svn repositories. In ihr passen wir ein paar Einstellungen wie folgt an:

[general]
anon-access = none
auth-access = write
realm = funkensturm

Damit geben wir anonymen usern “none” Rechte, future und manuel “write” rechte und noch einen Namen für unser Repository.

Auf dieser Seite habe ich ein paar Einstellungen gefunden, wie man den SSH Login usw. sicherer macht. Außerdem müssen wir ja noch unserer Gruppe subversion den Login per SSH erlauben!
Also ran an die Datei /etc/ssh/sshd_config (vorher backup machen!).
Die Datei ist ziemlich lang. Worauf es ankommt sind letzten Endes diese Werte:

Port 4444
AllowGroups subversion
LoginGraceTime 30
PermitRootLogin no

Damit haben wir den SSH Port von 22 (Standard) auf (z. B.) 4444 erhöht, damit nicht jeder Depp direkt den Port scannen kann. AllowGroups ist neu hinzugekommen, damit alle user in der Gruppe “subversion” SSH benutzen können. Die LoginGraceTime wurde auf 30 Sekunden gesetzt (Zeit zum Passwort eingeben). Da manuel und future sich jetzt einloggen können, können wir den login als root via SSH verbieten mit PermitRootLogin no.

Jetzt noch schnell SSH neu starten und die Änderungen sind aktiv:

/etc/init.d/ssh restart

FERTIG!

Ab jetzt kann man sich per SSH so auf dem Server (natürlich mit der richtigen IP und nicht 123…) einloggen:

ssh future@123.123.123.123 -p 4444

ABER!

Leider kapiert dein lokales svn nicht, dass er bei svn+ssh nicht den Port 22 nehmen soll, sondern unseren 4444. Dafür müssen wir LOKAL (also auf DEINEM MacBook oder PowerBook ;) folgende Datei anpassen: ~/.subversion/config

[tunnels]
fs = /usr/bin/ssh -p 4444

Damit sagen wir, dass ab jetzt svn+fs:// mit Port 4444 aufgerufen wird. (Man könnte auch anstelle “fs” auch “ssh” in die config Datei schreiben, dann würde bei svn+ssh:// immer Port 5777 verwendet werden. Aber das will man mit an Sicherheit grenzender Wahrscheinlichkeit nicht.)

Wenn man sein Projekt auschecken möchte, wird für unseren Server also ab jetzt das hier benutzt:

svn co svn+fs://future@123.123.123.123/var/svn/projekt

Zum hochladen eines neuen Projektes:

svn import projektname svn+fs://future@123.123.123.123/var/svn/projektname -m "Beschreibung der Änderung"

YEAH BABY!

Plugin: acts_as_list_in_tree und descendants in acts_as_tree

Ich habe eine Kategorienliste mit Unterkategorien. Nun möchte ich acts_as_list verwenden, um die Reihenfolge mit den Feld “position” leichter ändern zu können. Das geht auch soweit (Dann hat man so tolle Funktionen wie Category.move_higher).

Leider gibt es da aber immer wieder Probleme, da eine neu hinzugefügte Kategorie die Position max(alle_positionen)+1 erhält. Also die höchste ALLER Positionen +1. Ich möchte aber, dass eine neu hinzugefügte Kategorie die position+1 bezogen auf seine Vaterkategorie erhält!

Also sich als Liste verhält, aber nur in Bezug auf seine Geschwister!

Dafür muss man das Plugin (Rails 2.0) “acts_as_list” ändern. Und zwar muss man die Zeile 64, auskommentieren:

##{scope_condition_method}

Und die Funktion scope_condition() in Zeile 186 wie folgt ändern:

# Overwrite this method to define the scope of the list changes
#def scope_condition() "1" end
def scope_condition() "parent_id = #{self.parent_id}" end

So habe ich mir mein eigenes acts_as_list_in_tree (download) Plugin gebaut.

Vielleicht noch als Anmerkung:
In acts_as_tree gibt es KEINE DESCENDANTS Funktion! Das heißt, man kann sich in einem Tree nicht die Nachkommen ausgeben lassen. Die Vorfahren schon, mit .ancestors, aber halt nicht die Nachkommen.

Siehe da HIER habe ich die Lösung gefunden. Ich hatte nur gehofft, sie sei in Rails 2.0 schon implementiert, ist sie aber nicht!

Also selber hinzufügen (am besten unter die ancestors funktion) in
/vendor/plugins/acts_as_tree/lib/active_record/acts/tree.rb:

# Bugfix - Returns list of descendants
def descendants 
  descendants = [] 
  self.children.each { |child| descendants += [child] + child.descendants } if self.children.length > 0 
  descendants
end

Wenn es in Rails 2.x übernommen wird, kann man es wieder löschen ;)

Das war’s für heute :)