githubsync

package
v1.400.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecryptField

func DecryptField(dek []byte, value string) (string, error)

DecryptField decrypts a value produced by EncryptField. If the value does not have the enc:v1: prefix it is returned as-is (plaintext pass-through).

func EncryptField

func EncryptField(dek []byte, plaintext string) (string, error)

EncryptField encrypts a plaintext value with AES-256-GCM using a deterministic nonce. The nonce = HMAC-SHA256(DEK, plaintext)[:12] — same plaintext+DEK always yields the same ciphertext, keeping git diffs clean. Returns "enc:v1:<base64(nonce||ciphertext)>".

func IsEncrypted

func IsEncrypted(value string) bool

IsEncrypted returns true when the value carries the enc:v1: prefix.

func IsSensitiveKey

func IsSensitiveKey(key string) bool

IsSensitiveKey returns true when an env-var or header key name suggests its value is secret.

Types

type GitHubSyncClient

type GitHubSyncClient struct {
	// contains filtered or unexported fields
}

GitHubSyncClient wraps the GitHub Git Data API for atomic batch push/pull.

func NewGitHubSyncClient

func NewGitHubSyncClient(ctx context.Context, token, repoFullName string) (*GitHubSyncClient, error)

NewGitHubSyncClient creates a client authenticated with the given PAT. repoFullName must be "owner/repo".

func (*GitHubSyncClient) GetFile

func (c *GitHubSyncClient) GetFile(ctx context.Context, branch, path string) ([]byte, error)

GetFile returns the decoded content of a single file from the repository.

func (*GitHubSyncClient) ListFiles

func (c *GitHubSyncClient) ListFiles(ctx context.Context, branch, prefix string) ([]string, error)

ListFiles returns repo-relative paths of all blobs under the given prefix on the branch.

func (*GitHubSyncClient) PushFiles

func (c *GitHubSyncClient) PushFiles(ctx context.Context, branch, commitMessage string, files map[string][]byte) (string, error)

PushFiles commits files to the repository in a single atomic commit. files maps repo-relative paths to content. nil content entries delete the file. Returns the new commit SHA.

type Handlers

type Handlers struct {
	// contains filtered or unexported fields
}

Handlers implements app.CustomHandler for GitHub sync endpoints.

func NewHandlers

func NewHandlers(
	settingsRepo portrepos.SettingsRepository,
	scheduleRepo schedule.Manager,
	webhookRepo portrepos.WebhookRepository,
	memoryRepo portrepos.MemoryRepository,
	taskRepo portrepos.TaskRepository,
	taskGroupRepo portrepos.TaskGroupRepository,
	userFileRepo portrepos.UserFileRepository,
	slackbotRepo portrepos.SlackBotRepository,
	kmsKeyARN, awsRegion string,
	githubAppInstallID string,
) *Handlers

NewHandlers creates a Handlers instance registering all required repositories. kmsKeyARN and awsRegion come from proxy-level config; users cannot override them. githubAppInstallID is optional — when set, it enables GitHub App token fallback for users who have not configured a personal GitHub token.

func (*Handlers) DeleteConfig

func (h *Handlers) DeleteConfig(c echo.Context) error

DeleteConfig handles DELETE /sync/config/:name

func (*Handlers) GetConfig

func (h *Handlers) GetConfig(c echo.Context) error

GetConfig handles GET /sync/config/:name

func (*Handlers) GetName

func (h *Handlers) GetName() string

GetName implements app.CustomHandler.

func (*Handlers) Pull

func (h *Handlers) Pull(c echo.Context) error

Pull handles POST /sync/pull/:name

func (*Handlers) Push

func (h *Handlers) Push(c echo.Context) error

Push handles POST /sync/push/:name

func (*Handlers) RegisterRoutes

func (h *Handlers) RegisterRoutes(e *echo.Echo, _ *app.Server) error

RegisterRoutes implements app.CustomHandler.

func (*Handlers) RotateKey

func (h *Handlers) RotateKey(c echo.Context) error

RotateKey handles POST /sync/rotate-key/:name

func (*Handlers) Syncer

func (h *Handlers) Syncer() *Syncer

Syncer returns the underlying Syncer for use by the periodic worker.

func (*Handlers) UpdateConfig

func (h *Handlers) UpdateConfig(c echo.Context) error

UpdateConfig handles PUT /sync/config/:name

type LeaderWorker

type LeaderWorker struct {
	// contains filtered or unexported fields
}

