- PHP 100%
|
All checks were successful
Auto Release on version bump / release (push) Successful in 5s
|
||
|---|---|---|
| .forgejo/workflows | ||
| images | ||
| includes | ||
| languages | ||
| .distignore | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| changelog.txt | ||
| eg-forgejo-updater.php | ||
| index.php | ||
| LICENSE | ||
| README.it-IT.md | ||
| README.md | ||
| readme.txt | ||
| uninstall.php | ||
EG Forgejo Updater
🇬🇧 English · 🇮🇹 Italiano
WordPress plugin that installs and updates plugins hosted on your own Forgejo or Gitea instances, one or more at the same time, with access tokens kept in wp-config.php.
Development, releases and issues are on Forgejo. The GitHub repository is a read-only mirror.
Project structure
eg-forgejo-updater/
├── eg-forgejo-updater.php # Bootstrap, plugin header, class includes
├── uninstall.php # Data cleanup on uninstall
├── index.php # Directory listing protection
├── readme.txt # Standard WordPress readme
├── changelog.txt # Changelog of versions before 3.0.0
├── CHANGELOG.md # Development log (not in the package)
├── LICENSE # GPL-2.0-or-later
└── includes/
├── class-eg-forgejo-updater-api.php # Forgejo REST API client and token map
├── class-eg-forgejo-updater.php # WordPress hooks for update detection and install
├── class-eg-forgejo-updater-admin.php # Admin page (Plugins submenu)
└── index.php # Directory listing protection
Requirements
- WordPress 6.0 or later
- PHP 8.0 or later (uses typed properties and named arguments)
- Forgejo or Gitea instances reachable over HTTPS from the WordPress host
- For each managed plugin: a release in the corresponding Forgejo repo is recommended (without releases, updates come from the primary branch)
Installation
- Download
eg-forgejo-updater.zipfrom the latest release and upload it from Dashboard > Plugins > Add New > Upload Plugin - Add
EG_FORGEJO_UPDATER_TOKENStowp-config.php(required for private repos) - Activate the plugin from Dashboard > Plugins
- Plugins with a
Forgejo Plugin URIorGitea Plugin URIheader are detected automatically - To install additional plugins from Forgejo, use the built-in feature described below
Use the release ZIP, not GitHub's "Download ZIP" button: that archive contains the latest commits instead of the latest release, in a folder named
eg-forgejo-updater-main/.
Configuration in wp-config.php
Add one access token per instance before the line /* That's all, stop editing! */:
define( 'EG_FORGEJO_UPDATER_TOKENS', array(
'https://git.example.com' => 'token_for_this_instance',
'https://code.example.org' => 'token_for_the_other_instance',
) );
Keys are the https address of each instance; path, case and trailing slash are ignored, a port other than 443 must be written (https://git.example.com:8443). Entries with an http:// key or an empty token are ignored and reported on the admin page. Public repositories need no entry.
How to create an access token
- Log in to your Forgejo instance with your account
- Go to Settings → Applications → Manage Access Tokens
- Create a new token with the following permissions:
- Repository → Read (to read raw files and download ZIPs)
- Everything else: No access
- Copy the generated token and paste it into
wp-config.php
An administrator account is not required: the account that owns the repositories you need to manage is enough.
Where the tokens go
Each token is sent in the Authorization: token xxx header:
- of the plugin's own Forgejo REST API calls to that instance
- of the download of the package being installed or updated, when it comes from that instance
The match is exact on scheme, host and port: a token is never sent to another instance, not even when a plugin header points elsewhere. The http_request_args filter only attaches it to package URLs chosen by the updater, so requests made by other plugins to the same instance stay anonymous. When a token is attached, redirects are disabled, because WordPress would forward the Authorization header to the redirect target.
Upgrading from 2.x
FORGEJO_ACCESS_TOKEN is no longer read: move its value into EG_FORGEJO_UPDATER_TOKENS before updating, then remove it (the admin page reminds you while it is still defined). FORGEJO_BASE_URL and the short owner/repo header form are no longer used by this plugin; leave the constant in place if another plugin needs it.
Plugin auto-discovery
The plugin detects the plugins to manage by reading the header of the main PHP file. Two headers are supported; the value is always the full repository URL:
/**
* Plugin Name: My Plugin
* Forgejo Plugin URI: https://git.example.com/owner/my-plugin
*/
or:
/**
* Plugin Name: My Plugin
* Gitea Plugin URI: https://git.example.com/owner/my-plugin
*/
Plugins detected automatically cannot be removed from the list (just remove the header to exclude them); the tooltip on the Source column says whether a plugin was detected from its header or installed through the form.
Instances that cannot generate archives
The fallbacks (archive/{tag}.zip for a release without ZIP, archive/{branch}.zip for a repository without releases) depend on the instance generating archives. Some cannot: forgejo.it (Forgejo 15.0.3), for example, answers HTTP 500 to every /archive/ request. The version check still works, so the failure only shows up when the update is downloaded. On such an instance, attach the plugin ZIP to every release.
Deleting a plugin
The last column holds a trash icon for inactive plugins. It opens the standard WordPress confirmation page (plugins.php?action=delete-selected), which lists the files and runs the plugin's uninstall.php: its settings are removed too. Active plugins show a grey icon (deactivate first); EG Forgejo Updater itself and users without the delete_plugins capability get no icon.
Installation date
Under the installed version, the admin table shows when the plugin was installed or last updated on this site, in the site's date format (time in the tooltip). The date is the modification time of the plugin's main file, so it costs no request; it changes if the file is edited by hand, and a plugin copied with its original file dates (rsync -a, git clone) shows the date of the copy's source.
Plugins also listed on WordPress.org
When a managed plugin is published in the WordPress.org directory, the updater steps aside: the "View details" popup and the updates come from WordPress.org. In the admin table the Source column shows WordPress.org and the Repository column links to the plugin's WordPress.org page.
- Detection reads the
update_pluginstransient filled by WordPress's own update check: an entry withid = w.org/plugins/<slug>means the plugin is in the directory. No extra request for plugins that are not. - Slug collisions: WordPress.org matches plugins by folder slug only. The updater steps aside only if the
Contributors:line of the installedreadme.txtshares at least one name with the plugin's contributors on WordPress.org (plugins/info/1.2, cached for one week). Otherwise the plugin stays on Forgejo and the Status column shows "Slug taken on WordPress.org". - Manual switch: the "Use Forgejo" button forces a directory plugin back to Forgejo (stored in
eg_forgejo_updater_channels); "Use WordPress.org" undoes it.
Why in wp-config.php and not in the database
Tokens in wp-config.php stay out of database dumps and backups, cannot be read or changed from the WordPress admin, and do not depend on transients or the object cache.
How update detection works
Full flow
WordPress scheduler (every 12h) or manual action
└── Filter site_transient_update_plugins
└── For each plugin with a Forgejo/Gitea Plugin URI header:
└── get_transient(eg_forgejo_updater_release_{md5(plugin_file)})
├── Cache hit → use cached data
└── Cache miss → call Forgejo API (2 calls)
└── GET /api/v1/repos/{owner}/{repo}/releases/latest
├── Release found → version from tag_name, package = first attached .zip
│ └── GET …/raw/readme.txt?ref={tag}
├── HTTP 404 (no releases) → fallback to the primary branch
│ ├── GET …/raw/{file}.php?ref={branch} → parse "Version:"
│ └── GET …/raw/readme.txt?ref={branch}
└── Other error (401/403, 5xx, network) → no fallback, shown in Status
├── Remote version > installed → inject into $transient->response
└── Equal or lower version → remove from $transient->response
Check frequency
The version-data cache is saved as a WordPress transient with a TTL of 12 hours (CACHE_TTL = 43200). This means:
- The automatic check happens every 12 hours, in sync with the native WordPress cycle
- Going to Dashboard > Updates and clicking "Check again" forces an immediate check
- The "Check for updates now" button on the plugin page explicitly flushes all transients
Version comparison
WordPress internally uses version_compare() (PHP native). The remote version is the tag of the latest release without the leading v (v2.13.0 → 2.13.0). Only for repositories without releases is it read from the Version: header of the main PHP file on the primary branch.
ZIP file handling during the update
Archive download
The plugin downloads the first ZIP attached to the latest release:
https://git.example.com/owner/repo/releases/download/v2.13.0/repo.zip
If the release has no ZIP attached, it uses the tag archive (archive/v2.13.0.zip). For a repository without releases, the archive of the primary branch (archive/main.zip).
Every site receives the same files for a given version: commits pushed to the branch after a release are not distributed until the next release.
The folder-name problem
The folder inside the ZIP does not always match the plugin slug (e.g. eg-fediverso-box/): Forgejo archives use the repository name, and a ZIP attached to an older release may carry the name the repository had back then. WordPress expects the extracted folder to have the same name as the slug.
The plugin handles this via the upgrader_source_selection filter, which renames the extracted folder to the correct slug before WordPress finishes the installation.
Installing a plugin from Forgejo
EG Forgejo Updater lets you install any public plugin hosted on Forgejo directly from WordPress, without downloading ZIPs, without FTP, without manual upload.
How to do it
- Go to Plugins → Forgejo Updater
- In the "Install plugin from Forgejo" section, paste the repository URL
- Click "Verify and continue"
- The plugin runs an automatic checklist:
- Valid URL format
- Recognized Forgejo/Gitea instance
- Reachable repository
- Main PHP file found
Plugin NameandVersionheaders presentForgejo Plugin URIheader present (required for automatic updates)
- If all checks pass, a preview with name, version and author is shown
- Click "Install plugin" to proceed
- Activate the plugin from the plugin list
Once installed, the plugin is automatically detected by the auto-discovery feature and will receive updates like the others.
Adding via header (auto-discovery)
For plugins already installed with the header in the main PHP file:
* Forgejo Plugin URI: https://git.emanuelegori.uno/emanuelegori/repotest
* Primary Branch: main
The plugin is detected automatically when the page is loaded, with no additional configuration.
Forgejo icon in the plugin list
In Plugins > Installed Plugins, every plugin managed by this updater shows an extra link in the meta info row (below the name and description), with the Forgejo logo and a direct link to the repository.
The SVG logo is embedded inline in PHP (get_forgejo_svg()), with no separate asset files and no additional HTTP requests.
In Dashboard > Updates, managed plugins show the icon images/icon.svg, served by your own site: no request leaves for WordPress.org or the repository.
WordPress hooks used
| Hook | Class | Purpose |
|---|---|---|
plugins_loaded |
bootstrap | Class initialization |
site_transient_update_plugins |
Updater | Inject update data into the native WP cycle |
plugins_api |
Updater | "Version details" popup in the dashboard (left to WordPress for directory plugins) |
plugin_row_meta |
Updater | Forgejo icon and repo link in the plugin list |
http_request_args |
Updater | Add the instance token to package downloads chosen by the updater |
upgrader_source_selection |
Updater | Fix the ZIP folder name from zipball |
upgrader_process_complete |
Updater | Cache flush after a completed update |
admin_menu |
Admin | Register submenu under Plugins |
admin_post_eg_forgejo_updater_preview |
Admin | Verify repo URL and compatibility checklist |
admin_post_eg_forgejo_updater_install |
Admin | Install plugin from Forgejo zip |
admin_post_eg_forgejo_updater_delete |
Admin | Remove plugin from the list |
admin_post_eg_forgejo_updater_check |
Admin | Force an immediate update check |
admin_post_eg_forgejo_updater_channel |
Admin | Per-plugin channel: automatic or forced to Forgejo |
admin_notices |
Admin | Feedback notices |
current_screen |
Admin | Footer of the plugin page (admin_footer_text, update_footer) |
Redis Object Cache compatibility
Transients are saved through native WordPress set_transient() / get_transient() / delete_transient() functions. With Redis Object Cache active they are automatically stored in Redis without extra configuration. No direct SQL queries are used.
Publishing a new version
For every new version of a managed plugin:
- Update
Version:in the header of the plugin's main PHP file - Commit and push to the primary branch
- Publish a release on Forgejo with tag
v+ version (e.g.v2.13.0), with the plugin ZIP attached. A CI workflow can do this on every push that changes the version.
Within 12 hours (or immediately by clicking "Check for updates now") WordPress will show the available-update notice. Without releases the plugin keeps updating from the branch.
Updating EG Forgejo Updater itself
This plugin manages itself thanks to the Forgejo Plugin URI header in its own main file. It updates exactly like the other plugins.
License
GPL-2.0-or-later