Terminal 1.0: a-Shell 2.3.2 full + worse_query + native plugin environment

This commit is contained in:
ztby committed 2026-11-09 22:51:39 +08:00
commit 598ef7b1bf
43 files changed
+5118

No files matched your search

Vendored Executable
+316
View File
@@ -0,0 +1,316 @@
#!/bin/sh
# apt - native Terminal package front-end (Terminal-compatible .deb packages)
# This is not Debian apt: it manages packages built for iOS/Terminal.
ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
LISTS="$ROOT/var/lib/apt/lists"
INDEX="$LISTS/Packages"
CACHE="$ROOT/var/cache/apt/archives"
SOURCES="$ROOT/etc/apt/sources.list"
VISITED=""
DPKG="${APPDIR:-}/bin/dpkg"
[ -f "$DPKG" ] || DPKG="dpkg"
usage() {
printf '%s\n' "Usage: apt <command>"
printf '%s\n' ""
printf '%s\n' " update Refresh package indexes"
printf '%s\n' " search <text> Search package names/descriptions"
printf '%s\n' " show <package> Show package details"
printf '%s\n' " install <package|deb> Install a package (dependencies are resolved locally)"
printf '%s\n' " remove|purge <package> Remove a package"
printf '%s\n' " list --installed List installed packages"
printf '%s\n' " list List packages in the current index"
printf '%s\n' " clean Clear downloaded archives"
printf '%s\n' " --version Show version"
printf '%s\n' ""
printf '%s\n' "Default repository: file://$ROOT/repo"
printf '%s\n' "Create packages with: mkdeb <name> <version> <payload-dir> [arch]"
}
ensure() {
mkdir -p "$ROOT" "$LISTS" "$CACHE" "$ROOT/var/lib/dpkg" "$ROOT/tmp"
[ -f "$ROOT/var/lib/dpkg/status" ] || : > "$ROOT/var/lib/dpkg/status"
}
field() {
key="$1"; stanza="$2"
tmp="$ROOT/tmp/field.$$"
printf '%s\n' "$stanza" > "$tmp"
awk -v key="$key" 'index($0,key": ")==1 { sub("^" key ": ",""); print; exit }' "$tmp"
rm -f "$tmp"
}
find_stanza() {
local pkg="$1"
[ -f "$INDEX" ] || return 0
awk -v pkg="$pkg" 'BEGIN{RS=""; ORS="\n\n"} $0 ~ "(^|\\n)Package: " pkg "(\\n|$)" { print; exit }' "$INDEX"
}
is_installed() {
out="$ROOT/tmp/dpkg-status.$$"
sh "$DPKG" -s "$1" > "$out" 2>/dev/null
grep -q '^Status: install ok installed' "$out"
result=$?
rm -f "$out"
return $result
}
decompress_index() {
file="$1"
out="$2"
case "$file" in
*.xz)
xz -d -c "$file" >> "$out" 2>/dev/null || python3 -c 'import lzma,sys; sys.stdout.buffer.write(lzma.open(sys.argv[1],"rb").read())' "$file" >> "$out" 2>/dev/null
;;
*.gz)
gzip -dc "$file" >> "$out" 2>/dev/null || python3 -c 'import gzip,sys; sys.stdout.buffer.write(gzip.open(sys.argv[1],"rb").read())' "$file" >> "$out" 2>/dev/null
;;
*)
cat "$file" >> "$out"
;;
esac
}
fetch_index() {
URI="$1"
TMPIDX="$ROOT/tmp/index.$$"
rm -f "$TMPIDX" "$TMPIDX.xz" "$TMPIDX.gz"
case "$URI" in
file://*)
DIR="${URI#file://}"
for name in Packages Packages.xz Packages.gz; do
if [ -f "$DIR/$name" ]; then
decompress_index "$DIR/$name" "$INDEX"
return 0
fi
done
;;
http://*|https://*)
if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages.xz" -o "$TMPIDX.xz" 2>/dev/null; then
decompress_index "$TMPIDX.xz" "$INDEX"
rm -f "$TMPIDX.xz"
return 0
fi
if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages.gz" -o "$TMPIDX.gz" 2>/dev/null; then
decompress_index "$TMPIDX.gz" "$INDEX"
rm -f "$TMPIDX.gz"
return 0
fi
if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages" -o "$TMPIDX" 2>/dev/null; then
cat "$TMPIDX" >> "$INDEX"
rm -f "$TMPIDX"
return 0
fi
;;
esac
return 1
}
update_lists() {
ensure
echo "apt: updating package lists..."
: > "$INDEX"
if [ ! -f "$SOURCES" ]; then
mkdir -p "$(dirname "$SOURCES")"
printf 'deb [trusted=yes] file://%s/repo ./\n' "$ROOT" > "$SOURCES"
fi
n=1
while [ $n -le 8 ]; do
LINE=$(awk -v want="$n" '/^deb /{ count++; if (count == want) { print; exit } }' "$SOURCES")
[ -n "$LINE" ] || break
set -- $LINE
if [ "${2:-}" = "[trusted=yes]" ]; then
URI="${3:-}"; SUITE="${4:-./}"
else
URI="${2:-}"; SUITE="${3:-./}"
fi
if [ -n "$URI" ]; then
case "$URI" in
http://*|https://*)
if [ "${APT_SKIP_REMOTE:-0}" = "1" ]; then
echo "Ign:$n $URI (remote skipped)"
n=$((n + 1))
continue
fi
;;
esac
if fetch_index "$URI"; then
echo "Get:$n $URI $SUITE"
else
echo "Err:$n $URI (no index / unreachable)" >&2
fi
fi
n=$((n + 1))
done
echo "Reading package lists... Done"
}
search_packages() {
text="${1:-}"
ensure
[ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
awk -v pat="$text" 'BEGIN{RS="";FS="\n"} {
if (pat == "" || tolower($0) ~ tolower(pat)) {
p=""; v=""; d="";
for (i=1;i<=NF;i++) {
if ($i ~ /^Package: /) p=substr($i,10);
if ($i ~ /^Version: /) v=substr($i,10);
if ($i ~ /^Description: /) d=substr($i,14);
}
if (p != "") printf "%-28s %-16s %s\n", p, v, d;
}
}' "$INDEX"
}
show_package() {
pkg="${1:-}"
ensure
stanza=$(find_stanza "$pkg")
if [ -z "$stanza" ]; then
echo "E: Unable to locate package $pkg" >&2
return 1
fi
printf '%s\n' "$stanza"
}
install_one() {
local pkg="$1"
local stanza version depends dep filename local_archive base oldifs
[ -n "$pkg" ] || return 0
case " $VISITED " in *" $pkg "*) return 0 ;; esac
VISITED="$VISITED $pkg"
if is_installed "$pkg"; then
echo "$pkg is already the newest version."
return 0
fi
stanza=$(find_stanza "$pkg")
if [ -z "$stanza" ]; then
for localdeb in "$ROOT/repo/pool/${pkg}"_*.deb; do
if [ -f "$localdeb" ]; then
echo "Installing $pkg from local pool..."
sh "$DPKG" -i "$localdeb" || return 1
return 0
fi
done
echo "E: Unable to locate package $pkg" >&2
return 1
fi
version=$(field Version "$stanza")
depends=$(field Depends "$stanza")
if [ -n "$depends" ]; then
oldifs="$IFS"
IFS=','
for dep in $depends; do
IFS="$oldifs"
dep=${dep%%|*}
dep=${dep%%(*}
dep=$(echo "$dep" | tr -d ' ')
dep=${dep%:any}
[ -n "$dep" ] || continue
if ! is_installed "$dep"; then
if [ -n "$(find_stanza "$dep")" ]; then
echo "Installing dependency: $dep"
install_one "$dep" || return 1
else
echo "W: dependency '$dep' is not in the index" >&2
fi
fi
IFS=','
done
IFS="$oldifs"
fi
filename=$(field Filename "$stanza")
if [ -z "$filename" ]; then
echo "E: package $pkg has no Filename field" >&2
return 1
fi
filename="${filename#./}"
local_archive="$CACHE/${pkg}_${version}.deb"
if [ -f "$ROOT/repo/$filename" ]; then
cp "$ROOT/repo/$filename" "$local_archive" || return 1
else
fetched=0
BASES=$(awk '/^deb /{for(i=1;i<=NF;i++) if($i ~ /^https?:/) print $i}' "$SOURCES")
for base in $BASES; do
if curl -L --fail --silent --connect-timeout 10 --max-time 180 "$base/$filename" -o "$local_archive" 2>/dev/null; then
fetched=1
break
fi
done
if [ $fetched -ne 1 ]; then
echo "E: cannot fetch $filename" >&2
return 1
fi
fi
echo "Installing $pkg ($version)..."
echo "apt: invoking dpkg..."
sh "$DPKG" -i "$local_archive" || return 1
return 0
}
install_packages() {
ensure
echo "apt: resolving packages..."
[ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
status=0
for arg in "$@"; do
case "$arg" in
*.deb)
sh "$DPKG" -i "$arg" || status=1
;;
*)
install_one "$arg" || status=1
;;
esac
done
echo "Done."
return $status
}
remove_packages() {
status=0
for pkg in "$@"; do
sh "$DPKG" -r "$pkg" || status=1
done
return $status
}
list_installed() {
sh "$DPKG" -l
}
list_all() {
ensure
[ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
awk 'BEGIN{RS="";FS="\n"} {
p="";v="";a="";
for(i=1;i<=NF;i++){
if($i~/^Package: /)p=substr($i,10);
if($i~/^Version: /)v=substr($i,10);
if($i~/^Architecture: /)a=substr($i,14);
}
if(p!="") printf "%-28s %-16s %s\n", p, v, a;
}' "$INDEX"
}
case "${1:-}" in
update) update_lists ;;
search) shift; search_packages "${1:-}" ;;
show) shift; show_package "${1:-}" ;;
install) shift; install_packages "$@" ;;
remove|uninstall) shift; remove_packages "$@" ;;
purge) shift; remove_packages "$@" ;;
list)
shift
if [ "${1:-}" = "--installed" ]; then list_installed; else list_all; fi
;;
clean)
rm -f "$CACHE"/*.deb
echo "Cleaned package archives"
;;
--version|-v)
echo "apt 1.0 (Terminal native package front-end)"
;;
""|-h|--help|help) usage ;;
*) usage; exit 2 ;;
esac
Vendored Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
# apt-get compatibility wrapper for the native Terminal package manager.
sh "$APPDIR/bin/apt" "$@"
exit $?
Vendored
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Minimal ar archive packer/extractor for .deb files on a-Shell.
Usage:
ardeb pack <out.ar> <file> [file ...]
ardeb extract <in.ar> <dest-dir>
"""
import os
import sys
def pack(out_path, files):
with open(out_path, "wb") as out:
out.write(b"!<arch>\n")
for path in files:
data = open(path, "rb").read()
name = os.path.basename(path)
if len(name) > 15:
raise SystemExit("ardeb: member name too long: " + name)
header = (
name.ljust(16)
+ "0".ljust(12)
+ "0".ljust(6)
+ "0".ljust(6)
+ "100644".ljust(8)
+ str(len(data)).ljust(10)
+ "`\n"
).encode("ascii")
out.write(header)
out.write(data)
if len(data) & 1:
out.write(b"\n")
def extract(archive, dest):
data = open(archive, "rb").read()
if not data.startswith(b"!<arch>\n"):
raise SystemExit("ardeb: not an ar archive")
os.makedirs(dest, exist_ok=True)
pos = 8
while pos + 60 <= len(data):
header = data[pos:pos + 60]
pos += 60
name = header[:16].decode("ascii", "ignore").strip().rstrip("/")
if not name:
continue
try:
size = int(header[48:58].decode("ascii", "ignore").strip() or "0")
except ValueError:
size = 0
body = data[pos:pos + size]
pos += size + (size & 1)
if name in (".", ".."):
continue
open(os.path.join(dest, name), "wb").write(body)
def main():
if len(sys.argv) < 4:
raise SystemExit(__doc__)
if sys.argv[1] == "pack":
pack(sys.argv[2], sys.argv[3:])
elif sys.argv[1] == "extract":
extract(sys.argv[2], sys.argv[3])
else:
raise SystemExit(__doc__)
if __name__ == "__main__":
main()
Vendored Executable
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# bash-compat - a-Shell POSIX sh compatibility launcher.
#
# GNU bash is not bundled with Terminal. This launcher provides the common
# `bash script` / `bash -c 'command'` interface on top of a-Shell's sh so
# simple plugin scripts work. If a real bash is installed at
# $PREFIX/bin/bash, the prefix PATH takes precedence over this launcher.
if [ "${1:-}" = "--version" ] || [ "${1:-}" = "-version" ]; then
echo "bash-compat 1.0 (a-Shell POSIX sh; GNU bash is not bundled)"
exit 0
fi
if [ "${1:-}" = "-c" ]; then
shift
if [ $# -eq 0 ]; then
echo "bash-compat: -c requires a command" >&2
exit 2
fi
command="$1"
shift
sh -c "$command" "$@"
exit $?
fi
if [ $# -gt 0 ] && [ -f "$1" ]; then
sh "$@"
exit $?
fi
if [ $# -eq 0 ]; then
echo "bash-compat: a-Shell POSIX sh. GNU bash-specific syntax is not supported." >&2
sh -s
exit $?
fi
sh "$@"
exit $?
Vendored Executable
+391
View File
@@ -0,0 +1,391 @@
#!/bin/sh
# dpkg - native Terminal package manager (Debian .deb format subset)
#
# Packages must be built for the native Terminal/iOS environment (scripts or
# iOS/Darwin arm64 binaries). Debian Linux arm64/amd64 packages are rejected
# by default because their ELF binaries cannot run on iOS without an emulator.
ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
STATUS="$ROOT/var/lib/dpkg/status"
INFO="$ROOT/var/lib/dpkg/info"
FORCE_ARCH=0
usage() {
printf '%s\n' "Usage: dpkg <action>"
printf '%s\n' ""
printf '%s\n' " -i, --install <file.deb> Install a Terminal-compatible .deb"
printf '%s\n' " -r, --remove <package> Remove a package"
printf '%s\n' " -P, --purge <package> Remove a package (same as -r here)"
printf '%s\n' " -l, --list List installed packages"
printf '%s\n' " -s, --status <package> Show package status"
printf '%s\n' " -L, --listfiles <package> List files installed by a package"
printf '%s\n' " -x, --extract <deb> <dir> Extract a .deb data archive"
printf '%s\n' " --contents <deb> List the contents of a .deb"
printf '%s\n' " --print-architecture Print the native architecture"
printf '%s\n' " --force-architecture Allow non-native architecture packages"
printf '%s\n' " --version Show version"
}
ensure() {
mkdir -p "$ROOT" "$INFO" "$ROOT/tmp" "$ROOT/var/lib/dpkg"
[ -f "$STATUS" ] || : > "$STATUS"
}
field() {
key="$1"; file="$2"
awk -v key="$key" 'index($0,key": ")==1 { sub("^" key ": ",""); print; exit }' "$file"
}
stanza_field() {
key="$1"; stanza="$2"
printf '%s\n' "$stanza" | awk -v key="$key" 'index($0,key": ")==1 { sub("^" key ": ",""); print; exit }'
}
stanza_for() {
pkg="$1"
awk -v pkg="$pkg" 'BEGIN{RS=""; ORS="\n\n"} $0 ~ "(^|\\n)Package: " pkg "(\\n|$)" { print; exit }' "$STATUS"
}
remove_status() {
pkg="$1"
TMP="$ROOT/tmp/status.$$"
awk -v pkg="$pkg" 'BEGIN{RS=""; ORS="\n\n"} $0 !~ "(^|\\n)Package: " pkg "(\\n|$)" { print }' "$STATUS" > "$TMP"
mv "$TMP" "$STATUS"
}
append_status() {
{
echo "Package: $PKG"
echo "Status: install ok installed"
echo "Priority: ${PRIORITY:-optional}"
echo "Section: ${SECTION:-plugins}"
echo "Installed-Size: ${INSTALLED_SIZE:-0}"
echo "Maintainer: ${MAINTAINER:-unknown}"
echo "Architecture: ${ARCH:-iphoneos-arm64}"
echo "Version: $VERSION"
echo "Description: $DESCRIPTION"
echo ""
} >> "$STATUS"
}
ar_extract() {
archive="$1"
dest="$2"
tar -xf "$archive" -C "$dest" >/dev/null 2>&1
if [ ! -f "$dest/debian-binary" ]; then
if python3 -c 'pass' >/dev/null 2>&1 && [ -f "$APPDIR/bin/ardeb" ]; then
python3 "$APPDIR/bin/ardeb" extract "$archive" "$dest"
fi
fi
}
install_deb() {
DEB="$1"
if [ ! -f "$DEB" ]; then
echo "dpkg: cannot access archive '$DEB': No such file" >&2
return 1
fi
if python3 -c 'pass' >/dev/null 2>&1 && [ -f "$APPDIR/bin/pkgtool" ]; then
python3 "$APPDIR/bin/pkgtool" install "$DEB"
return $?
fi
ensure
echo "dpkg: unpacking $DEB"
TMP="$ROOT/tmp/dpkg.$$"
rm -rf "$TMP"
mkdir -p "$TMP"
cp "$DEB" "$TMP/pkg.deb" || { rm -rf "$TMP"; return 1; }
ar_extract "$TMP/pkg.deb" "$TMP"
echo "dpkg: ar extracted"
if [ ! -f "$TMP/debian-binary" ]; then
echo "dpkg: '$DEB' is not a Debian-format archive" >&2
rm -rf "$TMP"
return 1
fi
CTRL_ARCHIVE=""
for f in "$TMP"/control.tar.*; do [ -f "$f" ] && { CTRL_ARCHIVE="$f"; break; }; done
DATA_ARCHIVE=""
for f in "$TMP"/data.tar.*; do [ -f "$f" ] && { DATA_ARCHIVE="$f"; break; }; done
if [ -z "$CTRL_ARCHIVE" ] || [ -z "$DATA_ARCHIVE" ]; then
echo "dpkg: missing control or data archive" >&2
rm -rf "$TMP"
return 1
fi
mkdir -p "$TMP/control"
case "$CTRL_ARCHIVE" in
*.gz) tar -xzf "$CTRL_ARCHIVE" -C "$TMP/control" 2>/dev/null ;;
*.xz) tar -xJf "$CTRL_ARCHIVE" -C "$TMP/control" 2>/dev/null ;;
*) tar -xf "$CTRL_ARCHIVE" -C "$TMP/control" 2>/dev/null ;;
esac
CONTROL=""
if [ -f "$TMP/control/control" ]; then
CONTROL="$TMP/control/control"
elif [ -f "$TMP/control/DEBIAN/control" ]; then
CONTROL="$TMP/control/DEBIAN/control"
fi
if [ -z "$CONTROL" ]; then
echo "dpkg: no control file in package" >&2
rm -rf "$TMP"
return 1
fi
PKG=$(field Package "$CONTROL")
VERSION=$(field Version "$CONTROL")
ARCH=$(field Architecture "$CONTROL")
PRIORITY=$(field Priority "$CONTROL")
SECTION=$(field Section "$CONTROL")
MAINTAINER=$(field Maintainer "$CONTROL")
DESCRIPTION=$(field Description "$CONTROL")
[ -n "$PKG" ] && [ -n "$VERSION" ] || {
echo "dpkg: invalid control file (Package/Version missing)" >&2
rm -rf "$TMP"
return 1
}
case "$PKG" in
*[!A-Za-z0-9+.-]*|"") echo "dpkg: invalid package name: $PKG" >&2; rm -rf "$TMP"; return 1 ;;
esac
case "$ARCH" in
""|all|iphoneos-arm64|darwin-arm64|a-shell) ;;
*)
if [ "$FORCE_ARCH" != "1" ]; then
echo "dpkg: architecture '$ARCH' is not native to iOS/Terminal." >&2
echo " Debian Linux binaries cannot run without an emulator." >&2
echo " Use --force-architecture only for script-only packages." >&2
rm -rf "$TMP"
return 1
fi
echo "dpkg: warning: forcing architecture '$ARCH'" >&2
;;
esac
echo "Selecting previously unselected package $PKG."
echo "Unpacking $PKG ($VERSION) ..."
if [ -f "$TMP/control/preinst" ]; then
TERM_ROOT="$ROOT" PREFIX="$ROOT" sh "$TMP/control/preinst" install || {
echo "dpkg: preinst script failed" >&2
rm -rf "$TMP"
return 1
}
fi
echo "dpkg: extracting data archive..."
case "$DATA_ARCHIVE" in
*.gz) tar -tzf "$DATA_ARCHIVE" 2>/dev/null > "$TMP/filelist" ;;
*.xz) tar -tJf "$DATA_ARCHIVE" 2>/dev/null > "$TMP/filelist" ;;
*) tar -tf "$DATA_ARCHIVE" 2>/dev/null > "$TMP/filelist" ;;
esac
awk '{ sub(/^\.\//,""); if ($0 != "") print }' "$TMP/filelist" > "$INFO/$PKG.list"
DATA_OK=0
case "$DATA_ARCHIVE" in
*.gz) tar -xzf "$DATA_ARCHIVE" -C "$ROOT" --no-same-owner 2>/dev/null && DATA_OK=1 ;;
*.xz) tar -xJf "$DATA_ARCHIVE" -C "$ROOT" --no-same-owner 2>/dev/null && DATA_OK=1 ;;
*) tar -xf "$DATA_ARCHIVE" -C "$ROOT" --no-same-owner 2>/dev/null && DATA_OK=1 ;;
esac
if [ $DATA_OK -ne 1 ]; then
case "$DATA_ARCHIVE" in
*.gz) tar -xzf "$DATA_ARCHIVE" -C "$ROOT" 2>/dev/null && DATA_OK=1 ;;
*.xz) tar -xJf "$DATA_ARCHIVE" -C "$ROOT" 2>/dev/null && DATA_OK=1 ;;
*) tar -xf "$DATA_ARCHIVE" -C "$ROOT" 2>/dev/null && DATA_OK=1 ;;
esac
fi
if [ $DATA_OK -ne 1 ]; then
echo "dpkg: failed to extract data archive" >&2
rm -rf "$TMP"
return 1
fi
# Warn about native Mach-O payloads: a-Shell cannot execute them.
if [ -f "$INFO/$PKG.list" ]; then
for f in $(cat "$INFO/$PKG.list"); do
case "$f" in
bin/*|sbin/*|usr/bin/*|usr/sbin/*|usr/libexec/*)
if file "$ROOT/$f" 2>/dev/null | grep -q 'Mach-O'; then
echo "dpkg: note: $f is a native Mach-O executable; a-Shell cannot run it." >&2
fi
;;
esac
done
fi
remove_status "$PKG"
INSTALLED_SIZE=$(du -sk "$ROOT" 2>/dev/null)
INSTALLED_SIZE=${INSTALLED_SIZE%%[!0-9]*}
append_status
for script in preinst postinst prerm postrm; do
if [ -f "$TMP/control/$script" ]; then
cp "$TMP/control/$script" "$INFO/$PKG.$script"
chmod +x "$INFO/$PKG.$script"
fi
done
if [ -f "$TMP/control/postinst" ]; then
TERM_ROOT="$ROOT" PREFIX="$ROOT" sh "$TMP/control/postinst" configure || {
echo "dpkg: postinst script failed" >&2
rm -rf "$TMP"
return 1
}
fi
echo "Setting up $PKG ($VERSION) ..."
rm -rf "$TMP"
return 0
}
remove_pkg() {
PKG="$1"
[ -n "$PKG" ] || { usage; return 2; }
ensure
STANZA=$(stanza_for "$PKG")
if [ -z "$STANZA" ]; then
echo "dpkg: package '$PKG' is not installed" >&2
return 1
fi
if [ -f "$INFO/$PKG.prerm" ]; then
( TERM_ROOT="$ROOT" PREFIX="$ROOT" sh "$INFO/$PKG.prerm" remove ) || true
fi
if [ -f "$INFO/$PKG.list" ]; then
for f in $(cat "$INFO/$PKG.list"); do
case "$f" in
""|/*|../*|*/../*) continue ;;
esac
[ -d "$ROOT/$f" ] && continue
rm -f "$ROOT/$f"
done
fi
if [ -f "$INFO/$PKG.postrm" ]; then
( TERM_ROOT="$ROOT" PREFIX="$ROOT" sh "$INFO/$PKG.postrm" remove ) || true
fi
remove_status "$PKG"
rm -f "$INFO/$PKG.list" "$INFO/$PKG.preinst" "$INFO/$PKG.postinst" \
"$INFO/$PKG.prerm" "$INFO/$PKG.postrm"
echo "Removing $PKG ..."
echo "Purging configuration files for $PKG ..."
return 0
}
list_packages() {
ensure
echo "Desired=Unknown/Install/Remove/Purge/Hold"
echo "| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend"
echo "|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)"
echo "||/ Name Version Architecture Description"
echo "+++-====================-================-================-================================="
awk 'BEGIN{RS="";FS="\n"} {
p="";v="";a="";d="";s="";
for (i=1;i<=NF;i++) {
if ($i ~ /^Package: /) p=substr($i,10);
if ($i ~ /^Version: /) v=substr($i,10);
if ($i ~ /^Architecture: /) a=substr($i,14);
if ($i ~ /^Description: /) d=substr($i,14);
if ($i ~ /^Status: /) s=substr($i,9);
}
if (s ~ /installed/) printf "ii %-20s %-16s %-16s %s\n", p, v, a, d;
}' "$STATUS"
}
status_pkg() {
PKG="$1"
STANZA=$(stanza_for "$PKG")
if [ -z "$STANZA" ]; then
echo "dpkg-query: package '$PKG' is not installed" >&2
return 1
fi
printf '%s\n' "$STANZA"
}
list_files() {
PKG="$1"
if [ ! -f "$INFO/$PKG.list" ]; then
echo "dpkg-query: package '$PKG' is not installed" >&2
return 1
fi
sed "s#^#/#" "$INFO/$PKG.list"
}
extract_deb() {
DEB="$1"; DEST="$2"
[ -f "$DEB" ] || { echo "dpkg: cannot access '$DEB'" >&2; return 1; }
[ -n "$DEST" ] || { usage; return 2; }
TMP="$ROOT/tmp/dpkg-x.$$"
rm -rf "$TMP"; mkdir -p "$TMP" "$DEST"
cp "$DEB" "$TMP/pkg.deb"
ar_extract "$TMP/pkg.deb" "$TMP"
DATA=""
for f in "$TMP"/data.tar.*; do [ -f "$f" ] && { DATA="$f"; break; }; done
[ -n "$DATA" ] || { echo "dpkg: no data archive" >&2; rm -rf "$TMP"; return 1; }
tar -xf "$DATA" -C "$DEST" --no-same-owner 2>/dev/null || tar -xf "$DATA" -C "$DEST" 2>/dev/null
rm -rf "$TMP"
}
contents_deb() {
DEB="$1"
[ -f "$DEB" ] || { echo "dpkg: cannot access '$DEB'" >&2; return 1; }
TMP="$ROOT/tmp/dpkg-c.$$"
rm -rf "$TMP"; mkdir -p "$TMP"
cp "$DEB" "$TMP/pkg.deb"
ar_extract "$TMP/pkg.deb" "$TMP"
DATA=""
for f in "$TMP"/data.tar.*; do [ -f "$f" ] && { DATA="$f"; break; }; done
[ -n "$DATA" ] || { echo "dpkg: no data archive" >&2; rm -rf "$TMP"; return 1; }
case "$DATA" in
*.gz) tar -tzf "$DATA" 2>/dev/null > "$TMP/contents" ;;
*.xz) tar -tJf "$DATA" 2>/dev/null > "$TMP/contents" ;;
*) tar -tf "$DATA" 2>/dev/null > "$TMP/contents" ;;
esac
awk '{ sub(/^\.\//,""); if ($0 != "") print }' "$TMP/contents"
rm -rf "$TMP"
}
case "${1:-}" in
-i|--install)
shift
if [ "${1:-}" = "--force-architecture" ]; then FORCE_ARCH=1; shift; fi
[ -n "${1:-}" ] || { usage; exit 2; }
install_deb "$1"
;;
-r|--remove|-P|--purge)
shift
remove_pkg "${1:-}"
;;
-l|--list)
list_packages
;;
-s|--status)
shift
status_pkg "${1:-}"
;;
-L|--listfiles)
shift
list_files "${1:-}"
;;
-x|--extract)
shift
extract_deb "${1:-}" "${2:-}"
;;
--contents)
shift
contents_deb "${1:-}"
;;
--print-architecture)
echo "iphoneos-arm64"
;;
--force-architecture)
FORCE_ARCH=1
shift
[ -n "${1:-}" ] && install_deb "$1"
;;
--version|-V)
echo "dpkg 1.0 (Terminal native package manager)"
;;
""|-h|--help|help)
usage
;;
*)
usage
exit 2
;;
esac
Vendored Executable
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
# dpkg-deb compatibility wrapper (subset: -x, -c, --contents)
case "${1:-}" in
-x|--extract)
shift
sh "$APPDIR/bin/dpkg" -x "$1" "$2"
exit $?
;;
-c|--contents)
shift
sh "$APPDIR/bin/dpkg" --contents "$1"
exit $?
;;
-b|--build)
echo "dpkg-deb: use mkdeb <name> <version> <payload-dir> [arch]" >&2
exit 2
;;
*)
echo "usage: dpkg-deb -x <deb> <dir> | -c <deb> | (use mkdeb to build)" >&2
exit 2
;;
esac
Vendored Executable
+109
View File
@@ -0,0 +1,109 @@
#!/bin/sh
# mkdeb - build a Terminal-compatible .deb package and add it to the local repo
# Usage: mkdeb <name> <version> <payload-dir> [architecture]
# The payload directory is copied to the prefix root; put files under
# bin/, usr/bin/, lib/, share/, etc. Maintainer scripts may be placed in
# <payload-dir>/DEBIAN/{preinst,postinst,prerm,postrm}.
ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
NAME="${1:-}"
VERSION="${2:-}"
PAYLOAD="${3:-}"
ARCH="${4:-iphoneos-arm64}"
MAINTAINER="${MKDEB_MAINTAINER:-Terminal User <mobile@localhost>}"
DESCRIPTION="${MKDEB_DESCRIPTION:-$NAME plugin for Terminal}"
usage() {
echo "usage: mkdeb <name> <version> <payload-dir> [architecture]" >&2
echo " architecture: iphoneos-arm64 (default), all, darwin-arm64" >&2
exit 2
}
[ -n "$NAME" ] && [ -n "$VERSION" ] && [ -n "$PAYLOAD" ] || usage
[ -d "$PAYLOAD" ] || { echo "mkdeb: payload directory not found: $PAYLOAD" >&2; exit 1; }
case "$NAME" in
*[!A-Za-z0-9+.-]*|"") echo "mkdeb: invalid package name: $NAME" >&2; exit 1 ;;
esac
echo "mkdeb: start $NAME $VERSION"
mkdir -p "$ROOT/repo/pool" "$ROOT/tmp" "$ROOT/var/lib/dpkg/info"
DEB="$ROOT/repo/pool/${NAME}_${VERSION}_${ARCH}.deb"
if python3 -c 'pass' >/dev/null 2>&1 && [ -f "$APPDIR/bin/pkgtool" ]; then
MKDEB_MAINTAINER="$MAINTAINER" MKDEB_DESCRIPTION="$DESCRIPTION" \
python3 "$APPDIR/bin/pkgtool" build "$DEB" "$NAME" "$VERSION" "$ARCH" "$PAYLOAD" "${MKDEB_DEPENDS:-}"
exit $?
fi
TMP="$ROOT/tmp/mkdeb.$$"
rm -rf "$TMP"
mkdir -p "$TMP/DEBIAN"
# Control file.
{
echo "Package: $NAME"
echo "Version: $VERSION"
echo "Architecture: $ARCH"
echo "Maintainer: $MAINTAINER"
echo "Section: plugins"
echo "Priority: optional"
echo "Description: $DESCRIPTION"
[ -n "${MKDEB_DEPENDS:-}" ] && echo "Depends: $MKDEB_DEPENDS"
} > "$TMP/DEBIAN/control"
# Optional maintainer scripts from the payload's DEBIAN directory.
for script in preinst postinst prerm postrm; do
if [ -f "$PAYLOAD/DEBIAN/$script" ]; then
cp "$PAYLOAD/DEBIAN/$script" "$TMP/DEBIAN/$script"
chmod +x "$TMP/DEBIAN/$script"
fi
done
# Build the two tar archives. Copy the payload first (no shell pipeline;
# nested tar pipelines can stall inside a-Shell's in-process command model).
DATA_TMP="$TMP/data"
mkdir -p "$DATA_TMP"
echo "mkdeb: copying payload..."
cp -R "$PAYLOAD"/. "$DATA_TMP"/ 2>/dev/null
rm -rf "$DATA_TMP/DEBIAN"
echo "mkdeb: writing data archive..."
tar -czf "$TMP/data.tar.gz" -C "$DATA_TMP" .
echo "mkdeb: writing control archive..."
tar -czf "$TMP/control.tar.gz" -C "$TMP" DEBIAN
printf '2.0\n' > "$TMP/debian-binary"
DEB="$ROOT/repo/pool/${NAME}_${VERSION}_${ARCH}.deb"
rm -f "$DEB"
echo "mkdeb: writing ar archive..."
rm -f "$DEB"
tar --format=ar -cf "$DEB" -C "$TMP" debian-binary control.tar.gz data.tar.gz >/dev/null 2>&1
if [ ! -s "$DEB" ]; then
echo "mkdeb: tar ar writer unavailable; trying Python fallback..." >&2
if python3 -c 'pass' >/dev/null 2>&1 && [ -f "$APPDIR/bin/ardeb" ]; then
python3 "$APPDIR/bin/ardeb" pack "$DEB" "$TMP/debian-binary" "$TMP/control.tar.gz" "$TMP/data.tar.gz"
fi
fi
if [ ! -s "$DEB" ]; then
echo "mkdeb: failed to create $DEB" >&2
rm -rf "$TMP"
exit 1
fi
# Update Packages: append the new package stanza.
SIZE=$(wc -c < "$DEB" | tr -d ' ')
PACKAGES="$ROOT/repo/Packages"
touch "$PACKAGES"
{
echo "Package: $NAME"
echo "Version: $VERSION"
echo "Architecture: $ARCH"
echo "Maintainer: $MAINTAINER"
echo "Section: plugins"
echo "Priority: optional"
echo "Filename: pool/$(basename "$DEB")"
echo "Size: $SIZE"
echo "Description: $DESCRIPTION"
[ -n "${MKDEB_DEPENDS:-}" ] && echo "Depends: $MKDEB_DEPENDS"
echo ""
} >> "$PACKAGES"
rm -rf "$TMP"
echo "$DEB"
Vendored
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""pkgtool - Python package backend for Terminal's native apt/dpkg.
This avoids a-Shell's shell/tar/ar quirks for package construction and
extraction. It is used when Python 3 is available (the full a-Shell app has
it). Native Mach-O payloads are installed but flagged as non-runnable.
"""
import io
import os
import shutil
import sys
import tarfile
import tempfile
ROOT = os.environ.get("TERM_ROOT") or os.path.join(os.path.expanduser("~"), "Documents", ".terminal")
INFO = os.path.join(ROOT, "var", "lib", "dpkg", "info")
STATUS = os.path.join(ROOT, "var", "lib", "dpkg", "status")
PACKAGES = os.path.join(ROOT, "repo", "Packages")
def ar_pack(out_path, members):
with open(out_path, "wb") as out:
out.write(b"!<arch>\n")
for path in members:
data = open(path, "rb").read()
name = os.path.basename(path)
if len(name) > 15:
raise SystemExit("pkgtool: ar member name too long: " + name)
header = (
name.ljust(16) + "0".ljust(12) + "0".ljust(6) + "0".ljust(6)
+ "100644".ljust(8) + str(len(data)).ljust(10) + "`\n"
).encode("ascii")
out.write(header)
out.write(data)
if len(data) & 1:
out.write(b"\n")
def ar_unpack(archive, dest):
data = open(archive, "rb").read()
if not data.startswith(b"!<arch>\n"):
raise SystemExit("pkgtool: not a Debian ar archive")
pos = 8
while pos + 60 <= len(data):
header = data[pos:pos + 60]
pos += 60
name = header[:16].decode("ascii", "ignore").strip().rstrip("/")
if not name:
continue
try:
size = int(header[48:58].decode("ascii", "ignore").strip() or "0")
except ValueError:
size = 0
body = data[pos:pos + size]
pos += size + (size & 1)
if name in (".", ".."):
continue
open(os.path.join(dest, name), "wb").write(body)
def safe_extract(tar_path, dest):
os.makedirs(dest, exist_ok=True)
with tarfile.open(tar_path, "r:*") as tar:
for member in tar.getmembers():
name = member.name.lstrip("./")
if not name or name.startswith("/") or ".." in name.split("/"):
continue
target = os.path.join(dest, name)
if member.isdir():
os.makedirs(target, exist_ok=True)
elif member.isfile():
os.makedirs(os.path.dirname(target), exist_ok=True)
src = tar.extractfile(member)
if src is None:
continue
with open(target, "wb") as out:
shutil.copyfileobj(src, out)
os.chmod(target, member.mode & 0o777)
elif member.issym():
os.makedirs(os.path.dirname(target), exist_ok=True)
# Symlinks are installed relative to the package root.
try:
os.symlink(member.linkname, target)
except FileExistsError:
os.unlink(target)
os.symlink(member.linkname, target)
def parse_control(control_path):
fields = {}
with open(control_path, "r", errors="replace") as f:
for line in f:
if line.startswith(" ") or line.startswith("\t"):
continue
if ":" in line:
k, v = line.split(":", 1)
fields[k.strip()] = v.strip()
return fields
def append_packages(fields, filename):
os.makedirs(os.path.dirname(PACKAGES), exist_ok=True)
with open(PACKAGES, "a") as f:
f.write("Package: %s\n" % fields.get("Package", ""))
f.write("Version: %s\n" % fields.get("Version", ""))
f.write("Architecture: %s\n" % fields.get("Architecture", "iphoneos-arm64"))
f.write("Maintainer: %s\n" % fields.get("Maintainer", "unknown"))
f.write("Section: %s\n" % fields.get("Section", "plugins"))
f.write("Priority: %s\n" % fields.get("Priority", "optional"))
f.write("Filename: pool/%s\n" % os.path.basename(filename))
f.write("Size: %d\n" % os.path.getsize(filename))
if fields.get("Depends"):
f.write("Depends: %s\n" % fields["Depends"])
f.write("Description: %s\n\n" % fields.get("Description", ""))
def build(out_path, name, version, arch, payload, depends=""):
os.makedirs(os.path.dirname(packages_dir()), exist_ok=True)
tmp = tempfile.mkdtemp(prefix="mkdeb.", dir=os.path.join(ROOT, "tmp"))
try:
control_dir = os.path.join(tmp, "DEBIAN")
os.makedirs(control_dir, exist_ok=True)
control = (
"Package: %s\nVersion: %s\nArchitecture: %s\n"
"Maintainer: %s\nSection: plugins\nPriority: optional\n"
"Description: %s\n"
% (name, version, arch,
os.environ.get("MKDEB_MAINTAINER", "Terminal User <mobile@localhost>"),
os.environ.get("MKDEB_DESCRIPTION", name + " plugin for Terminal"))
)
if depends:
control += "Depends: %s\n" % depends
open(os.path.join(control_dir, "control"), "w").write(control)
payload_debian = os.path.join(payload, "DEBIAN")
if os.path.isdir(payload_debian):
for script in ("preinst", "postinst", "prerm", "postrm"):
src = os.path.join(payload_debian, script)
if os.path.isfile(src):
shutil.copy(src, os.path.join(control_dir, script))
control_tar = os.path.join(tmp, "control.tar.gz")
with tarfile.open(control_tar, "w:gz") as tar:
for root, dirs, files in os.walk(control_dir):
for fn in files:
full = os.path.join(root, fn)
arc = os.path.relpath(full, tmp)
tar.add(full, arcname=arc)
data_tar = os.path.join(tmp, "data.tar.gz")
with tarfile.open(data_tar, "w:gz") as tar:
for root, dirs, files in os.walk(payload):
dirs[:] = [d for d in dirs if not (root == payload and d == "DEBIAN")]
for fn in files:
full = os.path.join(root, fn)
arc = os.path.relpath(full, payload)
tar.add(full, arcname="./" + arc)
open(os.path.join(tmp, "debian-binary"), "w").write("2.0\n")
ar_pack(out_path, [os.path.join(tmp, "debian-binary"), control_tar, data_tar])
fields = parse_control(os.path.join(control_dir, "control"))
append_packages(fields, out_path)
print(out_path)
return 0
finally:
shutil.rmtree(tmp, ignore_errors=True)
def packages_dir():
return os.path.join(ROOT, "repo", "pool")
def install(deb):
if not os.path.isfile(deb):
print("pkgtool: cannot access archive %s" % deb, file=sys.stderr)
return 1
os.makedirs(INFO, exist_ok=True)
os.makedirs(os.path.dirname(STATUS), exist_ok=True)
tmp = tempfile.mkdtemp(prefix="dpkg.", dir=os.path.join(ROOT, "tmp"))
try:
ar_unpack(deb, tmp)
control_tar = data_tar = None
for fn in os.listdir(tmp):
if fn.startswith("control.tar"):
control_tar = os.path.join(tmp, fn)
elif fn.startswith("data.tar"):
data_tar = os.path.join(tmp, fn)
if not control_tar or not data_tar:
print("pkgtool: missing control or data archive", file=sys.stderr)
return 1
control_dir = os.path.join(tmp, "control")
safe_extract(control_tar, control_dir)
control_path = os.path.join(control_dir, "control")
if not os.path.isfile(control_path):
control_path = os.path.join(control_dir, "DEBIAN", "control")
if not os.path.isfile(control_path):
print("pkgtool: control file not found", file=sys.stderr)
return 1
fields = parse_control(control_path)
pkg = fields.get("Package", "")
version = fields.get("Version", "")
arch = fields.get("Architecture", "iphoneos-arm64")
if not pkg or not version:
print("pkgtool: invalid control (Package/Version missing)", file=sys.stderr)
return 1
if arch not in ("", "all", "iphoneos-arm64", "darwin-arm64", "a-shell"):
print("pkgtool: architecture '%s' is not native to iOS/Terminal" % arch, file=sys.stderr)
print("pkgtool: use the shell dpkg --force-architecture only for script packages", file=sys.stderr)
return 1
print("Selecting previously unselected package %s." % pkg)
print("Unpacking %s (%s) ..." % (pkg, version))
safe_extract(data_tar, ROOT)
file_list = []
with tarfile.open(data_tar, "r:*") as tar:
for member in tar.getmembers():
name = member.name.lstrip("./")
if name and not name.startswith("/") and ".." not in name.split("/"):
file_list.append(name)
if member.isfile() and any(part in ("bin", "sbin", "libexec") for part in name.split("/")):
full = os.path.join(ROOT, name)
try:
magic = open(full, "rb").read(4)
except OSError:
magic = b""
if magic in (b"\xcf\xfa\xed\xfe", b"\xfe\xed\xfa\xcf"):
print("pkgtool: note: %s is a native Mach-O binary; a-Shell cannot run it." % name, file=sys.stderr)
open(os.path.join(INFO, pkg + ".list"), "w").write("\n".join(file_list) + "\n")
# Maintainer scripts are kept for inspection/removal, but not executed:
# a-Shell has no fork/exec for arbitrary shell children in the Python
# backend. Script-only packages should use dpkg's shell path if needed.
for script in ("preinst", "postinst", "prerm", "postrm"):
src = os.path.join(control_dir, "DEBIAN", script)
if not os.path.isfile(src):
src = os.path.join(control_dir, script)
if os.path.isfile(src):
shutil.copy(src, os.path.join(INFO, pkg + "." + script))
# Replace the package stanza in the status database.
stanzas = []
if os.path.isfile(STATUS):
with open(STATUS, "r", errors="replace") as f:
current = []
for line in f:
if line.strip() == "":
if current:
stanzas.append("".join(current))
current = []
else:
current.append(line)
if current:
stanzas.append("".join(current))
kept = [s for s in stanzas if not s.startswith("Package: %s\n" % pkg)]
kept.append(
"Package: %s\nStatus: install ok installed\nPriority: %s\nSection: %s\n"
"Maintainer: %s\nArchitecture: %s\nVersion: %s\nDescription: %s\n\n"
% (pkg, fields.get("Priority", "optional"), fields.get("Section", "plugins"),
fields.get("Maintainer", "unknown"), arch, version,
fields.get("Description", ""))
)
open(STATUS, "w").write("\n".join(kept))
print("Setting up %s (%s) ..." % (pkg, version))
return 0
finally:
shutil.rmtree(tmp, ignore_errors=True)
def main():
if len(sys.argv) < 2:
print(__doc__)
return 2
cmd = sys.argv[1]
if cmd == "build" and len(sys.argv) >= 7:
return build(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6],
sys.argv[7] if len(sys.argv) > 7 else "")
if cmd == "install" and len(sys.argv) >= 3:
return install(sys.argv[2])
print(__doc__)
return 2
if __name__ == "__main__":
raise SystemExit(main())
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# plugin-run - run a native Terminal plugin safely.
# a-Shell's in-process command loader refuses standalone Mach-O binaries;
# script plugins and WebAssembly plugins can run. This wrapper gives a clear
# error instead of "command not found" for native packages.
ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
if [ $# -eq 0 ]; then
echo "usage: plugin-run <command> [args...]" >&2
exit 2
fi
CMD="$1"
shift
FOUND=$(which "$CMD" 2>/dev/null)
if [ -z "$FOUND" ]; then
echo "plugin-run: '$CMD' not found in PATH" >&2
exit 127
fi
MAGIC=$(dd if="$FOUND" bs=4 count=1 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \n')
case "$MAGIC" in
cffaedfe|feedfacf|cafebabe|bebafeca)
echo "plugin-run: '$CMD' is a native Mach-O executable." >&2
echo "a-Shell does not execute standalone Mach-O binaries in its sandbox." >&2
echo "Native Procursus/Havoc CLI packages require a jailbreak or a Linux emulator." >&2
echo "Script plugins (#!/bin/sh, python, perl, lua, wasm) can run normally." >&2
exit 126
;;
esac
"$FOUND" "$@"
Vendored Executable
+222
View File
@@ -0,0 +1,222 @@
#!/bin/sh
# termenv - independent native plugin environment for Terminal
# This is a POSIX sh environment, not a Linux userland. Packages are
# Terminal-compatible .deb files (scripts or iOS/Darwin arm64 binaries).
ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
APPDIR="${APPDIR:-/Applications/Terminal.app}"
PROFILE="$ROOT/etc/profile"
SOURCES="$ROOT/etc/apt/sources.list"
usage() {
printf '%s\n' "Usage: termenv <command>"
printf '%s\n' ""
printf '%s\n' " init Create the independent prefix and the local apt repository"
printf '%s\n' " status Show prefix, PATH, apt/dpkg and installed packages"
printf '%s\n' " enter Print the command that activates the environment"
printf '%s\n' " run <cmd...> Run a command inside the environment"
printf '%s\n' " on Add activation to ~/Documents/.profile"
printf '%s\n' " off Remove activation from ~/Documents/.profile"
printf '%s\n' " selftest init + apt update + install the sample \"hello\" package"
printf '%s\n' " clean Remove apt caches and temporary files"
printf '%s\n' ""
printf '%s\n' "Environment:"
printf '%s\n' " TERM_ROOT=~/Documents/.terminal"
printf '%s\n' " PREFIX=$TERM_ROOT"
}
ensure_dirs() {
mkdir -p "$ROOT/bin" "$ROOT/sbin" "$ROOT/lib" "$ROOT/include" \
"$ROOT/share/man" "$ROOT/etc/apt" \
"$ROOT/var/lib/dpkg/info" "$ROOT/var/lib/apt/lists" \
"$ROOT/var/cache/apt/archives" "$ROOT/var/log" \
"$ROOT/tmp" "$ROOT/repo/pool" "$ROOT/home"
[ -f "$ROOT/var/lib/dpkg/status" ] || : > "$ROOT/var/lib/dpkg/status"
}
write_profile() {
if [ ! -f "$PROFILE" ]; then
cat > "$PROFILE" <<EOF
# Terminal plugin environment (generated by termenv init)
TERM_ROOT="\$HOME/Documents/.terminal"
export TERM_ROOT
PREFIX="\$TERM_ROOT"
export PREFIX
TERM_PLUGIN_ENV=1
export TERM_PLUGIN_ENV
PATH="\$TERM_ROOT/bin:\$TERM_ROOT/sbin:\$TERM_ROOT/usr/bin:\$PATH"
export PATH
LD_LIBRARY_PATH="\$TERM_ROOT/lib:\$TERM_ROOT/usr/lib:\$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
CPATH="\$TERM_ROOT/include:\$CPATH"
export CPATH
LIBRARY_PATH="\$TERM_ROOT/lib:\$TERM_ROOT/usr/lib:\$LIBRARY_PATH"
export LIBRARY_PATH
PKG_CONFIG_PATH="\$TERM_ROOT/lib/pkgconfig:\$PKG_CONFIG_PATH"
export PKG_CONFIG_PATH
MANPATH="\$TERM_ROOT/share/man:\$MANPATH"
export MANPATH
EOF
fi
}
write_sources() {
LOCAL_LINE="deb [trusted=yes] file://$ROOT/repo ./"
PROCURUS_LINE="deb [trusted=yes] https://repo.procurs.us/ ./"
HAVOC_LINE="deb [trusted=yes] https://repo.havoc.app/ ./"
if [ ! -f "$SOURCES" ]; then
{
printf '%s\n' "$LOCAL_LINE"
printf '%s\n' "$PROCURUS_LINE"
printf '%s\n' "$HAVOC_LINE"
} > "$SOURCES"
return
fi
TMP="$ROOT/tmp/sources.$$"
awk -v desired="$LOCAL_LINE" '
/^deb .*file:\/\/.*\/\.terminal\/repo/ { print desired; found=1; next }
{ print }
END { if (!found) print desired }
' "$SOURCES" > "$TMP"
mv "$TMP" "$SOURCES"
if ! grep -qF "https://repo.procurs.us/" "$SOURCES"; then
printf '%s\n' "$PROCURUS_LINE" >> "$SOURCES"
fi
if ! grep -qF "https://repo.havoc.app/" "$SOURCES"; then
printf '%s\n' "$HAVOC_LINE" >> "$SOURCES"
fi
}
make_sample() {
if grep -q '^Package: hello$' "$ROOT/repo/Packages" 2>/dev/null; then
return 0
fi
SAMPLE="$ROOT/share/termenv/samples/hello"
mkdir -p "$SAMPLE/bin"
cat > "$SAMPLE/bin/hello" << 'EOF'
#!/bin/sh
echo "Hello from the Terminal plugin environment."
echo "PREFIX=$PREFIX"
echo "TERM_ROOT=$TERM_ROOT"
if [ -d "$HOME/Documents/_sandbox" ]; then
echo "Sandbox escape: available (see $HOME/Documents/_sandbox)"
fi
EOF
chmod +x "$SAMPLE/bin/hello"
if [ -f "$APPDIR/bin/mkdeb" ]; then
sh "$APPDIR/bin/mkdeb" hello 1.0.0 "$SAMPLE" iphoneos-arm64
echo "termenv: mkdeb exit=$?"
fi
}
do_init() {
echo "termenv: creating directories..."
ensure_dirs
echo "termenv: writing profile/sources..."
write_profile
write_sources
[ -f "$ROOT/repo/Packages" ] || : > "$ROOT/repo/Packages"
echo "termenv: creating sample package..."
make_sample
echo "termenv: sample ready"
echo "Prefix: $ROOT"
echo "Profile: $PROFILE"
echo "Run: source \"$PROFILE\" (or: termenv on)"
}
do_status() {
ensure_dirs
echo "Terminal native plugin environment"
echo " prefix: $ROOT"
echo " profile: $PROFILE"
echo " apt: $APPDIR/bin/apt"
echo " dpkg: $APPDIR/bin/dpkg"
echo " sources: $SOURCES"
if [ -f "$SOURCES" ]; then
sed 's/^/ /' "$SOURCES"
fi
echo " packages:"
if [ -s "$ROOT/var/lib/dpkg/status" ]; then
awk '/^Package: /{printf " %s", substr($0,10)} /^Version: /{printf " %s", substr($0,10)} /^Status: /{status=substr($0,9)} /^$/{if(status ~ /installed/) print ""}' "$ROOT/var/lib/dpkg/status"
else
echo " (none)"
fi
echo " sandbox: $HOME/Documents/_sandbox"
}
do_run() {
if [ $# -eq 0 ]; then
echo "usage: termenv run <command...>" >&2
exit 2
fi
if [ -f "$PROFILE" ]; then
. "$PROFILE"
fi
"$@"
}
do_on() {
do_init
PROFILE_FILE="$HOME/Documents/.profile"
MARK_BEGIN="# >>> Terminal plugin environment >>>"
MARK_END="# <<< Terminal plugin environment <<<"
if grep -qF "$MARK_BEGIN" "$PROFILE_FILE" 2>/dev/null; then
echo "Activation already present in $PROFILE_FILE"
return 0
fi
{
echo "$MARK_BEGIN"
echo "source \"\$HOME/Documents/.terminal/etc/profile\""
echo "$MARK_END"
} >> "$PROFILE_FILE"
echo "Activation added to $PROFILE_FILE"
}
do_off() {
PROFILE_FILE="$HOME/Documents/.profile"
if [ ! -f "$PROFILE_FILE" ]; then
echo "No $PROFILE_FILE"
return 0
fi
TMP="$ROOT/tmp/profile.$$"
awk '
/# >>> Terminal plugin environment >>>/ {skip=1; next}
/# <<< Terminal plugin environment <<</ {skip=0; next}
!skip {print}
' "$PROFILE_FILE" > "$TMP"
mv "$TMP" "$PROFILE_FILE"
echo "Activation removed from $PROFILE_FILE"
}
do_clean() {
rm -rf "$ROOT/tmp"/*
rm -f "$ROOT/var/cache/apt/archives/"*.deb
echo "Cleaned caches"
}
do_selftest() {
do_init
. "$PROFILE"
echo "=== apt update ==="
APT_SKIP_REMOTE=1 sh "$APPDIR/bin/apt" update
echo "=== apt install hello ==="
sh "$APPDIR/bin/apt" install hello
echo "=== hello ==="
hello
echo "=== installed packages ==="
sh "$APPDIR/bin/apt" list --installed
echo "=== dpkg -l ==="
sh "$APPDIR/bin/dpkg" -l
}
case "${1:-}" in
init) do_init ;;
status) do_status ;;
enter) echo "source \"$PROFILE\"" ;;
run) shift; do_run "$@" ;;
on) do_on ;;
off) do_off ;;
clean) do_clean ;;
selftest) do_selftest ;;
""|-h|--help|help) usage ;;
*) usage; exit 2 ;;
esac