Skip to content

Instantly share code, notes, and snippets.

@anthonyronda
Last active August 24, 2023 14:54
Show Gist options
  • Save anthonyronda/b54a23934c1febc32ad4be192f181f6d to your computer and use it in GitHub Desktop.
Save anthonyronda/b54a23934c1febc32ad4be192f181f6d to your computer and use it in GitHub Desktop.
What VTT Red uses to update all of its clients' Foundry VTT modules with one button push

Foundry VTT release automation workflow

The below yaml file can be included in your private repository in order to publicly host your premium content manifest files on GitHub, and privately host your premium content zip files on AWS S3. This saves me so much time and frustration whenever I need to make a release.

What CAN'T this do

  • You will still have to hand-type the release into the foundryvtt.com admin panel
  • If you are currently paying Foundry VTT for hosting your modules, to my knowledge this cannot be automated by you. You can contact me for competitvely priced automated Foundry module hosting, including premium modules.

Contact Info

This GitHub CI/CD workflow is not for the faint of heart! It involves advanced bash command usage and at least a passing familiarity with GitHub actions in order to adopt for your own needs.

If that doesn't sound like you, please message me and I'll happily look at what your specific needs are. I'm corporat on Discord, or you may also use the email in my GitHub profile page.

How it works

While I realize the yaml file has five steps, the actual work being done by the workflow can be summarized in three steps.

  1. Add or overwrite two lines in every module.json file. Those lines are "protected": true and version: "whatever the version number's supposed to be". In this case, since we're running this anytime we create a new release in our repository, we must name our release tag a valid version number, and that's what gets passed here.
  2. Create a new release on a completely different repository. This is important because these modules are paid content, and so I have to keep this repository private. Once the release is created, I get a URL that accepts a POST request that will allow me to upload all of my manifest files. Even though they're json files, and REST APIs are also JSON, we have to pass them as unencoded binary data instead. Since all the files have the same name, we tell GitHub to name the release assets after the directory they were found in.
  3. Finally, we make zip files and upload to S3. Each top level directory (ie, each module) gets its own zip file. AWS CLI is included in GitHub's default workflow-runner, so we apply the aws sync command which lets us multi-upload files. There are no zip files in the repo itself, only the zip files we just made get passed here. Also notably we're putting the files in a bucket folder named after the version name, which is how we're able to have multiple versions available for my users to download at once.

What are the prerequisites

  • a public repository and a private repository in the same organization/user. The private repository has your modules, the public one should have at least a README
  • a GitHub Personal Access Token with write access on the public repo
  • an AWS IAM user with S3 Put access (I used the S3FullAccess policy) and asymmetric keys
  • a private S3 bucket

What requires a rewrite

**Remember to change all of the uppercase strings/variables to your own strings/variable names.

I am willing to hear more about your needs, because I want to publish some more generalized GitHub Actions on the marketplace. Please contact me if you need help with one of these or something I haven't thought of.

  • You will have to modify the last step for Azure or GCP
  • Systems or worlds? You need to replace the module.json bit with the proper manifest filename
  • If you have a different place where manifests need to go, the middle part will need to be replaced
  • If there are additional manifest fields you need replaced, that will mean rewriting the first step
  • This workflow triggers on release creation, but you can modify it to trigger on a push to main or a webhook trigger
Copyright © 2023 VTT RED LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
name: Create Release on Public Repo and Upload Zips to S3
on:
release:
types:
- created
jobs:
build:
runs-on: ubuntu-latest
steps:
# Standard required steps for a Node.js package
- name: Checkout repository to add to workspace
uses: actions/checkout@v3
# Replace the values of the "protected", and "version" keys in the manifest files
# We need to do this for each module.json file in this repository
- name: Insert release-specific values in all manifest files
id: substitute_manifest_version
run: |
# Find all JSON files and update specific keys
find . -type f -name "module.json" -exec sh -c "jq -M '.protected = true | .version = \"${{ github.event.release.tag_name }}\"' {} > {}.tmp && mv {}.tmp {}" \;
- name: Create Release on public repo
id: create_version_release_on_public
run: |
# Create release on public repo script
owner="YOUR_GITHUB_ORG"
repo="YOUR_PUBLIC_REPO_NAME"
tag_name="${{ github.event.release.tag_name }}"
# Create the release using the GitHub API
response=$(curl -sSL \
-H "Authorization: token ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "'$tag_name'",
"draft": false,
"prerelease": false
}' \
"https://api.github.com/repos/$owner/$repo/releases")
# Get the upload_url from the response
upload_url=$(echo "$response" | jq -r '.upload_url')
# Save it to $GITHUB_OUTPUT (Don't change $GITHUB_OUTPUT)
echo "upload_url=$upload_url" >> $GITHUB_OUTPUT
- name: Upload Release Assets
id: upload_release_assets
run: |
# Upload release assets script
# Strip out the unwanted {?name, label} part from $UPLOAD_URL
# Don't change UPLOAD_URL when modifying
UPLOAD_URL=${UPLOAD_URL%\{?name,label\}}
echo "$UPLOAD_URL"
for file in $(find . -type f -name "module.json"); do
directory=$(dirname "${file}")
asset_name="${directory##*/}.json"
echo "Uploading $file as $asset_name"
curl -sSL \
-H "Authorization: token ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}" \
-H "Content-Type: $(file -b --mime-type "$file")" \
--upload-file "$file" \
"$UPLOAD_URL?name=$asset_name"
echo "Uploaded $file as $asset_name"
done
env:
UPLOAD_URL: ${{ steps.create_version_release_on_public.outputs.upload_url }}
- name: Zip and Upload to S3
run: |
# Iterate over directories and create zip files
for dir in */; do
dir=${dir%*/}
zip -r "$dir.zip" "$dir"
done
# Upload zip files to AWS S3
aws s3 sync . s3://YOUR_S3_BUCKET/${{ github.event.release.tag_name }} --exclude "*" --include "*.zip"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.YOUR_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.YOUR_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: YOUR_REGION
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment