Store and read back
Plain S3, plus one question: where does the object stand? First source the Quickstart’s first block to set HOST, AUTH and BUCKET.
Create a bucket
Section titled “Create a bucket”Only the tenant that created it can use it.
# Create the bucket. 200 means it exists and it is yours.
curl -sS -m 60 -X PUT -H "$AUTH" \ -o /dev/null -w 'bucket %{http_code}\n' "$HOST/$BUCKET"Store and read back
Section titled “Store and read back”An ordinary PUT, encrypted at rest by default. You get back exactly what you stored.
# Store a local file as report.bin. Bring your own bytes, or make 64 KiB.
[ -f local.bin ] || head -c 65536 /dev/urandom > local.bin
curl -sS -m 120 -X PUT -H "$AUTH" --data-binary @local.bin \ -o /dev/null -w 'put %{http_code}\n' "$HOST/$BUCKET/report.bin"# Read it back and compare it to what you sent.
curl -sS -m 120 -H "$AUTH" -o roundtrip.bin "$HOST/$BUCKET/report.bin"
cmp local.bin roundtrip.bin && echo byte-equalWhere the object stands
Section titled “Where the object stands”HEAD adds two headers:
x-amz-meta-permafrost-sync-state:PENDING, thenSYNCEDonce on Walrus;HELDorLOCALfor held and local objects (Placement).x-amz-meta-permafrost-blob-id: present once on Walrus. Absent isn’t an error.
Poll HEAD until it lands:
# HEAD carries Permafrost's own metadata. The sync state is always there. The# blob id appears only once the object is certified, so its absence is# "not yet certified", not an error.
HEADERS=$(curl -sS -m 60 -I -H "$AUTH" "$HOST/$BUCKET/report.bin")printf '%s\n' "$HEADERS" | grep -i '^x-amz-meta-permafrost-'
WAIT_SECS=${BLOBID_WAIT_SECS:-300}echo "waiting up to ${WAIT_SECS}s for the blob id"
WAITED=0BLOB_ID=""while [ "$WAITED" -lt "$WAIT_SECS" ]; do HEADERS=$(curl -sS -m 60 -I -H "$AUTH" "$HOST/$BUCKET/report.bin") BLOB_ID=$(printf '%s\n' "$HEADERS" \ | grep -i '^x-amz-meta-permafrost-blob-id:' | cut -d' ' -f2- | tr -d '\r' || true) if [ -n "$BLOB_ID" ]; then break; fi sleep 5 WAITED=$((WAITED + 5))done
echo "blob id: ${BLOB_ID:-not yet certified} (after ${WAITED}s)"A SYNCED object has a receipt on Sui (Verify a file).
Large media in parts
Section titled “Large media in parts”Standard S3 multipart. Reads back whole, or by range with 206.
# A large object goes up in parts. Every part but the last must be at least# 5 MiB - that is S3's minimum - so the last part is deliberately smaller.
KEY=${KEY:-big.bin}PARTS=${PARTS:-3}PART_MIB=${PART_MIB:-5}
UPLOAD_ID=$(curl -sS -m 60 -X POST -H "$AUTH" "$HOST/$BUCKET/$KEY?uploads" \ | sed -n 's:.*<UploadId>\(.*\)</UploadId>.*:\1:p')echo "upload id: $UPLOAD_ID"
# Upload each part, keeping the ETag the server answers with.: > all.binXML="<CompleteMultipartUpload>"i=1while [ "$i" -le "$PARTS" ]; do if [ "$i" -eq "$PARTS" ]; then MIB=1; else MIB=$PART_MIB; fi head -c "$((MIB * 1048576))" /dev/urandom > "part$i.bin" cat "part$i.bin" >> all.bin
ETAG=$(curl -sS -m 300 -X PUT -H "$AUTH" --data-binary @"part$i.bin" -D - -o /dev/null \ "$HOST/$BUCKET/$KEY?partNumber=$i&uploadId=$UPLOAD_ID" \ | grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r') XML="$XML<Part><PartNumber>$i</PartNumber><ETag>$ETAG</ETag></Part>" echo "part $i/$PARTS ($MIB MiB) $ETAG" i=$((i + 1))doneXML="$XML</CompleteMultipartUpload>"
# Completing is the slow call: the server assembles and registers the object.printf '%s' "$XML" > complete.xmlcurl -sS -m 600 -X POST -H "$AUTH" -H 'Content-Type: application/xml' \ --data-binary @complete.xml \ -o complete-response.xml -w 'complete %{http_code}\n' \ "$HOST/$BUCKET/$KEY?uploadId=$UPLOAD_ID"
curl -sS -m 600 -H "$AUTH" -o whole.bin "$HOST/$BUCKET/$KEY"cmp all.bin whole.bin && echo "byte-equal over $(wc -c < all.bin | tr -d ' ') bytes"
# 16 bytes at the 1 MiB mark. 206 means the range was served, not the object.curl -sS -m 120 -H "$AUTH" -H 'Range: bytes=1048576-1048591' \ -o slice.bin -w 'range %{http_code}\n' "$HOST/$BUCKET/$KEY"
curl -sS -m 120 -X DELETE -H "$AUTH" \ -o /dev/null -w 'delete %{http_code}\n' "$HOST/$BUCKET/$KEY"- Neither header above appears. Don’t wait on them.
- Every part but the last must meet S3’s minimum part size, and no part may be over the deployment’s limit on one sealed
PUT. SDK defaults (typically 5–8 MiB parts) are far below it.
Delete
Section titled “Delete”# Delete it. Expect 204, then 404 on the read path.
curl -sS -m 60 -X DELETE -H "$AUTH" \ -o /dev/null -w 'delete %{http_code}\n' "$HOST/$BUCKET/report.bin"
curl -sS -m 60 -H "$AUTH" \ -o /dev/null -w 'get after delete %{http_code}\n' "$HOST/$BUCKET/report.bin"204, then 404 on read and no answer from the keyless verify route.
Permafrost runs on Sui testnet and Walrus testnet. Everything here describes a shipped testnet instance, not a production service.