A central repository for distributing and syncing shared files across multiple repositories in the PSModule organization.
The Distributor service maintains a centralized collection of files that are automatically synced to subscribing repositories in the PSModule organization. This ensures consistency across repositories for configuration files, linter settings, documentation templates, GitHub Actions workflows, and other shared resources.
Files are organized in a two-level folder hierarchy under the Repos/ directory:
Repos/{Type}/{Selection}/
- Type (first level): Groups repositories by their kind (Module, Action, Template, Workflow, etc.)
- Selection (second level): Individual file sets that repositories can subscribe to
- Each selection folder mimics the root of a target repository
Repositories subscribe to file sets using two custom properties defined at the organization level:
| Property | Type | Description |
|---|---|---|
Type |
Single-select | Determines which type folder to use (Module, Action, Template, Workflow, Docs, Other) |
SubscribeTo |
Multi-select | Determines which file sets to receive |
Available SubscribeTo values (must match folder names exactly):
Custom Instructions- Copilot instructions for the repositoryPrompts- GitHub Copilot prompt filesHooks- GitHub Copilot agent hooks for session events (sessionStart, sessionEnd, userPromptSubmitted)CODEOWNERS- GitHub CODEOWNERS filedependabot.yml- Dependabot configurationLinter Settings- Linter configuration filesPSModule Settings- PSModule-specific configuration (Module type only)gitattributes- Git attributes filegitignore- Git ignore patternsLicense- License file- Additional values can be added as new folders are created
Important: The
SubscribeTovalues are organization-wide, but not all values are available for all types. For example,PSModule Settingsonly exists under theModuletype. If a repository subscribes to a selection that doesn't exist for its type, the workflow will log a warning and skip that selection.
Repositories self-manage their subscriptions by setting these custom property values.
A scheduled GitHub Actions workflow runs daily and:
- Discovers available file sets from the
Repos/directory structure - Queries all organization repositories for their subscription preferences
- For each subscribing repository:
- Clones the repository
- Copies files from the subscribed file sets
- Detects changes using git
- Creates or updates a pull request if changes are detected
- Outputs a summary of actions taken
When changes are detected, the workflow creates a pull request with:
- Title:
⚙️ [Maintenance]: Sync managed files - Label:
NoRelease - Branch:
managed-files/update - Description: Explains that files are centrally managed
If a PR already exists from a previous sync, the workflow updates it by force-pushing to the existing branch.
Distributor/
├── Repos/ # File sets organized by type and selection
│ ├── Module/ # Files for PowerShell modules
│ │ ├── Custom Instructions/ # Copilot instructions
│ │ ├── Linter Settings/ # Linter configurations
│ │ ├── PSModule Settings/ # PSModule-specific configs
│ │ ├── gitattributes/ # Git attributes (.gitattributes file)
│ │ ├── gitignore/ # Git ignore patterns (.gitignore file)
│ │ └── License/ # License file
│ ├── Action/ # Files for GitHub Actions
│ │ ├── Custom Instructions/
│ │ ├── gitattributes/
│ │ ├── gitignore/
│ │ └── License/
│ ├── Template/ # Files for repository templates
│ └── Workflow/ # Files for reusable workflows
├── scripts/
│ └── Sync-Files.ps1 # Main sync script
└── .github/
└── workflows/
└── sync-files.yml # Scheduled workflow
Note: Folder names must match the custom property values exactly. For git-specific files like
.gitattributesand.gitignore, the folders are named without the leading dot (gitattributes,gitignore) to avoid conflicts with Git's special handling of these filenames.
To add a new file set:
- Add the selection value to the
SubscribeTocustom property definition in the organization settings - Create a new folder under the appropriate type:
Repos/{Type}/{SelectionName}/ - Add files to the folder, organizing them as they should appear in target repositories
- Commit and push the changes
- The next scheduled sync will distribute these files to subscribing repositories
# 1. Add "CODEOWNERS" to the SubscribeTo custom property in GitHub organization settings
# 2. Create the folder structure
mkdir -p "Repos/Module/CODEOWNERS/.github"
# 3. Add the CODEOWNERS file
cat > "Repos/Module/CODEOWNERS/.github/CODEOWNERS" << 'EOF'
# Default owners for everything
* @PSModule/maintainers
# Specific paths
/.github/ @PSModule/infrastructure
EOF
# 4. Commit and push
git add Repos/Module/CODEOWNERS/
git commit -m "Add CODEOWNERS file set for modules"
git pushRepository owners can subscribe to file sets by setting their repository's custom properties:
- Go to the repository settings
- Navigate to the Custom properties section
- Set the Type property (e.g., "Module")
- Select one or more SubscribeTo values (e.g., "Linter Settings", "License")
- Save the changes
On the next scheduled sync (or manual trigger), the repository will receive a pull request with the selected files.
- The sync process creates new files and overwrites existing files
- Files are forcefully synchronized to match the source
- Local changes to managed files will be overwritten
- Files are never deleted from target repositories
- If a file is removed from a file set, the previously synced copy remains in target repos but is no longer managed
- Manual cleanup is required if you want to remove files from target repositories
- The workflow only creates PRs when git detects actual changes
- Repositories already in sync are skipped (no empty PRs)
- Only changed files are included in commits
- If a
managed-files/updatebranch already exists, the workflow force-pushes to update it - This updates the existing PR rather than creating duplicates
- Review and merge the PR to complete the sync
The sync workflow runs:
- Daily at 06:00 UTC (scheduled via cron)
- Manually via workflow_dispatch in the GitHub Actions UI
The workflow uses the PSModule's Custo GitHub App with the following permissions:
| Permission | Access | Purpose |
|---|---|---|
contents |
Write | Clone repos, push branches |
pull_requests |
Write | Create PRs, apply labels |
repository_custom_properties |
Read | Read subscription preferences |
metadata |
Read | Repository information |
The following secrets must be configured in this repository:
CUSTO_BOT_CLIENT_ID: The GitHub App's client IDCUSTO_BOT_PRIVATE_KEY: The GitHub App's private key (PEM format)
- Verify the repository has both
TypeandSubscribeTocustom properties set - Check that the
Typevalue matches a folder underRepos/ - Check that each
SubscribeTovalue has a corresponding folder underRepos/{Type}/ - Review the workflow logs for warnings or errors
- Check if the repository already has an open PR from the
managed-files/updatebranch - Verify that the source files in
Repos/have actually changed - Check the workflow logs for the repository in question
- Review the workflow run logs in the Actions tab
- Check that the GitHub App credentials are valid
- Verify that the GitHub App has the required permissions
- Ensure the GitHub App is installed on the target repositories
To test changes locally:
# Install the GitHub PowerShell module
Install-Module -Name GitHub -Force
# Authenticate (using a PAT for local testing)
$env:GITHUB_TOKEN = 'your-pat-here'
Connect-GitHub
# Run the sync script
./scripts/Sync-Files.ps1MIT License - See LICENSE file for details