A statically linked, native, platform agnostic Git-based package manager written in Rust.
- 1. Getting started
- 2. Build
- 3. Authentication
- 4. Best practices
- 5. Package reference notations
- 6. Matching package references
- 7. Working with multiple package repositories
- 8. Logging
- 9. Commands
- 10. CI integration
- 11. FAQ
- Create a git-lfs enabled Git repository, for example a GitHub or GitLab repository.
- Install git-lfs on your local computer.
- Clone the newly created repository on your local computer:
git clone ssh://path.to/my/package-repository.git
cd package-repository- Enable git-lfs tracking for
*.tar.gzfiles:
git lfs track "*.tar.gz"- Add, commit and push
.gitattributes:
git add .gitattributes
git commit .gitattributes -m "Enable git-lfs."
git pushVoilĂ ! You're all set to publish your first package!
In this example, we're going to create a simple hello-world package and publish it.
- Make sure you are at the root of the package repository created in the previous section.
- Create and enter the package directory:
mkdir hello-world && cd hello-world- Create the
hello-world.shscript:
echo "#/bin/sh\necho 'Hello World!'" > hello-world.sh- Create your package archive:
tar -cvzf hello-world.tar.gz hello-world.sh- Add and commit your package archive:
git add hello-world.tar.gz
git commit hello-world.tar.gz -m "Publish hello-world version 1.0"- Tag your package release with a specific version number:
git tag hello-world/1.0- Push your new package:
git push
git push --tagsYour hello-world/1.0 package is now stored in your package repository and can be installed using gpm!
- Download or build
gpm. - Add your package repository to the
gpmsources:
mkdir -p ~/.gpm/sources.list
echo "ssh://path.to/my/package-repository.git" >> ~/.gpm/sources.list- Update the
gpmcache:
gpm update- Install your package:
gpm install hello-world/1.0 --prefix ~/Your hello-world/1.0 package is now installed and you can run it with sh ~/hello-world.sh.
Dependencies:
- OpenSSL
cargo buildDependencies:
- Docker
docker run \
--rm -it \
-v "$(pwd)":/home/rust/src \
-v "/home/${USER}/.cargo":/home/rust/.cargo \
ekidd/rust-musl-builder \
cargo build --release --target x86_64-unknown-linux-muslIf the repository is "public", then no authentication should be required.
Otherwise, for now, only authentication through a passphrase-less SSH private key is supported.
The path to that SSH private key must be set in the GPM_SSH_KEY environment variable.
Commit the new package revision and tag it with the tag ${package}/${version}, where ${package}is the name of your package and ${version} the semver version of the package.
Use the ${package}/${version} shorthand notation (aka "implicit package name in revision").
Example:
gpm install my-package/2.1.0Use the ${package} shorthand notation (aka "latest revision notation").
Example:
gpm install my-packageA package can be referenced using the following notation:
${package}=${revision}
where:
packageis the name of the package (ex:my-package),revisionis a valid Git refspec at which the package archive can be found (ex:refs/heads/master,master,my-tag).
Example: if you have tagged a release of my-package with the tag my-package/2.0, use the reference my-package=my-package/2.0.
For such package reference to be found, you must make sure:
- the corresponding package repository remote is listed in
~/.gpm/sources.list(see Working with multiple package repositories), - the cache has been updated by calling
gpm update.
If therere is no package name explicitely provided and the revision contains a /, then the package name is deduced from the part before the /.
For example, the package reference my-package/2.0 will be interpreted as my-package=my-package/2.0.
If the package reference is not an URI and contains neither / nor =, then gpm assumes:
- the package reference is the package name;
- the package refspec is "master".
Thus, the package reference my-package will be interpreted as my-package=refs/heads/master.
This notation is handy to install the latest revision of a package.
A package can also be referenced using a full Git URI formatted like this:
${remote-uri}#${package}
where:
remote-uriis the full URI to the Git remote,packageis a shorthand or${name}=${revision}package reference.
Example:
ssh://github.com/my/awesome-packages.git#my-package/2.0
In this case, gpm will clone the corresponding Git repository and look for the package there.
gpm will look for the specified package only in the specified repository.
The following section explains how gpm finds the package archive for a package named ${name} at revision ${revision}.
For each available remote:
- Try to find the refspec matching
${revision}:- If
${revision}is a valid refspec and can be found, then it will be used directly. - Otherwise, if
refs/tags/${revision}can be found, it will be used. - Otherwise, if
refs/heads/${revision}can be found it will be used. - Otherwise, skip to the next remote.
- If
- If a valid refspec has been found, reset the repositories to this refspec. Throw an error otherwise.
- If the
${name}/${name}.tar.gzexists at this refspec, use it. Throw an error otherwise. - If
${name}/${name}.tar.gzis a git-lfs link, resolve it. Otherwise, use${name}/${name}.tar.gzdirectly.
Specifying a full package URI might not be practical. It's simpler to specify a package
refspec and let gpm find it. But where should it look for it?
When you specify a package using a refspec, gpm will have to find the proper package
repository. It will look for this refspec in the repositories listed in ~/.gpm/sources.list.
The following command lines will fill sources.list with a few (dummy) package repositories:
echo "ssh://path.to/my/package-repository.git" >> ~/.gpm/sources.list
echo "ssh://path.to/my/another-repository.git" >> ~/.gpm/sources.list
echo "ssh://path.to/my/yet-another-repository.git" >> ~/.gpm/sources.list
# ...After updating sources.list, don't forget to call gmp update to update the cache.
You can then install packages using their refspec.
By default, gpm will echo nothing on stdout.
Logs can be enable by setting the GPM_LOG environment variable to one of the following values:
tracedebuginfowarnerror
Example:
GPM_LOG=info gpm updateLogs can be very verbose. So it's best to keep only the gpm and gitlfs module logs.
For example:
GPM_LOG="gpm=debug,gitlfs=debug" gpm install hello-world/1.0Update the cache to feature the latest revision of each repository listed in ~/.gpm/sources.list.
Example:
# first add at least one remote
echo "ssh://github.com/my/awesome-packages.git" >> ~/.gpm/sources.list
echo "ssh://github.com/my/other-packages.git" >> ~/.gpm/sources.list
# ...
# then you can run an update:
gpm updateClean the cache. The cache is located in ~/.gpm/cache.
Cache can be rebuilt using the update command.
gpm cleanDownload and install a package.
Example:
# install the "app" package at version 2.0 from repository ssh://github.com/my/awesome-packages.git
# in the /var/www/app folder
gpm install ssh://github.com/my/awesome-packages.git#app/2.0 \
--prefix /var/www/app# assuming the repository ssh://github.com/my/awesome-packages.git is in ~/.gpm/sources.list
# and the cache has been updated using `gpm update`
gpm install app/2.0 --prefix /var/www/appDownload a package in the current working directory.
Example:
# install the "app" package at version 2.0 from repository ssh://github.com/my/awesome-packages.git
# in the /var/www/app folder
gpm download ssh://github.com/my/awesome-packages.git#app/2.0 \
--prefix /var/www/app# assuming the repository ssh://github.com/my/awesome-packages.git is in ~/.gpm/sources.list
# and the cache has been updated using `gpm update`
gpm download app/2.0 --prefix /var/www/appHere is a working Bash script to publish a package from Travis CI: script/publish.sh.
Here is a working PowerShell script to publish a package from AppVeyor: script/publish.ps1.
Here is a template to publish a package from GitLab CI:
.gpm_publish_template: &gpm_publish_template
stage: archive
only:
- tags
variables:
script:
- cd ${PACKAGE_ARCHIVE_ROOT} && tar -zcf /tmp/${PACKAGE_NAME}.tar.gz * && cd -
- echo "${PACKAGE_REPOSITORY_KEY}" > /tmp/package-repository-key && chmod 700 /tmp/package-repository-key
- mkdir -p ~/.ssh && echo -e "Host *\n StrictHostKeyChecking no\n IdentityFile /tmp/package-repository-key" > ~/.ssh/config
- GIT_LFS_SKIP_SMUDGE=1 git clone ${PACKAGE_REPOSITORY} /tmp/package-repository
- mkdir -p /tmp/package-repository/${PACKAGE_NAME}
- mv /tmp/${PACKAGE_NAME}.tar.gz /tmp/package-repository/${PACKAGE_NAME}
- cd /tmp/package-repository/${PACKAGE_NAME}
- git config --global user.email "[email protected]"
- git config --global user.name "Your Name"
- git add ${PACKAGE_NAME}.tar.gz
- git commit ${PACKAGE_NAME}.tar.gz -m "Publish ${PACKAGE_NAME} version ${PACKAGE_VERSION}."
- git tag ${PACKAGE_NAME}/${PACKAGE_VERSION}
- git push
- git push --tagsand an example of how to use this template:
gpm-publish:
<<: *gpm_publish_template
variables:
PACKAGE_VERSION: ${CI_COMMIT_TAG} # the version of the package
PACKAGE_REPOSITORY: ssh://my.gitlab.com/my-packages.git # the package repository to publish to
PACKAGE_NAME: ${CI_PROJECT_NAME} # the name of the package
PACKAGE_ARCHIVE_ROOT: ${CI_PROJECT_DIR} # the folder containing the files to put in the package archiveThis template relies on the PACKAGE_REPOSITORY_KEY GitLab CI variable.
It must contain a passphrase-less SSH deploy key (with write permissions) to your package repository.
GPM means "Git-based Package Manager".
The main motivation is to have a platform-agnostic package manager, mainly aimed at distributing binary packages as archives. GPM can be used to leverage any Git repository as a package repository.
Platforms like GitLab and GitHub are then very handy to manage such package archives, permissions, etc...
GPM is also available as an all-in-one static binary. It can be leveraged to download some packages that will be used to bootrasp a more complex provisioing process.
GPM aims at leveraging the Git ecosystem and features.
Git is great to manage revisions. So it's great at managing package versions! For example, Git is also used by the Docker registry to store Docker images.
Git also has a safe and secured native authentication/authorization strategy through SSH. With GitLab, you can safely setup deploy keys to give a read-only access to your packages.
Yes. Cloning a repository full of large binary files can take a lot of time and space. You certainly don't want to checkout all the versions of all your packages everytime you want to install one of them.
That's why you should use git-lfs for your GPM repositories.
Thanks to git-lfs, GPM will download the a actual binary package only when it is required.
Vanilla Git will compress objects. But git-lfs doesn't store objects in the actual Git repository: they are stored "somewhere else".
To make sure we don't use too much disk space/bandwidth "somewhere else", the package archive is stored compressed.