Skip to content

feat: Add Github action artifact download support#816

Merged
itzg merged 25 commits into
itzg:mainfrom
harrisonablack:feat/githubArtifactDownload
Jul 20, 2026
Merged

feat: Add Github action artifact download support#816
itzg merged 25 commits into
itzg:mainfrom
harrisonablack:feat/githubArtifactDownload

Conversation

@harrisonablack

Copy link
Copy Markdown
Contributor

Add sub-command under GithubCommands.java to allow downloading of specific Github action artifacts

refs: #810

@harrisonablack

harrisonablack commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@itzg @SgtMate

You can either pass a specific workflow run id, or a workflow name, along with organisation/repository.

Command then queries actions, selecting either the latest succesful action for a specific workflow, or the already provided workflow id. Then pattern matches against artifacts and downloads the matching artifact.

Usage: mc-image-helper github download-artifact [-h] [--overwrite] [--unzip]
       --artifact-pattern=<artifactPattern> [-o=<outputDirectory>]
       (--workflow=ID|FILE | --run-id=ID) [[--use-http2] [--wiretap]] org/repo
Download an artifact from a successful GitHub Actions workflow
      org/repo
      --artifact-pattern=<artifactPattern>
                           Regular expression that must match exactly one
                             artifact name
  -h, --help
  -o, --output-directory=<outputDirectory>
                           Output directory of downloaded artifact
      --overwrite          Overwrite existing files when extracing zip
      --run-id=ID          Query this specific workflow run to download an
                             artifact
      --unzip              Extract the downloaded artifact
      --use-http2          Whether to use HTTP/2.
                           Default: true
                           Env: FETCH_USE_HTTP2
      --wiretap            Whether to enable Reactor Netty wiretap logging.
                             Make sure to set logging level to trace.
                           Default: false
                           Env: FETCH_WIRETAP
      --workflow=ID|FILE   Query this workflow for the latest successful run to
                             download an artifact

For example, with @SgtMate use case, the below queries the latest successful daily build, downloading the Java17 server arifact, and extracting it to ./gtnh

GH_TOKEN="$(gh auth token)" ./gradlew run --args="github download-artifact --workflow daily-modpack-build.yml --name-pattern='GTNH-daily-\d{4}-\d{2}-\d{2}\+\d+-server-java17-25\.zip' -o=./gtnh --unzip GTNewHorizons/DreamAssemblerXXL"

Github requires a token to be passed when downloading Artifacts
https://docs.github.com/en/rest/actions/artifacts?apiVersion=2026-03-10

@harrisonablack
harrisonablack marked this pull request as ready for review July 17, 2026 05:03
@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

this looks great! this is exactly what i need. one thing i want to add tho is the --output-filename option (from the get command)

@harrisonablack

harrisonablack commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

this looks great! this is exactly what i need. one thing i want to add tho is the --output-filename option (from the get command)

When you want to change output name, do you want the file still as a zip i assume? And the zip renamed? Or whatever format the Artifact is

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

So what I need is for the command to return the filename that was downloaded. (independent of if its unpacked or not).
--output-filename Output the resulting filename

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

This looks great @harrisonablack and I like @SgtMate 's request.

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

another question, just so i understand. is it correct, that the gh token is not handed to the command directly? so i need to set it in env?

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

The token can be passed either way:

    @Option(names = "--token", defaultValue = "${env:GH_TOKEN}",
        description = "An access token for GitHub to elevate rate limit vs anonymous access"
    )

however, within itzg/minecraft-server the expectation is that the user has set the container env var to allow for token access across the whole startup.

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

@itzg ok get it, then i will adhere to the existing processes.

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Wait, that's bit of a usability problem. Users installing GTNH aren't expecting to need a GitHub token.

@harrisonablack are you certain the APIs used need authenticated access?

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

@itzg thats fine, I'll cleary state this in the addition to the documentation.

@harrisonablack

harrisonablack commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Wait, that's bit of a usability problem. Users installing GTNH aren't expecting to need a GitHub token.

@harrisonablack are you certain the APIs used need authenticated access?

Yes, when i remove the null token check, and don't set env, command fails with a 401.

I believe its only the Artifact download endpoint that requires auth

