xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
Commit 2fc4ec92c9f0d2c12df8a8f1b4a7ec1801bce3d6

First commit.
Author: Catalin(ux) M. BOIE
Author date (UTC): 2011-03-03 22:45
Committer name: Catalin(ux) M. BOIE
Committer date (UTC): 2011-03-03 22:45
Parent(s):
Signing key:
Tree: 36280d9bf0396a7f70faefcc8614dd9e47dde251
File Lines added Lines deleted
README 5 0
TODO 3 0
admin/sql.php 43 0
inc/db.inc.php 107 0
inc/keys.inc.php 109 0
inc/repo.inc.php 59 0
inc/state.inc.php 22 0
inc/xlog.inc.php 25 0
samples/config.php 8 0
scripts/ssh.php 88 0
tests/Makefile 4 0
tests/db.php 49 0
tests/keys.php 59 0
File README added (mode: 100644) (index 0000000..73843b5)
1 client does pull:
2 SSH_ORIGINAL_COMMAND=git-upload-pack '/aa/bb/cc/dd.git'
3
4 client does push:
5 SSH_ORIGINAL_COMMAND=git-receive-pack '/aa/bb/cc/dd.git'
File TODO added (mode: 100644) (index 0000000..54f3017)
1 [ ] Count the numbers of clones/pushes/pulls.
2 [ ] Add memcache caching for all database lookups.
3 [ ]
File admin/sql.php added (mode: 100644) (index 0000000..bcbc1b1)
1 <?php
2 error_reporting(E_ALL);
3
4 require_once("/etc/gg/config.php");
5
6 $INC = dirname(__FILE__) . "/../inc";
7 require_once($INC . "/xlog.inc.php");
8 require_once($INC . "/db.inc.php");
9 require_once($INC . "/repo.inc.php");
10
11 $db = sql_open($gg_db);
12 if ($db === FALSE)
13 fatal("Internal error (db)!");
14
15 // create repos table
16 $sql = "CREATE TABLE repos (repo_id INT PRIMARY KEY, uid INT, itime INT)";
17 $res = sql_query($db, $sql);
18 if ($res === FALSE)
19 echo "WARN: Cannot create 'repo_access' table!\n";
20
21 // create repo_access table
22 $sql = "CREATE TABLE repo_access (repo_id INT, uid INT, perm CHAR(1), itime INT)";
23 $res = sql_query($db, $sql);
24 if ($res === FALSE)
25 echo "WARN: Cannot create 'repo_access' table!\n";
26
27 $sql = "CREATE TABLE state (var TEXT PRIMARY KEY, value TEXT)";
28 $res = sql_query($db, $sql);
29 if ($res === FALSE)
30 echo "WARN: Cannot create 'state' table!\n";
31
32 $sql = "CREATE TABLE keys (key_id INT PRIMARY KEY, itime INT, uid INT, key TEXT)";
33 $res = sql_query($db, $sql);
34 if ($res === FALSE)
35 echo "WARN: Cannot create 'keys' table!\n";
36
37 $sql = "CREATE TABLE users (uid INT PRIMARY KEY, user TEXT, salt TEXT, pass TEXT, itime INT)";
38 $res = sql_query($db, $sql);
39 if ($res === FALSE)
40 echo "WARN: Cannot create 'keys' table!\n";
41
42 echo "Done!\n";
43 ?>
File inc/db.inc.php added (mode: 100644) (index 0000000..3a54b7b)
1 <?php
2 require_once($INC . "/xlog.inc.php");
3
4 $sql_debug = 0;
5
6 $sql_error = "";
7
8 /*
9 * Set error string
10 */
11 function sql_set_error($str)
12 {
13 global $sql_error;
14
15 $sql_error = $str;
16 }
17
18 function sql_error()
19 {
20 global $sql_error;
21
22 return $sql_error;
23 }
24
25 /*
26 * Connect to database
27 */
28 function sql_open($str)
29 {
30 global $sql_debug;
31
32 if ($sql_debug > 0)
33 xlog("DB: opening [$str]...");
34
35 if (strncmp($str, "sqlite:", 7) != 0) {
36 sql_set_error("$str connect string not supported");
37 return FALSE;
38 }
39
40 $file = substr($str, 7);
41
42 $db = new SQLite3($file);
43 if ($db === FALSE) {
44 sql_set_error("Cannot connect to database $file: " . $db->lastErrorMsg());
45 return FALSE;
46 }
47
48 return $db;
49 }
50
51 /*
52 * Escaping
53 */
54 function sql_escape($db, $str)
55 {
56 return $db->escapeString($str);
57 }
58
59 /*
60 * Do a query
61 */
62 function sql_query($db, $sql)
63 {
64 global $sql_debug;
65
66 if ($sql_debug > 0)
67 xlog("DB: running [$sql]...");
68
69 $res = $db->query($sql);
70 if ($res === FALSE) {
71 sql_set_error("$sql: " . $db->lastErrorMsg());
72 return FALSE;
73 }
74
75 return $res;
76 }
77
78 /*
79 * Close database
80 */
81 function sql_close($db)
82 {
83 $db->close();
84 }
85
86 /*
87 * Free results
88 */
89 function sql_free_result($res)
90 {
91 $res->finalize();
92 }
93
94 /*
95 * Returns a row as an associated array
96 */
97 function sql_fetch_array($res)
98 {
99 return $res->fetchArray(SQLITE3_ASSOC);
100 }
101
102 function sql_last_id($db)
103 {
104 return $db->lastInsertRowID();
105 }
106
107 ?>
File inc/keys.inc.php added (mode: 100644) (index 0000000..f813cdd)
1 <?php
2 require_once($INC . "/db.inc.php");
3 require_once($INC . "/state.inc.php");
4
5 $keys_error = "";
6
7 function keys_set_error($str)
8 {
9 global $keys_error;
10 $keys_error = $str;
11 }
12
13 function keys_error()
14 {
15 global $keys_error;
16 return $keys_error;
17 }
18
19 /*
20 * Remove a key from database
21 */
22 function keys_remove($db, $uid, $key_id)
23 {
24 // mark dirty
25 state_set($db, "authorized_keys", 1);
26
27 $e_uid = sprintf("%u", $uid);
28 $e_key_id = sprintf("%u", $key_id);
29
30 $sql = "DELETE FROM keys"
31 . " WHERE uid = $e_uid"
32 . " AND key_id = $e_key_id";
33 $res = sql_query($db, $sql);
34 if ($res === FALSE) {
35 keys_set_error("Cannot delete key $key_id (" . sql_error() . ")");
36 return FALSE;
37 }
38 sql_free_result($res);
39
40 return TRUE;
41 }
42
43 /*
44 * Add a key
45 * Returns the key_id of the key.
46 */
47 function keys_add($db, $uid, $key)
48 {
49 $itime = time();
50 $e_uid = sprintf("%u", $uid);
51 $e_key = sql_escape($db, $key);
52
53 // set dirty
54 if (state_set($db, "authorized_keys", 1) === FALSE)
55 return FALSE;
56
57 $sql = "INSERT INTO keys (itime, uid, key)"
58 . " VALUES ($itime, $e_uid, '$e_key')";
59 $res = sql_query($db, $sql);
60 if ($res === FALSE) {
61 keys_set_error("Cannot insert key (" . sql_error() . ")");
62 return FALSE;
63 }
64 sql_free_result($res);
65
66 return sql_last_id($db);
67 }
68
69 /*
70 * Regenerates authorized_keys files
71 */
72 function keys_regen($db, $file)
73 {
74 $tmp = $file . ".tmp";
75 $f = @fopen($tmp, "w+");
76 if (!$f)
77 return FALSE;
78
79 $sql = "SELECT uid, key FROM keys";
80 $res = sql_query($db, $sql);
81 while (($row = sql_fetch_array($res))) {
82 $buf = "command=\"/usr/bin/xxx " . $row['uid'] . "\""
83 . ",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"
84 . " " . $row['key'] . "\n";
85 if (fwrite($f, $buf) === FALSE) {
86 keys_set_error("Cannot write. Disk space problems?");
87 fclose($f);
88 unlink($tmp);
89 sql_free_result($res);
90 return FALSE;
91 }
92 }
93 sql_free_result($res);
94
95 fclose($f);
96
97 if (rename($tmp, $file) === FALSE) {
98 keys_set_error("Cannot rename $tmp to $file!");
99 unlink($tmp);
100 return FALSE;
101 }
102
103 // mark file as clean
104 state_set($db, "authorized_keys", 0);
105
106 return TRUE;
107 }
108
109 ?>
File inc/repo.inc.php added (mode: 100644) (index 0000000..efea197)
1 <?php
2 require_once($INC . "/xlog.inc.php");
3 require_once($INC . "/db.inc.php");
4 require_once($INC . "/state.inc.php");
5
6 $repo_error = "";
7
8 function repo_set_error($str)
9 {
10 global $repo_error;
11 $repo_error = $str;
12 }
13
14 function repo_error()
15 {
16 global $repo_error;
17 return $repo_error;
18 }
19
20 /*
21 * Check if a uid has access to repository
22 */
23 function repo_allow($db, $repo_id, $uid, $perms)
24 {
25 xlog("repo_allow: repo_id=$repo_id, uid=$uid, perms=$perms...");
26
27 $e_repo_id = sprintf("%u", $repo_id);
28 $e_uid = sprintf("%u", $uid);
29 $e_perms = preg_replace("/[^A-Z]/", "", $perms);
30
31 if (empty($e_perms))
32 return FALSE;
33
34 if (strcmp($perms, "R") == 0)
35 $perms_add = " AND (perm = 'R' OR perm = 'W')";
36 else
37 $perms_add = " AND perm = 'W'";
38
39 $sql = "SELECT 1 AS junk FROM repo_access"
40 . " WHERE repo_id = $e_repo_id"
41 . " AND uid = $e_uid"
42 . $perms_add;
43 $res = sql_query($db, $sql);
44 if ($res === FALSE)
45 return FALSE;
46 $row = sql_fetch_array($res);
47 sql_free_result($res);
48
49 if (!isset($row['junk'])) {
50 xlog("\tDo not allow access!");
51 return FALSE;
52 }
53
54 xlog("\tAllow access!");
55
56 return TRUE;
57 }
58
59 ?>
File inc/state.inc.php added (mode: 100644) (index 0000000..3731470)
1 <?php
2 require_once($INC . "/db.inc.php");
3
4 /*
5 * Set state
6 */
7 function state_set($db, $var, $value)
8 {
9 $e_var = sql_escape($db, $var);
10 $e_value = sql_escape($db, $value);
11
12 $sql = "UPDATE state SET value = '$e_value'"
13 . " WHERE var = '$e_var'";
14 $res = sql_query($db, $sql);
15 if ($res === FALSE)
16 return FALSE;
17 sql_free_result($res);
18
19 return TRUE;
20 }
21
22 ?>
File inc/xlog.inc.php added (mode: 100644) (index 0000000..a5eb6e3)
1 <?php
2
3 $_xlog_file = "/tmp/gg.log";
4 $_xlog_fd = FALSE;
5
6 function xlog($str)
7 {
8 global $_xlog_file;
9 global $_xlog_fd;
10
11 if ($_xlog_fd === FALSE) {
12 $_xlog_fd = @fopen($_xlog_file, "a+");
13 if ($_xlog_fd === FALSE)
14 return;
15 }
16
17 $buf = date("Y-m-d H:i:s");
18 if (!empty($_SERVER['REMOTE_ADDR']))
19 $buf .= " " . $_SERVER['REMOTE_ADDR'];
20 $buf .= " " . $str . "\n";
21
22 fwrite($_xlog_fd, $buf);
23 }
24
25 ?>
File samples/config.php added (mode: 100644) (index 0000000..6e9675b)
1 <?php
2 // Base for repositories
3 $gg_base_repo = "/home/gg/repositories";
4
5 // Database
6 $gg_db = "sqlite:/tmp/gg.sqlite";
7
8 ?>
File scripts/ssh.php added (mode: 100644) (index 0000000..757a20a)
1 <?php
2 error_reporting(E_ALL);
3
4 $_start = microtime(TRUE);
5
6 require_once("/etc/gg/config.php");
7
8 $INC = dirname(__FILE__) . "/../inc";
9 require_once($INC . "/xlog.inc.php");
10 require_once($INC . "/db.inc.php");
11 require_once($INC . "/repo.inc.php");
12
13 $sql_debug = $gg_db_debug;
14
15 function fatal($str)
16 {
17 xlog($str);
18 fwrite(STDERR, "FATAL ERROR: " . $str . "\n");
19 exit(1);
20 }
21
22 xlog("Start...");
23
24 umask(0022);
25
26 $ssh_conn = @$_SERVER['SSH_CONNECTION'];
27 xlog("SSH_CONNECTION: $ssh_conn.");
28
29 // first parameter must be uid of the user
30 $uid = @$_SERVER['argv'][1];
31 if (empty($uid))
32 fatal("uid not provided!");
33 xlog("uid is $uid.");
34
35 $cmd = trim(@$_SERVER['SSH_ORIGINAL_COMMAND']);
36 if (empty($cmd))
37 fatal("No SSH_ORIGINAL_COMMAND provided!");
38 xlog("SSH_ORIGINAL_COMMAND is [$cmd].");
39
40 // extract command
41 if (strncmp($cmd, "git-upload-pack", 15) == 0) {
42 $op = "git-upload-pack";
43 $perms = "R";
44 } else if (strncmp($cmd, "git-receive-pack", 16) == 0) {
45 $op = "git-receive-pack";
46 $perms = "W";
47 } else {
48 fatal("Unknown command!");
49 }
50 xlog("real operation is $op, perms is [$perms].");
51
52 // extract repository name
53 $repo = substr($cmd, strlen($op));
54 $repo = trim($repo, "' ");
55 if (empty($repo))
56 fatal("Repo is invalid!");
57 // security checks
58 if (preg_match('/\.\./', $repo))
59 fatal("Repo must not contain [..]!");
60 xlog("repo is [$repo].");
61
62 // Check if repository exists
63 $path = $gg_base_repo . "/" . $repo;
64 if (!file_exists($path))
65 fatal("Cannot find repo $path!");
66 xlog("path is [$path].");
67
68 // check access - uid is allowed to access this repo?
69 $repo_id = sprintf("%u", @file_get_contents($path . "/gg/repo_id"));
70 if ($repo_id == 0)
71 fatal("Invalid repo!");
72 xlog("repo_id is [$repo_id]");
73
74 $db = sql_open($gg_db);
75 if ($db === FALSE)
76 fatal("Internal error (db)!");
77
78 if (!repo_allow($db, $repo_id, $uid, $perms))
79 fatal("You do not have access to this repository!");
80
81 $run = "git-shell -c \"" . $op . " '" . escapeshellcmd($path) . "'\"";
82 xlog("Running [$run]...");
83 passthru($run, $ret);
84 xlog("[$run] returned $ret.");
85
86 $diff = sprintf("%u", (microtime(TRUE) - $_start) * 1000);
87 xlog("Took " . $diff . "ms.");
88 ?>
File tests/Makefile added (mode: 100644) (index 0000000..196fb8f)
1 .PHONY: test
2 test:
3 php db.php
4 php keys.php
File tests/db.php added (mode: 100644) (index 0000000..500d74a)
1 <?php
2 $INC = "../inc";
3 require_once($INC . "/db.inc.php");
4
5 @unlink("test.sqlite");
6
7 $db = sql_open("sqlite:test.sqlite");
8 if ($db === FALSE) {
9 echo "Cannot create a database (" . sql_error() . ")!";
10 exit(1);
11 }
12
13 // test creation
14 $sql = "CREATE TABLE test (id TEXT PRIMARY KEY)";
15 $res = sql_query($db, $sql);
16 if ($res === FALSE) {
17 echo "Cannot create table!";
18 exit(1);
19 }
20
21 // test insert
22 $sql = "INSERT INTO test (id) VALUES ('aaa')";
23 $res = sql_query($db, $sql);
24 if ($res === FALSE) {
25 echo "Cannot insert!";
26 exit(1);
27 }
28
29 // test insert with the same key
30 $sql = "INSERT INTO test (id) VALUES ('aaa')";
31 $res = @sql_query($db, $sql);
32 if ($res !== FALSE) {
33 echo "I can do double insert!";
34 exit(1);
35 }
36
37 // test delete
38 $sql = "DELETE FROM test WHERE id = 'aaa'";
39 $res = sql_query($db, $sql);
40 if ($res === FALSE) {
41 echo "Cannot delete!";
42 exit(1);
43 }
44
45 sql_close($db);
46
47 @unlink("test.sqlite");
48
49 ?>
File tests/keys.php added (mode: 100644) (index 0000000..a243c79)
1 <?php
2 $INC = "../inc";
3 require_once($INC . "/keys.inc.php");
4
5 @unlink("keys.sqlite");
6
7 $db = sql_open("sqlite:keys.sqlite");
8 if ($db === FALSE) {
9 echo "Cannot create a database (" . sql_error() . ")!";
10 exit(1);
11 }
12
13 // state table
14 $sql = "CREATE TABLE state (var TEXT PRIMARY KEY, value TEXT)";
15 $res = sql_query($db, $sql);
16 if ($res === FALSE) {
17 echo "Cannot create state table!";
18 exit(1);
19 }
20
21 // keys table
22 $sql = "CREATE TABLE keys (key_id INT PRIMARY KEY, itime INT, uid INT, key TEXT)";
23 $res = sql_query($db, $sql);
24 if ($res === FALSE) {
25 echo "Cannot create keys table!";
26 exit(1);
27 }
28
29 // insert a key
30 $uid = 1;
31 $key = "aaa'bbb'ccc";
32 $key_id = keys_add($db, $uid, $key);
33 if ($key_id === FALSE) {
34 echo "Cannot add key!";
35 exit(1);
36 }
37
38 // delete a key
39 $uid = 1;
40 $key = "aaa'bbb'ccc";
41 $r = keys_remove($db, $uid, $key_id);
42 if ($r === FALSE) {
43 echo "Cannot remove key (" . keys_error() . ")!";
44 exit(1);
45 }
46
47 // test key file generation
48 $r = keys_regen($db, "afile.txt");
49 if ($r === FALSE) {
50 echo "Cannot regenerate keys (" . keys_error() . ")!";
51 exit(1);
52 }
53 @unlink("afile.txt");
54
55 sql_close($db);
56
57 @unlink("keys.sqlite");
58
59 ?>
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master