--- /dev/null
+The "Artistic License"
+
+ Preamble
+
+The intent of this document is to state the conditions under which a
+Package may be copied, such that the Copyright Holder maintains some
+semblance of artistic control over the development of the package,
+while giving the users of the package the right to use and distribute
+the Package in a more-or-less customary fashion, plus the right to make
+reasonable modifications.
+
+Definitions:
+
+ "Package" refers to the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection of files
+ created through textual modification.
+
+ "Standard Version" refers to such a Package if it has not been
+ modified, or has been modified in accordance with the wishes
+ of the Copyright Holder as specified below.
+
+ "Copyright Holder" is whoever is named in the copyright or
+ copyrights for the package.
+
+ "You" is you, if you're thinking about copying or distributing
+ this Package.
+
+ "Reasonable copying fee" is whatever you can justify on the
+ basis of media cost, duplication charges, time of people involved,
+ and so on. (You will not be required to justify it to the
+ Copyright Holder, but only to the computing community at large
+ as a market that must bear the fee.)
+
+ "Freely Available" means that no fee is charged for the item
+ itself, though there may be fees involved in handling the item.
+ It also means that recipients of the item may redistribute it
+ under the same conditions they received it.
+
+1. You may make and give away verbatim copies of the source form of the
+Standard Version of this Package without restriction, provided that you
+duplicate all of the original copyright notices and associated disclaimers.
+
+2. You may apply bug fixes, portability fixes and other modifications
+derived from the Public Domain or from the Copyright Holder. A Package
+modified in such a way shall still be considered the Standard Version.
+
+3. You may otherwise modify your copy of this Package in any way, provided
+that you insert a prominent notice in each changed file stating how and
+when you changed that file, and provided that you do at least ONE of the
+following:
+
+ a) place your modifications in the Public Domain or otherwise make them
+ Freely Available, such as by posting said modifications to Usenet or
+ an equivalent medium, or placing the modifications on a major archive
+ site such as uunet.uu.net, or by allowing the Copyright Holder to include
+ your modifications in the Standard Version of the Package.
+
+ b) use the modified Package only within your corporation or organization.
+
+ c) rename any non-standard executables so the names do not conflict
+ with standard executables, which must also be provided, and provide
+ a separate manual page for each non-standard executable that clearly
+ documents how it differs from the Standard Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+4. You may distribute the programs of this Package in object code or
+executable form, provided that you do at least ONE of the following:
+
+ a) distribute a Standard Version of the executables and library files,
+ together with instructions (in the manual page or equivalent) on where
+ to get the Standard Version.
+
+ b) accompany the distribution with the machine-readable source of
+ the Package with your modifications.
+
+ c) give non-standard executables non-standard names, and clearly
+ document the differences in manual pages (or equivalent), together
+ with instructions on where to get the Standard Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+5. You may charge a reasonable copying fee for any distribution of this
+Package. You may charge any fee you choose for support of this
+Package. You may not charge a fee for this Package itself. However,
+you may distribute this Package in aggregate with other (possibly
+commercial) programs as part of a larger (possibly commercial) software
+distribution provided that you do not advertise this Package as a
+product of your own. You may embed this Package's interpreter within
+an executable of yours (by linking); this shall be construed as a mere
+form of aggregation, provided that the complete Standard Version of the
+interpreter is so embedded.
+
+6. The scripts and library files supplied as input to or produced as
+output from the programs of this Package do not automatically fall
+under the copyright of this Package, but belong to whoever generated
+them, and may be sold commercially, and may be aggregated with this
+Package. If such scripts or library files are aggregated with this
+Package via the so-called "undump" or "unexec" methods of producing a
+binary executable image, then distribution of such an image shall
+neither be construed as a distribution of this Package nor shall it
+fall under the restrictions of Paragraphs 3 and 4, provided that you do
+not represent such an executable image as a Standard Version of this
+Package.
+
+7. C subroutines (or comparably compiled subroutines in other
+languages) supplied by you and linked into this Package in order to
+emulate subroutines and variables of the language defined by this
+Package shall not be considered part of this Package, but are the
+equivalent of input as in Paragraph 6, provided these subroutines do
+not change the language in any way that would cause it to fail the
+regression tests for the language.
+
+8. Aggregation of this Package with a commercial distribution is always
+permitted provided that the use of this Package is embedded; that is,
+when no overt attempt is made to make this Package's interfaces visible
+to the end user of the commercial distribution. Such use shall not be
+construed as a distribution of this Package.
+
+9. The name of the Copyright Holder may not be used to endorse or promote
+products derived from this software without specific prior written permission.
+
+10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+ The End
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# Check if running as root
+if ($< != 0) {
+ die "Error: This script must be run as root (use sudo)\n";
+}
+
+# Repository base path
+my $REPO_BASE = '/path/to/your/repos';
+
+# ============================================================
+# MAIN SCRIPT
+# ============================================================
+
+print "===========================================\n";
+print " Add Contributor to Repository\n";
+print "===========================================\n\n";
+
+# List available repositories
+opendir(my $dh, $REPO_BASE) or die "Error: Cannot open $REPO_BASE: $!\n";
+my @repos = grep { -d "$REPO_BASE/$_" && $_ ne '.' && $_ ne '..' } readdir($dh);
+closedir($dh);
+
+if (@repos == 0) {
+ die "Error: No repositories found in $REPO_BASE\n";
+}
+
+# Display repositories
+print "Available repositories:\n";
+my $count = 1;
+my %repo_map;
+foreach my $repo (sort @repos) {
+ print " $count. $repo\n";
+ $repo_map{$count} = $repo;
+ $count++;
+}
+
+# Get repository selection
+print "\nEnter repository number: ";
+chomp(my $repo_num = <STDIN>);
+
+if (!exists $repo_map{$repo_num}) {
+ die "Error: Invalid repository number.\n";
+}
+
+my $selected_repo = $repo_map{$repo_num};
+my $repo_dir = "$REPO_BASE/$selected_repo";
+my $hook_file = "$repo_dir/hooks/update";
+
+# Check if hook file exists
+if (!-f $hook_file) {
+ die "Error: Update hook not found at $hook_file\n";
+}
+
+# Get contributor username
+print "Enter contributor username (e.g., john.doe): ";
+chomp(my $contributor = <STDIN>);
+
+if ($contributor eq '') {
+ die "Error: Contributor username cannot be empty.\n";
+}
+
+# Sanitize username
+$contributor =~ s/[^a-zA-Z0-9._-]//g;
+
+if ($contributor eq '') {
+ die "Error: Invalid contributor username.\n";
+}
+
+# Read current hook file
+open(my $fh, '<', $hook_file) or die "Error: Cannot read $hook_file: $!\n";
+my @lines = <$fh>;
+close($fh);
+
+# Find and modify the ALLOWED_CONTRIBUTORS line
+my $found = 0;
+my $already_exists = 0;
+
+for (my $i = 0; $i < @lines; $i++) {
+ if ($lines[$i] =~ /^ALLOWED_CONTRIBUTORS="(.+)"/) {
+ my $current_contributors = $1;
+
+ # Check if contributor already exists
+ if ($current_contributors =~ /\b\Q$contributor\E\b/) {
+ $already_exists = 1;
+ last;
+ }
+
+ # Add new contributor
+ $lines[$i] = "ALLOWED_CONTRIBUTORS=\"$current_contributors $contributor\"\n";
+ $found = 1;
+ last;
+ }
+}
+
+if ($already_exists) {
+ print "\nContributor '$contributor' is already authorized for $selected_repo\n";
+ exit 0;
+}
+
+if (!$found) {
+ die "Error: Could not find ALLOWED_CONTRIBUTORS line in hook file.\n";
+}
+
+# Write modified hook file
+open(my $out_fh, '>', $hook_file) or die "Error: Cannot write to $hook_file: $!\n";
+print $out_fh @lines;
+close($out_fh);
+
+# Ensure hook is executable
+chmod 0755, $hook_file;
+
+print "\n===========================================\n";
+print " Contributor Added Successfully!\n";
+print "===========================================\n";
+print "Repository: $selected_repo\n";
+print "Contributor: $contributor\n";
+print "===========================================\n";
+print "\n$contributor can now push to feature branches in $selected_repo\n";
+print "Only owner can push to master branch.\n";
+print "===========================================\n";
+
+exit 0;
--- /dev/null
+#!/usr/bin/perl
+use strict;
+use warnings;
+use File::Path qw(make_path remove_tree);
+use File::Spec;
+use Cwd qw(abs_path);
+
+# ============================================================
+# CONFIGURATION CONSTANTS - Edit these as needed
+# ============================================================
+
+# Repository base path - where all repositories will be created
+use constant REPO_BASE => '/path/to/your/repos';
+
+# Master branch user - who can push to master
+use constant MASTER_BRANCH_USER => 'YOUR-GIT-USERNAME-HERE';
+
+# ============================================================
+# Check if running as root
+# ============================================================
+
+if ($< != 0) {
+ die "Error: This script must be run as root (use sudo)\n";
+}
+
+# ============================================================
+# TEMPLATES
+# ============================================================
+
+my $README_TEMPLATE = <<'END_README';
+
+<h1>HELLO WORLD</h1>
+
+END_README
+
+my $CONFIG_JS_TEMPLATE = <<'END_JS';
+END_JS
+
+my $HOOK_UPDATE_TEMPLATE = <<'END_HOOK';
+#!/bin/bash
+
+# Branch protection hook
+# Controls who can push to master and who can push to the repository at all
+
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# Get the user who is pushing (set by SSH authorized_keys)
+PUSHER="${USER}"
+
+# Define the protected branch
+PROTECTED_BRANCH="refs/heads/master"
+
+# Define allowed user for master branch
+ALLOWED_MASTER_USER="__MASTER_USER__"
+
+# Define allowed contributors (space-separated list)
+# Edit this list to add/remove contributors for THIS repository
+ALLOWED_CONTRIBUTORS="__MASTER_USER__"
+
+# Check if user is allowed to push to this repository at all
+if [[ ! " ${ALLOWED_CONTRIBUTORS} " =~ " ${PUSHER} " ]]; then
+ echo "==============================================="
+ echo "ERROR: You are not authorized to push to this repository."
+ echo "==============================================="
+ echo ""
+ echo "You are authenticated as: $PUSHER"
+ echo ""
+ echo "Allowed contributors: $ALLOWED_CONTRIBUTORS"
+ echo ""
+ echo "Please contact the repository owner for access."
+ echo "==============================================="
+ exit 1
+fi
+
+# Check if pushing to protected branch
+if [ "$refname" = "$PROTECTED_BRANCH" ]; then
+ # Check if pusher is the allowed user
+ if [ "$PUSHER" != "$ALLOWED_MASTER_USER" ]; then
+ echo "==============================================="
+ echo "ERROR: Only $ALLOWED_MASTER_USER can push to master branch."
+ echo "==============================================="
+ echo ""
+ echo "You are authenticated as: $PUSHER"
+ echo ""
+ echo "Please:"
+ echo " 1. Push to a feature branch instead:"
+ echo " git push origin your-feature-branch"
+ echo ""
+ echo " 2. Request a merge to master via email"
+ echo "==============================================="
+ exit 1
+ fi
+fi
+
+# Allow the push
+exit 0
+END_HOOK
+
+# ============================================================
+# HELPER FUNCTIONS
+# ============================================================
+
+# Sanitize and validate project name
+sub validate_project_name {
+ my ($name) = @_;
+
+ # Check if empty
+ if (!defined $name || $name eq '') {
+ return (0, "Project name cannot be empty");
+ }
+
+ # Check length
+ if (length($name) > 100) {
+ return (0, "Project name too long (max 100 characters)");
+ }
+
+ # Check for invalid characters
+ if ($name =~ m{[^a-zA-Z0-9_\-]}) {
+ return (0, "Project name can only contain letters, numbers, hyphens, and underscores");
+ }
+
+ # Check for path traversal attempts
+ if ($name =~ /\.\./ || $name =~ m{/} || $name =~ m{\\}) {
+ return (0, "Project name cannot contain '..' or path separators");
+ }
+
+ # Check for reserved git names
+ my @reserved = qw(.git hooks objects refs info config description HEAD);
+ if (grep { lc($name) eq lc($_) } @reserved) {
+ return (0, "Project name '$name' is reserved by git");
+ }
+
+ return (1, $name);
+}
+
+# Safely execute a command with proper escaping
+sub safe_system {
+ my @args = @_;
+ my $result = system(@args);
+ return ($result == 0);
+}
+
+# Safely create a file with content
+sub safe_write_file {
+ my ($filepath, $content) = @_;
+
+ open(my $fh, '>', $filepath) or return 0;
+ print $fh $content;
+ close($fh) or return 0;
+
+ return 1;
+}
+
+# Cleanup function for partial repository
+sub cleanup_repo {
+ my ($repo_dir) = @_;
+
+ if (-d $repo_dir) {
+ print "Cleaning up partial repository...\n";
+ remove_tree($repo_dir, {error => \my $err});
+ if (@$err) {
+ warn "Warning: Some cleanup errors occurred\n";
+ }
+ }
+}
+
+# ============================================================
+# MAIN SCRIPT
+# ============================================================
+
+print "===========================================\n";
+print " Git Repository Creation Wizard\n";
+print "===========================================\n\n";
+
+# Verify REPO_BASE exists and is accessible
+my $repo_base = REPO_BASE;
+if (!-d $repo_base) {
+ die "Error: Repository base directory '$repo_base' does not exist.\n";
+}
+if (!-w $repo_base) {
+ die "Error: Repository base directory '$repo_base' is not writable.\n";
+}
+
+# Get and validate project name
+my $project_name;
+my $repo_dir;
+while (1) {
+ print "Enter project name: ";
+ chomp($project_name = <STDIN>);
+
+ my ($valid, $result) = validate_project_name($project_name);
+ if (!$valid) {
+ print "Error: $result\n";
+ print "Please try again.\n\n";
+ next;
+ }
+
+ $project_name = $result;
+ $repo_dir = File::Spec->catdir($repo_base, $project_name);
+
+ # Check if repository already exists
+ if (-e $repo_dir) {
+ print "Error: Repository '$project_name' already exists.\n";
+ print "Please choose a different name.\n\n";
+ next;
+ }
+
+ last;
+}
+
+# Get project description
+print "Enter project description: ";
+chomp(my $description = <STDIN>);
+if ($description eq '') {
+ $description = "No description provided";
+}
+
+# Get category
+# these are categories that I use, you can setup your own...
+my %CATEGORIES = (
+ 1 => '1 - programs',
+ 2 => '2 - scripts',
+ 3 => '3 - other',
+);
+
+print "\nSelect category:\n";
+foreach my $key (sort keys %CATEGORIES) {
+ print " $key - $CATEGORIES{$key}\n";
+}
+
+my $category;
+while (1) {
+ print "Enter category number: ";
+ chomp(my $category_num = <STDIN>);
+
+ if (!exists $CATEGORIES{$category_num}) {
+ print "Error: Invalid category number. Please try again.\n\n";
+ next;
+ }
+
+ $category = $CATEGORIES{$category_num};
+
+ last;
+}
+
+# Get owner name
+print "Enter owner name: ";
+chomp(my $owner = <STDIN>);
+if ($owner eq '') {
+ $owner = "Unknown";
+}
+
+## NOTE: language and license are not standard git information I use
+## them on my git server, but you can comment them out or just skip
+## during the wizards run.
+
+# Get license
+print "Enter license (e.g., MIT, GPL, Apache): ";
+chomp(my $license = <STDIN>);
+if ($license eq '') {
+ $license = "Unknown";
+}
+
+# Get programming language
+print "Enter primary language (e.g., Perl, Python, JavaScript): ";
+chomp(my $language = <STDIN>);
+if ($language eq '') {
+ $language = "Unknown";
+}
+
+# Ask if repository is public (default: yes)
+print "Is the repository public? (Y/n): ";
+chomp(my $is_public = <STDIN>);
+$is_public = lc($is_public);
+
+# Default to public if empty or yes
+my $public = ($is_public eq '' || $is_public eq 'y' || $is_public eq 'yes') ? 1 : 0;
+
+# Confirm creation
+print "\n===========================================\n";
+print " Repository Summary\n";
+print "===========================================\n";
+print "Name: $project_name\n";
+print "Description: $description\n";
+print "Category: $category\n";
+print "Owner: $owner\n";
+print "License: $license\n";
+print "Language: $language\n";
+print "Visibility: " . ($public ? "Public" : "Private") . "\n";
+print "Path: $repo_dir\n";
+print "===========================================\n";
+print "Create this repository? (yes/no): ";
+chomp(my $confirm = <STDIN>);
+
+if (lc($confirm) ne 'yes' && lc($confirm) ne 'y') {
+ print "Repository creation cancelled.\n";
+ exit 0;
+}
+
+# Create repository with error handling
+print "\nCreating repository...\n";
+
+eval {
+ # Create directory
+ if (!safe_system('mkdir', '-p', $repo_dir)) {
+ die "Cannot create directory $repo_dir";
+ }
+
+ # Change to repository directory
+ chdir($repo_dir) or die "Cannot change to directory $repo_dir: $!";
+
+ # Initialize bare repository
+ if (!safe_system('git', 'init', '--bare', '--shared')) {
+ die "git init failed";
+ }
+
+ # Create description file
+ if (!safe_write_file('description', $description)) {
+ die "Cannot create description file";
+ }
+
+ # Create git-daemon-export-ok if public
+ if ($public) {
+ if (!safe_write_file('git-daemon-export-ok', '')) {
+ die "Cannot create git-daemon-export-ok";
+ }
+ }
+
+ # Create category file
+ if (!safe_write_file('category', $category)) {
+ die "Cannot create category file";
+ }
+
+ # Set owner using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.owner', $owner)) {
+ die "Failed to set owner";
+ }
+
+ # Set license using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.license', $license)) {
+ die "Failed to set license";
+ }
+
+ # Set language using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.language', $language)) {
+ die "Failed to set language";
+ }
+
+ # Add to safe directories
+ if (!safe_system('git', 'config', '--global', '--add', 'safe.directory', $repo_dir)) {
+ warn "Warning: Failed to add to safe directories\n";
+ }
+
+ # Create branch protection hook with username substitution
+ my $hook_content = $HOOK_UPDATE_TEMPLATE;
+ my $master_user = MASTER_BRANCH_USER;
+ $hook_content =~ s/__MASTER_USER__/$master_user/g;
+
+ if (!safe_write_file('hooks/update', $hook_content)) {
+ die "Cannot create update hook";
+ }
+
+ # Make hook executable
+ if (!chmod(0755, 'hooks/update')) {
+ die "Cannot make update hook executable";
+ }
+
+ # Create custom-summary directory
+ if (!make_path("custom-summary")) {
+ die "Cannot create custom-summary directory";
+ }
+
+ # Create README.html
+ if (!safe_write_file('README.html', $README_TEMPLATE)) {
+ die "Cannot create README.html";
+ }
+
+ # Create config.js
+ if (!safe_write_file('custom-summary/config.js', $CONFIG_JS_TEMPLATE)) {
+ die "Cannot create config.js";
+ }
+
+ # Set ownership to git user
+ if (!safe_system('chown', '-R', 'git:git', $repo_dir)) {
+ die "Failed to set ownership";
+ }
+
+ # Set permissions
+ if (!safe_system('chmod', '-R', '755', $repo_dir)) {
+ die "Failed to set permissions";
+ }
+};
+
+# Handle errors with cleanup
+if ($@) {
+ my $error = $@;
+ print "\n===========================================\n";
+ print " ERROR: Repository Creation Failed\n";
+ print "===========================================\n";
+ print "Error: $error\n";
+
+ cleanup_repo($repo_dir);
+
+ print "===========================================\n";
+ exit 1;
+}
+
+# Success message
+print "\n===========================================\n";
+print " Repository Created Successfully!\n";
+print "===========================================\n";
+print "Repository: $project_name\n";
+print "Location: $repo_dir\n";
+print "Visibility: " . ($public ? "Public" : "Private") . "\n";
+print "\nYou can now clone it with:\n";
+print " git clone git\@git.dpolakovic.space:$project_name\n";
+if ($public) {
+ print "\nOr publicly via:\n";
+ print " git clone git://git.dpolakovic.space/$project_name\n";
+}
+print "===========================================\n";
+
+exit 0;