[mc-image-helper] 23:18:25.773 ERROR : 'download-artifact' command failed. Version is ???: FailedRequestException: HTTP request of https://api.github.com/repos/GTNewHorizons/DreamAssemblerXXL/actions/artifacts/8398696458/zip failed with 401 Unauthorized: Trying to retrieve file
./gradlew run --args="github download-artifact --workflow daily-modpack-build.yml --artifact-pattern='GTNH-daily-\d{4}-\d{2}-\d{2}\+\d+-server-java17-25\.zip' -o=./gtnh --unzip GTNewHorizons/DreamAssemblerXXL"

@harrisonablack

Copy link
Copy Markdown
Contributor Author

https://docs.github.com/en/rest/actions/artifacts?apiVersion=2026-03-10#download-an-artifact

The fine-grained token must have the following permission set:

  • 'Actions' repository permissions (read)

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

another thing i need to add to my request is some kind of dry-run functionality. where only the resulting download candidate is returned without downloading it. I need this to check if an update is even required once the container is restarted. Currently im just comparing the old and new filename. This would greatly help in reducing unneeded downloads.

@harrisonablack you think you can implement this with githubs given api?

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

It's almost like it needs a "skip existing" like the get command, but with a 3-way outcome

  • New file was available, here's its name
  • Existing file was already present
  • Something bad happened

@harrisonablack

harrisonablack commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

another thing i need to add to my request is some kind of dry-run functionality. where only the resulting download candidate is returned without downloading it. I need this to check if an update is even required once the container is restarted. Currently im just comparing the old and new filename. This would greatly help in reducing unneeded downloads.

@harrisonablack you think you can implement this with githubs given api?

@SgtMate My thought process is yes, but what are we trying to do? What level of automation of the process do we want to reach?

If you want to automate updating / installing daily builds of GTNH, i would lean towards a manifest similar to Paper, where when the server is installed, a file is created that states which version is installed.

Then when we go to restart the container, we can check for a new build and can compare artifact names / dates before downloading.

If we then automate isntalling / updating, my next questions are then GTNH specific; which files are safe to replace when updating a server, and which files do we need to keep? I'm not overly familiar with Modpacks / Modpack servers.

I would lean more towards implementing a install-gtnh command in mc-image-helper to automate this process.

But i digress, yes, i can add another function / command argument to return the name of the artifact only

@harrisonablack

harrisonablack commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

It's almost like it needs a "skip existing" like the get command, but with a 3-way outcome

  • New file was available, here's its name
  • Existing file was already present
  • Something bad happened

I did add an --overwrite flag, which when set overwrites already existing files during the unzip. Default behaviour is to skip existing files

… exists

Previously if the output directory already existed, the log.error would
be triggered, however proceces failed to create directory as directory
already existed
@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

I would lean more towards implementing a install-gtnh command in mc-image-helper to automate this process.

I gently suggested that too 😉

itzg/docker-minecraft-server#4141 (comment)

Seriously though, @SgtMate maybe you could provide some pseudo code or a draft PR to show where the new command fits in and what input/output would be involved in conditions.

@harrisonablack

Copy link
Copy Markdown
Contributor Author

another thing i need to add to my request is some kind of dry-run functionality. where only the resulting download candidate is returned without downloading it. I need this to check if an update is even required once the container is restarted. Currently im just comparing the old and new filename. This would greatly help in reducing unneeded downloads.

@harrisonablack you think you can implement this with githubs given api?

@SgtMate As well, i'm assuming you want some machine readable format for the output? Or is human readable fine?

I would look at using ResultsFileWriter. The tool gives docker-minecraft-server a list of Enviroment variables to set. We could then create a command, mc-image-helper install-gtnh --check-updates which would return a ResultsFile containing a env var stating the output; GTNH_UPDATES=TRUE or something similar

Previously, if you passed --no-download, the command would exit stating
token required, however token isn't required to query the data, only to
download
@harrisonablack

Copy link
Copy Markdown
Contributor Author

@itzg Added two arguments, --print-artifact and --no-download. Currently --print-artifact logs artifact name using a sl4j log

if (printArtifact) {
    log.info("Resolved artifact {}", candidate.block().getName());
}

For example, running

./gradlew run --args="github download-artifact --workflow daily-modpack-build.yml --artifact-pattern='GTNH-daily-\d{4}-\d{2}-\d{2}\+\d+-server-java17-25\.zip' --print-artifact --no-download GTNewHorizons/DreamAssemblerXXL"

returns

[mc-image-helper] 02:58:36.311 INFO  : Resolved artifact GTNH-daily-2026-07-17+628-server-java17-25.zip

Bool checks would error as booleans had null value, providing default
false value
@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

