first commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
# sh-goodies: a useful collection of single-file, POSIX-compatible shell scripts
|
||||||
|
|
||||||
|
## List
|
||||||
|
|
||||||
|
* [DaemonTamer V2](./dt): local service manager to daemonize any command
|
||||||
|
* [Outport](./outport): expose reverse tunnels via SSH
|
||||||
|
* [shee.sh](./shee.sh): simple LLM chat application for OpenAI-compatible endpoints
|
||||||
|
* [ShellFlow](./flow.sh): simple workflow orchestration script
|
||||||
|
* [LShar](./lshar.sh): create self-extracting archives in the form of shell scripts
|
||||||
|
* [ShellBeat](./shellbeat.sh): a [bytebeat](https://stellartux.github.io/websynth/guide.html) audio formula player in pure shell (also depends on xxd)
|
||||||
|
* [GHMD](./ghmd.sh): a command suite to set up your own self-hosted Git repositories
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# DaemonTamer: simple and portable local service manager in pure sh
|
||||||
|
# that allows to quickly daemonize any command and manage it as a service
|
||||||
|
# Depends on POSIX versions of sh, nohup, cut and grep
|
||||||
|
# the unreg option also depends on mktemp (not POSIX but present everywhere)
|
||||||
|
# Run sh dt help to see the parameters
|
||||||
|
# Created by Luxferre in 2025, released into public domain with no warranties
|
||||||
|
|
||||||
|
err() {
|
||||||
|
echo $*
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
msgexit() {
|
||||||
|
echo $*
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
find_service() {
|
||||||
|
sline="$(grep -E "^$1\s+" $CONF)"
|
||||||
|
[ -z "$sline" ] && err "No such service found in the config!"
|
||||||
|
echo "$sline"
|
||||||
|
}
|
||||||
|
|
||||||
|
CONF="$HOME/.dt.conf"
|
||||||
|
TMPDIR="$(dirname $(mktemp -u))"
|
||||||
|
srv_name="$2"
|
||||||
|
[ "$1" != "help" ] && [ -z "$srv_name" ] && err "Service name is required!"
|
||||||
|
pidfile="$TMPDIR/$srv_name.pid"
|
||||||
|
case $1 in
|
||||||
|
help)
|
||||||
|
echo 'DaemonTamer v2'
|
||||||
|
echo '--------------'
|
||||||
|
echo "Usage: $0 {reg|unreg|start|stop|restart} SERVICE_NAME [REG_PARAMS]"
|
||||||
|
echo 'Parameters for the reg subcommand:'
|
||||||
|
echo "$0 reg 'COMMAND' [WORKDIR] [STDOUT_LOG_FILE] [STDERR_LOG_FILE] [PRESTART] [POSTSTART] [PRESTOP] [POSTSTOP]"
|
||||||
|
echo '(the COMMAND must be specified in single quotes and without nohup)'
|
||||||
|
echo
|
||||||
|
echo 'Created by Luxferre in 2025, released into public domain with no warranties'
|
||||||
|
;;
|
||||||
|
reg)
|
||||||
|
srv_cmd="$3"
|
||||||
|
[ -z "$srv_cmd" ] && err "Service runner command is required!"
|
||||||
|
srv_wdir="$4"
|
||||||
|
srv_outlog="$5"
|
||||||
|
srv_errlog="$6"
|
||||||
|
srv_prestart="$7"
|
||||||
|
srv_poststart="$8"
|
||||||
|
srv_prestop="$9"
|
||||||
|
srv_poststop="$10"
|
||||||
|
[ -z "$srv_wdir" ] && srv_wdir='-'
|
||||||
|
[ -z "$srv_outlog" ] && srv_outlog='/dev/null'
|
||||||
|
[ -z "$srv_errlog" ] && srv_errlog='/dev/null'
|
||||||
|
[ -z "$srv_prestart" ] && srv_prestart='-'
|
||||||
|
[ -z "$srv_poststart" ] && srv_poststart='-'
|
||||||
|
[ -z "$srv_prestop" ] && srv_prestop='-'
|
||||||
|
[ -z "$srv_poststop" ] && srv_poststop='-'
|
||||||
|
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$srv_name" "$srv_cmd" \
|
||||||
|
"$srv_wdir" "$srv_outlog" "$srv_errlog" "$srv_prestart" "$srv_poststart" \
|
||||||
|
"$srv_prestop" "$srv_poststop" >> $CONF
|
||||||
|
echo "Service $srv_name registered"
|
||||||
|
;;
|
||||||
|
unreg)
|
||||||
|
srvline="$(find_service "$srv_name")"
|
||||||
|
TCONF="$(mktemp)"
|
||||||
|
grep -v "$srv_name" $CONF > $TCONF && mv $TCONF $CONF
|
||||||
|
echo "Service $srv_name unregistered"
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
[ -f "$pidfile" ] && msgexit "Service $srv is already running";
|
||||||
|
srvline="$(find_service "$srv_name")"
|
||||||
|
srv_cmd="$(echo "$srvline" | cut -f2)"
|
||||||
|
srv_wdir="$(echo "$srvline" | cut -f3)"
|
||||||
|
srv_outlog="$(echo "$srvline" | cut -f4)"
|
||||||
|
srv_errlog="$(echo "$srvline" | cut -f5)"
|
||||||
|
srv_prestart="$(echo "$srvline" | cut -f6)"
|
||||||
|
srv_poststart="$(echo "$srvline" | cut -f7)"
|
||||||
|
echo "Starting service $srv_name"
|
||||||
|
[ "$srv_wdir" != "-" ] && cd "$srv_wdir"
|
||||||
|
[ "$srv_prestart" != "-" ] && $SHELL -c "$srv_prestart"
|
||||||
|
nohup $srv_cmd >$srv_outlog 2>$srv_errlog & echo $! > $pidfile
|
||||||
|
[ "$srv_poststart" != "-" ] && $SHELL -c "$srv_poststart"
|
||||||
|
echo "Service $srv_name started"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
[ ! -f "$pidfile" ] && msgexit "Service $srv is not running";
|
||||||
|
srvline="$(find_service "$srv_name")"
|
||||||
|
srv_prestop="$(echo "$srvline" | cut -f8)"
|
||||||
|
srv_poststop="$(echo "$srvline" | cut -f9)"
|
||||||
|
echo "Stopping service $srv_name"
|
||||||
|
[ "$srv_prestop" != "-" ] && $SHELL -c "$srv_prestop"
|
||||||
|
kill $(cat $pidfile)
|
||||||
|
rm -f $pidfile
|
||||||
|
[ "$srv_poststop" != "-" ] && $SHELL -c "$srv_poststop"
|
||||||
|
echo "Service $srv_name stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
srvline="$(find_service "$srv_name")"
|
||||||
|
$SHELL $0 stop $srv_name
|
||||||
|
$SHELL $0 start $srv_name
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo 'Invalid operation! Pick start, stop, restart, reg, unreg or help'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# ShellFlow: the simplest workflow orchestrator in under
|
||||||
|
# 20 SLOC of POSIX shell, using Unix pipelines
|
||||||
|
#
|
||||||
|
# Usage: echo some_input | /path/to/flow.sh /path/to/workflow
|
||||||
|
#
|
||||||
|
# Exposes the following variables to workflows and tasks:
|
||||||
|
# - SFLIB - resolved path to this file
|
||||||
|
# - WFDIR - directory of the workflow
|
||||||
|
#
|
||||||
|
# Also exposes the debugout function to output to stderr
|
||||||
|
#
|
||||||
|
# Typical workflow script structure: ./task1 | ./task2 | ./task3 ...
|
||||||
|
# (the workflow directory becomes the current working directory)
|
||||||
|
#
|
||||||
|
# Data format: workflow name, tab character, all other input
|
||||||
|
#
|
||||||
|
# Typical task script structure (if shell-based):
|
||||||
|
# #!/bin/sh
|
||||||
|
# . $SFLIB # source the library part of the script
|
||||||
|
# do_task() { # accepts workflow name into $1 and all input into $2
|
||||||
|
# # your actions here
|
||||||
|
# }
|
||||||
|
# run-task
|
||||||
|
#
|
||||||
|
# You can call different workflows from inside tasks by referring to
|
||||||
|
# this very script using the $SFLIB environment variable or by calling
|
||||||
|
# the sfrun function
|
||||||
|
#
|
||||||
|
# Created by Luxferre in 2025, released into public domain
|
||||||
|
|
||||||
|
# ShellFlow task function library
|
||||||
|
|
||||||
|
# print to stderr
|
||||||
|
debugout() { printf '%s\n' "$*" >&2; }
|
||||||
|
|
||||||
|
# format the message and print to stdout
|
||||||
|
flowout() { printf '%s\t%s' "$1" "$2"; }
|
||||||
|
|
||||||
|
# use this in all task definitions
|
||||||
|
run-task() {
|
||||||
|
input="$(cat)"
|
||||||
|
wf="$(printf '%s' "$input" | cut -f 1)"
|
||||||
|
body="$(printf '%s' "$input" | cut -f 2-)"
|
||||||
|
flowout "$wf" "$(do_task "$wf" "$body")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ShellFlow entry point
|
||||||
|
|
||||||
|
sfrun() {
|
||||||
|
SFLIB="$(readlink -f "$0")"
|
||||||
|
wfbase="$(basename "$1")"
|
||||||
|
WFDIR="$(readlink -f $(dirname -- "$1"))"
|
||||||
|
cd "$WFDIR"
|
||||||
|
debugout "Starting workflow $wfbase..."
|
||||||
|
flowout $wfbase "$(cat)" | SFLIB="$SFLIB" WFDIR="$WFDIR" $SHELL $wfbase | cut -f 2-
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$(basename "$0")" == "flow.sh" ] && sfrun $*
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# GHMD is a shell script for setting up any of the following things:
|
||||||
|
# 1) a Git user on a remote instance (initial setup)
|
||||||
|
# 2) a private Git repo on the remote instance
|
||||||
|
# 3) a public Git repo using git:// protocol on the remote instance (if it uses systemd)
|
||||||
|
# 4) Git daemon (serving git:// protocol) on the remote instance
|
||||||
|
# 5) access via the Git daemon to individual repositories
|
||||||
|
# 6) repository description and clone URL for some services like stagit
|
||||||
|
# Usage: ghmd.sh [newsrv | newrepo | gitd-sd | gitd-publish | gitd-unpublish | set-repo-desc | set-repo-url | set-repo-owner] user@host [name] ("[desc|url]")
|
||||||
|
# regardless of the actions, the user must have root permissions
|
||||||
|
# prerequisite: git package must be already installed on the server
|
||||||
|
# Created by Luxferre in 2024, released into public domain
|
||||||
|
|
||||||
|
GITUSR="git"
|
||||||
|
PARAM="$1"
|
||||||
|
USERHOST="$2"
|
||||||
|
THOST="${USERHOST##*@}"
|
||||||
|
SSHCMD="ssh $USERHOST" # instance ssh cmd
|
||||||
|
|
||||||
|
help() {
|
||||||
|
cat <<EOF
|
||||||
|
GHMD: single shell script for self-hosted Git repository management
|
||||||
|
|
||||||
|
Initialize Git user on a new server (must have Git installed):
|
||||||
|
|
||||||
|
$0 newsrv root@hostname
|
||||||
|
|
||||||
|
Create a new Git repository (e.g. git@hostname:myrepo.git):
|
||||||
|
|
||||||
|
$0 newrepo root@hostname myrepo
|
||||||
|
|
||||||
|
Set up Git protocol daemon (if the host is running systemd):
|
||||||
|
|
||||||
|
$0 gitd-sd root@hostname
|
||||||
|
|
||||||
|
Publish an existing Git repository on the daemon (e.g. git://hostname/myrepo.git):
|
||||||
|
|
||||||
|
$0 gitd-publish root@hostname myrepo
|
||||||
|
|
||||||
|
Unpublish an existing Git repository on the daemon:
|
||||||
|
|
||||||
|
$0 gitd-unpublish root@hostname myrepo
|
||||||
|
|
||||||
|
Set the (displayed) description of an existing Git repository:
|
||||||
|
|
||||||
|
$0 set-repo-desc root@hostname myrepo "This is a cool repo"
|
||||||
|
|
||||||
|
Set the (displayed) clone URL of an existing Git repository:
|
||||||
|
|
||||||
|
$0 set-repo-url root@hostname myrepo git://hostname/myrepo.git
|
||||||
|
|
||||||
|
Set the (displayed) owner name of an existing Git repository:
|
||||||
|
|
||||||
|
$0 set-repo-owner root@hostname myrepo "Me, the Author"
|
||||||
|
|
||||||
|
Created by Luxferre in 2024, released into public domain
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$PARAM" in
|
||||||
|
"help")
|
||||||
|
help
|
||||||
|
;;
|
||||||
|
"newsrv") # set up Git user on the server
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
$SSHCMD "useradd ${GITUSR}; passwd ${GITUSR}; mkdir -p /home/${GITUSR}/.ssh; echo 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty' > /home/${GITUSR}/.ssh/authorized_keys; chown -R ${GITUSR} /home/${GITUSR}/.ssh"
|
||||||
|
echo "Now, you'll need to enter the newly created ${GITUSR} user password:"
|
||||||
|
ssh-copy-id ${GITUSR}@${THOST}
|
||||||
|
$SSHCMD "chsh $GITUSR -s $(which git-shell)"
|
||||||
|
echo "Git user set up on ${THOST}. Now use $0 newrepo ${GITUSR}@${THOST} [name] to set up new Git repos"
|
||||||
|
;;
|
||||||
|
"newrepo") # set up a new Git repo
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
FULLREPODIR="/home/${GITUSR}/$REPODIR"
|
||||||
|
$SSHCMD "mkdir -p $FULLREPODIR;cd $FULLREPODIR;git init --bare --shared;chown -R ${GITUSR}:${GITUSR} ."
|
||||||
|
echo "Empty repository created at ${USERHOST}:${REPODIR}"
|
||||||
|
;;
|
||||||
|
"set-repo-desc") # set the repo description for some services
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
VALUE="$4" # it may be empty
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
FULLREPODIR="/home/${GITUSR}/$REPODIR"
|
||||||
|
$SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > description;chown -R ${GITUSR}:${GITUSR} description"
|
||||||
|
echo "Description set for ${REPODIR}"
|
||||||
|
;;
|
||||||
|
"set-repo-url") # set the repo clone URL for some services
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
VALUE="$4" # it may be empty
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
FULLREPODIR="/home/${GITUSR}/$REPODIR"
|
||||||
|
$SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > url;chown -R ${GITUSR}:${GITUSR} url"
|
||||||
|
echo "Clone URL set for ${REPODIR}"
|
||||||
|
;;
|
||||||
|
"set-repo-owner") # set the repo owner name for some services
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
VALUE="$4" # it may be empty
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
FULLREPODIR="/home/${GITUSR}/$REPODIR"
|
||||||
|
$SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > owner;chown -R ${GITUSR}:${GITUSR} owner"
|
||||||
|
echo "Owner name set for ${REPODIR}"
|
||||||
|
;;
|
||||||
|
"gitd-sd") # set up the Git daemon using a systemd unit
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
TMPUNIT="/tmp/git-daemon.service"
|
||||||
|
cat << EOF > $TMPUNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Start Git Daemon
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/home/${GITUSR} /home/${GITUSR}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=500ms
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=git-daemon
|
||||||
|
User=$GITUSR
|
||||||
|
Group=$GITUSR
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
scp $TMPUNIT ${USERHOST}:/etc/systemd/system/git-daemon.service
|
||||||
|
$SSHCMD "systemctl enable git-daemon; systemctl start git-daemon"
|
||||||
|
echo "Git daemon installed on ${THOST} via systemd"
|
||||||
|
rm -f $TMPUNIT
|
||||||
|
;;
|
||||||
|
"gitd-publish") # make a repository public via the Git daemon
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
ACCFILE="/home/${GITUSR}/${REPODIR}/git-daemon-export-ok"
|
||||||
|
$SSHCMD "touch $ACCFILE; chown $GITUSR $ACCFILE"
|
||||||
|
echo "Repository git://${THOST}/$REPODIR made public"
|
||||||
|
;;
|
||||||
|
"gitd-unpublish") # revoke repository publishing via the Git daemon
|
||||||
|
[[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1
|
||||||
|
REPONAME="$3"
|
||||||
|
[[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1
|
||||||
|
REPODIR="${REPONAME}.git"
|
||||||
|
ACCFILE="/home/${GITUSR}/${REPODIR}/git-daemon-export-ok"
|
||||||
|
$SSHCMD "rm -f $ACCFILE"
|
||||||
|
echo "Repository git://${THOST}/$REPODIR made private"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
help
|
||||||
|
esac
|
||||||
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# lshar.sh - Luxferre's shell archiver
|
||||||
|
# Outputs to stdout only (for GNU and BSD shar compatibility)
|
||||||
|
# No external deps for .shars (sh commands required: mkdir, printf)
|
||||||
|
# Archive creation also depends on sort (Busybox, Toybox or GNU Coreutils)
|
||||||
|
# Works with any text or binary files and directory structures
|
||||||
|
# Created by Luxferre in 2023, released into public domain
|
||||||
|
|
||||||
|
# a portable, compact, no-deps serializer of any file into printf sequence
|
||||||
|
f2p() {
|
||||||
|
c=0 # init counter
|
||||||
|
l="$2" # get chunk length
|
||||||
|
[ -z "$l" ] && l=4096 # 4096-byte chunks by default (shouldn't be a problem)
|
||||||
|
cprefix="printf '%b' '" # pattern to prepend to every chunk
|
||||||
|
csuffix="' >> '$1'\n" # pattern to append to every chunk
|
||||||
|
printf '%b' "$cprefix" # always start with the prefix
|
||||||
|
IFS='' # separator
|
||||||
|
while read -r -s -d '' -n1 b; do # convert every byte
|
||||||
|
if [ "$c" == "$l" ]; then # roll over and output suffix+prefix
|
||||||
|
c=0 # reinit counter
|
||||||
|
printf '%b' "$csuffix" # end the chunk
|
||||||
|
printf '%b' "$cprefix" # begin a new chunk
|
||||||
|
else
|
||||||
|
c="$(( c + 1 ))" # increment c
|
||||||
|
fi
|
||||||
|
bcode="$(printf '%d' "'$b")" # get the ASCII code of the byte
|
||||||
|
if [ "$bcode" -gt 32 ] && [ "$bcode" -lt 127 ] &&
|
||||||
|
[ "$bcode" -ne 39 ] && [ "$bcode" -ne 92 ]
|
||||||
|
then # printable and not a single quote or a backslash
|
||||||
|
printf '%b' "$b" # codes from 34 to 126 are safe to print
|
||||||
|
else # subject to escaping, print the hex representation
|
||||||
|
printf '\\x%02x' "$bcode"
|
||||||
|
fi
|
||||||
|
done <"$1"
|
||||||
|
printf '%b' "$csuffix" # always end with the suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
# main code
|
||||||
|
LF=$'\n' # cache the linefeed character
|
||||||
|
flist='' # pre-build the file list from args
|
||||||
|
dlist='' # pre-build the directory list from args
|
||||||
|
for i; do # iterate over args
|
||||||
|
bname="${i##*/}" # get the basename
|
||||||
|
if [ -d "$i" ]; then # it's a directory, we skip . and ..
|
||||||
|
[ "$bname" == '.' ] || [ "$bname" == '..' ] && continue
|
||||||
|
dlist="${dlist}${i%/}${LF}" # append the directory
|
||||||
|
else # it's a file
|
||||||
|
dname="${i%%$bname}" # get the basedir
|
||||||
|
dlist="${dlist}${dname%/}${LF}" # append the basedir
|
||||||
|
flist="${flist}${i}${LF}" # append the file
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
flist="$(printf '%s' "$flist" | sort -u)" # remove file duplicates
|
||||||
|
dlist="$(printf '%s' "$dlist" | sort -u)" # remove directory duplicates
|
||||||
|
printf '%s\n' '#!/bin/sh' # output the shebang, just in case
|
||||||
|
IFS="$LF" # update the separator
|
||||||
|
for indir in $dlist; do # create the directory structure first
|
||||||
|
[ -z "$indir" ] && continue # skip empty names
|
||||||
|
printf '# dir: %s\n' "$indir"
|
||||||
|
printf "printf 'c - %%s\\\n' '%s'\n" "$indir" # announce creation
|
||||||
|
printf "mkdir -p '%s' > /dev/null 2>&1\n" "$indir" # create the dir
|
||||||
|
done
|
||||||
|
for infile in $flist; do # iterate over the file list afterwards
|
||||||
|
[ -z "$infile" ] && continue # skip empty names
|
||||||
|
printf '# file: %s\n' "$infile"
|
||||||
|
printf "printf 'x - %%s\\\n' '%s'\n" "$infile" # announce unrolling
|
||||||
|
printf "printf '' > '%s'\n" "$infile" # clear the file
|
||||||
|
f2p "$infile" # run the serializer
|
||||||
|
IFS="$LF" # update the separator
|
||||||
|
done
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Outport: a simple script to expose reverse tunnels via SSH
|
||||||
|
# Usage: outport.sh {start|stop} user@host [port1] [port2] ...
|
||||||
|
# Created by Luxferre in 2025, released into public domain
|
||||||
|
SOCKET_PATH=/var/run/sshcontrol.sock
|
||||||
|
ACTION="$1"
|
||||||
|
TARGET="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
PORT_OPTS=""
|
||||||
|
case "$ACTION" in
|
||||||
|
start)
|
||||||
|
while :; do
|
||||||
|
[ -z "$1" ] && break
|
||||||
|
PORT_OPTS="$PORT_OPTS -R :$1:localhost:$1"
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
MISC_OPTS="-o ServerAliveInterval=60 -o ServerAliveCountMax=15"
|
||||||
|
ssh -f -N -M -S $SOCKET_PATH $PORT_OPTS $MISC_OPTS $TARGET
|
||||||
|
echo "Tunnel started"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
ssh -S $SOCKET_PATH -O cancel $TARGET
|
||||||
|
ssh -S $SOCKET_PATH -O exit $TARGET
|
||||||
|
echo "Tunnel stopped"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid action (specify start or stop)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# shee.sh: a shell-based OpenAI-compatible local chat
|
||||||
|
# Depends on curl, jq, file and mktemp, otherwise fully POSIX-compatible
|
||||||
|
# (for history/line editing, run it via rlwrap)
|
||||||
|
# Configurable in-chat and with environment variables
|
||||||
|
# Created by Luxferre in 2025, released into public domain
|
||||||
|
|
||||||
|
# all configuration variables are here
|
||||||
|
|
||||||
|
[ -z "$SHEESH_API_ROOT" ] && SHEESH_API_ROOT='https://text.pollinations.ai/openai'
|
||||||
|
[ -z "$SHEESH_MODEL" ] && SHEESH_MODEL='openai-fast'
|
||||||
|
[ -z "$SHEESH_TEMP" ] && SHEESH_TEMP='0.6'
|
||||||
|
[ -z "$SHEESH_SYSPROMPT" ] && SHEESH_SYSPROMPT='You are a helpful assistant.'
|
||||||
|
|
||||||
|
histfile="$(mktemp -t furrfu.XXXXXX)"
|
||||||
|
datafile="$(mktemp -t furrfu.XXXXXX)"
|
||||||
|
addhistory() {
|
||||||
|
role="$1"; shift
|
||||||
|
jq -nc --slurpfile h $histfile --arg r "$role" --arg a "$*" '$h[0]+[{"role":$r,"content":$a}]' > ${histfile}.new
|
||||||
|
mv ${histfile}.new $histfile
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt() { printf "\e[1;32m>> \e[0m"; }
|
||||||
|
clearhist() {
|
||||||
|
printf '[]' > $histfile
|
||||||
|
addhistory system "$SHEESH_SYSPROMPT"
|
||||||
|
}
|
||||||
|
startswith() { case $1 in "$2"*) true;; *) false;; esac; }
|
||||||
|
setsys() {
|
||||||
|
SHEESH_SYSPROMPT="$*"
|
||||||
|
clearhist
|
||||||
|
}
|
||||||
|
setsysfile() { setsys "$(<"$1")"; }
|
||||||
|
addfile() {
|
||||||
|
tmp="$(mktemp -t furrfufile.XXXXXXX)"
|
||||||
|
fname="$(basename $1)"
|
||||||
|
if startswith "$1" "http*://"; then
|
||||||
|
[ -z "$fname" || "$fname" == "/" ] && $fname="${1//\//_}"
|
||||||
|
curl -sSLN $1 > $tmp
|
||||||
|
else
|
||||||
|
cp "$1" $tmp
|
||||||
|
fi
|
||||||
|
if file -b --mime-encoding $tmp | grep binary; then
|
||||||
|
uuencode -m $tmp $fname > ${tmp}.new
|
||||||
|
mv ${tmp}.new $tmp
|
||||||
|
else
|
||||||
|
echo "Contents of ${fname}:" >> ${tmp}.new
|
||||||
|
cat $tmp >> ${tmp}.new
|
||||||
|
mv ${tmp}.new $tmp
|
||||||
|
fi
|
||||||
|
jq -nc --slurpfile h $histfile --rawfile a "$tmp" '$h[0]+[{"role":"user","content":$a}]' > ${histfile}.new
|
||||||
|
mv ${histfile}.new $histfile
|
||||||
|
rm $tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
listmodels() {
|
||||||
|
echo 'Available models:'
|
||||||
|
curl -sSL ${SHEESH_API_ROOT}/models \
|
||||||
|
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
|
||||||
|
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
|
||||||
|
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
|
||||||
|
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
|
||||||
|
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
|
||||||
|
| jq -r '.data[].id' | while read -r modelname; do
|
||||||
|
printf '\e[0;36m* %s\e[0m\n' "$modelname"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
help() {
|
||||||
|
printf 'Current settings:\n\n\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
|
||||||
|
printf 'Model: %s\n' "$SHEESH_MODEL"
|
||||||
|
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
|
||||||
|
printf 'System prompt: "%s"\e[0m\n\n' "$SHEESH_SYSPROMPT"
|
||||||
|
echo 'Available commands:'
|
||||||
|
echo
|
||||||
|
echo '* /models: list available models'
|
||||||
|
echo '* /model: change the current model'
|
||||||
|
echo '* /add: add a file to the context'
|
||||||
|
echo '* /temp: change the current temperature'
|
||||||
|
echo '* /endpoint: change the current API endpoint'
|
||||||
|
echo '* /clear: clear the session context'
|
||||||
|
echo '* /sys [prompt]: set a system prompt and clear the context'
|
||||||
|
echo '* /sysload [file]: load the system prompt from a file and clear the context'
|
||||||
|
echo '* /bye: exit the chat immediately'
|
||||||
|
echo
|
||||||
|
echo 'Available environment variables for configuration:'
|
||||||
|
echo
|
||||||
|
echo '* OPENAI_API_KEY: API key string (default empty)'
|
||||||
|
echo '* SHEESH_API_ROOT: API endpoint (default https://text.pollinations.ai/openai)'
|
||||||
|
echo '* SHEESH_MODEL: model ID (default openai-fast)'
|
||||||
|
echo '* SHEESH_TEMP: model temperature (default 0.6)'
|
||||||
|
echo '* SHEESH_SYSPROMPT: system prompt (default "You are a helpful assistant.") '
|
||||||
|
echo '* SHEESH_UA: user agent override (default empty)'
|
||||||
|
echo '* SHEESH_REFERER: Referer header value (default empty)'
|
||||||
|
echo '* SHEESH_ORIGIN: Origin header value (default empty)'
|
||||||
|
echo '* SHEESH_COOKIES: Cookie header value (default empty)'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup SIGINT SIGABRT SIGHUP SIGQUIT
|
||||||
|
cleanup() {
|
||||||
|
rm $histfile $datafile
|
||||||
|
printf '\nBye!\n'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hdrline='----------------------'
|
||||||
|
title='SHEE.SH v2 by Luxferre'
|
||||||
|
printf '\e[1;36m%s\n%s\n%s\e[0m\n' "$hdrline" "$title" "$hdrline"
|
||||||
|
printf '\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
|
||||||
|
printf 'Model: %s\n' "$SHEESH_MODEL"
|
||||||
|
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
|
||||||
|
printf '\e[1;36m%s\e[0m\n' "$hdrline"
|
||||||
|
|
||||||
|
setsys "$SHEESH_SYSPROMPT"
|
||||||
|
while prompt && read -r line; do
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
[ "$line" == "/bye" ] && break
|
||||||
|
[ "$line" == "/clear" ] && clearhist && echo "Session cleared" && continue
|
||||||
|
[ "$line" == "/help" ] && help && continue
|
||||||
|
[ "$line" == "/models" ] && listmodels && continue
|
||||||
|
startswith "$line" '/sys ' && setsys "${line#\/sys }" && echo "System prompt set and session cleared" && continue
|
||||||
|
startswith "$line" '/sysload ' && setsysfile "${line#\/sysload }" && echo "System prompt loaded and session cleared" && continue
|
||||||
|
startswith "$line" '/temp ' && SHEESH_TEMP="${line#\/temp }" && echo "Temperature set to $SHEESH_TEMP" && continue
|
||||||
|
startswith "$line" '/model ' && SHEESH_MODEL="${line#\/model }" && echo "Model set to $SHEESH_MODEL" && continue
|
||||||
|
startswith "$line" '/endpoint ' && SHEESH_API_ROOT="${line#\/endpoint }" && echo "API endpoint set to $SHEESH_API_ROOT" && continue
|
||||||
|
if startswith "$line" '/add '; then
|
||||||
|
filelist="${line#\/add }"
|
||||||
|
for f in $filelist; do
|
||||||
|
addfile "$f"
|
||||||
|
printf "File %s added to the context\n" "$f"
|
||||||
|
done
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# commands end, normal message processing start
|
||||||
|
addhistory user "$line"
|
||||||
|
jq -nc --arg m "$SHEESH_MODEL" --arg t "$SHEESH_TEMP" --slurpfile msgs $histfile \
|
||||||
|
'{model:$m,stream:true,messages:$msgs[0],temperature:($t|tonumber)}' > $datafile
|
||||||
|
curl -sSLN "${SHEESH_API_ROOT}/chat/completions" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
|
||||||
|
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
|
||||||
|
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
|
||||||
|
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
|
||||||
|
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
|
||||||
|
-H 'Accept: text/event-stream' --data-binary "@$datafile" 2>/dev/null | \
|
||||||
|
while IFS= read -r chunk; do
|
||||||
|
[ -z "$chunk" ] && continue
|
||||||
|
data="${chunk#data: }"
|
||||||
|
[ "$data" == "[DONE]" ] && break
|
||||||
|
delta=''
|
||||||
|
startswith "$data" '{' && delta="$(printf '%s' "$data" | jq -rj '.choices[0].delta.content // empty' 2>/dev/null;printf x)"
|
||||||
|
delta="${delta%?}"
|
||||||
|
[ -n "$delta" ] && printf "\e[0;33m%s\e[0m" "$delta"
|
||||||
|
tailbuf="$tailbuf$delta"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
addhistory assistant "$tailbuf"
|
||||||
|
tailbuf=""
|
||||||
|
line=''
|
||||||
|
done
|
||||||
|
cleanup
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ShellBeat: a Busybox-compatible Bytebeat player with purely shell arithmetic syntax
|
||||||
|
# depends on xxd only
|
||||||
|
# By default, SoX is also required, or change the PLAYCMD variable to your engine
|
||||||
|
# (see some commented examples, some of them only accept 8Khz sample rate)
|
||||||
|
# Usage: shellbeat.sh 'formula'[ samplerate=8000]
|
||||||
|
# Example test on "42 melody": sh shellbeat.sh 't * (42 & (t >> 10))'
|
||||||
|
# Created in 2023 by Luxferre, released into public domain
|
||||||
|
FORMULA="$1"
|
||||||
|
SAMPLERATE="$2"
|
||||||
|
[ -z "$FORMULA" ] && printf 'No formula!\n' && exit
|
||||||
|
[ -z "$SAMPLERATE" ] && SAMPLERATE=8000
|
||||||
|
|
||||||
|
PLAYCMD="play -q -tu8 -r${SAMPLERATE} -c1 -" # most universal option with SoX play command
|
||||||
|
# or else uncomment one of these if you don't have SoX:
|
||||||
|
#PLAYCMD="tee /dev/dsp" # if you have bare OSS (only 8KHz input)
|
||||||
|
#PLAYCMD="aplay -q -f U8 -r ${SAMPLERATE}" # if you have ALSA
|
||||||
|
#PLAYCMD="pacat --raw --format=u8 --rate=${SAMPLERATE} --channels=1" # if you have PulseAudio
|
||||||
|
#PLAYCMD="pw-play --format=u8 --rate=${SAMPLERATE} --channels=1 -" # if you have PipeWire
|
||||||
|
|
||||||
|
printf 'ShellBeat by Luxferre\nPlaying formula: %s\nSample rate: %d Hz\n' "$FORMULA" "$SAMPLERATE"
|
||||||
|
(t=0; while true; do printf '%02X\n' "$((255&(${FORMULA})))"; t=$((t+1)); done) | xxd -r -p | $PLAYCMD > /dev/null
|
||||||
|
|
||||||
Reference in New Issue
Block a user