<種類> <鍵> sshdのAuthorizedKeysCommand(%u %t %k)。鍵の持ち主のauthorized_keysの行 authorized-keys 全部の鍵のauthorized_keys(AuthorizedKeysCommandを使わない場合) serve <ユーザー> [指紋] SSHの強制コマンド(SSH_ORIGINAL_COMMANDのgit-upload-pack等を処理する) gc 全部のリポジトリを整理する TXT; if (!Gitler::enabled()) { fwrite(STDERR, "Gitlerは無効です(config.phpのGITLER_ENABLED)。\n"); exit(1); } $args = array_slice($argv, 1); switch ($args[0] ?? '') { case 'keys': // %u(ログインのユーザー)、%t(鍵の種類)、%k(base64の鍵) [, $login, $type, $key] = $args + [null, '', '', '']; $sshUser = explode('@', (string)Gitler::config()['ssh'])[0] ?: 'git'; if ($login !== $sshUser) exit(0); $found = Keys::find((string)$type, (string)$key); if ($found !== null) { $u = Gitler::user($found[0]); if ($u !== null && (int)($u->role ?? 0) !== Roles::BANNED) echo Keys::authorizedKey($found[0], $found[1]), "\n"; } exit(0); case 'authorized-keys': foreach (Keys::all() as $user => $keys) { $u = Gitler::user($user); if ($u === null || (int)($u->role ?? 0) === Roles::BANNED) continue; foreach ($keys as $k) echo Keys::authorizedKey($user, $k), "\n"; } exit(0); case 'serve': $name = (string)($args[1] ?? ''); $user = Gitler::user($name); if ($user === null || (int)($user->role ?? 0) === Roles::BANNED) { fwrite(STDERR, "このユーザーは使えません。\n"); exit(1); } if (isset($args[2])) Keys::touchKey($name, (string)$args[2]); $command = (string)getenv('SSH_ORIGINAL_COMMAND'); if ($command === '') { fwrite(STDERR, "こんにちは、{$name}さん!Gitlerにはシェルが有りません。\n"); exit(1); } // gitユーザーはdata/に書けないので、通知とFediverseへの配信はリポジトリに溜める(cronのnotifyが送る) Gitler::$deferred = true; $owner = $repoName = ''; $repoAccess = Gitler::NONE; exit(Ssh::handle($command, function(string $path, bool $write) use ($user, &$owner, &$repoName, &$repoAccess): ?Repository { // 「所有者/名前.git」又は「所有者/名前」(前に「gitler/」が有っても良い) if (!preg_match('#^(?:gitler/)?([A-Za-z0-9]+)/([A-Za-z0-9._-]+?)(?:\.git)?/?$#', $path, $m)) return null; $access = Gitler::access($user, $m[1], $m[2]); $meta = $access > Gitler::NONE ? Gitler::meta($m[1], $m[2]) : []; // 読めてプルリクエストのロールが有ればrefs/for/(AGit)にだけプッシュ出来る $agit = $write && $access === Gitler::READ && $meta['pulls'] && !$meta['archived'] && Gitler::userCan($user, 'pulls'); if ($access < ($write && !$agit ? Gitler::WRITE : Gitler::READ)) return null; if ($write && $meta['archived']) { fwrite(STDERR, "アーカイブされたリポジトリにはプッシュ出来ません。\n"); return null; } [$owner, $repoName, $repoAccess] = [$m[1], $m[2], $access]; return Gitler::open($m[1], $m[2]); }, ['agent' => 'Gitler', 'receive' => GitlerController::receiveOptions($owner, $repoName, $user, $repoAccess)])); case 'gc': $res = Gitler::gcAll(); fwrite($res->isSuccess ? STDOUT : STDERR, $res->message."\n"); exit($res->isSuccess ? 0 : 1); default: fwrite(STDERR, $usage); exit(2); }