testing: initial commit with port from buroa and significant changes

This commit is contained in:
Tommy 2024-10-12 10:01:23 +02:00
commit 431d50d930
Signed by: tommy
SSH key fingerprint: SHA256:1LWgQT3QPHIT29plS8jjXc3S1FcE/4oGvsx3Efxs6Uc
14 changed files with 793 additions and 0 deletions

View file

@ -0,0 +1,26 @@
import os
import json
import yaml
def json_to_yaml(subdir, file):
obj = None
json_file = os.path.join(subdir, file)
with open(json_file) as f:
obj = json.load(f)
yaml_file = os.path.join(subdir, "metadata.yaml")
with open(yaml_file, "w") as f:
yaml.dump(obj, f)
os.remove(json_file)
if __name__ == "__main__":
for subdir, dirs, files in os.walk("./apps"):
for f in files:
if f != "metadata.json":
continue
json_to_yaml(subdir, f)

View file

@ -0,0 +1,222 @@
#!/usr/bin/env python3
import importlib.util
import sys
import os
import json
import yaml
import requests
from os.path import isfile
from subprocess import check_output
from packaging.version import Version
repo_owner = os.environ.get("REPO_OWNER", os.environ.get("GITHUB_REPOSITORY_OWNER"))
TESTABLE_PLATFORMS = ["linux/amd64"]
def load_metadata_file_yaml(file_path):
with open(file_path, "r") as f:
return yaml.safe_load(f)
def load_metadata_file_json(file_path):
with open(file_path, "r") as f:
return json.load(f)
def get_latest_version_py(latest_py_path, channel_name):
spec = importlib.util.spec_from_file_location("latest", latest_py_path)
latest = importlib.util.module_from_spec(spec)
sys.modules["latest"] = latest
spec.loader.exec_module(latest)
return latest.get_latest(channel_name)
def get_latest_version_sh(latest_sh_path, channel_name):
out = check_output([latest_sh_path, channel_name])
return out.decode("utf-8").strip()
def get_latest_version(subdir, channel_name):
ci_dir = os.path.join(subdir, "ci")
if os.path.isfile(os.path.join(ci_dir, "latest.py")):
return get_latest_version_py(os.path.join(ci_dir, "latest.py"), channel_name)
elif os.path.isfile(os.path.join(ci_dir, "latest.sh")):
return get_latest_version_sh(os.path.join(ci_dir, "latest.sh"), channel_name)
elif os.path.isfile(os.path.join(subdir, channel_name, "latest.py")):
return get_latest_version_py(
os.path.join(subdir, channel_name, "latest.py"), channel_name
)
elif os.path.isfile(os.path.join(subdir, channel_name, "latest.sh")):
return get_latest_version_sh(
os.path.join(subdir, channel_name, "latest.sh"), channel_name
)
return None
def get_published_version(image_name):
r = requests.get(
f"https://api.github.com/users/{repo_owner}/packages/container/{image_name}/versions",
headers={
"Accept": "application/vnd.github.v3+json",
"Authorization": "token " + os.environ["TOKEN"],
},
)
if r.status_code != 200:
return None
data = json.loads(r.text)
for image in data:
tags = image["metadata"]["container"]["tags"]
if "rolling" in tags:
tags.remove("rolling")
return max(tags, key=len)
def get_image_metadata(subdir, meta, forRelease=False, force=False, channels=None):
imagesToBuild = {"images": [], "imagePlatforms": []}
if channels is None:
channels = meta["channels"]
else:
channels = [
channel for channel in meta["channels"] if channel["name"] in channels
]
for channel in channels:
version = get_latest_version(subdir, channel["name"])
if not version:
continue
# Image Name
toBuild = {}
if channel.get("stable", False):
toBuild["name"] = meta["app"]
else:
toBuild["name"] = "-".join([meta["app"], channel["name"]])
# Skip if latest version already published
if not force:
published = get_published_version(toBuild["name"])
if published is not None and published == version:
continue
toBuild["published_version"] = published
toBuild["version"] = version
# Image Tags
toBuild["tags"] = ["rolling", version]
if meta.get("semver", False):
parts = version.split(".")[:-1]
while len(parts) > 0:
toBuild["tags"].append(".".join(parts))
parts = parts[:-1]
# Platform Metadata
for platform in channel["platforms"]:
if platform not in TESTABLE_PLATFORMS and not forRelease:
continue
toBuild.setdefault("platforms", []).append(platform)
target_os = platform.split("/")[0]
target_arch = platform.split("/")[1]
platformToBuild = {}
platformToBuild["name"] = toBuild["name"]
platformToBuild["platform"] = platform
platformToBuild["target_os"] = target_os
platformToBuild["target_arch"] = target_arch
platformToBuild["version"] = version
platformToBuild["channel"] = channel["name"]
platformToBuild["label_type"] = "org.opencontainers.image"
if isfile(os.path.join(subdir, channel["name"], "Dockerfile")):
platformToBuild["dockerfile"] = os.path.join(
subdir, channel["name"], "Dockerfile"
)
platformToBuild["context"] = os.path.join(subdir, channel["name"])
platformToBuild["goss_config"] = os.path.join(
subdir, channel["name"], "goss.yaml"
)
else:
platformToBuild["dockerfile"] = os.path.join(subdir, "Dockerfile")
platformToBuild["context"] = subdir
platformToBuild["goss_config"] = os.path.join(subdir, "ci", "goss.yaml")
platformToBuild["goss_args"] = (
"tail -f /dev/null"
if channel["tests"].get("type", "web") == "cli"
else ""
)
platformToBuild["tests_enabled"] = (
channel["tests"]["enabled"] and platform in TESTABLE_PLATFORMS
)
imagesToBuild["imagePlatforms"].append(platformToBuild)
imagesToBuild["images"].append(toBuild)
return imagesToBuild
if __name__ == "__main__":
apps = sys.argv[1]
forRelease = sys.argv[2] == "true"
force = sys.argv[3] == "true"
imagesToBuild = {"images": [], "imagePlatforms": []}
if apps != "all":
channels = None
apps = apps.split(",")
if len(sys.argv) == 5:
channels = sys.argv[4].split(",")
for app in apps:
if not os.path.exists(os.path.join("./apps", app)):
print(f'App "{app}" not found')
exit(1)
meta = None
if os.path.isfile(os.path.join("./apps", app, "metadata.yaml")):
meta = load_metadata_file_yaml(
os.path.join("./apps", app, "metadata.yaml")
)
elif os.path.isfile(os.path.join("./apps", app, "metadata.json")):
meta = load_metadata_file_json(
os.path.join("./apps", app, "metadata.json")
)
imageToBuild = get_image_metadata(
os.path.join("./apps", app),
meta,
forRelease,
force=force,
channels=channels,
)
if imageToBuild is not None:
imagesToBuild["images"].extend(imageToBuild["images"])
imagesToBuild["imagePlatforms"].extend(imageToBuild["imagePlatforms"])
else:
for subdir, dirs, files in os.walk("./apps"):
for file in files:
meta = None
if file == "metadata.yaml":
meta = load_metadata_file_yaml(os.path.join(subdir, file))
elif file == "metadata.json":
meta = load_metadata_file_json(os.path.join(subdir, file))
else:
continue
if meta is not None:
imageToBuild = get_image_metadata(
subdir, meta, forRelease, force=force
)
if imageToBuild is not None:
imagesToBuild["images"].extend(imageToBuild["images"])
imagesToBuild["imagePlatforms"].extend(
imageToBuild["imagePlatforms"]
)
print(json.dumps(imagesToBuild))

View file

@ -0,0 +1,67 @@
#!/usr/bin/env python3
import os
import yaml
import logging
from jinja2 import Environment, PackageLoader, select_autoescape
logging.basicConfig(level=logging.INFO)
repo_owner = os.getenv("REPO_OWNER") or os.getenv(
"GITHUB_REPOSITORY_OWNER", "default_owner"
)
repo_name = os.getenv("REPO_NAME") or os.getenv("GITHUB_REPOSITORY", "default_repo")
env = Environment(loader=PackageLoader("render-readme"), autoescape=select_autoescape())
def load_metadata(file_path):
try:
with open(file_path, "r") as f:
return yaml.safe_load(f)
except yaml.YAMLError as e:
logging.error(f"Error loading YAML file {file_path}: {e}")
except FileNotFoundError:
logging.error(f"File {file_path} not found.")
return None
def process_metadata(apps_dir):
app_images = []
for subdir, _, files in os.walk(apps_dir):
if "metadata.yaml" not in files:
continue # Skip if metadata file not found
meta = load_metadata(os.path.join(subdir, "metadata.yaml"))
if not meta:
continue # Skip if metadata couldn't be loaded
# Iterate through the channels and build image metadata
for channel in meta.get("channels", []):
name = (
meta["app"]
if channel.get("stable", False)
else f"{meta['app']}-{channel['name']}"
)
image = {
"name": name,
"channel": channel["name"],
"html_url": f"https://code.252.no/{repo_owner}/pkgs/container/{name}",
"owner": repo_owner,
}
app_images.append(image)
logging.info(f"Added image {name} from channel {channel['name']}")
return app_images
if __name__ == "__main__":
apps_dir = "./apps"
app_images = process_metadata(apps_dir)
try:
template = env.get_template("README.md.j2")
with open("./README.md", "w") as f:
f.write(template.render(app_images=app_images))
logging.info("README.md successfully generated.")
except Exception as e:
logging.error(f"Error rendering template: {e}")

View file

@ -0,0 +1,4 @@
requests
pyyaml
packaging
jinja2

View file

@ -0,0 +1,121 @@
#+DATE: 2024-10-12
#+OPTIONS: toc:nil
#+macro: issue [[https://code.252.no/tommy/containers/issues/$1][issue #$1]]
#+macro: pr [[https://code.252.no/tommy/containers/pulls/$1][PR #$1]]
#+export_file_name: kagi
#+property: header-args:elisp :results none :exports code
#+BEGIN_EXPORT html
<div align="center">
<h1>Container Collection</h1>
_Containers for Kubernetes deployment_
<img src="https://code.252.no/tommy/containers/raw/branch/main/assets/macchiato-palette.png" width="600" align="center"/>
<p></p>
<a href="https://nixos.wiki/wiki/Flakes" target="_blank">
<img alt="Nix Flakes Ready" src="https://img.shields.io/static/v1?logo=nixos&logoColor=d8dee9&label=Nix%20Flakes&labelColor=5e81ac&message=Ready&color=d8dee9&style=for-the-badge">
</a>
<p></p>
</div>
<hr/>
#+END_EXPORT
** Available Images
| Container | Channel | Image |
|-------------------------------------------+---------------------+------------------------------------------------|
{% for image in app_images | sort(attribute="name") -%}
|[[{{ image.html_url }}][{{ image.name }}]] | {{ image.channel }} | code.252.no/{{ image.owner }}/{{ image.name }} |
{% endfor %}
** Container Rules
Containers in this project should be useful in Kubernetes. They will be:
- [[https://semver.org/][semantically versioned]]
- [[https://rootlesscontaine.rs/][rootless]]
- logging to stdout in JSON format if possible
- using [[https://testdriven.io/tips/59de3279-4a2d-4556-9cd0-b444249ed31e/][one process per container]]
- having no [[https://github.com/just-containers/s6-overlay][s6-overlay]]
- built on [[https://hub.docker.com/_/alpine][Alpine]]
- [[https://glossary.cncf.io/immutable-infrastructure/][immutable]]
- do no monkey business after deployed
Additionally I may in the future support:
- [[https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/][multiple architecture]]. For now containers are
generated for amd64
## Tag immutability
Instead of using immutable version tags as seen on e.g. [[https://fleet.linuxserver.io/][linuxserver.io]] we
use [[https://www.letsdebug.it/post/41-calendar-versioning/][calver]] and sha256-digests.
If pinning an image to the sha256 digest, tools like [Renovate](https://github.com/renovatebot/renovate) support
updating the container on a digest or application version change.
The schema used is: =YYYY.MM.Minor@sha256:digest=. This is not as pretty, but functional and immutable. Examples:
| Container | Immutable |
|------------------------------------------------------------------------+-----------|
| =code.252.no/tommy/containers/forgejo-runner:v24.10.1= | ❌ |
| =code.252.no/tommy/containers/forgejo-runner:v24.10.1@sha256:1234...= | ✅ |
** Kubernetes Pod Security Standard
In Kubernetes we assume that you have pod-security.kubernetes.io/enforce set to
[[https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted][restricted]]. There may be
some exceptions to this if the container actually requires more privileges.
E.g. for the =forgejo-runner=, which runs as user ID =1000=, this means that the following settings should be
used for the pod (all containers in a pod):
#+begin_src yaml
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
#+end_src
For a container this means:
#+begin_src yaml
spec:
[...]
containers:
- name: forgejo-runner
[...]
securityContext:
runAsUser: 1001
capabilities:
drop: ["ALL"]
privileged: false
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
#+end_src
** Configuration volume
For applications that need to have persistent configuration data the config volume is hardcoded to `/config`
inside the container.
** Deprecations
Containers here can be **deprecated** at any point, this could be for any reason described below.
1. The upstream application is **no longer actively developed**
2. The upstream application has an **official upstream container** that follows closely to the mission statement described here
3. The upstream application has been **replaced with a better alternative**
4. The **maintenance burden** of keeping the container here **is too bothersome**
** Credits
The structure of this repo was inspired and partially ripped off from
[Buroa@github](https://github.com/buroa/containers/tree/master).

View file

@ -0,0 +1,47 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "Render Readme"
on:
workflow_call:
secrets:
BOT_APP_ID:
description: The App ID of the GitHub App
required: true
BOT_APP_PRIVATE_KEY:
description: The private key of the GitHub App
required: true
jobs:
render-readme:
name: Render README
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: "${{ secrets.GITHUB_TOKEN }}"
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.x
cache: pip
- name: Install Python Requirements
shell: bash
run: pip install -r ./.forgejo/scripts/requirements.txt && pip freeze
- name: Render README
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
shell: bash
run: python ./.github/scripts/render-readme.py
- name: Commit Changes
shell: bash
run: |
git config --global user.name "forgejo-workflow"
git config --global user.email "tommy+forgejo@252.no"
git add ./README.org
git commit -m "chore: render README.org" || echo "No changes to commit"
git push origin || echo "No changes to push"

202
LICENSE Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 @ Tommy Skaug, tommy@252.no
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

20
Taskfile.yaml Normal file
View file

@ -0,0 +1,20 @@
version: "3"
vars:
LABELS_CONFIG_FILE: '{{.ROOT_DIR}}/.github/labels.yaml'
tasks:
default:
cmd: task -l
silent: true
append-app-labels:
desc: Append app labels to the labels config file
cmds:
- for: {var: apps}
cmd: |
yq -i '. += [{"name": "app/{{.ITEM}}", "color": "0e8a16"}]' {{.LABELS_CONFIG_FILE}}
vars:
apps:
sh: for dir in {{.ROOT_DIR}}/apps/*/; do basename "${dir}"; done
silent: true

View file

@ -0,0 +1,47 @@
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.21-alpine3.19 as build-env
#
# Transparently cross compile for the target platform
#
COPY --from=xx / /
ARG TARGETPLATFORM
RUN apk --no-cache add clang lld
RUN xx-apk --no-cache add gcc musl-dev
RUN xx-go --wrap
# Do not remove `git` here, it is required for getting runner version when executing `make build`
RUN apk add --no-cache build-base git
COPY . /srv
WORKDIR /srv
RUN make clean && make build
FROM code.forgejo.org/oci/alpine:3.19
ARG RELEASE_VERSION
RUN apk add --no-cache git bash
COPY --from=build-env /srv/forgejo-runner /bin/forgejo-runner
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \
org.opencontainers.image.url="https://forgejo.org" \
org.opencontainers.image.documentation="https://forgejo.org/docs/latest/admin/actions/#forgejo-runner" \
org.opencontainers.image.source="https://code.forgejo.org/forgejo/runner" \
org.opencontainers.image.version="${RELEASE_VERSION}" \
org.opencontainers.image.vendor="Forgejo" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="Forgejo Runner" \
org.opencontainers.image.description="A runner for Forgejo Actions."
ENV HOME=/data
USER 1000:1000
WORKDIR /data
VOLUME ["/data"]
CMD ["/bin/forgejo-runner"]

View file

@ -0,0 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/goss-org/goss/master/docs/schema.yaml
file:
/usr/bin/git:
exists: true

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
version=$(curl -sX GET "https://api.github.com/repos/actions/runner/releases/latest" | jq --raw-output '.tag_name')
version="${version#*v}"
version="${version#*release-}"
printf "%s" "${version}"

View file

@ -0,0 +1,9 @@
app: forgejo-runner
versioning: calver
channels:
- name: stable
platforms: ["linux/amd64"]
stable: false
tests:
enabled: true
type: cli

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

19
metadata.rules.cue Normal file
View file

@ -0,0 +1,19 @@
#Spec: {
app: #AppName
semver?: bool
channels: [...#Channels]
}
#Channels: {
name: #ChannelName
platforms: [...#Platforms]
stable: bool
tests: {
enabled: bool
type?: =~"^(cli|web)$"
}
}
#AppName: string & !="" & =~"^[a-zA-Z0-9_-]+$"
#ChannelName: string & !="" & =~"^[a-zA-Z0-9._-]+$"
#Platforms: "linux/amd64" | "linux/arm64"