In the previous post on deploying Sieve filters to Stalwart using JMAP the workflow always created a new script. After some testing I realised it should update the existing script when one already exists. Below is the corrected workflow.

 1name: Deploy to Stalwart
 2"on":
 3  push:
 4    paths:
 5      - 'stalwart.sieve'
 6    branches:
 7      - main
 8  workflow_dispatch:
 9
10jobs:
11  deploy:
12    runs-on: ubuntu-latest
13    steps:
14      - uses: actions/checkout@v3
15      - name: Install tools
16        run: |
17          sudo apt-get update
18          sudo apt-get install -y jq          
19      - name: Upload via JMAP
20        env:
21          STALWART_JMAP_URL: ${{ vars.STALWART_JMAP_URL }}
22          STALWART_USER: ${{ secrets.STALWART_USER }}
23          STALWART_PASS: ${{ secrets.STALWART_PASS }}
24        run: |
25          set -e
26          set -x
27          base_url="${STALWART_JMAP_URL%/jmap*}"
28          session_json=$(curl -X GET -k -u "$STALWART_USER:$STALWART_PASS" "$base_url/.well-known/jmap")
29          account_id=$(echo "$session_json" | jq -r '.primaryAccounts["urn:ietf:params:jmap:sieve"]')
30
31          jmap_endpoint="${STALWART_JMAP_URL%/}"
32          upload_url="${jmap_endpoint}/upload/${account_id}"
33
34          curl -X POST -k -u "$STALWART_USER:$STALWART_PASS" \
35            -H "Content-Type: application/sieve" \
36            --data-binary @stalwart.sieve "$upload_url" > upload.json
37          blob_id=$(jq -r '.blobId' upload.json)
38          if [ "$blob_id" = "null" ] || [ -z "$blob_id" ]; then
39            echo "Upload failed" >&2
40            cat upload.json
41            exit 1
42          fi
43
44          list_body=$(jq -n --arg accountId "$account_id" '{
45            "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:sieve"],
46            "methodCalls": [["SieveScript/get", {"accountId": $accountId}, "0"]]
47          }')
48          list_resp=$(curl -X POST -k -u "$STALWART_USER:$STALWART_PASS" \
49            -H "Content-Type: application/json" \
50            -d "$list_body" "$jmap_endpoint")
51          echo "$list_resp" | jq
52          existing_id=$(echo "$list_resp" | jq -r '.methodResponses[0][1].list[] | select(.name=="stalwart.sieve") | .id')
53
54          if [ -n "$existing_id" ] && [ "$existing_id" != "null" ]; then
55            set_body=$(jq -n --arg accountId "$account_id" --arg blob "$blob_id" --arg id "$existing_id" '{
56              "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:sieve"],
57              "methodCalls": [["SieveScript/set", {
58                "accountId": $accountId,
59                "update": {($id): {"blobId": $blob}},
60                "onSuccessActivateScript": $id
61              }, "0"]]
62            }')
63          else
64            set_body=$(jq -n --arg accountId "$account_id" --arg blob "$blob_id" '{
65              "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:sieve"],
66              "methodCalls": [["SieveScript/set", {
67                "accountId": $accountId,
68                "create": {"A": {"name": "stalwart.sieve", "blobId": $blob}},
69                "onSuccessActivateScript": "#A"
70              }, "0"]]
71            }')
72          fi
73          curl -X POST -k -u "$STALWART_USER:$STALWART_PASS" \
74            -H "Content-Type: application/json" \
75            -d "$set_body" "$jmap_endpoint" | jq          

This version first queries existing scripts and either updates the current one or creates it if missing.

This post was written with the assistance of an AI tool.