Skip to content

Commit df35d02

Browse files
committed
add per-destination guides for storage destinations
1 parent aeb9d5c commit df35d02

12 files changed

Lines changed: 811 additions & 80 deletions

File tree

‎_includes/nav.html‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
{%- unless node.nav_exclude -%}
1414
{%- if node.parent == nil and node.title -%}
1515
{%- assign node_nav_title = node.nav_title | default: node.title -%}
16-
<li class="nav-list-item {% if node.expanded or page.url == node.url or page.parent == node.title or page.grand_parent == node.title %} active{% endif %}">
17-
{%- if page.parent == node.title or page.grand_parent == node.title -%}
16+
<li class="nav-list-item {% if node.expanded or page.url == node.url or page.parent == node.title or page.grand_parent == node.title or page.great_grand_parent == node.title %} active{% endif %}">
17+
{%- if page.parent == node.title or page.grand_parent == node.title or page.great_grand_parent == node.title -%}
1818
{%- assign first_level_url = node.url | absolute_url -%}
1919
{%- endif -%}
2020
{%- if node.has_children -%}
@@ -31,8 +31,8 @@
3131
{%- for child in children_list -%}
3232
{%- unless child.nav_exclude -%}
3333
{%- assign child_nav_title = child.nav_title | default: child.title -%}
34-
<li class="nav-list-item {% if page.url == child.url or page.parent == child.title %} active{% endif %}">
35-
{%- if page.url == child.url or page.parent == child.title -%}
34+
<li class="nav-list-item {% if page.url == child.url or page.parent == child.title or page.grand_parent == child.title %} active{% endif %}">
35+
{%- if page.url == child.url or page.parent == child.title or page.grand_parent == child.title -%}
3636
{%- assign second_level_url = child.url | absolute_url -%}
3737
{%- endif -%}
3838
{%- if child.has_children -%}
@@ -44,8 +44,27 @@
4444
<ul class="nav-list">
4545
{%- for grand_child in grand_children_list -%}
4646
{%- assign grand_child_nav_title = grand_child.nav_title | default: grand_child.title -%}
47-
<li class="nav-list-item {% if page.url == grand_child.url %} active{% endif %}">
47+
<li class="nav-list-item {% if page.url == grand_child.url or page.parent == grand_child.title %} active{% endif %}">
48+
{%- if page.url == grand_child.url or page.parent == grand_child.title -%}
49+
{%- assign third_level_url = grand_child.url | absolute_url -%}
50+
{%- endif -%}
51+
{%- if grand_child.has_children -%}
52+
<a href="#" class="nav-list-expander" aria-label="Expand {{ grand_child_nav_title }}"><svg viewBox="0 0 24 24" aria-hidden="true"><use xlink:href="#svg-arrow-right"></use></svg></a>
53+
{%- endif -%}
4854
<a href="{{ grand_child.url | absolute_url }}" class="nav-list-link child{% if page.url == grand_child.url %} active{% endif %}">{{ grand_child_nav_title }}</a>
55+
{%- if grand_child.has_children -%}
56+
{%- assign great_grand_children_list = pages_list | where: "parent", grand_child.title | where: "grand_parent", child.title | where: "great_grand_parent", node.title -%}
57+
<ul class="nav-list">
58+
{%- for great_grand_child in great_grand_children_list -%}
59+
{%- unless great_grand_child.nav_exclude -%}
60+
{%- assign great_grand_child_nav_title = great_grand_child.nav_title | default: great_grand_child.title -%}
61+
<li class="nav-list-item {% if page.url == great_grand_child.url %} active{% endif %}">
62+
<a href="{{ great_grand_child.url | absolute_url }}" class="nav-list-link child{% if page.url == great_grand_child.url %} active{% endif %}">{{ great_grand_child_nav_title }}</a>
63+
</li>
64+
{%- endunless -%}
65+
{%- endfor -%}
66+
</ul>
67+
{%- endif -%}
4968
</li>
5069
{%- endfor -%}
5170
</ul>

