From 8a8e4a7edbb9e2f4b8e9de0034ddba94b246ec89 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Thu, 26 Mar 2026 20:19:48 -0300 Subject: [PATCH 1/3] feat: add Copilot instructions for repository usage --- .github/copilot-instructions.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..eaf0f76d0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,19 @@ +# Copilot Instructions for This Repository +# workspace + +Please follow these guidelines when using Copilot or creating code in this repository: + +- Language: English. + +- All comments, function and variable names, and code must be written in English and follow the Inkypi coding standards and templates. Use the same naming and documentation model across the repository to keep style consistent. + +- When creating a new plugin, or if you have any questions, consult the repository documentation in the `docs/` folder first. Follow the guidance there (plugin architecture, contribution guidelines, coding standards) before opening issues or pull requests. + +- When you create or modify code, always run the project's test suite and include the exact command below at the end of your chat response (so the user can run it locally): + + source ./venv/bin/activate && pytest -q + +- If you add new runnable code, run the tests locally before finishing your response and report the test outcome (pass/fail and summary) in the chat. +- Keep changes small and focused; include any required instructions to reproduce test results. + +These instructions are for developer convenience and to keep the repository stable when code is proposed. From 7c2dbcebf46c21cc7ced3334629a2528248c3274 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Tue, 31 Mar 2026 23:40:56 -0300 Subject: [PATCH 2/3] feat: add update command for plugins with rollback safety --- install/cli/inkypi-plugin | 135 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index b7f926dd1..11bde55a7 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -10,6 +10,7 @@ usage() { echo "" echo "Plugin commands:" echo " inkypi plugin install " + echo " inkypi plugin update " echo " inkypi plugin remove " echo " inkypi plugin list" echo "" @@ -157,6 +158,137 @@ list_plugins() { done } | column -t -s $'\t' } +# ---------------------------- +# UPDATE +# ---------------------------- +update_plugin() { + if [[ $# -ne 1 ]]; then + echo "Usage: inkypi plugin update " + exit 1 + fi + + PLUGIN_ID="$1" + DEST_DIR="$PLUGINS_DIR/$PLUGIN_ID" + INFO_FILE="$DEST_DIR/plugin-info.json" + + require_plugins_dir + + # Check plugin is installed + if [[ ! -d "$DEST_DIR" ]]; then + echo "[ERROR] Plugin '$PLUGIN_ID' is not installed." + exit 1 + fi + + # Check plugin-info.json exists + if [[ ! -f "$INFO_FILE" ]]; then + echo "[ERROR] Plugin '$PLUGIN_ID' has no plugin-info.json." + exit 1 + fi + + # Read repository URL from plugin-info.json + REPO_URL=$(jq -r '.repository // empty' "$INFO_FILE") + + if [[ -z "$REPO_URL" ]]; then + echo "[ERROR] Plugin '$PLUGIN_ID' has no repository URL in plugin-info.json." + echo "[ERROR] Only third-party plugins with a saved repository can be updated." + exit 1 + fi + + echo "[INFO] Updating plugin '$PLUGIN_ID' from $REPO_URL" + + # Download new version to a temporary directory first (rollback safety) + TMP_DIR=$(mktemp -d) + trap 'rm -rf "$TMP_DIR"' EXIT + + echo "[INFO] Downloading latest version..." + + if ! git clone --depth 1 --filter=blob:none --sparse "$REPO_URL" "$TMP_DIR/repo" &>/dev/null; then + echo "[ERROR] Failed to clone repository: $REPO_URL" + exit 1 + fi + + # Check if the plugin folder exists in the repo + DEFAULT_BRANCH=$(git -C "$TMP_DIR/repo" symbolic-ref --short HEAD 2>/dev/null || echo main) + if ! git -C "$TMP_DIR/repo" ls-tree --name-only "$DEFAULT_BRANCH" | grep -qx "$PLUGIN_ID"; then + echo "[ERROR] Plugin '$PLUGIN_ID' does not exist in the repository." + exit 1 + fi + + cd "$TMP_DIR/repo" + if ! git sparse-checkout set "$PLUGIN_ID" &>/dev/null; then + echo "[ERROR] Failed to checkout plugin files." + exit 1 + fi + + # Validate that the download produced actual plugin files + if [[ ! -d "$TMP_DIR/repo/$PLUGIN_ID" ]]; then + echo "[ERROR] Downloaded repository does not contain '$PLUGIN_ID' directory." + exit 1 + fi + + # Prepare the new plugin files in a staging directory + STAGING_DIR="$TMP_DIR/staging" + mkdir -p "$STAGING_DIR" + shopt -s dotglob + mv "$TMP_DIR/repo/$PLUGIN_ID"/* "$STAGING_DIR/" + + echo "[INFO] Download successful. Applying update..." + + # Workaround for .pyc files created by inkypi app with sudo + sudo chown -R "$(whoami)":"$(whoami)" "$DEST_DIR" 2>/dev/null || true + chmod -R u+rw "$DEST_DIR" 2>/dev/null || true + + # Backup current plugin directory for rollback + BACKUP_DIR="$TMP_DIR/backup" + cp -a "$DEST_DIR" "$BACKUP_DIR" + + # Replace plugin files: remove old code files but preserve nothing yet + # We delete the old dir and copy in the new one + rm -rf "$DEST_DIR" + mkdir -p "$DEST_DIR" + cp -a "$STAGING_DIR"/. "$DEST_DIR/" + + # Restore the repository field in plugin-info.json (in case the upstream removed it) + if [[ -f "$DEST_DIR/plugin-info.json" ]]; then + # Ensure repository URL is preserved + CURRENT_REPO=$(jq -r '.repository // empty' "$DEST_DIR/plugin-info.json") + if [[ -z "$CURRENT_REPO" ]]; then + jq --arg repo "$REPO_URL" '.repository = $repo' "$DEST_DIR/plugin-info.json" > "$TMP_DIR/info_tmp.json" + mv "$TMP_DIR/info_tmp.json" "$DEST_DIR/plugin-info.json" + fi + else + # If new version lacks plugin-info.json, restore the old one + cp "$BACKUP_DIR/plugin-info.json" "$DEST_DIR/plugin-info.json" + fi + + echo "[INFO] Plugin files updated at $DEST_DIR" + + # Install requirements if any + REQ_FILE="$DEST_DIR/requirements.txt" + + if [[ -f "$REQ_FILE" ]]; then + echo "[INFO] requirements.txt found, installing dependencies..." + + if [[ ! -d "$VENV_PATH" ]]; then + echo "[ERROR] Virtual environment not found at $VENV_PATH" + exit 1 + fi + + source "$VENV_PATH/bin/activate" + pip install -r "$REQ_FILE" + deactivate + + echo "[INFO] Dependencies installed" + else + echo "[INFO] No requirements.txt found, skipping dependency install" + fi + + echo "[INFO] Restarting $APPNAME service." + sudo systemctl restart "$APPNAME.service" + + echo "[INFO] Updated plugin '$PLUGIN_ID' from $REPO_URL" +} + # ---------------------------- # COMMAND ROUTER # ---------------------------- @@ -165,6 +297,9 @@ case "$command" in install) install_plugin "$@" ;; + update) + update_plugin "$@" + ;; uninstall) uninstall_plugin "$@" ;; From 504190167aa848c7bfed4379881ffcd0a5b5714f Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Tue, 31 Mar 2026 23:55:28 -0300 Subject: [PATCH 3/3] feat: enhance plugin update process with validation and safer flow --- install/cli/inkypi-plugin | 48 +++++++++++++-------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index 11bde55a7..fcf0e7306 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -196,12 +196,10 @@ update_plugin() { echo "[INFO] Updating plugin '$PLUGIN_ID' from $REPO_URL" - # Download new version to a temporary directory first (rollback safety) + # Clone into a temporary directory to validate before touching DEST_DIR TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT - echo "[INFO] Downloading latest version..." - if ! git clone --depth 1 --filter=blob:none --sparse "$REPO_URL" "$TMP_DIR/repo" &>/dev/null; then echo "[ERROR] Failed to clone repository: $REPO_URL" exit 1 @@ -215,50 +213,36 @@ update_plugin() { fi cd "$TMP_DIR/repo" - if ! git sparse-checkout set "$PLUGIN_ID" &>/dev/null; then - echo "[ERROR] Failed to checkout plugin files." - exit 1 - fi + git sparse-checkout set "$PLUGIN_ID" &>/dev/null - # Validate that the download produced actual plugin files if [[ ! -d "$TMP_DIR/repo/$PLUGIN_ID" ]]; then - echo "[ERROR] Downloaded repository does not contain '$PLUGIN_ID' directory." + echo "[ERROR] Failed to checkout plugin files." exit 1 fi - # Prepare the new plugin files in a staging directory - STAGING_DIR="$TMP_DIR/staging" - mkdir -p "$STAGING_DIR" + # Prepare new plugin files in staging + mkdir -p "$TMP_DIR/staging" shopt -s dotglob - mv "$TMP_DIR/repo/$PLUGIN_ID"/* "$STAGING_DIR/" + mv "$TMP_DIR/repo/$PLUGIN_ID"/* "$TMP_DIR/staging/" - echo "[INFO] Download successful. Applying update..." - - # Workaround for .pyc files created by inkypi app with sudo - sudo chown -R "$(whoami)":"$(whoami)" "$DEST_DIR" 2>/dev/null || true - chmod -R u+rw "$DEST_DIR" 2>/dev/null || true - - # Backup current plugin directory for rollback - BACKUP_DIR="$TMP_DIR/backup" - cp -a "$DEST_DIR" "$BACKUP_DIR" + # All validated — now replace DEST_DIR + if [[ -d "$DEST_DIR" ]]; then + sudo chown -R "$(whoami)":"$(whoami)" "$DEST_DIR" 2>/dev/null || true + chmod -R u+rw "$DEST_DIR" 2>/dev/null || true + fi - # Replace plugin files: remove old code files but preserve nothing yet - # We delete the old dir and copy in the new one rm -rf "$DEST_DIR" mkdir -p "$DEST_DIR" - cp -a "$STAGING_DIR"/. "$DEST_DIR/" + cp -a "$TMP_DIR/staging"/. "$DEST_DIR/" - # Restore the repository field in plugin-info.json (in case the upstream removed it) + # Ensure repository URL is preserved in plugin-info.json if [[ -f "$DEST_DIR/plugin-info.json" ]]; then - # Ensure repository URL is preserved CURRENT_REPO=$(jq -r '.repository // empty' "$DEST_DIR/plugin-info.json") if [[ -z "$CURRENT_REPO" ]]; then - jq --arg repo "$REPO_URL" '.repository = $repo' "$DEST_DIR/plugin-info.json" > "$TMP_DIR/info_tmp.json" - mv "$TMP_DIR/info_tmp.json" "$DEST_DIR/plugin-info.json" + TMP_INFO=$(mktemp) + jq --arg repo "$REPO_URL" '.repository = $repo' "$DEST_DIR/plugin-info.json" > "$TMP_INFO" + mv "$TMP_INFO" "$DEST_DIR/plugin-info.json" fi - else - # If new version lacks plugin-info.json, restore the old one - cp "$BACKUP_DIR/plugin-info.json" "$DEST_DIR/plugin-info.json" fi echo "[INFO] Plugin files updated at $DEST_DIR"