Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c722c21d4 | ||
|
|
e112c1dc1e | ||
|
|
10a85e6bc5 | ||
|
|
cafa40decf |
No files matched your search
+60
-22
@@ -1,32 +1,32 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
version=$1
|
version=${1:-}
|
||||||
here=$(pwd)
|
here=$(pwd)
|
||||||
root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software}
|
root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software}
|
||||||
keep=${FDROID_KEEP:-3}
|
keep=${FDROID_KEEP:-3}
|
||||||
url=https://packages.arasaka.software/fdroid/repo
|
url=https://packages.arasaka.software/fdroid/repo
|
||||||
fingerprint=5F52CFF4F52F4E0B6DA6CD044BFCEAF78D1D42DB24F2470602DB7F19CBFDF846
|
fingerprint=5F52CFF4F52F4E0B6DA6CD044BFCEAF78D1D42DB24F2470602DB7F19CBFDF846
|
||||||
id=software.arasaka.meweb
|
id=software.arasaka.meweb
|
||||||
apk=$here/releases/$version/MeWeb-$version-android.apk
|
|
||||||
test -f "$apk"
|
|
||||||
if [ -z "${MEWEB_KEYSTORE_B64:-}" ] || [ -z "${MEWEB_KEYSTORE_PASS:-}" ]; then
|
if [ -z "${MEWEB_KEYSTORE_B64:-}" ] || [ -z "${MEWEB_KEYSTORE_PASS:-}" ]; then
|
||||||
echo "::error::the repository secrets MEWEB_KEYSTORE_B64 and MEWEB_KEYSTORE_PASS are not set"
|
echo "::error::the repository secrets MEWEB_KEYSTORE_B64 and MEWEB_KEYSTORE_PASS are not set"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
apk=$here/releases/$version/MeWeb-$version-android.apk
|
||||||
|
test -f "$apk"
|
||||||
major=$(echo "$version" | cut -d. -f1)
|
major=$(echo "$version" | cut -d. -f1)
|
||||||
minor=$(echo "$version" | cut -d. -f2)
|
minor=$(echo "$version" | cut -d. -f2)
|
||||||
patch=$(echo "$version" | cut -d. -f3)
|
patch=$(echo "$version" | cut -d. -f3)
|
||||||
code=$((major * 10000 + minor * 100 + patch))
|
code=$((major * 10000 + minor * 100 + patch))
|
||||||
|
elif [ ! -e "$root/fdroid/repo" ]; then
|
||||||
|
echo "no repository at $root/fdroid/repo yet: the first release makes it"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
tools/setup.sh --group fdroid
|
tools/setup.sh --group fdroid
|
||||||
tools=$(cd "$here/tools/fdroid/linux-x64" && pwd -P)
|
tools=$(cd "$here/tools/fdroid/linux-x64" && pwd -P)
|
||||||
pinned=$(sed -n 's/^fdroidserver==\([^ ]*\).*/\1/p' packaging/fdroid/requirements.txt)
|
uv=$tools/uv-x86_64-unknown-linux-gnu/uv
|
||||||
venv=$tools/fdroidserver-$pinned
|
python=$(command -v python3)
|
||||||
if ! "$venv/bin/fdroid" --version > /dev/null 2>&1; then
|
|
||||||
rm -rf "$venv"
|
|
||||||
UV_CACHE_DIR=$tools/cache UV_PYTHON_INSTALL_DIR=$tools/python "$tools/uv-x86_64-unknown-linux-gnu/uv" venv -q --python 3.12 "$venv"
|
|
||||||
UV_CACHE_DIR=$tools/cache "$tools/uv-x86_64-unknown-linux-gnu/uv" pip install -q --python "$venv/bin/python" --require-hashes -r packaging/fdroid/requirements.txt
|
|
||||||
fi
|
|
||||||
JAVA_HOME=$(ls -d "$here"/tools/jdk/linux-x64/*/ | head -n 1)
|
JAVA_HOME=$(ls -d "$here"/tools/jdk/linux-x64/*/ | head -n 1)
|
||||||
ANDROID_HOME=$here/tools/android-sdk
|
ANDROID_HOME=$here/tools/android-sdk
|
||||||
PATH=$JAVA_HOME/bin:$PATH
|
PATH=$JAVA_HOME/bin:$PATH
|
||||||
@@ -34,24 +34,52 @@ export JAVA_HOME ANDROID_HOME PATH
|
|||||||
|
|
||||||
exec 9>>"$root/.lock"
|
exec 9>>"$root/.lock"
|
||||||
flock -w 2700 9
|
flock -w 2700 9
|
||||||
|
work=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
keyless() {
|
||||||
|
env -u MEWEB_KEYSTORE_B64 -u MEWEB_KEYSTORE_PASS UV_CACHE_DIR="$tools/cache" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
keys=$(mktemp -d)
|
if [ -n "$version" ] && "$tools/current/bin/fdroid" --version > /dev/null 2>&1; then
|
||||||
trap 'rm -rf "$keys"' EXIT
|
install=$(readlink -f "$tools/current")
|
||||||
(umask 077 && printf '%s' "$MEWEB_KEYSTORE_B64" | base64 -d > "$keys/meweb.keystore")
|
else
|
||||||
MEWEB_KEYSTORE=$keys/meweb.keystore
|
settled=$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
echo fdroidserver | keyless "$uv" pip compile -q --python "$python" --exclude-newer "$settled" --generate-hashes \
|
||||||
|
--no-header --no-annotate -o "$work/lock.txt" -
|
||||||
|
install=$tools/fdroidserver-$(sha256sum < "$work/lock.txt" | cut -c1-12)
|
||||||
|
if ! "$install/bin/fdroid" --version > /dev/null 2>&1; then
|
||||||
|
rm -rf "$install"
|
||||||
|
keyless "$uv" venv -q --python "$python" "$install"
|
||||||
|
keyless "$uv" pip sync -q --python "$install/bin/python" "$work/lock.txt"
|
||||||
|
cp "$work/lock.txt" "$install/lock.txt"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
tool=$("$install/bin/fdroid" --version)
|
||||||
|
stamp=$( { cat "$install/lock.txt"; cd packaging/fdroid && find config.yml icon.png metadata -type f | LC_ALL=C sort | xargs sha256sum; } | sha256sum | cut -d' ' -f1)
|
||||||
|
if [ -z "$version" ] && [ "$(cat "$(readlink -f "$root/fdroid/repo")/../built-from" 2>/dev/null)" = "$stamp" ]; then
|
||||||
|
echo "nothing new: the repository is already built with fdroidserver $tool and this listing"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "fdroidserver $tool, $(basename "$install")"
|
||||||
|
|
||||||
|
(umask 077 && printf '%s' "$MEWEB_KEYSTORE_B64" | base64 -d > "$work/meweb.keystore")
|
||||||
|
MEWEB_KEYSTORE=$work/meweb.keystore
|
||||||
export MEWEB_KEYSTORE MEWEB_KEYSTORE_PASS
|
export MEWEB_KEYSTORE MEWEB_KEYSTORE_PASS
|
||||||
unset MEWEB_KEYSTORE_B64
|
unset MEWEB_KEYSTORE_B64
|
||||||
held=$(keytool -J-Duser.language=en -list -v -keystore "$MEWEB_KEYSTORE" -storepass:env MEWEB_KEYSTORE_PASS -alias meweb | sed -n 's/^.*SHA256: *//p' | tr -d ':')
|
held=$(keytool -J-Duser.language=en -list -v -keystore "$MEWEB_KEYSTORE" -storepass:env MEWEB_KEYSTORE_PASS -alias meweb | sed -n 's/^.*SHA256: *//p' | tr -d ':')
|
||||||
test "$held" = "$fingerprint"
|
test "$held" = "$fingerprint"
|
||||||
|
|
||||||
state=$root/.fdroid
|
state=$root/.fdroid
|
||||||
snap=$state/$(date -u +%Y%m%d%H%M%S)-$version
|
snap=$state/$(date -u +%Y%m%d%H%M%S)-${version:-tools}
|
||||||
mkdir -p "$snap/repo" "$snap/metadata" "$snap/tmp"
|
mkdir -p "$snap/repo" "$snap/metadata" "$snap/tmp"
|
||||||
if [ -e "$root/fdroid/repo" ]; then
|
if [ -e "$root/fdroid/repo" ]; then
|
||||||
prior=$(readlink -f "$root/fdroid/repo")/..
|
prior=$(readlink -f "$root/fdroid/repo")/..
|
||||||
for file in "$prior"/repo/*.apk; do
|
for file in "$prior"/repo/*.apk; do
|
||||||
[ -e "$file" ] && ln "$file" "$snap/repo/"
|
[ -e "$file" ] && ln "$file" "$snap/repo/"
|
||||||
done
|
done
|
||||||
|
for icons in "$prior"/repo/icons*; do
|
||||||
|
[ -d "$icons" ] && cp -a "$icons" "$snap/repo/"
|
||||||
|
done
|
||||||
cp -a "$prior/metadata/." "$snap/metadata/"
|
cp -a "$prior/metadata/." "$snap/metadata/"
|
||||||
cp "$prior/repo/index-v2.json" "$snap/repo/"
|
cp "$prior/repo/index-v2.json" "$snap/repo/"
|
||||||
if [ -f "$prior/tmp/apkcache.json" ]; then cp "$prior/tmp/apkcache.json" "$snap/tmp/"; fi
|
if [ -f "$prior/tmp/apkcache.json" ]; then cp "$prior/tmp/apkcache.json" "$snap/tmp/"; fi
|
||||||
@@ -60,15 +88,19 @@ cp packaging/fdroid/config.yml "$snap/config.yml"
|
|||||||
chmod 600 "$snap/config.yml"
|
chmod 600 "$snap/config.yml"
|
||||||
cp packaging/fdroid/icon.png "$snap/icon.png"
|
cp packaging/fdroid/icon.png "$snap/icon.png"
|
||||||
cp -r packaging/fdroid/metadata/. "$snap/metadata/"
|
cp -r packaging/fdroid/metadata/. "$snap/metadata/"
|
||||||
|
if [ -n "$version" ]; then
|
||||||
rm -f "$snap/repo/${id}_$code.apk"
|
rm -f "$snap/repo/${id}_$code.apk"
|
||||||
cp "$apk" "$snap/repo/${id}_$code.apk"
|
cp "$apk" "$snap/repo/${id}_$code.apk"
|
||||||
|
fi
|
||||||
(cd "$snap/repo" && ls "$id"_*.apk | sed 's/.*_\([0-9]*\)\.apk$/\1/' | sort -n | head -n "-$keep" | while read -r old; do
|
(cd "$snap/repo" && ls "$id"_*.apk | sed 's/.*_\([0-9]*\)\.apk$/\1/' | sort -n | head -n "-$keep" | while read -r old; do
|
||||||
rm -f "${id}_$old.apk"
|
rm -f "${id}_$old.apk"
|
||||||
done)
|
done)
|
||||||
|
newest=$(cd "$snap/repo" && ls "$id"_*.apk | sed 's/.*_\([0-9]*\)\.apk$/\1/' | sort -n | tail -n 1)
|
||||||
|
|
||||||
(cd "$snap" && "$venv/bin/fdroid" update)
|
(cd "$snap" && "$install/bin/fdroid" update)
|
||||||
rm -rf "$snap/repo/status"
|
rm -rf "$snap/repo/status"
|
||||||
chmod -R u=rwX,go=rX "$snap/repo"
|
chmod -R u=rwX,go=rX "$snap/repo"
|
||||||
|
echo "$stamp" > "$snap/built-from"
|
||||||
|
|
||||||
mkdir -p "$root/fdroid"
|
mkdir -p "$root/fdroid"
|
||||||
ln -sfn "../.fdroid/$(basename "$snap")/repo" "$root/fdroid/repo.new"
|
ln -sfn "../.fdroid/$(basename "$snap")/repo" "$root/fdroid/repo.new"
|
||||||
@@ -76,13 +108,19 @@ mv -T -f "$root/fdroid/repo.new" "$root/fdroid/repo"
|
|||||||
ls -1d "$state"/*/ | sort | head -n -3 | while read -r old; do
|
ls -1d "$state"/*/ | sort | head -n -3 | while read -r old; do
|
||||||
find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} +
|
find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} +
|
||||||
done
|
done
|
||||||
echo "published ${id}_$code.apk in $root/fdroid/repo"
|
echo "published $root/fdroid/repo, newest ${id}_$newest.apk, with fdroidserver $tool"
|
||||||
|
|
||||||
if [ "$root" = /var/www/packages.arasaka.software ]; then
|
if [ "$root" = /var/www/packages.arasaka.software ]; then
|
||||||
curl -fsS "$url/entry.jar" -o "$keys/entry.jar"
|
curl -fsS "$url/entry.jar" -o "$work/entry.jar"
|
||||||
signed=$(keytool -J-Duser.language=en -printcert -jarfile "$keys/entry.jar" | sed -n 's/^.*SHA256: *//p' | head -n 1 | tr -d ':')
|
signed=$(keytool -J-Duser.language=en -printcert -jarfile "$work/entry.jar" | sed -n 's/^.*SHA256: *//p' | head -n 1 | tr -d ':')
|
||||||
test "$signed" = "$fingerprint"
|
test "$signed" = "$fingerprint"
|
||||||
curl -fsS "$url/index-v2.json" | grep -Eq "\"versionCode\": ?$code\b"
|
curl -fsS "$url/index-v2.json" | grep -Eq "\"versionCode\": ?$newest\b"
|
||||||
curl -fsSI "$url/${id}_$code.apk" > /dev/null
|
curl -fsSI "$url/${id}_$newest.apk" > /dev/null
|
||||||
echo "live at $url: $id $version ($code)"
|
echo "live at $url: newest $id $newest"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$(basename "$install")" "$tools/current.new"
|
||||||
|
mv -T -f "$tools/current.new" "$tools/current"
|
||||||
|
for old in "$tools"/fdroidserver-*; do
|
||||||
|
[ "$old" = "$install" ] || rm -rf "$old"
|
||||||
|
done
|
||||||
@@ -1,18 +1,31 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
version=$1
|
version=${1:-}
|
||||||
releases=$(pwd)/releases/$version
|
|
||||||
root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software}
|
root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software}
|
||||||
keep=${PACKAGES_KEEP:-5}
|
keep=${PACKAGES_KEEP:-5}
|
||||||
url=https://packages.arasaka.software
|
url=https://packages.arasaka.software
|
||||||
fingerprint=F92C166E9B1C13EDD1629E76888DCF969D133FB2
|
fingerprint=F92C166E9B1C13EDD1629E76888DCF969D133FB2
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
releases=$(pwd)/releases/$version
|
||||||
deb=$releases/MeWeb-$version-linux-x64.deb
|
deb=$releases/MeWeb-$version-linux-x64.deb
|
||||||
rpm=$releases/MeWeb-$version-linux-x64.rpm
|
rpm=$releases/MeWeb-$version-linux-x64.rpm
|
||||||
test -f "$deb"
|
test -f "$deb"
|
||||||
test -f "$rpm"
|
test -f "$rpm"
|
||||||
|
elif [ ! -e "$root/apt" ] || [ ! -e "$root/rpm" ]; then
|
||||||
|
echo "no repositories at $root yet: the first release makes them"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
tools=$(dpkg-query -W -f '${Package} ${Version}\n' apt-utils createrepo-c dpkg gnupg rpm)
|
||||||
|
named=$(echo "$tools" | paste -sd, - | sed 's/,/, /g')
|
||||||
|
stamp=$( { echo "$tools"; cat "$0"; } | sha256sum | cut -d' ' -f1)
|
||||||
|
|
||||||
exec 9>>"$root/.lock"
|
exec 9>>"$root/.lock"
|
||||||
flock -w 2700 9
|
flock -w 2700 9
|
||||||
|
if [ -z "$version" ] && [ "$(cat "$(readlink -f "$root/apt")/../built-from" 2>/dev/null)" = "$stamp" ]; then
|
||||||
|
echo "nothing new: the repositories are already built with $named"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$named"
|
||||||
|
|
||||||
keys=$(mktemp -d)
|
keys=$(mktemp -d)
|
||||||
trap 'rm -rf "$keys"' EXIT
|
trap 'rm -rf "$keys"' EXIT
|
||||||
@@ -21,6 +34,7 @@ printf '%s\n' "$PACKAGES_SIGNING_KEY" | gpg --batch --quiet --import
|
|||||||
gpg --batch --with-colons --list-secret-keys | grep -q "^fpr:::::::::$fingerprint:$"
|
gpg --batch --with-colons --list-secret-keys | grep -q "^fpr:::::::::$fingerprint:$"
|
||||||
gpg --batch --armor --export "$fingerprint" > "$keys/public.asc"
|
gpg --batch --armor --export "$fingerprint" > "$keys/public.asc"
|
||||||
|
|
||||||
|
if [ -n "$version" ]; then
|
||||||
rpmsign --define "__gpg /usr/bin/gpg" --define "_gpg_name $fingerprint" --define "_gpg_path $keys" --addsign "$rpm" >/dev/null
|
rpmsign --define "__gpg /usr/bin/gpg" --define "_gpg_name $fingerprint" --define "_gpg_path $keys" --addsign "$rpm" >/dev/null
|
||||||
rpmkeys --dbpath "$keys/rpmdb" --import "$keys/public.asc"
|
rpmkeys --dbpath "$keys/rpmdb" --import "$keys/public.asc"
|
||||||
rpmkeys --dbpath "$keys/rpmdb" -K "$rpm" | grep -q 'digests signatures OK'
|
rpmkeys --dbpath "$keys/rpmdb" -K "$rpm" | grep -q 'digests signatures OK'
|
||||||
@@ -31,8 +45,9 @@ test "${full%-*}" = "$version"
|
|||||||
debname=meweb_${full}_$(dpkg-deb -f "$deb" Architecture).deb
|
debname=meweb_${full}_$(dpkg-deb -f "$deb" Architecture).deb
|
||||||
rpmname=$(rpm -qp --nosignature --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' "$rpm")
|
rpmname=$(rpm -qp --nosignature --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' "$rpm")
|
||||||
test "$(rpm -qp --nosignature --qf '%{NAME} %{VERSION}' "$rpm")" = "meweb $version"
|
test "$(rpm -qp --nosignature --qf '%{NAME} %{VERSION}' "$rpm")" = "meweb $version"
|
||||||
|
fi
|
||||||
|
|
||||||
snap=$root/.snap/$(date -u +%Y%m%d%H%M%S)-$version
|
snap=$root/.snap/$(date -u +%Y%m%d%H%M%S)-${version:-tools}
|
||||||
mkdir -p "$snap/apt/pool/main" "$snap/rpm"
|
mkdir -p "$snap/apt/pool/main" "$snap/rpm"
|
||||||
if [ -d "$root/apt/pool/main" ]; then cp -al "$root/apt/pool/main/." "$snap/apt/pool/main/"; fi
|
if [ -d "$root/apt/pool/main" ]; then cp -al "$root/apt/pool/main/." "$snap/apt/pool/main/"; fi
|
||||||
if [ -d "$root/rpm" ]; then
|
if [ -d "$root/rpm" ]; then
|
||||||
@@ -41,14 +56,21 @@ if [ -d "$root/rpm" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
mkdir -p "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb"
|
mkdir -p "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb"
|
||||||
|
if [ -n "$version" ]; then
|
||||||
rm -f "$snap/apt/pool/main/m/meweb/$debname" "$snap/rpm/meweb/$rpmname"
|
rm -f "$snap/apt/pool/main/m/meweb/$debname" "$snap/rpm/meweb/$rpmname"
|
||||||
cp "$deb" "$snap/apt/pool/main/m/meweb/$debname"
|
cp "$deb" "$snap/apt/pool/main/m/meweb/$debname"
|
||||||
cp "$rpm" "$snap/rpm/meweb/$rpmname"
|
cp "$rpm" "$snap/rpm/meweb/$rpmname"
|
||||||
|
fi
|
||||||
for folder in "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb"; do
|
for folder in "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb"; do
|
||||||
ls "$folder" | sed -E 's/^meweb[_-]([0-9.]+)-.*/\1/' | sort -uV | head -n "-$keep" | while read -r old; do
|
ls "$folder" | sed -E 's/^meweb[_-]([0-9.]+)-.*/\1/' | sort -uV | head -n "-$keep" | while read -r old; do
|
||||||
rm -f "$folder/meweb_$old-"* "$folder/meweb-$old-"*
|
rm -f "$folder/meweb_$old-"* "$folder/meweb-$old-"*
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
debname=$(ls "$snap/apt/pool/main/m/meweb" | sort -V | tail -n 1)
|
||||||
|
rpmname=$(ls "$snap/rpm/meweb" | sort -V | tail -n 1)
|
||||||
|
full=$(dpkg-deb -f "$snap/apt/pool/main/m/meweb/$debname" Version)
|
||||||
|
fi
|
||||||
|
|
||||||
binary=$snap/apt/dists/stable/main/binary-amd64
|
binary=$snap/apt/dists/stable/main/binary-amd64
|
||||||
mkdir -p "$binary"
|
mkdir -p "$binary"
|
||||||
@@ -74,6 +96,7 @@ if [ -f "$root/rpm/repodata/repomd.xml" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
chmod -R u=rwX,go=rX "$snap"
|
chmod -R u=rwX,go=rX "$snap"
|
||||||
|
echo "$stamp" > "$snap/built-from"
|
||||||
|
|
||||||
for part in apt rpm; do
|
for part in apt rpm; do
|
||||||
ln -sfn ".snap/$(basename "$snap")/$part" "$root/$part.new"
|
ln -sfn ".snap/$(basename "$snap")/$part" "$root/$part.new"
|
||||||
@@ -82,10 +105,12 @@ done
|
|||||||
ls -1d "$root"/.snap/* | sort | head -n -3 | while read -r old; do
|
ls -1d "$root"/.snap/* | sort | head -n -3 | while read -r old; do
|
||||||
find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} +
|
find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} +
|
||||||
done
|
done
|
||||||
echo "published $debname and $rpmname in $root"
|
echo "published $root/apt and $root/rpm, newest $debname and $rpmname"
|
||||||
|
|
||||||
|
if [ -n "$version" ]; then
|
||||||
(cd "$releases" && for file in $(ls | LC_ALL=C sort); do [ "$file" = SHA256SUMS.txt ] || sha256sum "$file"; done) > "$keys/sums"
|
(cd "$releases" && for file in $(ls | LC_ALL=C sort); do [ "$file" = SHA256SUMS.txt ] || sha256sum "$file"; done) > "$keys/sums"
|
||||||
mv "$keys/sums" "$releases/SHA256SUMS.txt"
|
mv "$keys/sums" "$releases/SHA256SUMS.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$root" = /var/www/packages.arasaka.software ]; then
|
if [ "$root" = /var/www/packages.arasaka.software ]; then
|
||||||
curl -fsS "$url/apt/dists/stable/main/binary-amd64/Packages" | grep -qx "Version: $full"
|
curl -fsS "$url/apt/dists/stable/main/binary-amd64/Packages" | grep -qx "Version: $full"
|
||||||
@@ -95,5 +120,5 @@ if [ "$root" = /var/www/packages.arasaka.software ]; then
|
|||||||
curl -fsS "$url/rpm/repodata/repomd.xml.asc" > "$keys/repomd.xml.asc"
|
curl -fsS "$url/rpm/repodata/repomd.xml.asc" > "$keys/repomd.xml.asc"
|
||||||
gpg --batch --quiet --verify "$keys/repomd.xml.asc" "$keys/repomd.xml"
|
gpg --batch --quiet --verify "$keys/repomd.xml.asc" "$keys/repomd.xml"
|
||||||
curl -fsSI "$url/rpm/meweb/$rpmname" > /dev/null
|
curl -fsSI "$url/rpm/meweb/$rpmname" > /dev/null
|
||||||
echo "live at $url: meweb $version"
|
echo "live at $url: meweb $full"
|
||||||
fi
|
fi
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
name: F-Droid tools
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '23 5 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
refresh:
|
||||||
|
name: The newest F-Droid tools, tried on the repository
|
||||||
|
runs-on: nuvola
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: .gitea/scripts/prepare.sh
|
||||||
|
|
||||||
|
- name: Rebuild the repository on packages.arasaka.software when the tools or the listing changed
|
||||||
|
env:
|
||||||
|
MEWEB_KEYSTORE_B64: ${{ secrets.MEWEB_KEYSTORE_B64 }}
|
||||||
|
MEWEB_KEYSTORE_PASS: ${{ secrets.MEWEB_KEYSTORE_PASS }}
|
||||||
|
run: .gitea/scripts/fdroid.sh
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Package tools
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '43 5 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
refresh:
|
||||||
|
name: The box's updated package tools, tried on the apt and rpm repositories
|
||||||
|
runs-on: nuvola
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check that the box brought the tools up to date this morning
|
||||||
|
run: |
|
||||||
|
systemctl show packages-tools -p Result -p ExecMainExitTimestamp
|
||||||
|
systemctl is-active --quiet packages-tools.timer
|
||||||
|
test "$(systemctl show packages-tools -p Result --value)" = success
|
||||||
|
|
||||||
|
- name: Rebuild the apt and rpm repositories on packages.arasaka.software when the tools changed
|
||||||
|
env:
|
||||||
|
PACKAGES_SIGNING_KEY: ${{ secrets.PACKAGES_SIGNING_KEY }}
|
||||||
|
run: .gitea/scripts/packages.sh
|
||||||
@@ -44,7 +44,9 @@ is root-owned, so CI uses the runner user's own `~/.dotnet` (passolo's `tools/re
|
|||||||
| `MEWEB_KEYSTORE` | release step | a temporary file in `$RUNNER_TEMP` decoded by `.gitea/scripts/android.sh`, removed on exit |
|
| `MEWEB_KEYSTORE` | release step | a temporary file in `$RUNNER_TEMP` decoded by `.gitea/scripts/android.sh`, removed on exit |
|
||||||
|
|
||||||
`build.yml` runs on every push to `master`; `release.yml` on a `v*` tag (publishes the Gitea release) or by hand
|
`build.yml` runs on every push to `master`; `release.yml` on a `v*` tag (publishes the Gitea release) or by hand
|
||||||
(`publish: false` builds every platform and deploys the server only). Both run on the Arasaka org runner `nuvola`,
|
(`publish: false` builds every platform and deploys the server only); `fdroid.yml` daily at 05:23 UTC or by hand
|
||||||
|
(the F-Droid tools, below); `packages.yml` daily at 05:43 UTC or by hand (the deb and rpm tools, below). All four
|
||||||
|
run on the Arasaka org runner `nuvola`,
|
||||||
user `gitea-runner`, home `/var/lib/gitea-runner`: `tools/sdk.sh` installs the SDK from `global.json` into its
|
user `gitea-runner`, home `/var/lib/gitea-runner`: `tools/sdk.sh` installs the SDK from `global.json` into its
|
||||||
`~/.dotnet` (the system SDK in `/opt/dotnet` is older and root-owned), and the JDK, nfpm and the Android SDK are
|
`~/.dotnet` (the system SDK in `/opt/dotnet` is older and root-owned), and the JDK, nfpm and the Android SDK are
|
||||||
cached in `~/.cache/meweb`. There is deliberately no `pull_request` trigger: the runner is the production box.
|
cached in `~/.cache/meweb`. There is deliberately no `pull_request` trigger: the runner is the production box.
|
||||||
@@ -75,7 +77,7 @@ The Android release key: alias `meweb`, certificate SHA-256
|
|||||||
| `src/MeWeb.Server/KnowledgeFeeder.cs` | refreshes the open network data under `/var/lib/meweb/knowledge` every six hours (iptoasn daily, DB-IP monthly, eight RouteViews RIBs weekly through `bzcat` (one or two collectors per continent, `Collectors`; each counts once per network in `UpstreamTally`, and a path's first AS, the collector's own neighbour, is never taken for an upstream), the AOSP carrier list weekly, the address registries' block files weekly (`Registries`: RIPE's and APNIC's `inetnum` and `inet6num` split files, AFRINIC's whole dump, LACNIC's `lacnic.db`)), builds `ip2asn.bin`, `networks.bin`, `carriers.json`, `registry.bin` (every block of the four, `RegistryBuilder`) and `SOURCES.txt` with `MeWeb.Core/Knowledge/Sources.cs`, and serves them at `/api/knowledge` |
|
| `src/MeWeb.Server/KnowledgeFeeder.cs` | refreshes the open network data under `/var/lib/meweb/knowledge` every six hours (iptoasn daily, DB-IP monthly, eight RouteViews RIBs weekly through `bzcat` (one or two collectors per continent, `Collectors`; each counts once per network in `UpstreamTally`, and a path's first AS, the collector's own neighbour, is never taken for an upstream), the AOSP carrier list weekly, the address registries' block files weekly (`Registries`: RIPE's and APNIC's `inetnum` and `inet6num` split files, AFRINIC's whole dump, LACNIC's `lacnic.db`)), builds `ip2asn.bin`, `networks.bin`, `carriers.json`, `registry.bin` (every block of the four, `RegistryBuilder`) and `SOURCES.txt` with `MeWeb.Core/Knowledge/Sources.cs`, and serves them at `/api/knowledge` |
|
||||||
| `tools/knowledge.cs` | takes the server's knowledge files (`MEWEB_KNOWLEDGE_URL`, default the public server; the release sets `http://127.0.0.1:5480` after deploying the server first) into `artifacts/knowledge/`, checking each SHA-256, and falls back to building `ip2asn.bin` alone from iptoasn (`--local` forces that); `MeWeb.Core` embeds whatever is there; `tools/release.cs` runs it before every app build |
|
| `tools/knowledge.cs` | takes the server's knowledge files (`MEWEB_KNOWLEDGE_URL`, default the public server; the release sets `http://127.0.0.1:5480` after deploying the server first) into `artifacts/knowledge/`, checking each SHA-256, and falls back to building `ip2asn.bin` alone from iptoasn (`--local` forces that); `MeWeb.Core` embeds whatever is there; `tools/release.cs` runs it before every app build |
|
||||||
| `tools/setup.cs` + `setup.json` | pinned build tools |
|
| `tools/setup.cs` + `setup.json` | pinned build tools |
|
||||||
| `tools/Screenshots` | headless renders of every view in every language |
|
| `tools/Screenshots` | headless renders of every view in every language; it runs a real check, in a throwaway app folder (`AppServices.Folder`), so the developer's own history and settings stay untouched |
|
||||||
| `branding/` | the icon (SVG source, PNG, ICO) |
|
| `branding/` | the icon (SVG source, PNG, ICO) |
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
@@ -173,19 +175,40 @@ The Android release key: alias `meweb`, certificate SHA-256
|
|||||||
`arasaka_repository_known`, or installed copies are treated as edited by hand and left alone. The package owns
|
`arasaka_repository_known`, or installed copies are treated as edited by hand and left alone. The package owns
|
||||||
`/usr/share/arasaka-repository/meweb`, the marker that keeps the repository configured while any Arasaka package
|
`/usr/share/arasaka-repository/meweb`, the marker that keeps the repository configured while any Arasaka package
|
||||||
is installed. The signing key's only copies are that secret and the owner's Nextcloud backup.
|
is installed. The signing key's only copies are that secret and the owner's Nextcloud backup.
|
||||||
|
**The tools that build these two repositories are the box's own Ubuntu packages** (`apt-utils` for
|
||||||
|
`apt-ftparchive`, `createrepo-c`, `dpkg`, `gnupg`, `rpm`), and **a daily job keeps them current** (owner, 31 Oct
|
||||||
|
2026: the same daily update as the F-Droid tools). The runner cannot install packages, so the update is a root
|
||||||
|
timer on the box, `packages-tools.timer` at 05:13 UTC (source and setup in arasaka.software,
|
||||||
|
`deploy/packages.arasaka.software/`). It upgrades only those packages, from Ubuntu's updates and security
|
||||||
|
fixes, with package lists of its own, so it never brings the rest of the box forward. There is no two-week wait,
|
||||||
|
as there is for fdroidserver: Ubuntu reviews and signs these updates itself. `packages.yml` follows at 05:43 UTC. It fails if
|
||||||
|
the morning's update failed (it reads the result with `systemctl show`, which needs no rights), then runs this
|
||||||
|
script with no version. When the tools' versions or this script differ from what the live repositories were built
|
||||||
|
from (`built-from` beside each snapshot), it rebuilds and re-signs both from the current pools with no new package,
|
||||||
|
swaps the links and checks the live site; otherwise it says "nothing new" and names the versions. Unlike
|
||||||
|
fdroidserver, these tools cannot be kept beside an older copy, so a release uses whatever is installed; an update
|
||||||
|
that breaks them fails the next morning's job first.
|
||||||
- **The APK reaches an F-Droid repository on packages.arasaka.software from the same job** (31 Oct 2026), after the
|
- **The APK reaches an F-Droid repository on packages.arasaka.software from the same job** (31 Oct 2026), after the
|
||||||
release is published, so the repository can lag the release page but never lead it. `.gitea/scripts/fdroid.sh`
|
release is published, so the repository can lag the release page but never lead it. `.gitea/scripts/fdroid.sh`
|
||||||
installs fdroidserver into `tools/fdroid` (cached like the JDK) with uv from `setup.json` and the hash-locked
|
installs fdroidserver with uv from `setup.json` (its download cache in `tools/fdroid`, cached like the JDK), because
|
||||||
`packaging/fdroid/requirements.txt`, because the box has no `ensurepip` and the apt package would bring 242 others.
|
the box has no `ensurepip` and the apt package would bring 242 others. **Nothing pins fdroidserver; a daily job
|
||||||
The environment is made at the cache's real path (`pwd -P`) and rebuilt whenever `fdroid --version` fails: its
|
keeps it current** (owner, 31 Oct 2026: a daily service, not patching it from time to time). `fdroid.yml` runs the
|
||||||
scripts carry the absolute path they were made at, `tools/fdroid` is a symlink from a workspace that can move, and
|
same script with no version at 05:23 UTC: it resolves the newest fdroidserver, and the newest of everything it
|
||||||
0.15.1's step failed with `fdroid: not found` on exactly that.
|
needs, published at least 14 days earlier (a bad upload has usually been pulled by then), against the box's own
|
||||||
|
`python3`, into a hash-locked list; a new list gets its own install, `fdroidserver-<hash of the list>` in the cache's
|
||||||
|
real path (an install's scripts carry the absolute path they were made at; 0.15.1 failed with `fdroid: not found` on
|
||||||
|
one made from a deleted folder). When that list or the listing in `packaging/fdroid/` differs from what the live
|
||||||
|
repository was built from (`built-from` beside each snapshot), it rebuilds the repository with it, and only once
|
||||||
|
that is live and verified does the install become `current` and the older ones go. A release uses `current` and
|
||||||
|
downloads nothing, so a new fdroidserver that breaks fails the daily job, never a release; the job's log names the
|
||||||
|
version. Every install runs with the signing key out of its environment.
|
||||||
It signs the repository with the APK's own key (`MEWEB_KEYSTORE_B64` and `MEWEB_KEYSTORE_PASS`, alias `meweb`), so
|
It signs the repository with the APK's own key (`MEWEB_KEYSTORE_B64` and `MEWEB_KEYSTORE_PASS`, alias `meweb`), so
|
||||||
the F-Droid fingerprint is the certificate's `5F52CFF4…FDF846` and there is no second key to lose; it refuses any
|
the F-Droid fingerprint is the certificate's `5F52CFF4…FDF846` and there is no second key to lose; it refuses any
|
||||||
other. It builds a new snapshot in
|
other. It builds a new snapshot in
|
||||||
`/var/www/packages.arasaka.software/.fdroid/` from the live one (the APKs as hard links, so `rm` then `cp` as above;
|
`/var/www/packages.arasaka.software/.fdroid/` from the live one (the APKs as hard links, so `rm` then `cp` as above;
|
||||||
the metadata, `apkcache.json` and the previous `index-v2.json`, which is where fdroidserver reads each APK's first
|
the metadata, the icons, `apkcache.json` and the previous `index-v2.json`, which is where fdroidserver reads each
|
||||||
date), adds this version, keeps the newest three, runs `fdroid update`, deletes `repo/status/` (it names the
|
APK's first date; without the icons a rebuild with no new APK fails, because the cache skips extracting them), adds
|
||||||
|
this version (a release) or nothing (the daily job), keeps the newest three, runs `fdroid update`, deletes `repo/status/` (it names the
|
||||||
build machine's paths and `/etc/issue.net`), swaps the `fdroid/repo` link and checks the live signature. The key
|
build machine's paths and `/etc/issue.net`), swaps the `fdroid/repo` link and checks the live signature. The key
|
||||||
was already never to be regenerated; now everyone who added the repository trusts only its fingerprint too. `packaging/fdroid/` holds the
|
was already never to be regenerated; now everyone who added the repository trusts only its fingerprint too. `packaging/fdroid/` holds the
|
||||||
repository's settings, its icon (the Arasaka emblem) and the app's listing in English and Italian; the download
|
repository's settings, its icon (the Arasaka emblem) and the app's listing in English and Italian; the download
|
||||||
@@ -209,6 +232,9 @@ The Android release key: alias `meweb`, certificate SHA-256
|
|||||||
phone with several): `AndroidPlatform` reads `ActivityManager.MemoryInfo.TotalMem`, Linux `/proc/meminfo`.
|
phone with several): `AndroidPlatform` reads `ActivityManager.MemoryInfo.TotalMem`, Linux `/proc/meminfo`.
|
||||||
- **`DriveInfo` on Unix takes any path** (it calls `statvfs`): pass the app's folder, not `/`, or Android reads the
|
- **`DriveInfo` on Unix takes any path** (it calls `statvfs`): pass the app's folder, not `/`, or Android reads the
|
||||||
read-only root and reports no free disk at all.
|
read-only root and reports no free disk at all.
|
||||||
|
- **A wireless mouse is a battery to Linux.** `/sys/class/power_supply` lists the batteries of mice, keyboards, headsets
|
||||||
|
and game controllers too (`type` Battery, `scope` Device); only one without `scope` Device makes a laptop. Until
|
||||||
|
0.15.4 the owner's desktop read as a laptop because of an MX Master 4 (MW-VRST-EK5A).
|
||||||
- **An IPv6 address only wraps at a colon if it is allowed to**: the path's detail lines put U+200B after each colon;
|
- **An IPv6 address only wraps at a colon if it is allowed to**: the path's detail lines put U+200B after each colon;
|
||||||
they are plain `TextBlock`s for that reason, so a copy never carries the invisible character (the cards' values,
|
they are plain `TextBlock`s for that reason, so a copy never carries the invisible character (the cards' values,
|
||||||
which are selectable, stay untouched).
|
which are selectable, stay untouched).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>0.15.3</Version>
|
<Version>0.15.4</Version>
|
||||||
<Product>me-web?</Product>
|
<Product>me-web?</Product>
|
||||||
<Company>Arasaka di Hvozdikov Yevheniy</Company>
|
<Company>Arasaka di Hvozdikov Yevheniy</Company>
|
||||||
<Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright>
|
<Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright>
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,8 @@ public static class AppServices
|
|||||||
public const string Secure = "secure";
|
public const string Secure = "secure";
|
||||||
public const string Plain = "plain";
|
public const string Plain = "plain";
|
||||||
|
|
||||||
|
public static string Folder { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MeWeb");
|
||||||
|
|
||||||
public static IServiceCollection AddMeWebApp(this IServiceCollection services, IPlatform platform)
|
public static IServiceCollection AddMeWebApp(this IServiceCollection services, IPlatform platform)
|
||||||
{
|
{
|
||||||
var agent = $"me-web/{BuildInfo.Version}";
|
var agent = $"me-web/{BuildInfo.Version}";
|
||||||
@@ -28,7 +30,7 @@ public static class AppServices
|
|||||||
return new ServerProbe(factory.CreateClient(Secure), factory.CreateClient(Plain));
|
return new ServerProbe(factory.CreateClient(Secure), factory.CreateClient(Plain));
|
||||||
});
|
});
|
||||||
services.AddSingleton<KnowledgeBase>();
|
services.AddSingleton<KnowledgeBase>();
|
||||||
var folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MeWeb");
|
var folder = Folder;
|
||||||
services.AddSingleton(new HistoryStore(folder));
|
services.AddSingleton(new HistoryStore(folder));
|
||||||
services.AddSingleton(new Leftovers(folder));
|
services.AddSingleton(new Leftovers(folder));
|
||||||
services.AddSingleton(new Submissions(folder));
|
services.AddSingleton(new Submissions(folder));
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ public class StoredSettings
|
|||||||
|
|
||||||
public static class AppSettings
|
public static class AppSettings
|
||||||
{
|
{
|
||||||
static readonly string Folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MeWeb");
|
static string FilePath => Path.Combine(AppServices.Folder, "settings.json");
|
||||||
static readonly string FilePath = Path.Combine(Folder, "settings.json");
|
|
||||||
|
|
||||||
public static StoredSettings Read()
|
public static StoredSettings Read()
|
||||||
{
|
{
|
||||||
@@ -32,7 +31,7 @@ public static class AppSettings
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Folder);
|
Directory.CreateDirectory(AppServices.Folder);
|
||||||
var temporary = FilePath + ".tmp";
|
var temporary = FilePath + ".tmp";
|
||||||
using (var stream = File.Create(temporary))
|
using (var stream = File.Create(temporary))
|
||||||
JsonSerializer.Serialize(stream, settings, AppJsonContext.Default.StoredSettings);
|
JsonSerializer.Serialize(stream, settings, AppJsonContext.Default.StoredSettings);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace MeWeb.App.Services;
|
|||||||
public static class ErrorLog
|
public static class ErrorLog
|
||||||
{
|
{
|
||||||
const long MaxBytes = 256 * 1024;
|
const long MaxBytes = 256 * 1024;
|
||||||
static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MeWeb", "errors.log");
|
static string FilePath => Path.Combine(AppServices.Folder, "errors.log");
|
||||||
static readonly Lock Gate = new();
|
static readonly Lock Gate = new();
|
||||||
|
|
||||||
public static void Watch()
|
public static void Watch()
|
||||||
|
|||||||
@@ -337,14 +337,14 @@ public static class Verdicts
|
|||||||
{ Port25: Core.Mail.PortState.Blocked } => (Outcome.Bad, "MailBlocked"),
|
{ Port25: Core.Mail.PortState.Blocked } => (Outcome.Bad, "MailBlocked"),
|
||||||
_ when listed.Count > 0 => (Outcome.Bad, "MailListed"),
|
_ when listed.Count > 0 => (Outcome.Bad, "MailListed"),
|
||||||
_ when mail.Listings.Any(l => l.Residential) => (Outcome.Caution, "MailResidential"),
|
_ when mail.Listings.Any(l => l.Residential) => (Outcome.Caution, "MailResidential"),
|
||||||
_ when wide.Count > 0 => (Outcome.Caution, "MailNetworkListed"),
|
|
||||||
{ ReverseName: null } => (Outcome.Caution, "MailNoName"),
|
{ ReverseName: null } => (Outcome.Caution, "MailNoName"),
|
||||||
{ ReverseLooksDynamic: true } => (Outcome.Caution, "MailDynamicName"),
|
{ ReverseLooksDynamic: true } => (Outcome.Caution, "MailDynamicName"),
|
||||||
|
_ when wide.Count > 0 => (Outcome.Caution, "MailNetworkListed"),
|
||||||
{ Port25: Core.Mail.PortState.Open } => (Outcome.Good, "MailClean"),
|
{ Port25: Core.Mail.PortState.Open } => (Outcome.Good, "MailClean"),
|
||||||
_ => (Outcome.Unknown, "MailUnknown"),
|
_ => (Outcome.Unknown, "MailUnknown"),
|
||||||
};
|
};
|
||||||
if (listed.Count > 0) finding.Args = [string.Join(", ", listed.Select(l => l.List))];
|
if (finding.Summary == "MailListed") finding.Args = [string.Join(", ", listed.Select(l => l.List))];
|
||||||
else if (wide.Count > 0) finding.Args = [string.Join(", ", wide.Select(l => l.List))];
|
else if (finding.Summary == "MailNetworkListed") finding.Args = [string.Join(", ", wide.Select(l => l.List))];
|
||||||
finding.WithKey("FactPort25", mail.Port25 switch
|
finding.WithKey("FactPort25", mail.Port25 switch
|
||||||
{
|
{
|
||||||
Core.Mail.PortState.Open => "ValuePort25Open",
|
Core.Mail.PortState.Open => "ValuePort25Open",
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class SystemPlatform : IPlatform
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Directory.Exists("/sys/class/power_supply") && Directory.EnumerateDirectories("/sys/class/power_supply")
|
return Directory.Exists("/sys/class/power_supply") && Directory.EnumerateDirectories("/sys/class/power_supply")
|
||||||
.Any(d => File.Exists(Path.Combine(d, "type")) && File.ReadAllText(Path.Combine(d, "type")).Trim() == "Battery");
|
.Any(d => Attribute(d, "type") == "Battery" && Attribute(d, "scope") != "Device");
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
@@ -183,6 +183,12 @@ public class SystemPlatform : IPlatform
|
|||||||
return product.StartsWith(maker, StringComparison.OrdinalIgnoreCase) ? product : $"{maker} {product}";
|
return product.StartsWith(maker, StringComparison.OrdinalIgnoreCase) ? product : $"{maker} {product}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string Attribute(string folder, string name)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(folder, name);
|
||||||
|
return File.Exists(path) ? File.ReadAllText(path).Trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
static string Dmi(string name)
|
static string Dmi(string name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
var output = args.Length > 0 ? args[0] : Path.Combine("dist", "screenshots");
|
var output = args.Length > 0 ? args[0] : Path.Combine("dist", "screenshots");
|
||||||
var codes = args.Length > 1 ? args[1].Split(',', StringSplitOptions.RemoveEmptyEntries) : Languages.All.Select(l => l.Code).ToArray();
|
var codes = args.Length > 1 ? args[1].Split(',', StringSplitOptions.RemoveEmptyEntries) : Languages.All.Select(l => l.Code).ToArray();
|
||||||
Directory.CreateDirectory(output);
|
Directory.CreateDirectory(output);
|
||||||
|
var data = Directory.CreateTempSubdirectory("meweb-screenshots-");
|
||||||
|
AppServices.Folder = data.FullName;
|
||||||
|
|
||||||
var session = HeadlessUnitTestSession.StartNew(typeof(ScreenshotEntry));
|
var session = HeadlessUnitTestSession.StartNew(typeof(ScreenshotEntry));
|
||||||
await session.Dispatch(async () =>
|
await session.Dispatch(async () =>
|
||||||
{
|
{
|
||||||
var saved = AppSettings.Read();
|
|
||||||
var run = await App.Services.GetRequiredService<CheckRunner>().Run((_, _) => { }, CancellationToken.None);
|
var run = await App.Services.GetRequiredService<CheckRunner>().Run((_, _) => { }, CancellationToken.None);
|
||||||
Console.WriteLine($"run: {string.Join(", ", run.Findings.Select(f => $"{f.Id}={f.Outcome}"))}");
|
Console.WriteLine($"run: {string.Join(", ", run.Findings.Select(f => $"{f.Id}={f.Outcome}"))}");
|
||||||
foreach (var (width, height, label) in new[] { (1100, 1500, "desktop"), (400, 8000, "phone") })
|
foreach (var (width, height, label) in new[] { (1100, 1500, "desktop"), (400, 8000, "phone") })
|
||||||
@@ -48,9 +49,9 @@ await session.Dispatch(async () =>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppSettings.Write(saved);
|
|
||||||
return 0;
|
return 0;
|
||||||
}, CancellationToken.None);
|
}, CancellationToken.None);
|
||||||
|
data.Delete(true);
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
|
|
||||||
void Shoot(int width, int height, string name, Action<MainViewModel> prepare, Action<MainView> arrange = null)
|
void Shoot(int width, int height, string name, Action<MainViewModel> prepare, Action<MainView> arrange = null)
|
||||||
|
|||||||
Reference in new issue
Block a user