LeaderWorker wraps Worker with Kubernetes leader election so only one replica in the cluster runs the periodic sync at a time.

func NewLeaderWorker

func NewLeaderWorker(
	syncer *Syncer,
	settingsRepo portrepos.SettingsRepository,
	interval time.Duration,
	client kubernetes.Interface,
	electionConfig schedule.LeaderElectionConfig,
) *LeaderWorker

NewLeaderWorker creates a LeaderWorker.

func (*LeaderWorker) Run

func (lw *LeaderWorker) Run(ctx context.Context)

Run starts the leader election loop. Only the elected leader runs the sync worker.

type PullRequest

type PullRequest struct {
	// DeleteOrphans removes local resources that no longer exist in GitHub.
	DeleteOrphans bool `json:"delete_orphans,omitempty"`
}

PullRequest is the HTTP request body for POST /sync/pull.

type PullResponse

type PullResponse struct {
	PulledAt time.Time   `json:"pulled_at"`
	Summary  SyncSummary `json:"summary"`
}

PullResponse is the HTTP response body for POST /sync/pull.

type PushRequest

type PushRequest struct {
	// CommitMessage overrides the default commit message.
	CommitMessage string `json:"commit_message,omitempty"`
}

PushRequest is the HTTP request body for POST /sync/push.

type PushResponse

type PushResponse struct {
	CommitSHA string      `json:"commit_sha"`
	PushedAt  time.Time   `json:"pushed_at"`
	Summary   SyncSummary `json:"summary"`
}

PushResponse is the HTTP response body for POST /sync/push.

type RotateKeyResponse

type RotateKeyResponse struct {
	CommitSHA  string    `json:"commit_sha"`
	RotatedAt  time.Time `json:"rotated_at"`
	DEKVersion int       `json:"dek_version"`
}

RotateKeyResponse is the HTTP response body for POST /sync/rotate-key.

type SyncConfigResponse

type SyncConfigResponse struct {
	Enabled        bool                   `json:"enabled"`
	RepoFullName   string                 `json:"repo_full_name"`
	Branch         string                 `json:"branch"`
	RootPath       string                 `json:"root_path"`
	AutoPush       bool                   `json:"auto_push"`
	HasGitHubToken bool                   `json:"has_github_token"`
	Encryption     SyncEncryptionResponse `json:"encryption"`
}

SyncConfigResponse is returned by GET /sync/config (github_token redacted).

type SyncEncryptionResponse

type SyncEncryptionResponse struct {
	DEKVersion int  `json:"dek_version"`
	DEKReady   bool `json:"dek_ready"` // true when encryptedDEK is non-empty
}

SyncEncryptionResponse is the public view of encryption status (no secrets, no KMS ARN).

type SyncEncryptor

type SyncEncryptor struct {
	// contains filtered or unexported fields
}

SyncEncryptor manages fixed-DEK AWS KMS envelope encryption for GitHub sync. The DEK is generated once per GitSyncConfig and stored encrypted in Settings.

func NewSyncEncryptor

func NewSyncEncryptor(ctx context.Context, kmsKeyARN, awsRegion string) (*SyncEncryptor, error)

NewSyncEncryptor creates a SyncEncryptor using AWS default credentials (IRSA > env vars > ~/.aws).

func (*SyncEncryptor) DecryptDEK

func (e *SyncEncryptor) DecryptDEK(ctx context.Context, encryptedDEK string) ([]byte, error)

DecryptDEK decrypts the base64-encoded encrypted DEK stored in GitSyncConfig.

func (*SyncEncryptor) GenerateAndEncryptDEK

func (e *SyncEncryptor) GenerateAndEncryptDEK(ctx context.Context) (dek []byte, encryptedDEK string, err error)

GenerateAndEncryptDEK generates a fresh random 256-bit DEK, encrypts it with KMS, and returns both the raw DEK bytes (for immediate use) and the base64 encrypted DEK (for storage in GitSyncConfig.Encryption.EncryptedDEK).

type SyncEventRecord

type SyncEventRecord struct {
	At        time.Time   `json:"at"`
	CommitSHA string      `json:"commit_sha,omitempty"`
	Summary   SyncSummary `json:"summary"`
}

SyncEventRecord records the result of the last push or pull.

type SyncMeta

type SyncMeta struct {
	APIVersion string      `yaml:"apiVersion"`
	Kind       string      `yaml:"kind"`
	SyncedAt   time.Time   `yaml:"syncedAt"`
	Version    string      `yaml:"version"`
	Encryption SyncMetaEnc `yaml:"encryption"`
}

