From 8a8e4a7edbb9e2f4b8e9de0034ddba94b246ec89 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Thu, 26 Mar 2026 20:19:48 -0300 Subject: [PATCH 1/8] 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/8] 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/8] 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" From eac02087938662336abe50483dcc9b00488648a4 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Wed, 1 Apr 2026 00:09:18 -0300 Subject: [PATCH 4/8] Removed Copilot instructions file as it is not relevant to the repository --- .github/copilot-instructions.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index eaf0f76d0..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,19 +0,0 @@ -# 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 71fa260317068237c9d1bc91d49c507c6c2b823c Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Wed, 1 Apr 2026 00:25:02 -0300 Subject: [PATCH 5/8] feat: add validation for plugin update to ensure required files are present --- install/cli/inkypi-plugin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index fcf0e7306..37231dde5 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -225,6 +225,16 @@ update_plugin() { shopt -s dotglob mv "$TMP_DIR/repo/$PLUGIN_ID"/* "$TMP_DIR/staging/" + # Validate staged plugin structure before replacing existing installation + if [[ ! -f "$TMP_DIR/staging/plugin-info.json" ]]; then + echo "[ERROR] Downloaded plugin is missing required file 'plugin-info.json'. Aborting update." + exit 1 + fi + if [[ ! -f "$TMP_DIR/staging/$PLUGIN_ID.py" ]]; then + echo "[ERROR] Downloaded plugin is missing required entrypoint '$PLUGIN_ID.py'. Aborting update." + exit 1 + fi + # All validated — now replace DEST_DIR if [[ -d "$DEST_DIR" ]]; then sudo chown -R "$(whoami)":"$(whoami)" "$DEST_DIR" 2>/dev/null || true From 3f86f36e0ade17ad2e0c6aecbdbcdf8b48f34906 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Wed, 1 Apr 2026 00:32:38 -0300 Subject: [PATCH 6/8] feat: add validation for plugin_id in install, uninstall, and update functions --- install/cli/inkypi-plugin | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index 37231dde5..bfbde62d1 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -23,6 +23,14 @@ require_plugins_dir() { fi } +validate_plugin_id() { + local id="$1" + if [[ ! "$id" =~ ^[a-z0-9_-]+$ ]]; then + echo "[ERROR] Invalid plugin_id '$id'. Only lowercase letters, digits, underscores, and hyphens are allowed." + exit 1 + fi +} + # ---------------------------- # INSTALL # ---------------------------- @@ -36,6 +44,7 @@ install_plugin() { REPO_URL="$2" DEST_DIR="$PLUGINS_DIR/$PLUGIN_ID" + validate_plugin_id "$PLUGIN_ID" require_plugins_dir # Workaround for .pyc files created by inkypi app with sudo @@ -107,6 +116,7 @@ uninstall_plugin() { PLUGIN_ID="$1" DEST_DIR="$PLUGINS_DIR/$PLUGIN_ID" + validate_plugin_id "$PLUGIN_ID" require_plugins_dir if [[ ! -d "$DEST_DIR" ]]; then @@ -171,6 +181,7 @@ update_plugin() { DEST_DIR="$PLUGINS_DIR/$PLUGIN_ID" INFO_FILE="$DEST_DIR/plugin-info.json" + validate_plugin_id "$PLUGIN_ID" require_plugins_dir # Check plugin is installed From eccb5214188726b49b43ed3a1bb97d73f19c6795 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Wed, 1 Apr 2026 00:39:49 -0300 Subject: [PATCH 7/8] feat: improve plugin installation and update process with consistent directory handling --- install/cli/inkypi-plugin | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index bfbde62d1..6f535273e 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -68,13 +68,12 @@ install_plugin() { exit 1 fi - cd "$DEST_DIR" - git sparse-checkout set "$PLUGIN_ID" &>/dev/null + git -C "$DEST_DIR" sparse-checkout set "$PLUGIN_ID" &>/dev/null # Move plugin files to root of DEST_DIR shopt -s dotglob - mv "$PLUGIN_ID"/* ./ - rm -rf "$PLUGIN_ID" + mv "$DEST_DIR/$PLUGIN_ID"/* "$DEST_DIR/" + rm -rf "$DEST_DIR/$PLUGIN_ID" echo "[INFO] Plugin copied to $DEST_DIR" @@ -223,8 +222,7 @@ update_plugin() { exit 1 fi - cd "$TMP_DIR/repo" - git sparse-checkout set "$PLUGIN_ID" &>/dev/null + git -C "$TMP_DIR/repo" sparse-checkout set "$PLUGIN_ID" &>/dev/null if [[ ! -d "$TMP_DIR/repo/$PLUGIN_ID" ]]; then echo "[ERROR] Failed to checkout plugin files." From 21b8a0f95249dc8dd40d446601602692da1fa144 Mon Sep 17 00:00:00 2001 From: Saulo Benigno Date: Wed, 1 Apr 2026 19:20:41 -0300 Subject: [PATCH 8/8] feat: initialize git repository during plugin update process --- install/cli/inkypi-plugin | 1 + 1 file changed, 1 insertion(+) diff --git a/install/cli/inkypi-plugin b/install/cli/inkypi-plugin index 6f535273e..dfd9f113f 100644 --- a/install/cli/inkypi-plugin +++ b/install/cli/inkypi-plugin @@ -253,6 +253,7 @@ update_plugin() { rm -rf "$DEST_DIR" mkdir -p "$DEST_DIR" cp -a "$TMP_DIR/staging"/. "$DEST_DIR/" + git -C "$DEST_DIR" init -q # Ensure repository URL is preserved in plugin-info.json if [[ -f "$DEST_DIR/plugin-info.json" ]]; then