自部署 Gitlab 生成批量 clone 项目脚本
// ==UserScript== // @name GitLab - 一键导出所有项目 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在 GitLab 项目页面顶部添加自定义按钮用于一键导出所有项目 // @author LeonShaw // @match *://*/dashboard/projects // @grant none // @run-at document-idle // ==/UserScript==
(function () {
'use strict';
const { protocol, host } = window.location;
const baseUrl = `${protocol}//${host}`;
const apiUrl = `${baseUrl}/api/v4/projects`;
// 获取所有项目(分页) async function fetchAllProjects() {
const statusEl = document.getElementById('clone-status');
statusEl.style.display = 'block';
statusEl.textContent = '正在加载项目...';
let page = 1;
let allProjects = [];
try {
while (true) {
const url = `${apiUrl}?per_page=100&page=${page}&order_by=created_at&sort=desc`;
const res = await fetch(url, {
credentials: 'include' // 携带 cookie,确保认证
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
const data = await res.json();
if (!Array.isArray(data) || data.length === 0) break;
allProjects.push(...data);
statusEl.textContent = `已加载 ${allProjects.length} 个项目...`;
page++;
}
generateAndDownloadScript(allProjects);
statusEl.textContent = `完成!共 ${allProjects.length} 个项目。`;
} catch (err) {
console.error('获取项目失败:', err);
statusEl.textContent = `错误: ${err.message || '未知错误'}`;
} finally {
setTimeout(() => {
statusEl.style.display = 'none';
statusEl.textContent = '';
}, 30000);
}
}
// 生成并下载 .sh 脚本 function generateAndDownloadScript(projects) {
const lines = ['#!/bin/bash\n'];
projects.forEach(proj => {
if (!proj.ssh_url_to_repo) return;
const sshUrl = proj.ssh_url_to_repo;
const localPath = proj.path_with_namespace;
lines.push(`git clone "${sshUrl}" "./${localPath}"`);
});
const scriptContent = lines.join('\n') + '\n';
const blob = new Blob([scriptContent], { type: 'text/plain;charset=utf-8' });
const filename = `clone-all-git-repos(${host}).sh`;
// 创建下载链接 const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
function addButton() {
const breadcrumbWrapper = document.querySelector('#js-vue-page-breadcrumbs-wrapper');
if (!breadcrumbWrapper) return;
// 防止重复添加 if (breadcrumbWrapper.querySelector('.custom-gitlab-button')) return;
// 创建按钮 const button = document.createElement('button');
button.textContent = '一键导出所有项目';
button.className = 'custom-gitlab-button gl-button btn btn-default btn-sm gl-mx-2';
button.style.marginLeft = '10px';
button.addEventListener('click', function () {
fetchAllProjects();
});
breadcrumbWrapper.appendChild(button);
const statusElement = document.createElement('span');
statusElement.id = 'clone-status';
statusElement.className = 'custom-gitlab-status gl-ml-3 gl-font-sm gl-font-weight-bold';
statusElement.style.padding = '4px 8px';
statusElement.style.borderRadius = '4px';
statusElement.style.backgroundColor = '#e9ecef';
statusElement.style.color = '#495057';
statusElement.textContent = '';
statusElement.style.display = 'none';
breadcrumbWrapper.appendChild(statusElement);
}
addButton();
const observer = new MutationObserver(() => {
addButton();
});
observer.observe(document.body, { childList: true, subtree: true });
})();
AIGC 声明:本脚本部分函数由 通义千问 辅助创造,本人已验证其生成内容的真实性和有效性
使用方法
本脚本已在 Gitlab v18.0.1 - v18.8.1 验证

![[Cloudflare 自部署项目] Cursor byok 支持, 已适配 Claude-* 模型,支持 thinking 和工具调用,需自备 Cursor Pro 与 可用渠道1](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/images/2026/01/14/20260114110314_69670772a89ef.png!mark)
![[Cloudflare 自部署项目] Cursor byok 支持, 已适配 Claude-* 模型,支持 thinking 和工具调用,需自备 Cursor Pro 与 可用渠道2](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/images/2026/01/14/20260114110317_69670775941ef.png!mark)
![[Cloudflare 自部署项目] Cursor byok 支持, 已适配 Claude-* 模型,支持 thinking 和工具调用,需自备 Cursor Pro 与 可用渠道3](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/images/2026/01/14/20260114110319_69670777ee9e1.png!mark)