# Copyright © 2010 Julian Blake Kongslie # Licensed under the BSD 3-clause license. use strict; use warnings; package Piny::Repo; use Moose; use Moose::Util::TypeConstraints; use MooseX::StrictConstructor; use Digest::SHA qw( sha256_hex ); use File::Copy; use File::Find qw( find ); use File::Temp qw( ); use IO::Dir qw( ); use Math::GMP qw( ); use POSIX qw( ); use IkiWiki::FakeSetup qw( readSetup writeSetup ); use Piny::Config; use Piny::Environment; use Piny::Group; use Piny::User; use Piny::User::IkiWiki; # Types subtype 'Reponame' => as 'Str' => where { $_ =~ /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/ } => message { 'That name is not in the correct format for a piny repo.' } ; subtype 'SimpleText' => as 'Str' => where { $_ =~ /^[\x{0020}-\x{FDCF}\x{FDF0}-\x{FFFD}]{1,80}$/ } => message { 'That description is not in the correct format for a piny repo.' } ; # Attributes has 'name' => ( is => 'ro' , isa => 'Reponame' , required => 1 ); has 'shortname' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'group' => ( is => 'ro' , isa => 'Piny::Group' , lazy_build => 1 , init_arg => undef ); has 'path' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'description' => ( is => 'rw' , isa => 'SimpleText' , trigger => \&_set_description , lazy_build => 1 , init_arg => undef ); has 'repostat' => ( is => 'ro' , isa => 'ArrayRef' , lazy_build => 1 , init_arg => undef ); has 'owner' => ( is => 'rw' , isa => 'Piny::User' , trigger => \&_change_owner , lazy_build => 1 , init_arg => undef ); has 'globally_readable' => ( is => 'ro' , isa => 'Bool' , lazy_build => 1 , init_arg => undef ); has 'globally_writable' => ( is => 'ro' , isa => 'Bool' , lazy_build => 1 , init_arg => undef ); has 'cgit_url' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_setup' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_destdir' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_srcdir' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_url' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_cgiurl' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_historyurl' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_diffurl' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'ikiwiki_cgipath' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'secure_path' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'apache_global_config' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'apache_secure_config' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'apache_www_config' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'config' => ( is => 'ro' , isa => 'Piny::Config' , lazy_build => 1 , init_arg => undef , clearer => 'clear_config' ); # Public methods sub add_access { my ( $s, @users ) = @_; $s->group->add_member( @users ); }; sub remove_access { my ( $s, @users ) = @_; $s->group->remove_member( @users ); }; sub has_access { my ( $s, $user ) = @_; return $s->owner->uid == $user->uid || $user->has_group( $s->group ); }; sub rebuild { my ( $s ) = @_; if( POSIX::access( $s->path, &POSIX::W_OK ) ){ $s->rebuild_git; } else { warn "Read-only git repository detected. Skipping git rebuild."; }; $s->rebuild_apache; $s->destroy_ikiwiki; if ( $s->config->piny_ikiwiki =~ /^(1|true)$/ ) { $s->rebuild_ikiwiki; }; }; sub rebuild_git { my ( $s ) = @_; my $dirperm; my $immutablefileperm; my $fileperm; unless( getgrnam("git-" . $s->shortname ) ) { system( "/usr/sbin/addgroup", "--quiet", "git-" . $s->shortname ) and die "Could not create repo group!"; system( "/usr/sbin/adduser", "--quiet", $s->owner->name, "git-" . $s->shortname ) and die "Could not add you to the repo group!"; getpwnam("iki-" . $s->shortname) and system( "/usr/sbin/adduser", "--quiet", "iki-" . $s->shortname, "git-" . $s->shortname ) and die "Could not add ikiwiki user to git group!"; }; foreach( "git-daemon-export-ok", "packed-refs" ) { unless( -e $s->path . "/" . $_ ) { open( TOUCH, ">", $s->path . "/" . $_ ) or die "Could not touch $_ for repo: $!"; close( TOUCH ); }; }; unless( -d $s->path . "/snowflakes" ) { mkdir( $s->path . "/snowflakes" ) or die "Could not mkdir snowflakes: $!"; }; unless( -l $s->path . "/packed-refs" ) { rename( $s->path . "/packed-refs", $s->path . "/snowflakes/packed-refs" ) or die "Could not rename packed-refs: $!"; symlink( "snowflakes/packed-refs", $s->path . "/packed-refs" ) or die "Could not symlink packed-refs: $!"; }; unless( -l $s->path . "/HEAD" ) { rename( $s->path . "/HEAD", $s->path . "/refs/HEAD" ) or die "Could not rename HEAD: $!"; symlink( "refs/HEAD", $s->path . "/HEAD" ) or die "Could not symlink HEAD: $!"; }; foreach( "info", "logs", "branches" ) { (-e $s->path . "/" . $_) or mkdir( $s->path . "/" . $_ ) or die "Could not mkdir $_ for repo: $!"; }; if ( $s->config->core_sharedrepository eq "0666" ) { $dirperm = "2777"; $immutablefileperm = "0644"; $fileperm = "0666"; } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { $dirperm = "2775"; $immutablefileperm = "0644"; $fileperm = "0664"; } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) { $dirperm = "2770"; $immutablefileperm = "0640"; $fileperm = "0660"; } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { $dirperm = "2750"; $immutablefileperm = "0640"; $fileperm = "0640"; } else { die $s->config->core_sharedrepository . "is an unhandled value!" }; # FIXME: we should verify we are not breaking someone else's object hardlinks with these chmod or chown operations system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/objects " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs -type d -print0 | /usr/bin/xargs -0 /bin/chmod $dirperm" ) and die "Could not chmod shared git resources!"; system( "/usr/bin/find " . $s->path . "/objects " . "-type f -print0 | /usr/bin/xargs -0 --no-run-if-empty /bin/chmod $immutablefileperm" ) and die "Could not chmod shared git resources!"; # most files are either immutable or replaced at link level system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs -type f -print0 | /usr/bin/xargs -0 /bin/chmod $fileperm" ) and die "Could not chmod shared git resources!"; # most files are either immutable or replaced at link level system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/objects " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs -print0 | /usr/bin/xargs -0 /bin/chgrp " . $s->group->name ) and die "Could not chgrp shared git resources!"; $ENV{"GIT_DIR"} = $s->path; system( "/usr/bin/git", "config", "gitweb.owner", $s->owner->email->address ) and die "Could not git config gitweb.owner!"; # packed-refs files aren't modifiable under our permission system and are poorly supported on old version of git anyway. system( "/usr/bin/git", "config", "gc.packrefs", "0" ) and die "Could not git config gc.packrefs!"; # git-upload-pack will default to core.sharedrepository 'false', so we make sure the pinyconfig value is set here. system( "/usr/bin/git", "config", "core.sharedrepository", $s->config->core_sharedrepository ) and die "Could not git config core.sharedrepository!"; delete $ENV{"GIT_DIR"}; chown( 0, 0, $s->path ) or die "Could not change ownership of " . $s->path; chmod( 00755, $s->path ) or die "Could not change mode of " . $s->path; foreach( "config", "description", "git-daemon-export-ok" ) { chown( 0, 0, $s->path . "/" . $_ ) or die "Could not change ownership of $_!"; chmod( 00644, $s->path . "/" . $_ ) or die "Could not change mode of $_!"; }; $s->clear_config; $s->config->save; }; sub rebuild_apache { my ( $s ) = @_; my $globaltemp = File::Temp->new( ) or die "Could not create temporary file: $!"; $globaltemp->unlink_on_destroy( 0 ); print $globaltemp $s->apache_global_config; $globaltemp->close or die "Could not close new wikilist: $!"; rename( $globaltemp->filename, "/etc/apache2/piny/global/" . $s->name ) or die "Could not rename apache config: $!"; my $securetemp = File::Temp->new( ) or die "Could not create temporary file: $!"; $securetemp->unlink_on_destroy( 0 ); print $securetemp $s->apache_secure_config; $securetemp->close or die "Could not close new wikilist: $!"; rename( $securetemp->filename, "/etc/apache2/piny/secure/" . $s->name ) or die "Could not rename apache config: $!"; my $wwwtemp = File::Temp->new( ) or die "Could not create temporary file: $!"; $wwwtemp->unlink_on_destroy( 0 ); print $wwwtemp $s->apache_www_config; $wwwtemp->close or die "Could not close new wikilist: $!"; rename( $wwwtemp->filename, "/etc/apache2/piny/www/" . $s->name ) or die "Could not rename apache config: $!"; if ( $s->config->piny_apachereload =~ /^(1|true)$/ ) { system( "/etc/init.d/apache2", "reload" ) and die "Could not reload apache config!"; }; }; sub rebuild_ikiwiki { my ( $s ) = @_; chdir( $s->config->piny_gitpath ); # because git likes to chdir back to its original cwd for some dumb reason, which isn't guaranteed to work. unless( getpwnam("iki-" . $s->shortname ) ) { system( "/usr/sbin/adduser", "--quiet", "--system", "--group", "--gecos", $s->shortname, "iki-" . $s->shortname ) and die "Could not create ikiwiki user!"; system( "/usr/sbin/adduser", "--quiet", "iki-" . $s->shortname, "git-" . $s->shortname ) and warn "Could not add ikiwiki user to the repo group!"; }; my $ikiuser = Piny::User::IkiWiki->new( "name" => "iki-" . $s->shortname ); system( "/bin/chown", $ikiuser->name . "." . $ikiuser->name, $s->path . "/hooks" ) and warn "Could not change ownership of git hooks!"; open( SETUP, ">", "/etc/ikiwiki/piny/" . $s->name . ".setup" ) or die "Could not open new ikiwiki setup file: $!"; print SETUP $s->ikiwiki_setup; close( SETUP ) or die "Could not close new ikiwiki setup file: $!"; chmod( 00644, "/etc/ikiwiki/piny/" . $s->name . ".setup" ) or die "Could not chmod ikiwiki setup file: $!"; foreach( $s->ikiwiki_srcdir, $s->config->piny_ikiwikidestdir . $s->name, $s->ikiwiki_destdir, $s->secure_path ) { unless( -d $_ ) { mkdir( $_ ) }; system( "/bin/chown", "-R", $ikiuser->name . ".", $_ ) and die "Could not change ownership of ikiwiki directories!"; }; system( "/usr/bin/find " . $s->ikiwiki_srcdir . " -type d -name .ikiwiki -print0 | xargs -0 --no-run-if-empty rm -r") and die "Could not remove old Ikiwiki state dir!"; unless( -d $s->ikiwiki_srcdir . ".git" ) { # do this after the chown -R because there are a lot of object hardlinks flying around that don't want to chown system( "/usr/bin/sudo", "-H", "-u", $ikiuser->name, "/usr/bin/git", "clone", "--quiet", $s->path, $s->ikiwiki_srcdir ) and die "Could not clone repo to ikiwiki srcdir!"; }; open( WIKILIST, ">", "/etc/ikiwiki/wikilist.d/" . $s->name ) or die "Could not create wikilist.d file: $!"; print WIKILIST $ikiuser->name . " /etc/ikiwiki/piny/" . $s->name . ".setup\n"; close( WIKILIST ) or die "Could not close wikilist.d file: $!"; $s->rebuild_wikilist; system( "/usr/bin/sudo", "-H", "-u", $ikiuser->name, "/usr/bin/ikiwiki", "--setup", "/etc/ikiwiki/piny/" . $s->name . ".setup" ) and die "Could not do initial compile of ikiwiki!"; }; sub rebuild_wikilist { my $temp = File::Temp->new( ) or die "Could not create temporary file: $!"; $temp->unlink_on_destroy( 0 ); my $dh = IO::Dir->new( "/etc/ikiwiki/wikilist.d" ) or die "Could not open wikilist.d directory: $!"; while ( defined ( my $entry = $dh->read ) ) { next if ( $entry =~ /^\./ ); open( FILE, "<", "/etc/ikiwiki/wikilist.d/" . $entry ) or die "Could not open wikilist.d entry $entry: $!"; print $temp ; close( FILE ) or die "Could not close wikilist.d entry $entry: $!"; }; $temp->close or die "Could not close new wikilist: $!"; chmod( 00644, $temp->filename ) or die "Could not fix mode of new wikilist: $!"; rename( $temp->filename, "/etc/ikiwiki/wikilist" ) or die "Could not rename over old wikilist: $!"; }; sub destroy { my ( $s ) = @_; $s->destroy_ikiwiki; $s->destroy_apache; $s->destroy_git; }; sub destroy_git { my ( $s ) = @_; system( "rm", "-rf", $s->path ); system( "delgroup", $s->group->name ); }; sub destroy_apache { my ( $s ) = @_; foreach( "/etc/apache2/piny/global/" . $s->name, "/etc/apache2/piny/secure/" . $s->name, "/etc/apache2/piny/www/" . $s->name, ) { if ( -e $_ ) { unlink( $_ ); }; }; if ( $s->config->piny_apachereload =~ /^(1|true)$/ ) { system( "/etc/init.d/apache2", "reload" ) and die "Could not reload apache config!"; }; }; sub destroy_ikiwiki { my ( $s ) = @_; my $user = Piny::Environment->instance->user; if ( -e "/etc/ikiwiki/wikilist.d/" . $s->name ) { unlink( "/etc/ikiwiki/wikilist.d/" . $s->name ); $s->rebuild_wikilist; }; foreach( $s->secure_path, $s->config->piny_ikiwikidestdir . $s->name, $s->config->piny_ikiwikisecurepath . "read/" . $s->name, $s->ikiwiki_destdir, $s->ikiwiki_srcdir, "/etc/ikiwiki/piny/" . $s->name . ".setup" ) { if ( -e $_ ) { system( "rm", "-rf", $_ ); }; }; my $ikiuser = Piny::User::IkiWiki->new( "name" => "iki-" . $s->name ); getpwnam( "iki-" . $s->shortname ) and system( "deluser", "--quiet", "--remove-home", "iki-" . $s->shortname ); getgrnam( "iki-" . $s->shortname ) and system( "delgroup", "--quiet", "iki-" . $s->shortname ); }; # Triggers sub _set_description { my ( $s, $new_description, $old_description ) = @_; open( my $fd, ">", $s->path . "/description" ) or die "Unable to open " . $s->path . "/description for writing: $!"; print $fd $new_description; close( $fd ) or die "Error when closing " . $s->path . "/description: $!"; }; sub _change_owner { my ( $s, $new_owner, $old_owner ) = @_; find( { wanted => sub { chown( $new_owner->uid, -1, $_ ) or die "Couldn't chown $_: $!"; }, no_chdir => 1 }, $s->path . "/objects" ); $s->clear_ikiwiki_setup; }; # Class methods sub all_repos { my ( $class, $dir ) = @_; my $config = Piny::Config->new( ); $dir = $config->piny_gitpath unless defined $dir; my @ret; find( { wanted => sub { if ( /^[^.].*\.git$/ ) { $File::Find::prune = 1; push( @ret, $File::Find::name ); }; } }, $dir ); @ret = map { s/^\Q$dir\E\/?//; s/\.git$//; $class->new( name => $_ ); } @ret; return @ret; }; # Only sets up needed data; ->rebuild can take care of the rest. sub create { my ( $class, $name, $description, $remote ) = @_; if ( not defined $remote) { $remote = Piny::Config->new->piny_template; }; my $user = Piny::Environment->instance->user; find_type_constraint( "Reponame" )->assert_valid( $name ); find_type_constraint( "SimpleText" )->assert_valid( $description ); my $repo = $class->new( "name" => $name ); mkdir( $repo->path ) or die "The repo $name appears to already exist! ($!)"; chdir( $repo->config->piny_gitpath ); # so git-clone can do the work of resolving local repo names for us if ( defined $remote ) { system( "/bin/chown", $user->name, $repo->path ) and die "Could not change ownership of repo path!"; # permissions are overridden later if ( system( "/usr/bin/sudo", "-H", "-u", $user->name, "/usr/bin/git", "clone", "--bare", "--no-hardlinks", "--quiet", $remote, $repo->path ) ) { # sudo -u $user needed in order to test user readability of whatever they want to clone. the mode o+rx should probably be masked off if we want private repos to stay private. system( "/bin/rm", "-r", $repo->path ) and die "Failed to clean up after failed clone operation!"; die( "Could not clone repository!\n" ); } else { my $valid = 0; eval { Piny::Repo->new( $remote )->owner; $valid = 1; }; if( $valid ) { copy( $remote . ".git/config", $repo->path . "/config" ) or die "Config copy failed: $!"; } else { print( "$remote does not appear to be a piny reponame, continuing without copying config file...\n"); } }; } else { system( "/usr/bin/git", "init", "--bare", "--quiet", $repo->path ) and die "Could not initialize git repo!"; }; system( "/bin/chown", "-R", $user->name, $repo->path . "/objects" ) and die "Could not change ownership of $_ for repo: $!"; $repo->description( $description ); $repo = $class->new( $name ); return $repo; }; # Builder methods # If constructed with just one argument, then treat it as a repo name. around BUILDARGS => sub { my ( $orig, $class ) = ( shift, shift ); if ( @_ == 1 && ! ref $_[0] ) { return $class->$orig( name => $_[0] ); } else { return $class->$orig( @_ ); }; }; my @letter1 = ( 'a' .. 'z' ); # First digit is base 26. my @lettern = ( '0' .. '9', 'a' .. 'z', '-', '_' ); # Remaining digits are base 38. sub _build_shortname { my ( $s ) = @_; my $str = $s->name; # Short inputs don't need to be hashed. return $str if length $str <= 28; # We have to use Math::GMP here instead of Math::BigInt or pragma bignum # support because we actually require infinite precision integer arithmetic, # and bignum tends to only give us 40 decimal digits of precision during # division because the Perl maintainers are jackasses. my $n = Math::GMP->new( "0x" . sha256_hex( $str ) ); my ( $m ); # First digit. ( $n, $m ) = $n->bdiv( scalar @letter1 ); my $out = $letter1[$m]; # Remaining digits. while ( length $out < 28 and $n > 0 ) { ( $n, $m ) = $n->bdiv( scalar @lettern ); $out .= $lettern[$m]; }; return $out; }; sub _build_group { my ( $s ) = @_; return Piny::Group->new( name => "git-" . $s->shortname ); }; sub _build_path { my ( $s ) = @_; my $config = Piny::Config->new( ); # New, otherwise we'd wind up recursing. It's considered global anyway. return $config->piny_gitpath . "/" . $s->name . ".git"; }; sub _build_description { my ( $s ) = @_; open( my $d, "<", $s->path . "/description" ) or die "Unable to open " . $s->path . "/description: $!"; my $desc; { local $/ = undef; $desc = <$d>; }; close( $d ); return $desc; }; sub _build_repostat { my ( $s ) = @_; my @res = stat( $s->path . "/objects" ); die "stat( " . $s->path . "/objects ) failed: $!" unless @res; return \@res; }; sub _build_owner { my ( $s ) = @_; my ( $uid ) = $s->repostat->[4]; return Piny::User->new( uid => $uid ); }; sub _build_globally_readable { my ( $s ) = @_; return ( $s->repostat->[2] & 0444 ) == 0444; }; sub _build_globally_writable { my ( $s ) = @_; return ( $s->repostat->[2] & 0111 ) == 0111; }; sub _build_cgit_url { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" ) { return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name; } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name; } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) { return $s->config->piny_ikiwikisecureurl . "auth/cgit/" . $s->name; } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { return $s->config->piny_ikiwikisecureurl . "auth/cgit/" . $s->name; } else { die ( $s->config->core_sharedrepository . "is an unhandled value!" ); }; } sub _build_ikiwiki_setup { my ( $s ) = @_; my ( $package, $config ) = readSetup( "/usr/share/libpiny/ikiwiki.setup" ); $config->{"wikiname"} = $s->name; $config->{"adminemail"} = $s->owner->email->address; $config->{"srcdir"} = $s->ikiwiki_srcdir; $config->{"destdir"} = $s->ikiwiki_destdir; $config->{"url"} = $s->ikiwiki_url; $config->{"historyurl"} = $s->ikiwiki_historyurl; $config->{"diffurl"} = $s->ikiwiki_diffurl; if ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { warn( "warning: ikiwiki web-based editing interface relies on group writability.\n" ); $config->{"wrappers"} = [ { "wrapper" => $s->path . "/hooks/post-update" , "wrappergroup" => $s->group->name , "wrappermode" => "06755" , "notify" => 0 } ]; } else { $config->{"cgiurl"} = $s->ikiwiki_cgiurl; $config->{"wrappers"} = [ { "wrapper" => $s->ikiwiki_cgipath , "wrappergroup" => $s->group->name , "wrappermode" => "06755" , "cgi" => 1 } , { "wrapper" => $s->path . "/hooks/post-update" , "wrappergroup" => $s->group->name , "wrappermode" => "06755" , "notify" => 0 } ]; # Search relies on cgiurl push(@{$config->{add_plugins}}, qw{ search }); }; if ( -e "/etc/ikiwiki/piny.setup.pl" ) { system( "perl", "-C", "/etc/ikiwiki/piny.setup.pl" ) && die ( 'Failed to compile global ikiwiki overrides file!' ); undef $@; eval { package TEMP; use Piny; $TEMP::repo = $s; $TEMP::conf = $config; no strict 'vars'; do "/etc/ikiwiki/piny.setup.pl"; }; }; if ( -e "/etc/ikiwiki/piny/" . $s->name . ".setup.pl" ) { system( "perl", "-C", "/etc/ikiwiki/piny/" . $s->name . ".setup.pl" ) && die ( 'Failed to compile ikiwiki overrides file!' ); undef $@; eval { package TEMP; use Piny; $TEMP::repo = $s; $TEMP::conf = $config; no strict 'vars'; do "/etc/ikiwiki/piny/" . $s->name . ".setup.pl"; }; }; return writeSetup( $package, $config ); }; sub _build_ikiwiki_destdir { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" ) { return ( $s->config->piny_ikiwikidestdir . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { return ( $s->config->piny_ikiwikidestdir . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) { return ( $s->config->piny_ikiwikisecurepath . "read/" . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { return ( $s->config->piny_ikiwikisecurepath . "read/" . $s->name ); } else { die ( $s->config->core_sharedrepository . "is an unhandled value!" ); }; }; sub _build_ikiwiki_srcdir { my ( $s ) = @_; return $s->config->piny_ikiwikisrcdir . "/" . $s->name; }; sub _build_ikiwiki_url { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" ) { return ( $s->config->piny_ikiwikiurl . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { return ( $s->config->piny_ikiwikiurl . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) { return ( $s->config->piny_ikiwikisecureurl . "read/" . $s->name ); } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { return ( $s->config->piny_ikiwikisecureurl . "read/" . $s->name ); } else { die ( $s->config->core_sharedrepository . "is an unhandled value!" ); }; }; sub _build_ikiwiki_cgiurl { my ( $s ) = @_; return $s->config->piny_ikiwikisecureurl . "write/" . $s->name . "/ikiwiki.cgi"; }; sub _build_secure_path { my ( $s ) = @_; return $s->config->piny_ikiwikisecurepath . "write/" . $s->name; }; sub _build_ikiwiki_cgipath { my ( $s ) = @_; return $s->secure_path . "/ikiwiki.cgi"; }; sub _build_ikiwiki_historyurl { my ( $s ) = @_; return $s->cgit_url . "/log/[[file]]"; }; sub _build_ikiwiki_diffurl { my ( $s ) = @_; return $s->cgit_url . "/commit/?id=[[sha1_commit]]"; }; sub _build_apache_global_config { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" ) { return ( "secure_path . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"Valid Piny user needed.\"\n" . " Require valid-user\n" . " \n" ); } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { return ( "secure_path . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"User with access to " . $s->name . " repository needed.\"\n Require unix-group " . $s->group->name . "\n\n" ); } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) { return ( "secure_path . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"User with access to " . $s->name . " repository needed.\"\n Require unix-group " . $s->group->name . "\n\n" . "ikiwiki_destdir . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"User with access to " . $s->name . " repository needed.\"\n Require unix-group " . $s->group->name . "\n\n" ); } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { return ( "secure_path . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"Owner of " . $s->name . " repository needed.\"\n Require user " . $s->owner->name . "\n\n" . "ikiwiki_destdir . ">\n AuthBasicProvider PAM\n AuthPAMService other\n AuthBasicAuthoritative off\n AuthType Basic\n AuthName \"User with access to " . $s->name . " repository needed.\"\n Require unix-group " . $s->group->name . "\n\n" ); } else { die ( $s->config->core_sharedrepository . " is an unhandled value!" ); }; }; sub _build_apache_secure_config { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" or $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { return ( "Redirect /read/" . $s->name . " " . $s->ikiwiki_url . "\n" ); } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ or $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { if ( $s->config->piny_ikiwiki =~ /^(1|true)$/ ) { return ( "Redirect /cgit/" . $s->name . " /auth/cgit/" . $s->name . "\n" ); } elsif ( $s->config->piny_ikiwiki =~ /^(0|false)$/ ) { return ( "Redirect /cgit/" . $s->name . " /auth/cgit/" . $s->name . "\nRedirect /read/" . $s->name . " /cgit/" . $s->name . "/tree\n" ); } else { die ( $s->config->piny_ikiwiki . " is an unhandled value!" ); }; } else { die ( $s->config->core_sharedrepository . " is an unhandled value!" ); }; }; sub _build_apache_www_config { my ( $s ) = @_; if ( $s->config->core_sharedrepository eq "0666" or $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) { if ( $s->config->piny_ikiwiki =~ /^(1|true)$/ ) { return ( "\n" ); } elsif ( $s->config->piny_ikiwiki =~ /^(0|false)$/ ) { return ( "Redirect /" . $s->name . " " . $s->cgit_url . "/tree\n" ); } else { die ( $s->config->piny_ikiwiki . " is an unhandled value!" ); }; } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ or $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) { return ( "Redirect /" . $s->name . " " . $s->ikiwiki_url . "\n" ); } else { die ( $s->config->core_sharedrepository . " is an unhandled value!" ); }; }; sub _build_config { my ( $s ) = @_; return Piny::Config->new( confpath => $s->path . "/config" ); }; # Moose boilerplate __PACKAGE__->meta->make_immutable; 1;