‎_layouts/default.html‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@
8585
<div class="breadcrumb-with-actions">
8686
<nav aria-label="Breadcrumb" class="breadcrumb-nav">
8787
<ol class="breadcrumb-nav-list">
88-
{% if page.grand_parent %}
88+
{% if page.great_grand_parent %}
89+
<li class="breadcrumb-nav-list-item"><a href="{{ first_level_url }}">{{ page.great_grand_parent }}</a></li>
90+
<li class="breadcrumb-nav-list-item"><a href="{{ second_level_url }}">{{ page.grand_parent }}</a></li>
91+
<li class="breadcrumb-nav-list-item"><a href="{{ third_level_url }}">{{ page.parent }}</a></li>
92+
{% elsif page.grand_parent %}
8993
<li class="breadcrumb-nav-list-item"><a href="{{ first_level_url }}">{{ page.grand_parent }}</a></li>
9094
<li class="breadcrumb-nav-list-item"><a href="{{ second_level_url }}">{{ page.parent }}</a></li>
9195
{% else %}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
layout: page
3+
title: Upload to Backblaze B2
4+
nav_title: Backblaze B2
5+
permalink: /guides/advanced/storage-destinations/backblaze-b2/
6+
parent: Storage Destinations
7+
grand_parent: Advanced
8+
great_grand_parent: Guides
9+
nav_order: 3
10+
description: >-
11+
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Backblaze B2 using its S3-compatible API and HTML/CSS to Image.
12+
---
13+
14+
# Upload images to Backblaze B2
15+
{: .no_toc }
16+
{: .fs-9 }
17+
18+
Send every rendered image or PDF to a Backblaze B2 bucket you control.
19+
{: .fs-6 .fw-300 }
20+
21+
<hr>
22+
23+
HTML/CSS to Image connects to Backblaze B2 through its S3-compatible API. A scoped B2 application key supplies the access key ID and secret used by HCTI.
24+
25+
{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
26+
27+
## Before you begin
28+
29+
You need:
30+
31+
- An existing B2 bucket.
32+
- The bucket's B2 region, such as `us-west-004`.
33+
- An optional key prefix, such as `hcti-renders`.
34+
35+
The region appears in the bucket's S3 endpoint: `https://s3.<region>.backblazeb2.com`.
36+
37+
## Create a Backblaze application key
38+
39+
In the Backblaze web console, open **Application Keys** and create a new application key.
40+
41+
1. Give the key a recognizable name.
42+
2. Restrict it to the destination bucket.
43+
3. Choose **Read and Write** access if HCTI may need to create later transformations while HCTI Storage is disabled. Write access is enough for base uploads.
44+
4. If offered, restrict the key to the same file-name prefix you will enter in HCTI.
45+
5. Copy the resulting `keyID` and `applicationKey`. The application key secret is shown only once.
46+
47+
Do not use the master application key. Backblaze does not support it with the S3-compatible API.
48+
49+
The credentials map to HCTI fields like this:
50+
51+
| Backblaze value | HCTI field |
52+
|:----------------|:-----------|
53+
| `keyID` | Access key ID |
54+
| `applicationKey` | Secret access key |
55+
56+
For least-privilege custom capabilities, `writeFiles` provides uploads, `readFiles` supports later transformations, and `deleteFiles` supports connection-test cleanup. Some S3 integrations also need `listAllBucketNames` for a bucket-scoped key, although HCTI's connection test writes directly to the configured bucket.
57+
58+
## Add Backblaze B2 to HCTI
59+
60+
Open the [Storage Destinations dashboard](https://htmlcsstoimage.com/dashboard/storage-destinations), click **Add storage destination**, and enter:
61+
62+
| HCTI field | Backblaze value |
63+
|:-----------|:----------------|
64+
| Provider | **Backblaze B2** |
65+
| Bucket | Existing B2 bucket name |
66+
| Key prefix | Optional folder-like prefix, without leading or trailing slashes |
67+
| Region | Region from the B2 S3 endpoint, such as `us-west-004` |
68+
| Access key ID | Application key `keyID` |
69+
| Secret access key | `applicationKey` secret |
70+
71+
Click **Test connection**, then enable and save the destination after the test succeeds. Copy the destination `id` for your API requests.
72+
73+
The test writes a small object under `<key-prefix>/.hcti/connection-tests/`. Cleanup is best-effort, so missing delete access does not make a successful write test fail.
74+
75+
## Upload an HCTI image to B2
76+
77+
Pass the destination ID as `storage_destination_id` when you create an image:
78+
79+
```bash
80+
curl -X POST https://hcti.io/v1/image \
81+
-u 'user-id:api-key' \
82+
-H 'Content-Type: application/json' \
83+
-d '{
84+
"url": "https://example.com",
85+
"storage_destination_id": "your-storage-destination-id"
86+
}'
87+
```
88+
89+
Rendering is lazy. With normal HCTI storage enabled, request the returned image URL to render the image and upload the B2 copy. With **Disable HCTI Storage** selected, send an authenticated `PUT` request to the returned `/v1/store/...` URL.
90+
91+
The base object is stored as `<key-prefix>/<image-id>.png`, or `.pdf` when the request uses `pdf_options`. Your B2 bucket settings determine whether that object is private or publicly accessible.
92+
93+
## Troubleshooting Backblaze B2 uploads
94+
95+
- **Authentication fails with a master key** — Create a standard application key; the master application key does not work with the S3-compatible API.
96+
- **The connection test uses the wrong endpoint** — Enter the region embedded in the bucket's S3 endpoint, including its numeric suffix.
97+
- **A bucket-scoped key is rejected** — Confirm the key is scoped to the exact bucket. If the error involves listing buckets, enable **Allow List All Bucket Names**.
98+
- **A later transformation fails with HCTI Storage disabled** — Add `readFiles` access.
99+
- **Connection-test objects remain** — Add the capabilities Backblaze requires for deleting objects, including `deleteFiles`.
100+
101+
Backblaze documents [creating scoped application keys](https://www.backblaze.com/docs/en/cloud-storage-get-started-with-a-backblaze-integration) and [calling the B2 S3-compatible API](https://www.backblaze.com/docs/en/cloud-storage-call-the-s3-compatible-api).
102+
103+
{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
104+
105+
{% include code_footer.md version=1 %}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
layout: page
3+
title: Upload to DigitalOcean Spaces
4+
nav_title: DigitalOcean Spaces
5+
permalink: /guides/advanced/storage-destinations/digitalocean-spaces/
6+
parent: Storage Destinations
7+
grand_parent: Advanced
8+
great_grand_parent: Guides
9+
nav_order: 4
10+
description: >-
11+
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to DigitalOcean Spaces with a scoped Spaces key and HTML/CSS to Image.
12+
---
13+
14+
# Upload images to DigitalOcean Spaces
15+
{: .no_toc }
16+
{: .fs-9 }
17+
18+
Send every rendered image or PDF to a DigitalOcean Space you control.
19+
{: .fs-6 .fw-300 }
20+
21+
<hr>
22+
23+
HTML/CSS to Image connects to DigitalOcean Spaces through its S3-compatible API. HCTI derives the service endpoint from the Space's region and authenticates with a Spaces access key and secret.
24+
25+
{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
26+
27+
## Before you begin
28+
29+
You need:
30+
31+
- An existing DigitalOcean Space.
32+
- The Space's region, such as `nyc3`, `sfo3`, or `ams3`.
33+
- An optional key prefix, such as `hcti-renders`.
34+
35+
The region appears at the beginning of the Spaces endpoint: `https://<region>.digitaloceanspaces.com`.
36+
37+
## Create a Spaces access key
38+
39+
In the DigitalOcean control panel, open the **Spaces Keys** section and generate a new key.
40+
41+
1. Give the key a recognizable name.
42+
2. Restrict it to the destination Space when possible.
43+
3. Grant object read, write, and delete access for the complete HCTI workflow.
44+
4. Copy the access key and secret. Save the secret when it is shown.
45+
46+
Write access is required for images and connection tests. Read access lets HCTI reload the original for a later resize, crop, or format conversion when **Disable HCTI Storage** is selected. Delete access only cleans up connection-test objects; HCTI does not delete your rendered images.
47+
48+
If you configure both a scoped key and an HCTI key prefix, make sure the key's permitted object path includes that same prefix.
49+
50+
## Add DigitalOcean Spaces to HCTI
51+
52+
Open the [Storage Destinations dashboard](https://htmlcsstoimage.com/dashboard/storage-destinations), click **Add storage destination**, and enter:
53+
54+
| HCTI field | DigitalOcean value |
55+
|:-----------|:-------------------|
56+
| Provider | **DigitalOcean Spaces** |
57+
| Bucket | Existing Space name |
58+
| Key prefix | Optional folder-like prefix, without leading or trailing slashes |
59+
| Region | Space region, such as `nyc3` |
60+
| Access key ID | Spaces access key |
61+
| Secret access key | Spaces secret key |
62+
63+
Click **Test connection**, then enable and save the destination after the test succeeds. Copy the destination `id` for your API requests.
64+
65+
The test writes a small object under `<key-prefix>/.hcti/connection-tests/`. Cleanup is best-effort, so missing delete access does not make a successful write test fail.
66+
67+
## Upload an HCTI image to Spaces
68+
69+
Pass the destination ID as `storage_destination_id` when you create an image:
70+
71+
```bash
72+
curl -X POST https://hcti.io/v1/image \
73+
-u 'user-id:api-key' \
74+
-H 'Content-Type: application/json' \
75+
-d '{
76+
"url": "https://example.com",
77+
"storage_destination_id": "your-storage-destination-id"
78+
}'
79+
```
80+
81+
Rendering is lazy. With normal HCTI storage enabled, request the returned image URL to render the image and upload the Spaces copy. With **Disable HCTI Storage** selected, send an authenticated `PUT` request to the returned `/v1/store/...` URL.
82+
83+
The base object is stored as `<key-prefix>/<image-id>.png`, or `.pdf` when the request uses `pdf_options`. HCTI does not change the Space's file permissions or CDN configuration.
84+
85+
## Troubleshooting DigitalOcean Spaces uploads
86+
87+
- **The connection test cannot find the Space** — Confirm the Space name and the region at the beginning of its endpoint.
88+
- **Authentication fails** — Use a Spaces key pair, not a DigitalOcean personal access token.
89+
- **A scoped key returns Access Denied** — Confirm that it grants write access to the Space and to the configured prefix.
90+
- **A later transformation fails with HCTI Storage disabled** — Add object read access.
91+
- **Connection-test objects remain** — Add object delete access for `.hcti/connection-tests/`.
92+
93+
DigitalOcean documents the [Spaces S3-compatible API, endpoints, and permission levels](https://docs.digitalocean.com/reference/api/spaces/).
94+
95+
{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
96+
97+
{% include code_footer.md version=1 %}

0 commit comments

Comments
 (0)