I would like it to be consistent with https://github.com/itzg/mc-image-helper#get where it's --skip-existing and --output-filename.

For --output-filename it needs to be a System.out.println so scripts can consume the output of the call like:

file=$(mc-image-helper github download-artifact --output-filename ...)

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

...unless @SgtMate you had a different intention with the skipping of download or not.

Changed --print-artifact to --output-filename to maintain consistency,
aswell as used System.out.println
@harrisonablack

Copy link
Copy Markdown
Contributor Author

@itzg

Fixed the print command.

My only concern with --skip-existing is how will it be used and when?

For the GTNH instance, we will always modify the downloaded artifact to use it, unzip it for example. Then its not an easy "does this file already exist?", it comes back to the same thought of "Do we need a manifest"?

Otherwise if the thought of --skip-existing was only, "does this exact file already exist in the output dir?", thats easily implemented and am happy to do so!

@itzg

itzg commented Jul 17, 2026

Copy link
Copy Markdown
Owner

That'll be for @SgtMate to answer 😄

@SgtMate

SgtMate commented Jul 17, 2026

Copy link
Copy Markdown

oh i see I`ve cause some discussions. Ill try to adress things one by one.

Seriously though, @SgtMate maybe you could provide some pseudo code or a draft PR to show where the new command fits in and what input/output would be involved in conditions.

I simply do a comparison of filenames from the downloaded zip file, if the name matches with the last version installed (stored in a file) the installation is skipped, if it does not match the update is triggered and if the file is missing a fresh install is triggered.

if [[ ! -f /data/.gtnh-version || "$(basename "$gtnh_download_path")" != "$(cat /data/.gtnh-version)" ]]; then

Here gtnh_download_path normaly stores the download location for the download and installation step. After download and install the value $gtnh_download is stored in the file which is derived from the download.

    if ! gtnh_download=$(mc-image-helper github download-artifact --output-filename -o=$base_dir --unzip $gtnh_download_options GTNewHorizons/DreamAssemblerXXL); then
      logError "Failed to download Daily Build for GTNH server files."
      exit 1
    fi

I would lean more towards implementing a install-gtnh command in mc-image-helper to automate this process.

Yes I also see this as the best solution in the longway, but as this results in more complex migration from bash scripts over to java I'd like to split this. also because i think the now requested feature will be helpfull for other contributors of the minecraft server project, I try to have this be as usefull to the overall project as possible even tho I'm focussing on the GTNH implementation.


...unless @SgtMate you had a different intention with the skipping of download or not.

Close, --output-filename is important for me. The thing I need in addition is a way to just get the filename of the zip I would get back to compare to the last installed version before download.


I would like it to be consistent with https://github.com/itzg/mc-image-helper#get where it's --skip-existing and --output-filename.

Agree, this also makes things easier to mainain from the script side same functionality same name.


...unless @SgtMate you had a different intention with the skipping of download or not.

No I dont use skip existing.

I hope i was able to clarify all questions! Thanks for all the work you are putting in! ♥️

@harrisonablack

Copy link
Copy Markdown
Contributor Author

@itzg

Given that @SgtMate doesn't require --skip-existing, and his needed --output-filename is implemented, is there anything else you want to see changed or added before merge?

@itzg

itzg commented Jul 18, 2026

Copy link
Copy Markdown
Owner

I'm happy if @SgtMate is happy 😄

I'll proceed with code review from here.

@SgtMate

SgtMate commented Jul 18, 2026

Copy link
Copy Markdown

I think Im happy, if something is still missing Ill adress this in a separate issue. Lets just get things implemented for now and improve incrementally.

@itzg itzg left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor things.

Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java
Comment thread src/main/java/me/itzg/helpers/github/DownloadArtifactCommand.java Outdated
Comment thread src/main/java/me/itzg/helpers/github/GithubClient.java Outdated
@harrisonablack
harrisonablack requested a review from itzg July 20, 2026 07:30

@itzg itzg left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@itzg
itzg merged commit 2aa1367 into itzg:main Jul 20, 2026
1 check passed
@itzg itzg added the enhancement New feature or request label Jul 20, 2026
@itzg

itzg commented Jul 20, 2026

Copy link
Copy Markdown
Owner

@harrisonablack

Copy link
Copy Markdown
Contributor Author

Thanks!

All good! Anything else you want me to work on, just mention me in the issue or PR and I'll be there!

@harrisonablack
harrisonablack deleted the feat/githubArtifactDownload branch July 20, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants