diff --git a/docs/2025-budget-withdrawals.md b/docs/2025-budget-withdrawals.md index 119cb6a..773c20f 100644 --- a/docs/2025-budget-withdrawals.md +++ b/docs/2025-budget-withdrawals.md @@ -13,7 +13,25 @@ This is done via Google docs. For Intersect [governance-actions](https://github.com/IntersectMBO/governance-actions) will be used as working directory. -### 2. Create the metadata documents +### 2. Set environment variables + +Set secrets + +```shell +source ./scripts/.env +``` + +Set some useful variables + +```shell +export DEPOSIT_RETURN_ADDR="" +export WITHDRAWAL_ADDR="" +``` + +Make sure that `CARDANO_NODE_NETWORK_ID` and `CARDANO_NODE_SOCKET_PATH` are set. + + +### 3. Create the metadata documents Convert the `.docx` to [intersect's metadata standard](https://github.com/IntersectMBO/governance-actions/tree/main/schemas) this is a modified CIP-108 document. @@ -21,10 +39,10 @@ this is a modified CIP-108 document. With the `metadata-create` script taking the data from the doc and creating a `.jsonld`. ```shell -./scripts/metadata-create.sh my-metadata.docx +./scripts/metadata-create.sh my-metadata.docx --deposit-return-addr $DEPOSIT_RETURN_ADDR ``` -### 3. Sanity check the metadata +### 4. Sanity check the metadata Generate a markdown representation from the created `.jsonld` and manually compare against the `.docx`. @@ -33,21 +51,13 @@ and manually compare against the `.docx`. ./scripts/cip-108-create-human-readable.sh my-metadata.jsonld ``` -### 4. Formally validate the metadata +### 5. Validate the metadata -Ensure that the metadata documents are correct. - -automated checks: +automated formal checks: - compliance with CIP schema(s) - compliance with Intersect schema - spelling check -```shell -./scripts/metadata-validate.sh my-metadata.jsonld -``` - -### 5. Budget specific tests to validate the metadata - Then do specific budget checks: - is author valid? - is metadata discoverable on ipfs? @@ -60,8 +70,12 @@ Then do specific budget checks: - Provided withdrawal address matches the metadata - All IPFS references are discoverable via IPFS +We will pass `--no-author` as we know there is no author witness yet. + +We will pass `--no-ipfs` as we know we didn't put it on ipfs yet. + ```shell -./scripts/budget-metadata-validate.sh ./my-metadata-directory +./scripts/budget-metadata-validate.sh ./my-metadata-directory --no-author --no-ipfs --deposit-return-addr $DEPOSIT_RETURN_ADDR --withdrawal-addr $WITHDRAWAL_ADDR ``` ### 6. Sign with author's key @@ -72,45 +86,34 @@ Sign it with the Intersect author key (this will be done via an air-gapped setup) ```shell -./scripts/author-create.sh my-metadata.jsonld intersect-key.skey +./scripts/author-create.sh ./my-metadata-directory intersect-key.skey ``` ### 7. Verify the author's witness -Check the author witness. +Check the author witness via the same script +as we know it checks everything. -Ensure it is from the expected intersect key. +Ensure it is from the expected intersect key + +We will pass `--no-ipfs` as we know we didn't put it on ipfs yet. ```shell -./scripts/author-validate.sh my-metadata.jsonld +./scripts/budget-metadata-validate.sh ./my-metadata-directory --no-ipfs --deposit-return-addr $DEPOSIT_RETURN_ADDR --withdrawal-addr $WITHDRAWAL_ADDR ``` ### 8. Host on IPFS Pin the metadata to different IPFS pinning services. -You'll need to set the secrets for these pinning services first. - -```shell -source ./scripts/.env - -./scripts/ipfs-pin.sh my-metadata.jsonld -``` - -### 9. Check metadata is accessible via IPFS - -Hit a couple of gateways and see if it is accessible. - ```shell -./scripts/ipfs-check.sh my-metadata.jsonld +./scripts/ipfs-pin.sh ./my-metadata-directory ``` -### 10. Create the action file +### 9. Create the action file Now we can create a governance action file from our metadata. -This does require `CARDANO_NODE_NETWORK_ID` and `CARDANO_NODE_SOCKET_PATH` to be set. - This performs some validations - can check against some known deposit return and withdrawal address - checks that metadata fields are present and look right @@ -122,15 +125,15 @@ This performs some validations - has user manually confirm the addresses and the amount ```shell -./scripts/ipfs-check.sh my-metadata.jsonld --withdraw-to-script --deposit-return-addr --withdrawal-addr +./scripts/action-create.jsonld my-metadata.jsonld --withdraw-to-script --deposit-return-addr $DEPOSIT_RETURN_ADDR --withdrawal-addr $WITHDRAWAL_ADDR ``` -### 11. Share the action file +### 10. Share the action file -Share the action file and the .json representation publicly. +Share the action file and the `.action.json` representation publicly. Have people check that this looks good. -You dont want to mess this up. +You **don't** want to mess this up. Checks; - withdrawal and stake address are correct @@ -139,7 +142,7 @@ Checks; - metadata compliance with .docx - hash and URI match -### 12. Check action file +### 11. Check action file Automated checks. @@ -153,13 +156,13 @@ Checks; - manually have the user confirm aspects too ```shell -./scripts/action-validate.sh my-action.action +./scripts/action-validate.sh my-metadata.action --withdraw-to-script --deposit-return-addr $DEPOSIT_RETURN_ADDR --withdrawal-addr $WITHDRAWAL_ADDR ``` ### 13. Build the transaction -todo +Manually, dependent on where the deposit is from. -### . check the transactions +### 14. check the transactions -todo \ No newline at end of file +Manually. \ No newline at end of file diff --git a/scripts/author-create.sh b/scripts/author-create.sh index ab34e93..8b334b6 100755 --- a/scripts/author-create.sh +++ b/scripts/author-create.sh @@ -13,19 +13,36 @@ DEFAULT_NEW_FILE="false" # default to false, to the script overwrites the input ################################################## +# Exit immediately if a command exits with a non-zero status, +# treat unset variables as an error, and fail if any command in a pipeline fails +set -euo pipefail + +# Colors +#BLACK='\033[0;30m' +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BRIGHTWHITE='\033[0;37;1m' +NC='\033[0m' + # Check if cardano-signer is installed if ! command -v cardano-signer >/dev/null 2>&1; then - echo "Error: cardano-signer is not installed or not in your PATH." >&2 + echo -e "${RED}Error: cardano-signer is not installed or not in your PATH.${NC}" >&2 exit 1 fi # Usage message usage() { - echo "Usage: $0 [--author-name NAME] [--use-cip8] [--new-file]" - echo "Options:" - echo " --author-name NAME Specify the author name (default: $DEFAULT_AUTHOR_NAME)" - echo " --new-file Create a new file with the signed metadata (default: $DEFAULT_NEW_FILE)" - echo " --use-cip8 Use CIP-8 signing algorithm (default: $DEFAULT_USE_CIP8)" + echo -e "${YELLOW}Usage: $0 [--author-name NAME] [--use-cip8] [--new-file]${NC}" + echo -e "${CYAN}Sign metadata files with author witness using cardano-signer${NC}" + echo -e " " + echo -e "Options:" + echo -e " --author-name NAME Specify the author name (default: ${YELLOW}$DEFAULT_AUTHOR_NAME${NC})" + echo -e " --new-file Create a new file with the signed metadata (default: ${YELLOW}$DEFAULT_NEW_FILE${NC})" + echo -e " --use-cip8 Use CIP-8 signing algorithm (default: ${YELLOW}$DEFAULT_USE_CIP8${NC})" + echo -e " -h, --help Show this help message and exit" exit 1 } @@ -70,31 +87,43 @@ done # Check for required arguments if [ -z "$input_path" ] || [ -z "$input_key" ]; then - echo "Error: Missing required arguments" + echo -e "${RED}Error: Missing required arguments${NC}" >&2 usage fi +echo -e " " +echo -e "${YELLOW}Creating author witness for metadata files${NC}" +echo -e "${CYAN}This script signs JSON-LD metadata files using cardano-signer${NC}" + # Check if the key input file exists if [ ! -f "$input_key" ]; then - echo "Error: Signing key file '$input_key' not found!" + echo -e "${RED}Error: Signing key file '$input_key' not found!${NC}" >&2 exit 1 fi +echo -e "${CYAN}Using signing key: ${YELLOW}$input_key${NC}" +echo -e "${CYAN}Author name: ${YELLOW}$author_name${NC}" +echo -e "${CYAN}Algorithm: ${YELLOW}$([ "$use_cip8" = "true" ] && echo "CIP-8" || echo "Ed25519")${NC}" +echo -e "${CYAN}Output mode: ${YELLOW}$([ "$new_file" = "true" ] && echo "New file (.authored.jsonld)" || echo "Overwrite original")${NC}" + sign_file() { local file="$1" local use_cip8="$2" local new_file="$3" + echo -e " " + echo -e "${CYAN}Signing file: ${YELLOW}$file${NC}" + if [ $new_file = "true" ]; then - echo "Creating a new file with the signed metadata..." + echo -e "${CYAN}Creating a new file with the signed metadata...${NC}" extension=".authored.jsonld" # New file will have .authored.jsonld extension else - echo "Overwriting the original file with the signed metadata..." + echo -e "${CYAN}Overwriting the original file with the signed metadata...${NC}" extension=".jsonld" fi if [ "$use_cip8" = "true" ]; then - echo "Signing with CIP-8 algorithm..." + echo -e "${CYAN}Signing with CIP-8 algorithm...${NC}" temp_vkey=$(mktemp) temp_hash=$(mktemp) @@ -111,44 +140,70 @@ sign_file() { rm "$temp_vkey" rm "$temp_hash" - echo "Signing with CIP8 algorithm..." + echo -e "${CYAN}Using public key hash: ${YELLOW}$public_key_hash${NC}" cardano-signer sign --cip100 \ --data-file "$file" \ --secret-key "$input_key" \ --author-name "$author_name" \ --address "$public_key_hash" \ --out-file "${file%.jsonld}$extension" - return else - echo "Signing with Ed25519 algorithm..." + echo -e "${CYAN}Signing with Ed25519 algorithm...${NC}" cardano-signer sign --cip100 \ --data-file "$file" \ --secret-key "$input_key" \ --author-name "$author_name" \ --out-file "${file%.jsonld}$extension" - return fi + + echo -e "${GREEN}Successfully signed: ${YELLOW}${file%.jsonld}$extension${NC}" } # Use cardano-signer to sign author metadata if [ -d "$input_path" ]; then # If input is a directory: sign all .jsonld files + echo -e " " + echo -e "${CYAN}Processing directory: ${YELLOW}$input_path${NC}" + shopt -s nullglob jsonld_files=("$input_path"/*.jsonld) # check if any .jsonld files were found if [ ${#jsonld_files[@]} -eq 0 ]; then - echo "Error: No .jsonld files found in directory '$input_path'." + echo -e "${RED}Error: No .jsonld files found in directory '$input_path'.${NC}" >&2 exit 1 fi + + echo -e "${CYAN}Found ${YELLOW}${#jsonld_files[@]}${NC}${CYAN} .jsonld files to process${NC}" + # for each .jsonld file in the directory, sign it for file in "${jsonld_files[@]}"; do + echo -e " " + # skip for the first file + if [ "$file" != "${jsonld_files[0]}" ]; then + echo -e " " + echo -e "${CYAN}The next file is: ${YELLOW}$file${NC}" + read -p "Do you want to continue with the next file? (y/n): " choice + case "$choice" in + y|Y ) echo -e "${GREEN}Continuing with the next file...${NC}";; + n|N ) echo -e "${YELLOW}Exiting...${NC}"; exit 0;; + * ) echo -e "${RED}Invalid choice, exiting...${NC}"; exit 1;; + esac + fi sign_file "$file" "$use_cip8" "$new_file" done + + echo -e " " + echo -e "${GREEN}All files processed successfully!${NC}" + elif [ -f "$input_path" ]; then # Input is a single file + echo -e " " + echo -e "${CYAN}Processing single file: ${YELLOW}$input_path${NC}" sign_file "$input_path" "$use_cip8" "$new_file" + echo -e " " + echo -e "${GREEN}✓ File processed successfully!${NC}" else - echo "Error: '$input_path' is not a valid file or directory." + echo -e "${RED}Error: '$input_path' is not a valid file or directory.${NC}" >&2 exit 1 fi \ No newline at end of file diff --git a/scripts/ipfs-pin.sh b/scripts/ipfs-pin.sh index d9d63d8..cff52f0 100755 --- a/scripts/ipfs-pin.sh +++ b/scripts/ipfs-pin.sh @@ -18,24 +18,40 @@ DEFAULT_HOST_ON_PINATA="true" ###################################################### +# Exit immediately if a command exits with a non-zero status, +# treat unset variables as an error, and fail if any command in a pipeline fails +set -euo pipefail + +# Colors +#BLACK='\033[0;30m' +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BRIGHTWHITE='\033[0;37;1m' +NC='\033[0m' + # check if user has ipfs cli installed if ! command -v ipfs >/dev/null 2>&1; then - echo "Error: ipfs cli is not installed or not in your PATH." >&2 + echo -e "${RED}Error: ipfs cli is not installed or not in your PATH.${NC}" >&2 exit 1 fi # Usage message usage() { - echo "Usage: $0 [--check-too] [--no-local] [--no-pinata] [--no-blockfrost] [--no-nmkr]" - echo "Pin a file to local IPFS node and pin via Blockfrost, NMKR and Pinata. Optionally check if file is already discoverable on IPFS." - echo " " - echo "Options:" - echo " Path to your file." - echo " --check-too Run a check if file is discoverable on ipfs, only pin if not discoverable (default: $CHECK_TOO)" - echo " --no-local Don't try to pin file on local ipfs node (default: $DEFAULT_HOST_ON_LOCAL_NODE)" - echo " --no-pinata Don't try to pin file on pinata service (default: $DEFAULT_HOST_ON_PINATA)" - echo " --no-blockfrost Don't try to pin file on blockfrost service (default: $DEFAULT_HOST_ON_BLOCKFROST)" - echo " --no-nmkr Don't try to pin file on NMKR service (default: $DEFAULT_HOST_ON_NMKR)" + echo -e "${YELLOW}Usage: $0 [--check-too] [--no-local] [--no-pinata] [--no-blockfrost] [--no-nmkr]${NC}" + echo -e "${CYAN}Pin a file or directory of .jsonld files to local IPFS node and pin via Blockfrost, NMKR and Pinata.${NC}" + echo -e "${CYAN}Optionally check if file is already discoverable on IPFS.${NC}" + echo -e " " + echo -e "Options:" + echo -e " Path to your file or directory containing .jsonld files." + echo -e " --check-too Run a check if file is discoverable on ipfs, only pin if not discoverable (default: ${YELLOW}$CHECK_TOO${NC})" + echo -e " --no-local Don't try to pin file on local ipfs node (default: ${YELLOW}$DEFAULT_HOST_ON_LOCAL_NODE${NC})" + echo -e " --no-pinata Don't try to pin file on pinata service (default: ${YELLOW}$DEFAULT_HOST_ON_PINATA${NC})" + echo -e " --no-blockfrost Don't try to pin file on blockfrost service (default: ${YELLOW}$DEFAULT_HOST_ON_BLOCKFROST${NC})" + echo -e " --no-nmkr Don't try to pin file on NMKR service (default: ${YELLOW}$DEFAULT_HOST_ON_NMKR${NC})" + echo -e " -h, --help Show this help message and exit" exit 1 } @@ -82,155 +98,216 @@ while [[ $# -gt 0 ]]; do esac done -# Generate CID from the given file -echo "Generating CID for the file..." - -# use ipfs add to generate a CID -# use CIDv1 -ipfs_cid=$(ipfs add -Q --cid-version 1 "$input_path") -echo "CID: $ipfs_cid" - -# If user wants to check if file is discoverable on IPFS -if [ "$check_discoverable" = "true" ]; then - echo "Using ./scripts/ipfs-check.sh script to check if file is discoverable on IPFS..." - # check if file is discoverable on IPFS - if ! ./scripts/ipfs-check.sh "$input_path"; then - echo "File is not discoverable on IPFS. Proceeding to pin it." - else - echo "File is already discoverable on IPFS. No need to pin it." - exit 0 - fi - -else - echo "Skipping check of file on ipfs..." +# If no input path provided, show usage +if [ -z "$input_path" ]; then + echo -e "${RED}Error: No file or directory specified${NC}" >&2 + usage fi -echo " " -echo "File is not hosted on IPFS, so pinning it..." -echo " " +echo -e " " +echo -e "${YELLOW}IPFS File Pinning Service${NC}" +echo -e "${CYAN}This script pins files to IPFS using multiple pinning services${NC}" -# Pin on local node -if [ "$local_host" = "true" ]; then - echo "Pinning file on local IPFS node..." - if ipfs pin add "$ipfs_cid"; then - echo "File pinned successfully on local IPFS node." +# Function to pin a single file +pin_single_file() { + local file="$1" + + echo -e " " + echo -e "${CYAN}Processing file: ${YELLOW}$file${NC}" + + # Generate CID from the given file + echo -e "${CYAN}Generating CID for the file...${NC}" + + # use ipfs add to generate a CID + # use CIDv1 + ipfs_cid=$(ipfs add -Q --cid-version 1 "$file") + echo -e "CID: ${YELLOW}$ipfs_cid${NC}" + + # If user wants to check if file is discoverable on IPFS + if [ "$check_discoverable" = "true" ]; then + echo -e "${CYAN}Using ./scripts/ipfs-check.sh script to check if file is discoverable on IPFS...${NC}" + # check if file is discoverable on IPFS + if ! ./scripts/ipfs-check.sh "$file"; then + echo -e "${YELLOW}File is not discoverable on IPFS. Proceeding to pin it.${NC}" + else + echo -e "${GREEN}File is already discoverable on IPFS. No need to pin it.${NC}" + return 0 + fi else - echo "Failed to pin file on local IPFS node." >&2 - exit 1 - fi -else - echo "Skipping pinning on local IPFS node." -fi - -# Pin on local node's remote services -# todo -local_node_pinning_services=$(ipfs pin remote service ls) - -# Pin on Pinata -echo " " -echo "Pinning file to Pinata..." - -if [ "$pinata_host" = "true" ]; then - # Check for secret environment variables - # todo, this in a nicer way - echo "Reading Pinata API key from environment variable..." - if [ -z "$PINATA_API_KEY" ]; then - echo "Error: PINATA_API_KEY environment variable is not set." >&2 - exit 1 + echo -e "${CYAN}Skipping check of file on ipfs...${NC}" fi - echo "Uploading file to Pinata service..." - response=$(curl -s -X POST "https://uploads.pinata.cloud/v3/files" \ - -H "Authorization: Bearer ${PINATA_API_KEY}" \ - -F "file=@$input_path" \ - -F "network=public" \ - ) - # Check response for errors - if echo "$response" | grep -q '"errors":'; then - echo "Error in Pinata response:" >&2 - echo "$response" | jq . >&2 - exit 1 - fi - - echo "Pinata upload successful!" -else - echo "Skipping pinning on Pinata." -fi - -# Pin on Blockfrost -echo " " -echo "Pinning file to Blockfrost..." - -if [ "$blockfrost_host" = "true" ]; then - # Check for secret environment variables - # todo, this in a nicer way - echo "Reading Blockfrost API key from environment variable..." - if [ -z "$BLOCKFROST_API_KEY" ]; then - echo "Error: BLOCKFROST_API_KEY environment variable is not set." >&2 - exit 1 - fi + echo -e " " + echo -e "${CYAN}File is not hosted on IPFS, so pinning it...${NC}" - echo "Uploading file to Blockfrost service..." - response=$(curl -s -X POST "https://ipfs.blockfrost.io/api/v0/ipfs/add" \ - -H "project_id: $BLOCKFROST_API_KEY" \ - -F "file=@$input_path" \ - ) - # Check response for errors - if echo "$response" | grep -q '"errors":'; then - echo "Error in Blockfrost response:" >&2 - echo "$response" | jq . >&2 - exit 1 + # Pin on local node + if [ "$local_host" = "true" ]; then + echo -e " " + echo -e "${CYAN}Pinning file on local IPFS node...${NC}" + if ipfs pin add "$ipfs_cid"; then + echo -e "${GREEN}File pinned successfully on local IPFS node.${NC}" + else + echo -e "${RED}Failed to pin file on local IPFS node.${NC}" >&2 + return 1 + fi + else + echo -e "${YELLOW}Skipping pinning on local IPFS node.${NC}" fi - - echo "Blockfrost upload successful!" -else - echo "Skipping pinning on Blockfrost." -fi - -# Pin on NMKR -echo " " -echo "Pinning file to NMKR..." - -if [ "$nmkr_host" = "true" ]; then - # Check for secret environment variables - # todo, this in a nicer way - echo "Reading NMKR API key from environment variable..." - if [ -z "$NMKR_API_KEY" ]; then - echo "Error: NMKR_API_KEY environment variable is not set." >&2 - exit 1 + + # Pin on Pinata + if [ "$pinata_host" = "true" ]; then + echo -e " " + echo -e "${CYAN}Pinning file to Pinata...${NC}" + + # Check for secret environment variables + echo -e "${CYAN}Reading Pinata API key from environment variable...${NC}" + if [ -z "$PINATA_API_KEY" ]; then + echo -e "${RED}Error: PINATA_API_KEY environment variable is not set.${NC}" >&2 + return 1 + fi + + echo -e "${CYAN}Uploading file to Pinata service...${NC}" + response=$(curl -s -X POST "https://uploads.pinata.cloud/v3/files" \ + -H "Authorization: Bearer ${PINATA_API_KEY}" \ + -F "file=@$file" \ + -F "network=public" \ + ) + # Check response for errors + if echo "$response" | grep -q '"errors":'; then + echo -e "${RED}Error in Pinata response:${NC}" >&2 + echo "$response" | jq . >&2 + return 1 + fi + + echo -e "${GREEN}Pinata upload successful!${NC}" + else + echo -e "${YELLOW}Skipping pinning on Pinata.${NC}" fi - echo "Reading NMKR user id from environment variable..." - if [ -z "$NMKR_USER_ID" ]; then - echo "Error: NMKR_USER_ID environment variable is not set." >&2 - exit 1 + + # Pin on Blockfrost + if [ "$blockfrost_host" = "true" ]; then + echo -e " " + echo -e "${CYAN}Pinning file to Blockfrost...${NC}" + + # Check for secret environment variables + echo -e "${CYAN}Reading Blockfrost API key from environment variable...${NC}" + if [ -z "$BLOCKFROST_API_KEY" ]; then + echo -e "${RED}Error: BLOCKFROST_API_KEY environment variable is not set.${NC}" >&2 + return 1 + fi + + echo -e "${CYAN}Uploading file to Blockfrost service...${NC}" + response=$(curl -s -X POST "https://ipfs.blockfrost.io/api/v0/ipfs/add" \ + -H "project_id: $BLOCKFROST_API_KEY" \ + -F "file=@$file" \ + ) + # Check response for errors + if echo "$response" | grep -q '"errors":'; then + echo -e "${RED}Error in Blockfrost response:${NC}" >&2 + echo "$response" | jq . >&2 + return 1 + fi + + echo -e "${GREEN}Blockfrost upload successful!${NC}" + else + echo -e "${YELLOW}Skipping pinning on Blockfrost.${NC}" fi - # base64 encode the file because NMKR API requires it - echo "Encoding file to base64..." - base64_content=$(base64 -i "$input_path") - - echo "Uploading file to NMKR service..." - response=$(curl -s -X POST "https://studio-api.nmkr.io/v2/UploadToIpfs/${NMKR_USER_ID}" \ - -H 'accept: text/plain' \ - -H 'Content-Type: application/json' \ - -H "Authorization: Bearer ${NMKR_API_KEY}" \ - -d @- <&2 + return 1 + fi + echo -e "${CYAN}Reading NMKR user id from environment variable...${NC}" + if [ -z "$NMKR_USER_ID" ]; then + echo -e "${RED}Error: NMKR_USER_ID environment variable is not set.${NC}" >&2 + return 1 + fi + + # base64 encode the file because NMKR API requires it + echo -e "${CYAN}Encoding file to base64...${NC}" + base64_content=$(base64 -i "$file") + + echo -e "${CYAN}Uploading file to NMKR service...${NC}" + response=$(curl -s -X POST "https://studio-api.nmkr.io/v2/UploadToIpfs/${NMKR_USER_ID}" \ + -H 'accept: text/plain' \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer ${NMKR_API_KEY}" \ + -d @- <&2 - echo "$response" | jq . >&2 - exit 1 + ) + # Check response for errors + if echo "$response" | grep -q '"errors":'; then + echo -e "${RED}Error in NMKR response:${NC}" >&2 + echo "$response" | jq . >&2 + return 1 + fi + + echo -e "${GREEN}NMKR upload successful!${NC}" + else + echo -e "${YELLOW}Skipping pinning on NMKR.${NC}" fi + + echo -e " " + echo -e "${GREEN}File pinning completed: ${YELLOW}$file${NC}" +} - echo "NMKR upload successful!" +# Main processing logic +if [ -d "$input_path" ]; then + # If input is a directory: pin all .jsonld files + echo -e " " + echo -e "${CYAN}Processing directory: ${YELLOW}$input_path${NC}" + + shopt -s nullglob + jsonld_files=("$input_path"/*.jsonld) + # check if any .jsonld files were found + if [ ${#jsonld_files[@]} -eq 0 ]; then + echo -e "${RED}Error: No .jsonld files found in directory: ${YELLOW}$input_path${NC}" >&2 + exit 1 + fi + + echo -e "${CYAN}Found ${YELLOW}${#jsonld_files[@]}${NC}${CYAN} .jsonld files to process${NC}" + + # for each .jsonld file in the directory, pin it + for file in "${jsonld_files[@]}"; do + # ask user if they want to continue with the next file + # skip for the first file + if [ "$file" != "${jsonld_files[0]}" ]; then + echo -e " " + echo -e "${CYAN}The next file is: ${YELLOW}$file${NC}" + read -p "Do you want to continue with the next file? (y/n): " choice + case "$choice" in + y|Y ) echo -e "${GREEN}Continuing with the next file...${NC}";; + n|N ) echo -e "${YELLOW}Exiting...${NC}"; exit 0;; + * ) echo -e "${RED}Invalid choice, exiting...${NC}"; exit 1;; + esac + fi + pin_single_file "$file" + done + + echo -e " " + echo -e "${GREEN}All files processed successfully!${NC}" + +elif [ -f "$input_path" ]; then + # Input is a single file + echo -e " " + echo -e "${CYAN}Processing single file: ${YELLOW}$input_path${NC}" + pin_single_file "$input_path" + echo -e " " + echo -e "${GREEN}File processed successfully!${NC}" else - echo "Skipping pinning on NMKR." + echo -e "${RED}Error: '$input_path' is not a valid file or directory.${NC}" >&2 + exit 1 fi