Last active
June 4, 2026 02:52
-
-
Save boypt/3a9ec68832f27df6d0d0efb913071595 to your computer and use it in GitHub Desktop.
Installation script to install assistant.koplugin in KOReader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!./luajit | |
| require("setupkoenv") | |
| local socket = require("socket") | |
| local ltn12 = require("ltn12") | |
| local http = require("socket.http") | |
| local Archiver = require("ffi/archiver") | |
| local DataStorage = require("datastorage") | |
| local lfs = require("libs/libkoreader-lfs") | |
| local logger = require("logger") | |
| local FFIUtil = require("ffi/util") | |
| local koutil = require("frontend/util") | |
| -- logger:setLevel(1) -- debug switch | |
| -- --- Configuration --- | |
| local PLUGIN_NAME = "assistant.koplugin" | |
| -- Defaults to 'main' branch if no argument is given | |
| local VERSION = arg[1] or "main" | |
| -- Proxy mirror source | |
| local GITHUB_BASE = "https://ghfast.top/https://github.com" | |
| local GITHUB_REPO = "omer-faruq/assistant.koplugin" | |
| -- --- URL and Path Construction --- | |
| -- Determine if the version is a tag or a branch | |
| local REPO_REF = VERSION:sub(1, 1) == "v" and "tags" or "heads" | |
| local RELEASE_URL = string.format("%s/%s/archive/refs/%s/%s.tar.gz", GITHUB_BASE, GITHUB_REPO, REPO_REF, VERSION) | |
| local KOREADER_DIR = DataStorage:getFullDataDir() | |
| local SCRIPT_DIR = lfs.currentdir() | |
| local PLUGIN_DIR = KOREADER_DIR .. "/plugins" | |
| local ASSISTANT_DIR = PLUGIN_DIR .. "/" .. PLUGIN_NAME | |
| local UPDATE_TMPDIR = KOREADER_DIR .. "/ota/" .. PLUGIN_NAME .. ".update" | |
| local UPDATE_BAKDIR = UPDATE_TMPDIR .. "/backup" | |
| local TARGET_PLUGIN_PATH = ASSISTANT_DIR | |
| local BACKUP_PLUGIN_PATH = UPDATE_TMPDIR .. "/backup/" .. PLUGIN_NAME | |
| local DL_TAR = string.format("%s/SOURCE-%s-%s.tar.gz", UPDATE_TMPDIR, PLUGIN_NAME, VERSION) | |
| -- --- Helper Functions --- | |
| -- HTTP download function using LuaSocket | |
| local function download_file(url, output_file) | |
| logger.info("--> Downloading from: ", url, "\n----> Saving to:", output_file) | |
| local file_handle = io.open(output_file, "wb") | |
| if not file_handle then | |
| logger.warn("Error: Could not open file for writing: " .. output_file) | |
| return false | |
| end | |
| local sink = ltn12.sink.file(file_handle) | |
| local status_code = socket.skip(1, http.request{ | |
| url = url, | |
| method = "GET", | |
| sink = sink, | |
| }) | |
| if status_code == 200 then | |
| logger.info(string.format("--> Download successful. (HTTP %d) (FileSize %d)", | |
| status_code, lfs.attributes(output_file, "size"))) | |
| return true | |
| else | |
| logger.warn(string.format("--> Download failed. Status: %s", tostring(status_code))) | |
| if file_handle then file_handle:close() end | |
| return false | |
| end | |
| end | |
| -- Check if an extracted path matches any exclude pattern (equivalent to tar --exclude) | |
| local function is_excluded(path) | |
| if path:find("/%.") or path:sub(1,1) == "." then | |
| return true | |
| end | |
| if path:find("LEXRANK_LANGUAGES%.md$") | |
| or path:find("l10n/templates") | |
| or path:find("l10n/AI_TRANSLATE%.sh$") | |
| or path:find("l10n/Makefile$") | |
| or path:find("l10n/README%.md$") then | |
| return true | |
| end | |
| return false | |
| end | |
| -- Encapsulated archive extraction function | |
| -- @param archive_path : Full absolute path to the tar.gz archive | |
| -- @param dest_base_path: Target base directory for extraction (e.g., UPDATE_TMPDIR) | |
| -- @return boolean, string : Returns true on success, or false and an error message on failure | |
| local function extract_archive(archive_path, dest_base_path) | |
| logger.info("--> Extracting archive: " .. archive_path .. " to " .. dest_base_path) | |
| local arc = Archiver.Reader:new() | |
| if not arc:open(archive_path) then | |
| local err_msg = "Failed to open archive: " .. tostring(arc.err) | |
| logger.warn("--> " .. err_msg) | |
| return false, err_msg | |
| end | |
| local extract_success = true | |
| local err_msg = nil | |
| for entry in arc:iterate() do | |
| if not is_excluded(entry.path) then | |
| local dest_path = dest_base_path .. "/" .. entry.path | |
| local parent_dir = dest_path:match("(.*)" .. package.config:sub(1,1)) | |
| if parent_dir and not koutil.pathExists(parent_dir) then | |
| koutil.makePath(parent_dir) | |
| end | |
| if not arc:extractToPath(entry.path, dest_path) then | |
| err_msg = "Failed to extract entry " .. entry.path .. ": " .. tostring(arc.err) | |
| logger.warn("--> " .. err_msg) | |
| extract_success = false | |
| break | |
| end | |
| end | |
| end | |
| arc:close() | |
| return extract_success, err_msg | |
| end | |
| -- --- Main Workflow --- | |
| -- 1. Create a temporary directory for downloading and extraction | |
| logger.info("--> Creating temporary directory...") | |
| koutil.makePath(UPDATE_BAKDIR) | |
| -- 2. Download the release archive | |
| if not download_file(RELEASE_URL, DL_TAR) then | |
| logger.warn("Error: Download failed, exiting.") | |
| FFIUtil.purgeDir(UPDATE_TMPDIR) | |
| os.exit(1) | |
| end | |
| -- 3. Extract files using the KOReader ffi/archiver library | |
| logger.info("--> Extracting new plugin ...") | |
| local success, err = extract_archive(DL_TAR, UPDATE_TMPDIR) | |
| if not success then | |
| FFIUtil.purgeDir(UPDATE_TMPDIR) | |
| os.exit(1) | |
| end | |
| -- 4. Back up the existing plugin directory if it exists | |
| if koutil.pathExists(TARGET_PLUGIN_PATH) then | |
| logger.info("--> Backing up existing plugin directory...") | |
| if koutil.pathExists(BACKUP_PLUGIN_PATH) then | |
| FFIUtil.purgeDir(BACKUP_PLUGIN_PATH) | |
| end | |
| os.rename(TARGET_PLUGIN_PATH, BACKUP_PLUGIN_PATH) | |
| end | |
| -- 5. Locate and move the newly extracted directory to the destination | |
| local found_extracted_dir = nil | |
| for file in lfs.dir(UPDATE_TMPDIR) do | |
| if file:sub(1, #PLUGIN_NAME) == PLUGIN_NAME then | |
| if koutil.directoryExists(UPDATE_TMPDIR .. "/" .. file) then | |
| found_extracted_dir = UPDATE_TMPDIR .. "/" .. file | |
| break | |
| end | |
| end | |
| end | |
| if found_extracted_dir then | |
| logger.info("--> Found Extracted dir:", found_extracted_dir) | |
| os.rename(found_extracted_dir, TARGET_PLUGIN_PATH) | |
| logger.info("--> New plugin directory is ready.") | |
| else | |
| logger.warn("Error: Could not find extracted plugin directory in temporary folder.") | |
| if koutil.pathExists(BACKUP_PLUGIN_PATH) then | |
| logger.info("--> Restoring backup due to failure...") | |
| os.rename(BACKUP_PLUGIN_PATH, TARGET_PLUGIN_PATH) | |
| end | |
| FFIUtil.purgeDir(UPDATE_TMPDIR) | |
| os.exit(1) | |
| end | |
| -- 6. Restore configuration and libraries from the old backup | |
| if koutil.pathExists(BACKUP_PLUGIN_PATH) then | |
| logger.info("--> Restoring configuration from backup...") | |
| local restore_targets = {"configuration.lua", "lib"} | |
| for _, filename in ipairs(restore_targets) do | |
| local old_file = BACKUP_PLUGIN_PATH .. "/" .. filename | |
| local new_file = TARGET_PLUGIN_PATH .. "/" .. filename | |
| if koutil.pathExists(old_file) then | |
| if koutil.pathExists(new_file) then FFIUtil.purgeDir(new_file) end | |
| os.rename(old_file, new_file) | |
| end | |
| end | |
| end | |
| -- 7. Clean up temporary directories | |
| FFIUtil.purgeDir(UPDATE_TMPDIR) | |
| logger.info("--> Removed temporary download directory.") | |
| -- 8. If an external configuration.lua exists alongside the script, relocate it | |
| local script_config = SCRIPT_DIR .. "/configuration.lua" | |
| if koutil.pathExists(script_config) then | |
| logger.info("--> Found configuration.lua, moving to plugin directory...") | |
| local dest_config = TARGET_PLUGIN_PATH .. "/configuration.lua" | |
| if koutil.pathExists(dest_config) then | |
| os.rename(dest_config, dest_config .. ".bak") | |
| end | |
| os.rename(script_config, dest_config) | |
| end | |
| logger.info("--> Update complete.") | |
| os.exit(0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # This script downloads and installs/updates the assistant.koplugin for KOReader. | |
| # Exit immediately if a command exits with a non-zero status. | |
| set -e | |
| # --- Configuration --- | |
| # Set the version to download (e.g., a tag like 'v1.08' or a branch like 'main'). | |
| # Defaults to 'main' branch if no argument is given. | |
| # Usage: ./assi.sh v1.08 | |
| VERSION=${1:-main} | |
| # Base URL for GitHub. Can be changed to a mirror/proxy if needed. | |
| GITHUB_BASE=https://github.com | |
| #GITHUB_BASE=https://gh.llkk.cc/$GITHUB_BASE | |
| #GITHUB_BASE=https://ghfast.top/$GITHUB_BASE | |
| # The repository to download from. | |
| RELEASE_REPO=omer-faruq/assistant.koplugin | |
| #RELEASE_REPO=boypt/assistant.koplugin | |
| # --- URL and Path Construction --- | |
| # Determine the archive path based on the version format. | |
| # If VERSION starts with 'v' (e.g., v1.0.8), assume it's a tag. | |
| [[ "${VERSION#v}" != "$VERSION" ]] && REPO_ARCHIVE=archive/refs/tags/$VERSION.tar.gz | |
| # Otherwise, assume it's a branch name (e.g., main). | |
| [[ -z $REPO_ARCHIVE ]] && REPO_ARCHIVE=archive/refs/heads/$VERSION.tar.gz | |
| # Construct the full download URL. | |
| RELEASE_URL=$GITHUB_BASE/$RELEASE_REPO/$REPO_ARCHIVE | |
| echo "--> Download From: $RELEASE_URL" | |
| # Get the absolute path of the directory where the script is located. | |
| SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) | |
| # Determine KOReader's root directory. Default to the script's directory if not set. | |
| [[ -z $KOREADER_DIR ]] && KOREADER_DIR=$SCRIPT_DIR | |
| [[ ! -d $KOREADER_DIR ]] && exit 1 | |
| # Define plugin and temporary directory paths. | |
| PLUGIN_NAME=assistant.koplugin | |
| PLUGIN_DIR=$KOREADER_DIR/plugins/${PLUGIN_NAME} | |
| UPDATE_TMPDIR=$KOREADER_DIR/ota/${PLUGIN_NAME}.update | |
| OTA_EXTRACT_DIR=$UPDATE_TMPDIR/EXT | |
| DL_TAR="$UPDATE_TMPDIR/SOURCE.tar.gz" | |
| # Set the 'tar' command. Use koreader's bundled tar | |
| TAR="$KOREADER_DIR/tar" | |
| TAR_OPTIONS=" --exclude='*/.*' --exclude='*/LEXRANK_LANGUAGES.md' --exclude='*/l10n/templates' --exclude='*/l10n/AI_TRANSLATE.sh' --exclude='*/l10n/Makefile' --exclude='*/l10n/README.md'" | |
| if [[ ! -x $TAR ]]; then | |
| # if bundled tar not exists, fallback to system tar (does not support --exclude) | |
| TAR=tar | |
| TAR_OPTIONS="" | |
| fi | |
| # --- Helper Function --- | |
| download_file() { | |
| echo -e "--> Downloading from: $1\n--> Saving to $2" | |
| # Try to download using curl, wget, or a fallback luajit script. | |
| if command -v curl >/dev/null 2>&1; then | |
| curl -L -o "$2" "$1" # -L to follow redirects | |
| elif command -v wget >/dev/null 2>&1; then | |
| wget -O "$2" "$1" | |
| elif [[ -e $SCRIPT_DIR/luaget.lua ]]; then | |
| env LUA_PATH="$SCRIPT_DIR/common/?.lua;;" LUA_CPATH="$SCRIPT_DIR/common/?.so;;" \ | |
| $SCRIPT_DIR/luajit $SCRIPT_DIR/luaget.lua "$1" "$2" | |
| else | |
| echo "Error: Neither curl, wget, nor luaget.lua found. Cannot download." | |
| return 1 | |
| fi | |
| } | |
| # --- Main Script Logic --- | |
| # 1. Create a temporary directory for the download and extraction process. | |
| echo "--> Creating temporary directory..." | |
| mkdir -p $UPDATE_TMPDIR $OTA_EXTRACT_DIR | |
| # 2. Download the release archive. | |
| download_file "$RELEASE_URL" "$DL_TAR" | |
| # 3. Verify the integrity of the downloaded archive. Exit if it's corrupted. | |
| if ! $TAR -tvf "$DL_TAR" >/dev/null; then | |
| echo "--> Downloaded file is corrupted, exiting." | |
| rm -rf "$UPDATE_TMPDIR" | |
| exit 1 | |
| fi | |
| # 4. If an old version of the plugin exists, back it up. | |
| if [[ -e "$PLUGIN_DIR" ]]; then | |
| echo "--> Backing up existing plugin directory..." | |
| mv $PLUGIN_DIR $UPDATE_TMPDIR | |
| fi | |
| # 5. Extract the new version from the archive, excluding unnecessary files, | |
| # and move it to the plugins directory. | |
| echo "--> Extracting new plugin version..." | |
| eval $TAR -xzf "$DL_TAR" -C "$OTA_EXTRACT_DIR" $TAR_OPTIONS | |
| mv "$OTA_EXTRACT_DIR/$PLUGIN_NAME"* "$PLUGIN_DIR" | |
| echo "--> New plugin directory is ready." | |
| # 6. If a backup of the old plugin exists, restore user configuration and libraries, | |
| # then remove the backup directory. | |
| if [[ -d "$UPDATE_TMPDIR/$PLUGIN_NAME" ]]; then | |
| echo "--> Restoring configuration from backup..." | |
| for _OLDFILE in configuration.lua lib; do | |
| if [[ -e "$UPDATE_TMPDIR/$PLUGIN_NAME/$_OLDFILE" ]]; then | |
| mv -v "$UPDATE_TMPDIR/$PLUGIN_NAME/$_OLDFILE" "$PLUGIN_DIR/" | |
| fi | |
| done | |
| fi | |
| # 7. Clean up the temporary download directory. | |
| rm -rf "$UPDATE_TMPDIR" | |
| echo "--> Removed temporary ota directory." | |
| # 8. If a `configuration.lua` file exists alongside the script, move it into the | |
| # plugin directory. This is useful for initial setup. | |
| if [[ -e "$SCRIPT_DIR/configuration.lua" ]]; then | |
| echo "--> Found configuration.lua, moving to plugin directory..." | |
| if [[ -e "$PLUGIN_DIR/configuration.lua" ]]; then | |
| mv "$PLUGIN_DIR/configuration.lua" "$PLUGIN_DIR/configuration.lua.bak" | |
| fi | |
| mv "$SCRIPT_DIR/configuration.lua" "$PLUGIN_DIR/" | |
| fi | |
| echo "--> Update complete." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- Set search path for `require()`. | |
| package.path = | |
| "common/?.lua;frontend/?.lua;plugins/exporter.koplugin/?.lua;" .. | |
| package.path | |
| package.cpath = | |
| "common/?.so;common/?.dll;/usr/lib/lua/?.so;" .. | |
| package.cpath | |
| local socket = require("socket") | |
| local ltn12 = require("ltn12") | |
| local http = require("socket.http") | |
| local url = arg[1] | |
| local output_file = arg[2] | |
| if not url then | |
| io.stderr:write("Usage: lua luaget.lua <url> [output_file]\n") | |
| print(" url: URL to download from") | |
| print(" output_file: Optional filename to save to (default: stdout)") | |
| os.exit(1) | |
| end | |
| print("Downloading from: " .. url) | |
| if output_file then | |
| print("Saving to file: " .. output_file) | |
| end | |
| local file_handle = nil | |
| local success = false | |
| local status_code = 0 | |
| local headers = {} | |
| local sink | |
| if output_file then | |
| file_handle = io.open(output_file, "wb") -- "wb" for write binary | |
| if not file_handle then | |
| io.stderr:write("Error: Could not open file for writing: " .. output_file .. "\n") | |
| os.exit(1) | |
| end | |
| sink = ltn12.sink.file(file_handle) | |
| else | |
| sink = ltn12.sink.file(io.stdout) | |
| end | |
| local success, status_code, headers = http.request{ | |
| url = url, | |
| method = "GET", | |
| sink = sink, | |
| } | |
| if success then | |
| print(string.format("Download successful. \nHTTP Status: %d", status_code)) | |
| for k, v in pairs(headers) do | |
| if type(v) == "string" and (k:lower() == "content-type" or k:lower() == "content-length") then | |
| print(string.format(" %s: %s", k, v)) | |
| end | |
| end | |
| os.exit(0) | |
| end | |
| io.stderr:write("Error: " .. tostring(success) .. "\n") | |
| os.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment