Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,20 @@ steps:
#### 必选参数
- `src` 需要被同步的源端账户名,如github/kunpengcompute,表示Github的kunpengcompute账户。
- `dst` 需要同步到的目的端账户名,如gitee/kunpengcompute,表示Gitee的kunpengcompute账户。
- `dst_key` 用于在目的端上传代码的私钥(默认可以从~/.ssh/id_rsa获取),可参考[生成/添加SSH公钥](https://gitee.com/help/articles/4181)或[generating SSH keys](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)生成,并确认对应公钥已经被正确配置在目的端。对应公钥,Github可以在[这里](https://github.com/settings/keys)配置,Gitee可以[这里](https://gitee.com/profile/sshkeys)配置,Gitlab可以在[这里](https://gitlab.com/-/user_settings/ssh_keys)配置
- `dst_key` 用于在目的端推送代码的私钥,对应公钥需配置在目的端。`dst_key` 优先于 `private_key`
- `dst_token` 创建仓库的API tokens, 用于自动创建不存在的仓库,Github可以在[这里](https://github.com/settings/tokens)找到,Gitee可以在[这里](https://gitee.com/profile/personal_access_tokens)找到,Gitlab可以在[这里](https://gitlab.com/-/user_settings/personal_access_tokens)找到(Required scopes: api, read_api, read_repository, write_repository)。

密钥规则:`dst_key` 必须提供,除非设置了 `private_key`。当 `clone_style=ssh` 时,需要提供 `src_key` 或 `private_key`。建议优先使用独立的 `src_key`/`dst_key`,`private_key` 仅作为兜底。

#### 可选参数
- `src_key` 用于在源端拉取代码的私钥,对应公钥需配置在源端。`src_key` 优先于 `private_key`。当 `clone_style=ssh` 时必需(除非提供 `private_key`)。
- `private_key` 通用私钥(可选)。当 `src_key` 或 `dst_key` 为空时使用。
- `account_type` 默认为user,源和目的的账户类型,可以设置为org(组织)、user(用户)或者group(组),该参数支持**同类型账户**(即组织到组织,或用户到用户,或组到组)的同步。如果源目的仓库是不同类型,请单独使用`src_account_type`和`dst_account_type`配置。
- `src_account_type` 默认为`account_type`,源账户类型,可以设置为org(组织)、user(用户)或者group(组)。
- `dst_account_type` 默认为`account_type`,目的账户类型,可以设置为org(组织)、user(用户)或者group(组)。
- `src_endpoint` 默认为空,仅在源端为GitLab时生效,用于自托管GitLab地址,例如`gitlab.example.com`(无需包含`https://`前缀),为空时使用`gitlab.com`。
- `dst_endpoint` 默认为空,仅在目的端为GitLab时生效,用于自托管GitLab地址,例如`gitlab.example.com`(无需包含`https://`前缀),为空时使用`gitlab.com`。
- `clone_style` 默认为https,可以设置为ssh或者https。当设置为ssh时,你需要将`dst_key`所对应的公钥同时配置到源端和目的端
- `clone_style` 默认为https,可以设置为ssh或者https。当设置为ssh时,需要将 `src_key` 公钥配置到源端,将 `dst_key` 公钥配置到目的端;若使用 `private_key`,则其公钥需配置到两端。Action 会在 git 操作时注入 `GIT_SSH_COMMAND` 绑定对应私钥
- `cache_path` 默认为`hub-mirror-cache`,将代码缓存在指定目录,用于与actions/cache配合以加速镜像过程。若为相对路径,会自动拼接`${{ github.workspace }}`。
- `black_list` 默认为'', 配置后,黑名单中的repos将不会被同步,如“repo1,repo2,repo3”。
- `white_list` 默认为'', 配置后,仅同步白名单中的repos,如“repo1,repo2,repo3”。
Expand Down Expand Up @@ -113,14 +117,15 @@ steps:

使用ssh方式进行clone

说明:请把`dst_key`所的公钥配置到源端(在这里为github)及目的端(在这里为gitee)
说明:请把 `src_key` 的公钥配置到源端(在这里为github),把 `dst_key` 的公钥配置到目的端(在这里为gitee)。若使用 `private_key`,其公钥需配置到两端。

```yaml
- name: ssh clone style
uses: Yikun/hub-mirror-action@master
with:
src: github/Yikun
dst: gitee/yikunkero
src_key: ${{ secrets.GITHUB_PRIVATE_KEY }}
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
clone_style: "ssh"
Expand Down Expand Up @@ -223,15 +228,15 @@ steps:

## FAQ

- 如何在secrets添加dst_token和dst_key
- 如何在secrets添加token和SSH key
下面是添加secrets的方法,也可以参考[secrets官方文档](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets)了解更多:
1. **获取Token和Key**,例如
- Github: 配置并保存[ssh key](https://github.com/settings/keys)和[token](https://github.com/settings/tokens)
- Gitee: 配置并保存[ssh key](https://gitee.com/profile/sshkeys)和[token](https://gitee.com/profile/personal_access_tokens)
- Gitlab: 配置并保存[ssh key](https://gitlab.com/-/user/settings/keys)和[token](https://gitlab.com/-/user_settings/personal_access_tokens)
- Gitcode: 配置并保存[ssh key](https://gitcode.com/setting/key-ssh)和[token](https://gitcode.com/setting/token-classic)
2. **增加Secrets配置**,在配置仓库的Setting-Secrets中新增Secrets,例如`GITEE_PRIVATE_KEY`\`GITLAB_PRIVATE_KEY`\`GITCODE_PRIVATE_KEY`、`GITEE_TOKEN`\`GITLAB_TOKEN`\`GITCODE_TOKEN`。
3. **在Workflow中引用**, 可以用过类似`${{ secrets.GITEE_PRIVATE_KEY }}`来访问。
2. **增加Secrets配置**,在配置仓库的Setting-Secrets中新增Secrets,例如`GITHUB_PRIVATE_KEY`\`GITEE_PRIVATE_KEY`\`GITLAB_PRIVATE_KEY`\`GITCODE_PRIVATE_KEY`\`PRIVATE_KEY`、`GITEE_TOKEN`\`GITLAB_TOKEN`\`GITCODE_TOKEN`。
3. **在Workflow中引用**, 可以用过类似`${{ secrets.GITHUB_PRIVATE_KEY }}`或`${{ secrets.PRIVATE_KEY }}`来访问。

## 参考
- [Hub mirror template](https://github.com/yi-Xu-0100/hub-mirror): 一个用于展示如何使用这个action的模板仓库. from @yi-Xu-0100
Expand Down
13 changes: 9 additions & 4 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,20 @@ More than [100+](https://github.com/search?p=2&q=hub-mirror-action+%22account_ty
#### Required
- `src` source account, such as `github/kunpengcompute`, is the Github kunpengcompute account.
- `dst` Destination account, such as `/kunpengcompute`, is the Gitee kunpengcompute account.
- `dst_key` the private key to push code in destination account (default in ~/.ssh/id_rsa), you can see [generating SSH keys](https://docs.github.com/articles/generating-an-ssh-key/) to generate the pri/pub key, and make sure the pub key has been added in destination. You can set Github ssh key in [here](https://github.com/settings/keys),set the Gitee ssh key in [here](https://gitee.com/profile/sshkeys), set the Gitlab ssh key in [here](https://gitlab.com/-/user_settings/ssh_keys), set the Gitcode ssh key in [here](https://gitcode.com/setting/key-ssh).
- `dst_key` the private key used to push code to destination. The public key must be added to the destination side. `dst_key` takes precedence over `private_key`.
- `dst_token` the API token to create non-existent repo, You can get Github token in [here](https://github.com/settings/tokens), and the Gitee in [here](https://gitee.com/profile/personal_access_tokens). and for GitLab in [here](https://gitlab.com/-/user_settings/personal_access_tokens) ,and for GitCode in [here](https://gitcode.com/setting/token-classic) (Required scopes: api, read_api, read_repository, write_repository).

Key rule: `dst_key` is required unless `private_key` is set. When `clone_style=ssh`, provide `src_key` or `private_key`. Prefer separate `src_key`/`dst_key` for least privilege.

#### Optional
- `src_key` the private key used to fetch code from source. The public key must be added to the source side. `src_key` takes precedence over `private_key`. Required when `clone_style=ssh` unless `private_key` is set.
- `private_key` (optional) common private key. Used when `src_key` or `dst_key` is empty.
- `account_type` (optional) default is `user`, the account type of src and dst account, can be set to `org` or `user`,For GitLab: can be set to `group` or `user`,only support mirror between same account type (that is "org to org" or "user to user" or "group to group"). if u wanna mirror difference account type, use the `src_account_type` and `dst_account_type` please.
- `src_account_type` (optional) default is `account_type`, the account type of src account, can be set to `org` or `user` or `group`.
- `dst_account_type` (optional) default is `account_type`, the account type of dst account, can be set to `org` or `user`r `group`.
- `src_endpoint` (optional) only for self-hosted GitLab source, used for self-hosted GitLab endpoint like `gitlab.example.com` (no https:// prefix), default is empty.
- `dst_endpoint` (optional) only for self-hosted GitLab destination, used for self-hosted GitLab endpoint like `gitlab.example.com` (no https:// prefix), default is `gitlab.com`.
- `clone_style` (optional) default is `https`, can be set to `ssh` or `https`.When you are using ssh clone style, you need to configure the public key of `dst_key` to both source end and destination end.
- `clone_style` (optional) default is `https`, can be set to `ssh` or `https`. When using ssh, add the public key of `src_key` to the source and `dst_key` to the destination; if using `private_key`, add its public key to both sides. The action injects `GIT_SSH_COMMAND` per git operation to bind the right key.
- `cache_path` (optional) defaults to `hub-mirror-cache`, let code clone in specific path, can be used with actions/cache to speed up mirror. If it is a relative path, it will be prefixed with `${{ github.workspace }}`.
- `black_list` (optional) the black list, such as “repo1,repo2,repo3”.
- `white_list` (optional) the white list, such as “repo1,repo2,repo3”.
Expand Down Expand Up @@ -114,14 +118,15 @@ More than [100+](https://github.com/search?p=2&q=hub-mirror-action+%22account_ty
```

#### clone style, use `ssh` clone style
Note: please configure the public key of `dst_key` to the source (github in here) and destination(gitee in here)
Note: add the public key of `src_key` to the source (github in here) and `dst_key` to the destination (gitee in here). If using `private_key`, add its public key to both.

```yaml
- name: ssh clone style
uses: Yikun/hub-mirror-action@master
with:
src: github/Yikun
dst: gitee/yikunkero
src_key: ${{ secrets.GITHUB_PRIVATE_KEY }}
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
clone_style: "ssh"
Expand Down Expand Up @@ -216,7 +221,7 @@ Note: see also the real example in `.github/workflows/verify-on-ubuntu-user-cach
- Gitee: Configure and save your [ssh key](https://gitee.com/profile/sshkeys) and [token](https://gitee.com/profile/personal_access_tokens)
- Gitlab: Configure and save your [ssh key](https://gitlab.com/-/user/settings/keys) and [token](https://gitlab.com/-/user_settings/personal_access_tokens)
- Gitcode: Configure and save your [ssh key](https://gitcode.com/setting/key-ssh) and[token](https://gitcode.com/setting/token-classic)
2. **Add Secrets**,add settings-secrets in repo,like `GITEE_PRIVATE_KEY`、`GITEE_TOKEN` or `GITLAB_PRIVATE_KEY`、`GITLAB_TOKEN` or `GITCODE_PRIVATE_KEY`、`GITCODE_TOKEN`
2. **Add Secrets**,add settings-secrets in repo,like `GITHUB_PRIVATE_KEY`、`GITEE_PRIVATE_KEY`、`GITLAB_PRIVATE_KEY`、`GITCODE_PRIVATE_KEY`、`PRIVATE_KEY` and `GITEE_TOKEN`、`GITLAB_TOKEN`、`GITCODE_TOKEN`
3. **Add workflow**,add the workflow file into .github/workflows.

## Reference
Expand Down
43 changes: 37 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ branding:
icon: "upload-cloud"
color: "gray-dark"
inputs:
src_key:
description: "The private SSH key used to fetch code from source hub. Required when clone_style=ssh unless private_key is set. Takes precedence over private_key."
required: false
dst_key:
description: "The private SSH key which is used to to push code in destination hub."
required: true
description: "The private SSH key used to push code to destination hub. Takes precedence over private_key."
required: false
private_key:
description: "Fallback private SSH key used when src_key or dst_key is empty."
required: false
dst_token:
description: "The app token which is used to create repo in destination hub."
required: true
Expand Down Expand Up @@ -110,11 +116,36 @@ runs:

- name: Configure SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "${{ inputs.dst_key }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# Try to modify system config with sudo, fallback to user config
sudo sh -c 'echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config' 2>/dev/null || echo "StrictHostKeyChecking no" >> ~/.ssh/config

SRC_KEY="${{ inputs.src_key }}"
DST_KEY="${{ inputs.dst_key }}"
PRIVATE_KEY="${{ inputs.private_key }}"

CLONE_STYLE="${{ inputs.clone_style }}"

if [[ -z "$PRIVATE_KEY" ]]; then
if [[ -z "$DST_KEY" ]]; then
echo "Error: dst_key is required unless private_key is set." >&2
exit 1
fi
if [[ "$CLONE_STYLE" == "ssh" && -z "$SRC_KEY" ]]; then
echo "Error: src_key is required when clone_style=ssh unless private_key is set." >&2
exit 1
fi
fi

if [[ -z "$SRC_KEY" ]]; then
SRC_KEY="$PRIVATE_KEY"
fi
if [[ -z "$DST_KEY" ]]; then
DST_KEY="$PRIVATE_KEY"
fi

printf "%s" "$SRC_KEY" > ~/.ssh/id_rsa_src
printf "%s" "$DST_KEY" > ~/.ssh/id_rsa_dst
chmod 600 ~/.ssh/id_rsa_src ~/.ssh/id_rsa_dst
shell: bash

- name: Run Hub Mirror
Expand Down
92 changes: 75 additions & 17 deletions hub-mirror/mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import re
import shutil
import shlex
from typing import List

import git
Expand Down Expand Up @@ -30,33 +31,78 @@ def __init__(
self.src_url: str = hub.src_repo_base + "/" + src_name + ".git"
self.dst_url: str = hub.dst_repo_base + "/" + dst_name + ".git"
self.repo_path: str = os.path.join(cache, src_name)
self.src_key_path: str = os.path.expanduser("~/.ssh/id_rsa_src")
self.dst_key_path: str = os.path.expanduser("~/.ssh/id_rsa_dst")
self.timeout: int = 0
if re.match(r"^\d+[dhms]?$", timeout):
self.timeout = cov2sec(timeout)
self.force_update: bool = force_update
self.lfs: bool = lfs

def _ssh_command(self, key_path: str) -> str:
options: List[str] = [
"-i",
key_path,
"-o",
"IdentitiesOnly=yes",
"-o",
"BatchMode=yes",
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
]
return "ssh " + " ".join(shlex.quote(option) for option in options)

def _uses_ssh_for_src(self) -> bool:
return self.src_url.startswith("git@")

@retry(wait=wait_exponential(), reraise=True, stop=stop_after_attempt(3))
def _clone(self) -> None:
# TODO: process empty repo
logger.info(f"Starting git clone {self.src_url}")
mygit: git.cmd.Git = git.cmd.Git(os.getcwd())
mygit.clone(
git.cmd.Git.polish_url(self.src_url),
self.repo_path,
kill_after_timeout=self.timeout,
)
# Clone the src repository using SSH need to add private key
if self._uses_ssh_for_src():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add note here to show branch case

with mygit.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.src_key_path)
):
mygit.clone(
git.cmd.Git.polish_url(self.src_url),
self.repo_path,
kill_after_timeout=self.timeout,
)
else:
mygit.clone(
git.cmd.Git.polish_url(self.src_url),
self.repo_path,
kill_after_timeout=self.timeout,
)
local_repo: git.Repo = git.Repo(self.repo_path)
if self.lfs:
local_repo.git.lfs("fetch", "--all", "origin")
if self._uses_ssh_for_src():
with local_repo.git.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.src_key_path)
):
local_repo.git.lfs("fetch", "--all", "origin")
else:
local_repo.git.lfs("fetch", "--all", "origin")
logger.info(f"Clone completed: {os.getcwd() + self.repo_path}")

@retry(wait=wait_exponential(), reraise=True, stop=stop_after_attempt(3))
def _update(self, local_repo: git.Repo) -> None:
try:
local_repo.git.pull(kill_after_timeout=self.timeout)
if self.lfs:
local_repo.git.lfs("fetch", "--all", "origin")
if self._uses_ssh_for_src():
with local_repo.git.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.src_key_path)
):
local_repo.git.pull(kill_after_timeout=self.timeout)
if self.lfs:
local_repo.git.lfs("fetch", "--all", "origin")
else:
local_repo.git.pull(kill_after_timeout=self.timeout)
if self.lfs:
local_repo.git.lfs("fetch", "--all", "origin")
except git.exc.GitCommandError:
# Cleanup local repo and re-clone
logger.warning(f"Updating failed, re-clone {self.src_name}")
Expand Down Expand Up @@ -92,7 +138,13 @@ def push(self, force: bool = False) -> None:
logger.info(f"Empty repo {self.src_url}, skip pushing.")
return
cmd: List[str] = ["set-head", "origin", "-d"]
local_repo.git.remote(*cmd)
if self._uses_ssh_for_src():
with local_repo.git.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.src_key_path)
):
local_repo.git.remote(*cmd)
else:
local_repo.git.remote(*cmd)
try:
local_repo.create_remote(self.hub.dst_type, self.dst_url)
except git.exc.GitCommandError:
Expand All @@ -110,12 +162,18 @@ def push(self, force: bool = False) -> None:
]
if not self.force_update:
logger.info("(3/3) Pushing...")
local_repo.git.push(*cmd, kill_after_timeout=self.timeout)
if self.lfs:
git_cmd.lfs("push", self.hub.dst_type, "--all")
with git_cmd.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.dst_key_path)
):
git_cmd.push(*cmd, kill_after_timeout=self.timeout)
if self.lfs:
git_cmd.lfs("push", self.hub.dst_type, "--all")
else:
logger.info("(3/3) Force pushing...")
if self.lfs:
git_cmd.lfs("push", self.hub.dst_type, "--all")
cmd = ["-f"] + cmd
local_repo.git.push(*cmd, kill_after_timeout=self.timeout)
with git_cmd.custom_environment(
GIT_SSH_COMMAND=self._ssh_command(self.dst_key_path)
):
if self.lfs:
git_cmd.lfs("push", self.hub.dst_type, "--all")
cmd = ["-f"] + cmd
git_cmd.push(*cmd, kill_after_timeout=self.timeout)
Loading