build: fix automation permissions
diff --git a/internal/pkg/image_repositories/build_workflow.go b/internal/pkg/image_repositories/build_workflow.go
index ba40d9d..c55db62 100644
--- a/internal/pkg/image_repositories/build_workflow.go
+++ b/internal/pkg/image_repositories/build_workflow.go
@@ -69,8 +69,14 @@
 
 	return &GithubWorkflow{
 		Name: "build",
+		Concurrency: GithubWorkflowConcurrency{
+			Group:            "${{ github.head_ref || github.run_id }}",
+			CancelInProgress: true,
+		},
 		On: GithubWorkflowTrigger{
-			PullRequest: GithubWorkflowPullRequest{},
+			PullRequest: GithubWorkflowPullRequest{
+				Types: []string{"opened", "synchronize", "reopened"},
+			},
 			Push: GithubWorkflowPush{
 				Branches: []string{"main"},
 			},
diff --git a/internal/pkg/image_repositories/github_workflow.go b/internal/pkg/image_repositories/github_workflow.go
index 06e523a..c8b202d 100644
--- a/internal/pkg/image_repositories/github_workflow.go
+++ b/internal/pkg/image_repositories/github_workflow.go
@@ -1,6 +1,7 @@
 package image_repositories
 
 import (
+	"fmt"
 	"io"
 
 	"github.com/go-git/go-billy/v5"
@@ -8,26 +9,49 @@
 )
 
 type GithubWorkflow struct {
-	Name string                       `yaml:"name"`
-	On   GithubWorkflowTrigger        `yaml:"on"`
-	Jobs map[string]GithubWorkflowJob `yaml:"jobs"`
+	Name        string                       `yaml:"name"`
+	Concurrency GithubWorkflowConcurrency    `yaml:"concurrency"`
+	On          GithubWorkflowTrigger        `yaml:"on"`
+	Jobs        map[string]GithubWorkflowJob `yaml:"jobs"`
 }
 
 type GithubWorkflowTrigger struct {
-	PullRequest GithubWorkflowPullRequest `yaml:"pull_request"`
-	Push        GithubWorkflowPush        `yaml:"push"`
+	PullRequest      GithubWorkflowPullRequest `yaml:"pull_request,omitempty"`
+	Push             GithubWorkflowPush        `yaml:"push,omitempty"`
+	Schedule         []GithubWorkflowSchedule  `yaml:"schedule,omitempty"`
+	WorkflowDispatch GithubWorkflowDispatch    `yaml:"workflow_dispatch,omitempty"`
 }
 
 type GithubWorkflowPullRequest struct {
+	Types []string `yaml:"types,omitempty"`
 }
 
 type GithubWorkflowPush struct {
 	Branches []string `yaml:"branches"`
 }
 
+type GithubWorkflowSchedule struct {
+	Cron string `yaml:"cron"`
+}
+
+type GithubWorkflowDispatch struct {
+	Inputs map[string]GithubWorkflowDispatchInput `yaml:"inputs,omitempty"`
+}
+
+type GithubWorkflowDispatchInput struct {
+	Description string `yaml:"description"`
+	Required    bool   `yaml:"required"`
+	Default     string `yaml:"default"`
+}
+
+type GithubWorkflowConcurrency struct {
+	Group            string `yaml:"group"`
+	CancelInProgress bool   `yaml:"cancel-in-progress"`
+}
+
 type GithubWorkflowJob struct {
 	RunsOn   string                 `yaml:"runs-on"`
-	Strategy GithubWorkflowStrategy `yaml:"strategy"`
+	Strategy GithubWorkflowStrategy `yaml:"strategy,omitempty"`
 	Steps    []GithubWorkflowStep   `yaml:"steps"`
 }
 
@@ -54,7 +78,9 @@
 }
 
 func (g *GithubWorkflow) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create(".github/workflows/build.yml")
+	file := fmt.Sprintf(".github/workflows/%s.yml", g.Name)
+
+	f, err := fs.Create(file)
 	if err != nil {
 		return err
 	}
diff --git a/internal/pkg/image_repositories/image_repository.go b/internal/pkg/image_repositories/image_repository.go
index bcb9f7a..45b49d5 100644
--- a/internal/pkg/image_repositories/image_repository.go
+++ b/internal/pkg/image_repositories/image_repository.go
@@ -21,8 +21,9 @@
 type ImageRepository struct {
 	Project string
 
-	githubClient *github.Client
-	gitAuth      *git_http.BasicAuth
+	githubClient      *github.Client
+	githubProjectName string
+	gitAuth           *git_http.BasicAuth
 }
 
 func NewImageRepository(project string) *ImageRepository {
@@ -37,7 +38,8 @@
 	return &ImageRepository{
 		Project: project,
 
-		githubClient: github.NewClient(tc),
+		githubClient:      github.NewClient(tc),
+		githubProjectName: fmt.Sprintf("docker-openstack-%s", project),
 		gitAuth: &git_http.BasicAuth{
 			Username: githubToken,
 		},
@@ -53,6 +55,11 @@
 	}
 
 	// .github/workflows/sync.yml
+	sync := NewSyncWorkflow(i.Project)
+	err = sync.WriteFile(fs)
+	if err != nil {
+		return err
+	}
 
 	// .github/dependabot.yml
 	dab := NewDependabotConfig()
@@ -108,19 +115,16 @@
 	return nil
 }
 
-func (i *ImageRepository) CreateOrGet(ctx context.Context) (*github.Repository, error) {
-	projectName := fmt.Sprintf("docker-openstack-%s", i.Project)
-
-	_, resp, err := i.githubClient.Repositories.Get(ctx, "vexxhost", projectName)
-	if err != nil && resp.StatusCode == http.StatusNotFound {
-		_, _, err = i.githubClient.Repositories.Create(ctx, "vexxhost", &github.Repository{
-			Name: github.String(projectName),
-		})
-		if err != nil {
-			return nil, err
-		}
+func (i *ImageRepository) GetGitHubRepository(ctx context.Context) (*github.Repository, error) {
+	repo, _, err := i.githubClient.Repositories.Get(ctx, "vexxhost", i.githubProjectName)
+	if err != nil {
+		return nil, err
 	}
 
+	return repo, nil
+}
+
+func (i *ImageRepository) UpdateGithubConfiguration(ctx context.Context) error {
 	// Description
 	description := fmt.Sprintf("Docker image for OpenStack: %s", i.Project)
 
@@ -138,9 +142,9 @@
 	}
 
 	// Update the repository with the correct settings
-	repo, _, err = i.githubClient.Repositories.Edit(ctx, "vexxhost", projectName, repo)
+	repo, _, err := i.githubClient.Repositories.Edit(ctx, "vexxhost", i.githubProjectName, repo)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	// Branch protection
@@ -177,14 +181,14 @@
 	}
 	_, _, err = i.githubClient.Repositories.UpdateBranchProtection(ctx, *repo.Owner.Login, *repo.Name, "main", protection)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	return repo, err
+	return nil
 }
 
 func (i *ImageRepository) Synchronize(ctx context.Context) error {
-	githubRepo, err := i.CreateOrGet(ctx)
+	githubRepo, err := i.GetGitHubRepository(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/internal/pkg/image_repositories/sync_workflow.go b/internal/pkg/image_repositories/sync_workflow.go
new file mode 100644
index 0000000..2939dd8
--- /dev/null
+++ b/internal/pkg/image_repositories/sync_workflow.go
@@ -0,0 +1,56 @@
+package image_repositories
+
+import "fmt"
+
+func NewSyncWorkflow(project string) *GithubWorkflow {
+	return &GithubWorkflow{
+		Name: "sync",
+		Concurrency: GithubWorkflowConcurrency{
+			Group:            "sync",
+			CancelInProgress: true,
+		},
+		On: GithubWorkflowTrigger{
+			WorkflowDispatch: GithubWorkflowDispatch{
+				Inputs: map[string]GithubWorkflowDispatchInput{
+					"ref": {
+						Description: "Atmosphere branch, tag or SHA to checkout",
+						Required:    true,
+						Default:     "main",
+					},
+				},
+			},
+			Schedule: []GithubWorkflowSchedule{
+				{
+					Cron: "0 0 * * *",
+				},
+			},
+		},
+		Jobs: map[string]GithubWorkflowJob{
+			"image": {
+				RunsOn: "ubuntu-latest",
+				Steps: []GithubWorkflowStep{
+					{
+						Name: "Checkout Atmosphere",
+						Uses: "actions/checkout@v3",
+						With: map[string]string{
+							"repository": "vexxhost/atmosphere",
+							"ref":        "${{ inputs.ref || 'main' }}",
+						},
+					},
+					{
+						Name: "Setup Go",
+						Uses: "actions/setup-go@v3",
+						With: map[string]string{
+							"go-version-file": "go.mod",
+							"cache":           "true",
+						},
+					},
+					{
+						Name: "Synchronize Image Repository",
+						Run:  fmt.Sprintf("go run ./cmd/atmosphere-ci image repo sync %s", project),
+					},
+				},
+			},
+		},
+	}
+}