SyncMeta is stored as .sync-meta.yaml at the rootPath in the GitHub repository. It records the last sync timestamp and encryption parameters (NOT the DEK itself).

type SyncMetaEnc

type SyncMetaEnc struct {
	Provider   string `yaml:"provider"` // always "aws_kms"
	KMSKeyARN  string `yaml:"kmsKeyArn"`
	Algorithm  string `yaml:"algorithm"` // "AES-256-GCM"
	DEKVersion int    `yaml:"dekVersion"`
}

SyncMetaEnc holds encryption metadata stored in .sync-meta.yaml. The encryptedDEK is NOT stored here — it lives in Settings (K8s Secret).

type SyncStatusResponse

type SyncStatusResponse struct {
	Config   *SyncConfigResponse `json:"config,omitempty"`
	LastPush *SyncEventRecord    `json:"last_push,omitempty"`
	LastPull *SyncEventRecord    `json:"last_pull,omitempty"`
}

SyncStatusResponse is the HTTP response body for GET /sync/status.

type SyncSummary

type SyncSummary struct {
	FilesWritten int `json:"files_written"`
	FilesDeleted int `json:"files_deleted"`
}

SyncSummary summarises what happened during a push or pull.

type Syncer

type Syncer struct {
	// contains filtered or unexported fields
}

Syncer orchestrates bidirectional GitHub sync for a user or team.

File layout in GitHub:

<rootPath>/<settingsName>/schedules/<id>.yaml
<rootPath>/<settingsName>/webhooks/<id>.yaml
<rootPath>/<settingsName>/settings.yaml   (team only)
<rootPath>/<settingsName>/files/<id>.yaml (personal only)
<rootPath>/<settingsName>/slackbots/<id>.yaml
<rootPath>/<settingsName>/.sync-meta.yaml

For personal sync settingsName == userID; for team sync settingsName is the team name. Each settings has its own subdirectory under rootPath so multiple settings can share the same repository without conflicting.

func NewSyncer

NewSyncer creates a Syncer. Non-nil repos are synced. githubAppInstallID is used to generate a GitHub App installation token when a user has no personal GitHub token configured (empty string disables fallback).

func (*Syncer) Pull

func (s *Syncer) Pull(ctx context.Context, settingsName, userID string, deleteOrphans bool) (*PullResponse, error)

Pull downloads resources from GitHub and imports them.

func (*Syncer) Push

func (s *Syncer) Push(ctx context.Context, settingsName, userID string, commitMessage string) (*PushResponse, error)

Push exports all resources for settingsName and commits them to GitHub.

func (*Syncer) RotateKey

func (s *Syncer) RotateKey(ctx context.Context, settingsName, userID string) (*RotateKeyResponse, error)

RotateKey generates a fresh DEK, re-encrypts all GitHub files, and updates Settings.

type UpdateSyncConfigRequest

type UpdateSyncConfigRequest struct {
	Enabled      bool   `json:"enabled"`
	RepoFullName string `json:"repo_full_name"`
	Branch       string `json:"branch"`
	RootPath     string `json:"root_path"`
	AutoPush     bool   `json:"auto_push"`
	GitHubToken  string `json:"github_token,omitempty"`
}

UpdateSyncConfigRequest is the HTTP request body for PUT /sync/config. Encryption settings (KMS key ARN, AWS region) are set at the proxy level and cannot be provided by the user.

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

Worker runs periodic bidirectional sync for all enabled git_sync configs.

Each settings is synced on a per-tenant schedule: the first sync time is offset by hash(settingsName) % interval so tenants don't hit GitHub simultaneously. Subsequent syncs happen at lastSyncedAt + interval.

Direction is determined by comparing the remote .sync-meta.yaml syncedAt timestamp against the locally stored LastPushedAt:

remoteSyncedAt > LastPushedAt  →  Pull  (GitHub is newer)
otherwise                      →  Push  (local is newer or equal)

func NewWorker

func NewWorker(syncer *Syncer, settingsRepo portrepos.SettingsRepository, interval time.Duration) *Worker

NewWorker creates a Worker. interval must be > 0.

func (*Worker) Start

func (w *Worker) Start(ctx context.Context) error

Start begins the periodic sync loop. It returns immediately; the loop runs in the background.

func (*Worker) Stop

func (w *Worker) Stop()

Stop signals the sync loop to stop and waits for it to exit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL