Basking: a place to dump thoughts, jot notes and keep a journal
Plain markdown files, tags by typing #hashtags, a page per day, search, pin and trash with undo, autosave with conflict warnings, API tokens, zip export, and the Lightwell dashboard. Includes install.sh, CI and release workflows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
b548faa258
23 files changed
+4026
No files matched your search
@@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
# Builds basking for every release platform and packs the archives into dist/.
|
||||
#
|
||||
# deploy/build.sh [version] version defaults to `git describe`
|
||||
#
|
||||
# Output:
|
||||
# dist/basking_<version>_<os>_<arch>.tar.gz or .zip
|
||||
# dist/SHA256SUMS
|
||||
#
|
||||
# Releases are published for linux/amd64, linux/arm64 and windows/amd64. Set TARGETS="linux/arm64 darwin/arm64"
|
||||
# (any GOOS/GOARCH pair Go supports) to build something else by hand.
|
||||
set -eu
|
||||
NAME=basking
|
||||
CMDS="basking"
|
||||
cd "$(dirname "$0")/.."
|
||||
VERSION=${1:-$(git describe --tags --always 2>/dev/null || echo dev)}
|
||||
TARGETS=${TARGETS:-"linux/amd64 linux/arm64 windows/amd64"}
|
||||
|
||||
rm -rf dist
|
||||
mkdir -p dist/stage
|
||||
for t in $TARGETS; do
|
||||
os=${t%/*}
|
||||
arch=${t#*/}
|
||||
ext=""
|
||||
[ "$os" = windows ] && ext=".exe"
|
||||
pkg="${NAME}_${VERSION}_${os}_${arch}"
|
||||
stage="dist/stage/$pkg"
|
||||
mkdir -p "$stage"
|
||||
for c in $CMDS; do
|
||||
echo "building $c $VERSION for $os/$arch"
|
||||
CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -trimpath -ldflags "-s -w -X main.version=$VERSION" -o "$stage/$c$ext" "./cmd/$c"
|
||||
done
|
||||
for f in LICENSE README.md; do
|
||||
[ -f "$f" ] && cp "$f" "$stage/"
|
||||
done
|
||||
if [ "$os" = windows ] && command -v zip >/dev/null 2>&1; then
|
||||
(cd dist/stage && zip -qr "../$pkg.zip" "$pkg")
|
||||
else
|
||||
tar -C dist/stage -czf "dist/$pkg.tar.gz" "$pkg"
|
||||
fi
|
||||
done
|
||||
rm -rf dist/stage
|
||||
(cd dist && sha256sum $(ls | grep -E '\.(tar\.gz|zip)$') > SHA256SUMS)
|
||||
echo "done: $(ls dist | grep -cE '\.(tar\.gz|zip)$') archives in dist/"
|
||||
@@ -0,0 +1,10 @@
|
||||
# Basking. Reach it through a reverse proxy or Hammerhead (HTTPS), and set "cookie_secure": true
|
||||
# and "trust_proxy": true in basking.json when you do.
|
||||
services:
|
||||
basking:
|
||||
image: git.veillax.com/veillax/basking:latest # or: build: ..
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8490:8490"
|
||||
volumes:
|
||||
- ./config:/config # basking.json and data/ (your notes) live here
|
||||
@@ -0,0 +1,211 @@
|
||||
#!/bin/sh
|
||||
# Installs (or upgrades) Basking as a systemd service.
|
||||
#
|
||||
# curl -fsSL https://git.veillax.com/Veillax/basking/raw/branch/main/deploy/install.sh | sudo sh
|
||||
# sudo sh install.sh [--start] [--version v0.1.0] [--listen 127.0.0.1:8490] [--source]
|
||||
# sudo sh install.sh --uninstall [--purge]
|
||||
#
|
||||
# What it does:
|
||||
# 1. downloads the release archive for this machine and checks its SHA-256 (or, with --source,
|
||||
# clones the repository and builds it with Go),
|
||||
# 2. installs the binary to /usr/local/bin/basking,
|
||||
# 3. creates a "basking" user, /etc/basking (settings) and /var/lib/basking (data),
|
||||
# 4. writes /etc/basking/basking.env and the systemd service,
|
||||
# 5. tells you what to do next: edit the env file, start the service, watch the log.
|
||||
#
|
||||
# Running it again upgrades the binary and keeps your settings; a service that is already running
|
||||
# is restarted. Add --start to start it straight away instead of editing the env file first.
|
||||
set -eu
|
||||
|
||||
REPO_URL=${BASKING_REPO_URL:-https://git.veillax.com/Veillax/basking}
|
||||
API_URL=${BASKING_API_URL:-https://git.veillax.com/api/v1/repos/Veillax/basking}
|
||||
BIN=/usr/local/bin/basking
|
||||
CONF_DIR=/etc/basking
|
||||
DATA_DIR=/var/lib/basking
|
||||
ENV_FILE=$CONF_DIR/basking.env
|
||||
CONF_FILE=$CONF_DIR/basking.json
|
||||
UNIT=/etc/systemd/system/basking.service
|
||||
USER_NAME=basking
|
||||
|
||||
VERSION=""
|
||||
LISTEN="127.0.0.1:8490"
|
||||
SOURCE=0
|
||||
START=0
|
||||
UNINSTALL=0
|
||||
PURGE=0
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
die() { printf 'install.sh: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--version) VERSION=${2:?--version needs a value}; shift ;;
|
||||
--listen) LISTEN=${2:?--listen needs a value}; shift ;;
|
||||
--source) SOURCE=1 ;;
|
||||
--start) START=1 ;;
|
||||
--uninstall) UNINSTALL=1 ;;
|
||||
--purge) PURGE=1 ;;
|
||||
-h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) die "unknown option $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "run as root (sudo sh install.sh)"
|
||||
[ "$(uname -s)" = Linux ] || die "this installer is for Linux; on Windows, download the .zip from the releases page"
|
||||
command -v systemctl >/dev/null 2>&1 || HAVE_SYSTEMD=0
|
||||
HAVE_SYSTEMD=${HAVE_SYSTEMD:-1}
|
||||
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
if [ "$HAVE_SYSTEMD" -eq 1 ]; then
|
||||
systemctl disable --now basking 2>/dev/null || true
|
||||
rm -f "$UNIT"
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
rm -f "$BIN"
|
||||
if [ "$PURGE" -eq 1 ]; then
|
||||
rm -rf "$CONF_DIR" "$DATA_DIR"
|
||||
userdel "$USER_NAME" 2>/dev/null || true
|
||||
say "Basking removed, including its settings and data."
|
||||
else
|
||||
say "Basking removed. Settings ($CONF_DIR) and data ($DATA_DIR) were kept; add --purge to delete them."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fetch() { # fetch URL DEST
|
||||
if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1"
|
||||
elif command -v wget >/dev/null 2>&1; then wget -qO "$2" "$1"
|
||||
else die "need curl or wget"; fi
|
||||
}
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# ---- 1. get the binary ----
|
||||
if [ "$SOURCE" -eq 1 ]; then
|
||||
command -v git >/dev/null 2>&1 || die "--source needs git"
|
||||
command -v go >/dev/null 2>&1 || die "--source needs Go (https://go.dev/dl/)"
|
||||
say "Cloning $REPO_URL ..."
|
||||
git clone --quiet ${VERSION:+--branch "$VERSION"} --depth 1 "$REPO_URL" "$TMP/src"
|
||||
(cd "$TMP/src" && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=${VERSION:-source}" -o "$TMP/basking" ./cmd/basking)
|
||||
else
|
||||
case $(uname -m) in
|
||||
x86_64|amd64) ARCH=amd64 ;;
|
||||
aarch64|arm64) ARCH=arm64 ;;
|
||||
*) die "no release build for $(uname -m); try --source" ;;
|
||||
esac
|
||||
if [ -z "$VERSION" ]; then
|
||||
fetch "$API_URL/releases/latest" "$TMP/latest.json"
|
||||
VERSION=$(sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$TMP/latest.json" | head -n 1)
|
||||
[ -n "$VERSION" ] || die "could not find the latest release; use --version or --source"
|
||||
fi
|
||||
ARCHIVE="basking_${VERSION}_linux_${ARCH}.tar.gz"
|
||||
say "Downloading Basking $VERSION ($ARCH) ..."
|
||||
fetch "$REPO_URL/releases/download/$VERSION/$ARCHIVE" "$TMP/$ARCHIVE"
|
||||
fetch "$REPO_URL/releases/download/$VERSION/SHA256SUMS" "$TMP/SHA256SUMS"
|
||||
want=$(grep " $ARCHIVE\$" "$TMP/SHA256SUMS" | cut -d' ' -f1)
|
||||
[ -n "$want" ] || die "$ARCHIVE is not listed in SHA256SUMS"
|
||||
got=$(sha256sum "$TMP/$ARCHIVE" | cut -d' ' -f1)
|
||||
[ "$want" = "$got" ] || die "checksum mismatch for $ARCHIVE; refusing to install"
|
||||
tar -xzf "$TMP/$ARCHIVE" -C "$TMP"
|
||||
cp "$TMP/basking_${VERSION}_linux_${ARCH}/basking" "$TMP/basking"
|
||||
fi
|
||||
|
||||
# ---- 2. install it ----
|
||||
UPGRADE=0
|
||||
[ -x "$BIN" ] && UPGRADE=1
|
||||
if [ "$UPGRADE" -eq 1 ] && [ "$HAVE_SYSTEMD" -eq 1 ]; then systemctl stop basking 2>/dev/null || true; fi
|
||||
install -m 0755 "$TMP/basking" "$BIN"
|
||||
say "Installed $("$BIN" version) to $BIN"
|
||||
|
||||
# ---- 3. user and folders ----
|
||||
id "$USER_NAME" >/dev/null 2>&1 || useradd --system --home-dir "$DATA_DIR" --shell /usr/sbin/nologin "$USER_NAME" 2>/dev/null \
|
||||
|| adduser --system --home "$DATA_DIR" --shell /usr/sbin/nologin "$USER_NAME" 2>/dev/null \
|
||||
|| die "could not create the $USER_NAME user"
|
||||
# The dashboard rewrites its settings file, so the service must own the folder that holds it.
|
||||
mkdir -p "$CONF_DIR" "$DATA_DIR"
|
||||
chown "$USER_NAME" "$CONF_DIR" "$DATA_DIR"
|
||||
chmod 0750 "$CONF_DIR" "$DATA_DIR"
|
||||
|
||||
# ---- 4. settings and environment file ----
|
||||
if [ ! -f "$CONF_FILE" ]; then
|
||||
printf '{\n "listen": "%s",\n "data_dir": "%s"\n}\n' "$LISTEN" "$DATA_DIR" > "$CONF_FILE"
|
||||
chown "$USER_NAME" "$CONF_FILE"
|
||||
chmod 0600 "$CONF_FILE"
|
||||
fi
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
# Environment for the basking service. Restart it after editing: systemctl restart basking
|
||||
|
||||
# Where the settings file is (it holds the password hash and API tokens).
|
||||
BASKING_CONFIG=$CONF_FILE
|
||||
|
||||
# Optional. These override what the settings file says.
|
||||
#BASKING_LISTEN=$LISTEN
|
||||
#BASKING_DATA=$DATA_DIR
|
||||
EOF
|
||||
chmod 0600 "$ENV_FILE" # systemd reads it as root, so the service user does not need access
|
||||
say "Created $ENV_FILE."
|
||||
fi
|
||||
|
||||
# ---- 5. service ----
|
||||
if [ "$HAVE_SYSTEMD" -eq 0 ]; then
|
||||
say "systemd was not found, so no service was installed. Run it yourself with:"
|
||||
say " sudo -u $USER_NAME env \$(grep -v '^#' $ENV_FILE | xargs) $BIN serve -config $CONF_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat > "$UNIT" <<EOF
|
||||
[Unit]
|
||||
Description=Basking, a place to dump your thoughts
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=$USER_NAME
|
||||
EnvironmentFile=$ENV_FILE
|
||||
WorkingDirectory=$DATA_DIR
|
||||
ExecStart=$BIN serve
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
# Basking only needs the network and its own folders.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=$CONF_DIR $DATA_DIR
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable basking >/dev/null 2>&1
|
||||
|
||||
if [ "$START" -eq 1 ] || systemctl is-active --quiet basking; then
|
||||
systemctl restart basking
|
||||
sleep 3
|
||||
if systemctl is-active --quiet basking; then
|
||||
say "Basking is running."
|
||||
else
|
||||
say "Basking did not start. Look at: journalctl -u basking -n 50"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
say "Installed $("$BIN" version)."
|
||||
say "Next:"
|
||||
say " 1. sudoedit $ENV_FILE (optional: the address to listen on and where notes are kept)"
|
||||
say " 2. systemctl start basking (or run this again with --start)"
|
||||
say " 3. journalctl -u basking -f to watch it; a first install prints the password here, once"
|
||||
say ""
|
||||
say "The dashboard is at http://$LISTEN"
|
||||
case $LISTEN in
|
||||
127.*|localhost*|[::1]*) say "It only listens on this machine. To reach it elsewhere, put an HTTPS reverse proxy (or Hammerhead) in front of it." ;;
|
||||
esac
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# Creates (or updates) the Gitea release for a tag and uploads what deploy/build.sh made.
|
||||
#
|
||||
# TOKEN=<access token that can write releases> deploy/publish-release.sh v0.2.0
|
||||
#
|
||||
# In Gitea Actions GITHUB_SERVER_URL and GITHUB_REPOSITORY are already set. Safe to
|
||||
# re-run: an existing release is reused and assets with the same name are replaced.
|
||||
set -eu
|
||||
TAG=${1:?usage: publish-release.sh <tag>}
|
||||
: "${TOKEN:?set TOKEN to a Gitea access token that can write releases}"
|
||||
SERVER=${GITHUB_SERVER_URL:-${GITEA_SERVER_URL:-https://git.veillax.com}}
|
||||
REPO=${GITHUB_REPOSITORY:-Veillax/basking}
|
||||
API="$SERVER/api/v1/repos/$REPO"
|
||||
AUTH="Authorization: token $TOKEN"
|
||||
|
||||
# a JSON helper without needing jq: any python will do
|
||||
PY=python3
|
||||
python3 -c 'pass' > /dev/null 2>&1 || PY=python
|
||||
json() { "$PY" -c "$1"; }
|
||||
|
||||
# release notes: the commit subjects since the previous tag
|
||||
prev=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true)
|
||||
if [ -n "$prev" ]; then
|
||||
notes=$(git log --pretty='format:- %s' "$prev..$TAG")
|
||||
else
|
||||
notes=$(git log --pretty='format:- %s' "$TAG")
|
||||
fi
|
||||
prerelease=false
|
||||
case "$TAG" in *-*) prerelease=true ;; esac
|
||||
|
||||
body=$(NOTES="$notes" TAG="$TAG" PRE="$prerelease" json '
|
||||
import json, os
|
||||
print(json.dumps({"tag_name": os.environ["TAG"], "name": os.environ["TAG"], "body": os.environ["NOTES"],
|
||||
"draft": False, "prerelease": os.environ["PRE"] == "true"}))')
|
||||
|
||||
# reuse the release when a failed job is re-run
|
||||
id=$(curl -fsS -H "$AUTH" "$API/releases/tags/$TAG" 2>/dev/null | json 'import sys, json; print(json.load(sys.stdin)["id"])' 2>/dev/null || true)
|
||||
if [ -z "$id" ]; then
|
||||
echo "creating release $TAG"
|
||||
id=$(curl -fsS -X POST -H "$AUTH" -H 'Content-Type: application/json' -d "$body" "$API/releases" | json 'import sys, json; print(json.load(sys.stdin)["id"])')
|
||||
else
|
||||
echo "release $TAG already exists (id $id): replacing its files"
|
||||
fi
|
||||
|
||||
# the ids of the release's assets with a given name (a failed upload can leave a partial one behind)
|
||||
asset_ids() {
|
||||
curl -fsS -H "$AUTH" "$API/releases/$id/assets" | "$PY" -c 'import sys, json
|
||||
for a in json.load(sys.stdin):
|
||||
if a["name"] == sys.argv[1]:
|
||||
print(a["id"])' "$1"
|
||||
}
|
||||
|
||||
for f in dist/*.tar.gz dist/*.zip dist/SHA256SUMS; do
|
||||
[ -f "$f" ] || continue
|
||||
name=$(basename "$f")
|
||||
n=1
|
||||
while :; do
|
||||
for aid in $(asset_ids "$name"); do
|
||||
curl -fsS -X DELETE -H "$AUTH" "$API/releases/$id/assets/$aid" > /dev/null
|
||||
done
|
||||
echo "uploading $name (attempt $n)"
|
||||
if curl -fsS --max-time 1200 -X POST -H "$AUTH" -F "attachment=@$f" "$API/releases/$id/assets?name=$name" > /dev/null; then
|
||||
break
|
||||
fi
|
||||
# large uploads can hit a proxy timeout (HTTP 502); try again
|
||||
[ "$n" -lt 5 ] || { echo "giving up on $name after $n attempts" >&2; exit 1; }
|
||||
n=$((n + 1))
|
||||
sleep $((n * 10))
|
||||
done
|
||||
done
|
||||
echo "published $SERVER/$REPO/releases/tag/$TAG"
|
||||
Reference in new issue
Block a user