package main import ( "context" "encoding/json" "fmt" "github.com/google/go-github/github" "golang.org/x/crypto/ssh" "golang.org/x/oauth2" "io/ioutil" "log" "strings" ) const ( ACCESS_TOKEN = "" KEY_PASSWORD = "" KEY_PATH = "" SSH_USERNAME = "" SSH_HOST = "" REPO_PREFIX = "mcrute/github-mirror/" GITHUB_USERNAME = "mcrute" ) func GetGitoliteRepos(c *ssh.Client, prefix string) (map[string]string, error) { repos := make(map[string]string) session, err := c.NewSession() if err != nil { return nil, err } defer session.Close() output, err := session.Output(fmt.Sprintf("info -json %s", prefix)) if err != nil { return nil, err } var d map[string]interface{} if err = json.Unmarshal(output, &d); err != nil { return nil, err } for k, _ := range d["repos"].(map[string]interface{}) { name := strings.TrimPrefix(k, REPO_PREFIX) if name == ".*" { continue } repos[name] = k } return repos, nil } func SyncGitoliteRepo(c *ssh.Client, name string) error { session, err := c.NewSession() if err != nil { return err } defer session.Close() if err = session.Run(fmt.Sprintf("sync-mirror %s", name)); err != nil { return err } return nil } func CreateGitoliteMirror(c *ssh.Client, name, remote string) error { session, err := c.NewSession() if err != nil { return err } defer session.Close() if err = session.Run(fmt.Sprintf("create-mirror %s %s", remote, name)); err != nil { return err } return nil } func SSHConnect(key, password, username, host string) (*ssh.Client, error) { pem_bytes, err := ioutil.ReadFile(key) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKeyWithPassphrase(pem_bytes, []byte(password)) if err != nil { return nil, err } client_config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } ssh_client, err := ssh.Dial("tcp", host, client_config) if err != nil { return nil, err } return ssh_client, nil } func NewGithubClient(token string) *github.Client { ctx := context.Background() ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ) tc := oauth2.NewClient(ctx, ts) return github.NewClient(tc) } type GitHubRepo struct { Name string FullName string IsFork bool CloneURL string } func ListGithubRepos(c *github.Client, username string) ([]GitHubRepo, error) { ctx := context.Background() gh_repos := make([]GitHubRepo, 0) opts := &github.RepositoryListOptions{} for { repos, resp, err := c.Repositories.List(ctx, username, opts) if err != nil { return nil, err } for _, r := range repos { gh_repos = append(gh_repos, GitHubRepo{ Name: strings.TrimPrefix(*r.FullName, fmt.Sprintf("%s/", username)), FullName: *r.FullName, IsFork: *r.Fork, CloneURL: *r.CloneURL, }) } if resp.NextPage == 0 { break } opts.Page = resp.NextPage } return gh_repos, nil } func main() { gh_client := NewGithubClient(ACCESS_TOKEN) ssh_client, err := SSHConnect(KEY_PATH, KEY_PASSWORD, SSH_USERNAME, SSH_HOST) if err != nil { log.Fatal(err) } server_repos, err := GetGitoliteRepos(ssh_client, REPO_PREFIX) if err != nil { log.Fatal(err) } gh_repos, err := ListGithubRepos(gh_client, GITHUB_USERNAME) if err != nil { log.Fatal(err) } for _, r := range gh_repos { gitolite_name, have := server_repos[r.Name] if have { err = SyncGitoliteRepo(ssh_client, gitolite_name) if err != nil { log.Fatal("Failed to sync: %s", r.Name) } else { log.Printf("Synced: %s", r.Name) } } else if !have && !r.IsFork { err = CreateGitoliteMirror(ssh_client, fmt.Sprintf("%s%s", REPO_PREFIX, r.Name), r.CloneURL) if err != nil { log.Fatal("Failed to mirror: %s", r.Name) } else { log.Printf("Mirrored: %s", r.Name) } } } }