ci(security): no ${{ }} in run:, validate resolved versions, LiteSpeed index over HTTPS
Security review of #30: step outputs derived from the LiteSpeed apt index (fetched over plain HTTP) were spliced into shell via ${{ }} in jobs that hold registry push secrets — a MITM'd "version" would be shell injection. - every run: block in build-push.yaml and lsphp-source-emergency.yaml now receives matrix values, step outputs, inputs and run_id via env: only (incl. the pre-existing php.net / LiteSpeed resolve steps from #24/#29) - resolved versions are regex-checked before GITHUB_OUTPUT: PHP ^[0-9]+\.[0-9]+\.[0-9]+$, deb ^[0-9][0-9A-Za-z.+~:-]*$ - LiteSpeed Packages index fetched over HTTPS (--proto =https); measured rpms.litespeedtech.com serves it (HTTP/2 200) - lsphp-src-build.sh / lsphp-src-overlay.sh validate PHPVER too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83c2ee9c2a
commit
42993f42bd
4 files changed
+82
-32
No files matched your search
@@ -97,19 +97,26 @@ jobs:
|
||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||
- name: Cache-bust value
|
||||
id: bust
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
RUN_ID: ${{ gitea.run_id }}
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# The minimum PHP version this build must ship: php.net's current release for
|
||||
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
|
||||
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
|
||||
- name: Resolve current PHP release from php.net
|
||||
id: phpmin
|
||||
# Values reach the script via env, never via ${{ }} inside run: (a
|
||||
# crafted string would otherwise be shell); outputs are regex-checked.
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
|
||||
v="$PHPVER"; branch="${v:0:1}.${v:1}"
|
||||
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
|
||||
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
|
||||
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
|
||||
[[ "$min" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::php.net returned a malformed version for ${branch}"; exit 1; }
|
||||
echo "php.net current ${branch}: ${min}"
|
||||
echo "min=${min}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -157,19 +164,26 @@ jobs:
|
||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||
- name: Cache-bust value
|
||||
id: bust
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
RUN_ID: ${{ gitea.run_id }}
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# The minimum PHP version this build must ship: php.net's current release for
|
||||
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
|
||||
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
|
||||
- name: Resolve current PHP release from php.net
|
||||
id: phpmin
|
||||
# Values reach the script via env, never via ${{ }} inside run: (a
|
||||
# crafted string would otherwise be shell); outputs are regex-checked.
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
|
||||
v="$PHPVER"; branch="${v:0:1}.${v:1}"
|
||||
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
|
||||
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
|
||||
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
|
||||
[[ "$min" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::php.net returned a malformed version for ${branch}"; exit 1; }
|
||||
echo "php.net current ${branch}: ${min}"
|
||||
echo "min=${min}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -222,7 +236,9 @@ jobs:
|
||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||
- name: Cache-bust value
|
||||
id: bust
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
RUN_ID: ${{ gitea.run_id }}
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the
|
||||
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
|
||||
@@ -230,14 +246,21 @@ jobs:
|
||||
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
||||
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
||||
id: lsphp
|
||||
# HTTPS (rpms.litespeedtech.com serves it; measured 2026-10-21), values via
|
||||
# env, and both outputs regex-checked before they reach GITHUB_OUTPUT.
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
|
||||
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
|
||||
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||
php="${pkg%%-*}"
|
||||
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||
[[ "$php" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '${php}' derived from lsphp${PHPVER} ${pkg}"; exit 1; }
|
||||
echo "lsphp${PHPVER} in repo: $pkg (PHP ${php})"
|
||||
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||
echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT"
|
||||
echo "php=${php}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
@@ -252,12 +275,15 @@ jobs:
|
||||
# the job goes green again by itself once the LiteSpeed repo catches up.
|
||||
# Fail-open if the tag cannot be inspected (first build, registry blip).
|
||||
- name: Do not roll back a newer php.net emergency image
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
NEW_PHP: ${{ steps.lsphp.outputs.php }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ref=repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
|
||||
ref="repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${PHPVER}"
|
||||
src=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.lsphp-source" }}' 2>/dev/null || true)
|
||||
cur=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.php-version" }}' 2>/dev/null || true)
|
||||
new='${{ steps.lsphp.outputs.php }}'
|
||||
new="$NEW_PHP"
|
||||
echo "published $ref: source=${src:-?} php=${cur:-?}; this build: packaged $new"
|
||||
if [ "$src" = "php.net" ] && [ "$cur" != "$new" ] && [ "$(printf '%s\n%s\n' "$cur" "$new" | sort -V | tail -n1)" = "$cur" ]; then
|
||||
echo "::error::$ref is a php.net emergency build of PHP $cur; LiteSpeed still ships $new. Not overwriting it with an older PHP. Re-run lsphp-source-emergency.yaml for a newer fix, or wait for LiteSpeed."
|
||||
@@ -313,7 +339,9 @@ jobs:
|
||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||
- name: Cache-bust value
|
||||
id: bust
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
RUN_ID: ${{ gitea.run_id }}
|
||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the
|
||||
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
|
||||
@@ -321,14 +349,21 @@ jobs:
|
||||
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
||||
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
||||
id: lsphp
|
||||
# HTTPS (rpms.litespeedtech.com serves it; measured 2026-10-21), values via
|
||||
# env, and both outputs regex-checked before they reach GITHUB_OUTPUT.
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
|
||||
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
|
||||
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||
php="${pkg%%-*}"
|
||||
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||
[[ "$php" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '${php}' derived from lsphp${PHPVER} ${pkg}"; exit 1; }
|
||||
echo "lsphp${PHPVER} in repo: $pkg (PHP ${php})"
|
||||
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||
echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT"
|
||||
echo "php=${php}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
@@ -343,12 +378,15 @@ jobs:
|
||||
# the job goes green again by itself once the LiteSpeed repo catches up.
|
||||
# Fail-open if the tag cannot be inspected (first build, registry blip).
|
||||
- name: Do not roll back a newer php.net emergency image
|
||||
env:
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
NEW_PHP: ${{ steps.lsphp.outputs.php }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ref=repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php${{ matrix.phpver }}
|
||||
ref="repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php${PHPVER}"
|
||||
src=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.lsphp-source" }}' 2>/dev/null || true)
|
||||
cur=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.php-version" }}' 2>/dev/null || true)
|
||||
new='${{ steps.lsphp.outputs.php }}'
|
||||
new="$NEW_PHP"
|
||||
echo "published $ref: source=${src:-?} php=${cur:-?}; this build: packaged $new"
|
||||
if [ "$src" = "php.net" ] && [ "$cur" != "$new" ] && [ "$(printf '%s\n%s\n' "$cur" "$new" | sort -V | tail -n1)" = "$cur" ]; then
|
||||
echo "::error::$ref is a php.net emergency build of PHP $cur; LiteSpeed still ships $new. Not overwriting it with an older PHP. Re-run lsphp-source-emergency.yaml for a newer fix, or wait for LiteSpeed."
|
||||
|
||||
@@ -49,6 +49,8 @@ jobs:
|
||||
IN_VERSIONS: ${{ inputs.php_versions }}
|
||||
IN_IMAGES: ${{ inputs.images }}
|
||||
IN_PROD: ${{ format('{0}', inputs.push_production_tags) }}
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
IMG: ${{ matrix.image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$IN_IMAGES" in both|lsphp|litespeed) ;; *) echo "::error::images must be both|lsphp|litespeed, got '$IN_IMAGES'"; exit 1 ;; esac
|
||||
@@ -56,16 +58,17 @@ jobs:
|
||||
v=""
|
||||
for pair in $IN_VERSIONS; do
|
||||
[[ "$pair" =~ ^(8[1-5])=([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "::error::bad php_versions entry '$pair' (want NN=X.Y.Z, NN in 81..85)"; exit 1; }
|
||||
[ "${BASH_REMATCH[1]}" = '${{ matrix.phpver }}' ] && v="${BASH_REMATCH[2]}"
|
||||
[ "${BASH_REMATCH[1]}" = "$PHPVER" ] && v="${BASH_REMATCH[2]}"
|
||||
done
|
||||
echo "prod=$IN_PROD" >> "$GITHUB_OUTPUT"
|
||||
if [ -z "$v" ] || { [ "$IN_IMAGES" != both ] && [ "$IN_IMAGES" != '${{ matrix.image }}' ]; }; then
|
||||
echo "not requested: php${{ matrix.phpver }} / ${{ matrix.image }}"; echo "skip=1" >> "$GITHUB_OUTPUT"; exit 0
|
||||
if [ -z "$v" ] || { [ "$IN_IMAGES" != both ] && [ "$IN_IMAGES" != "$IMG" ]; }; then
|
||||
echo "not requested: php${PHPVER} / ${IMG}"; echo "skip=1" >> "$GITHUB_OUTPUT"; exit 0
|
||||
fi
|
||||
echo "skip=0" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
echo "image_name=cac-${{ matrix.image }}" >> "$GITHUB_OUTPUT"
|
||||
echo "dockerfile=./Dockerfile.${{ matrix.image }}" >> "$GITHUB_OUTPUT"
|
||||
case "$IMG" in lsphp|litespeed) ;; *) echo "::error::unexpected matrix image '$IMG'"; exit 1 ;; esac
|
||||
echo "image_name=cac-${IMG}" >> "$GITHUB_OUTPUT"
|
||||
echo "dockerfile=./Dockerfile.${IMG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout
|
||||
if: steps.t.outputs.skip == '0'
|
||||
@@ -77,26 +80,33 @@ jobs:
|
||||
- name: Resolve php.net release + packaged lsphp
|
||||
if: steps.t.outputs.skip == '0'
|
||||
id: v
|
||||
env:
|
||||
V: ${{ steps.t.outputs.version }}
|
||||
PHPVER: ${{ matrix.phpver }}
|
||||
RUN_ID: ${{ gitea.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v='${{ steps.t.outputs.version }}'
|
||||
v="$V"
|
||||
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed version '$v'"; exit 1; }
|
||||
json=$(curl -fsS --retry 3 --max-time 60 "https://www.php.net/releases/index.php?json&version=$v")
|
||||
sha=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); v=sys.argv[1]; print(next((s["sha256"] for s in d.get("source", []) if s.get("filename")=="php-%s.tar.xz"%v), ""))' "$v")
|
||||
[ -n "$sha" ] || { echo "::error::php.net has no release php-$v.tar.xz (EOL branch? typo?): $json"; exit 1; }
|
||||
[[ "$sha" =~ ^[0-9a-f]{64}$ ]] || { echo "::error::no sha256 for php-$v.tar.xz in the php.net releases JSON"; exit 1; }
|
||||
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
[ -n "$pkg" ] || { echo "::error::lsphp${{ matrix.phpver }} not in the LiteSpeed repo index"; exit 1; }
|
||||
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||
[ -n "$pkg" ] || { echo "::error::lsphp${PHPVER} not in the LiteSpeed repo index"; exit 1; }
|
||||
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||
pphp=${pkg%%-*}
|
||||
[[ "$pphp" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '$pphp' from lsphp${PHPVER} $pkg"; exit 1; }
|
||||
if [ "$(printf '%s\n%s\n' "$pphp" "$v" | sort -V | tail -n1)" != "$v" ] || [ "$pphp" = "$v" ]; then
|
||||
echo "::error::LiteSpeed already ships lsphp${{ matrix.phpver }} $pkg (PHP $pphp) >= $v — use the normal build (push to trunk / dispatch build-push.yaml)."
|
||||
echo "::error::LiteSpeed already ships lsphp${PHPVER} $pkg (PHP $pphp) >= $v — use the normal build (push to trunk / dispatch build-push.yaml)."
|
||||
exit 1
|
||||
fi
|
||||
echo "php.net $v sha256=$sha ; LiteSpeed lsphp${{ matrix.phpver }}=$pkg"
|
||||
echo "php.net $v sha256=$sha ; LiteSpeed lsphp${PHPVER}=$pkg"
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||
echo "pkgphp=$pphp" >> "$GITHUB_OUTPUT"
|
||||
echo "bust=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
||||
echo "bust=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.t.outputs.skip == '0'
|
||||
|
||||
@@ -59,6 +59,7 @@ die() { echo "FATAL(lsphp-src-build): $*" >&2; exit 1; }
|
||||
log() { echo "lsphp-src-build: $*"; }
|
||||
|
||||
PHPVER="${PHPVER:?PHPVER required}"
|
||||
[[ "$PHPVER" =~ ^[0-9]{2}$ ]] || die "PHPVER='$PHPVER' is not NN."
|
||||
if [ -z "${PHP_SRC_VERSION:-}" ]; then
|
||||
[ -z "${PHP_SRC_SHA256:-}" ] || die "PHP_SRC_SHA256 given without PHP_SRC_VERSION."
|
||||
log "PHP_SRC_VERSION empty — packaged lsphp build, nothing to do."
|
||||
|
||||
@@ -37,6 +37,7 @@ MODE="${1:?usage: lsphp-src-overlay.sh runtime|dev PHPVER}"
|
||||
PHPVER="${2:?usage: lsphp-src-overlay.sh runtime|dev PHPVER}"
|
||||
OUT="${3:-/lsphp-src-out}"
|
||||
case "$MODE" in runtime|dev) ;; *) echo "FATAL(lsphp-src-overlay): mode must be runtime|dev" >&2; exit 1 ;; esac
|
||||
[[ "$PHPVER" =~ ^[0-9]{2}$ ]] || { echo "FATAL(lsphp-src-overlay): PHPVER='$PHPVER' is not NN" >&2; exit 1; }
|
||||
|
||||
die() { echo "FATAL(lsphp-src-overlay): $*" >&2; exit 1; }
|
||||
log() { echo "lsphp-src-overlay[$MODE]: $*"; }
|
||||
|
||||
Reference in new issue
Block a user