Dropover から Cloudflare R2 に上げて、カスタムドメインの URL をクリップボードに載せるまで

Image in a image block

Dropover の「Copy S3 Link」は R2 でも使えるが、生成される URL はエンドポイント(*.r2.cloudflarestorage.com)ベースで、公式でもカスタムドメインや URL 形式の変更は未対応と書かれている。

Image in a image block

標準のS3へのアップロードでは、カスタムドメインに対応していないので、https://accesidxxxx.r2.cloudflarestorage.com/bucket/sample.jpgのようなURLとなるため、カスタムドメインのhttps://cdn.exampleblog.dev/sample.jpgのようにしたかったのです。

そこで Custom Script(bash)でアップロードと URL 組み立てを自前化し、公開用ドメイン(例として https://cdn.exampleblog.dev のような独自ドメイン)に寄せた。

この記事は、その試行錯誤でハマった点と、最終的に安定した構成のメモである。同じことをやる人のショートカットになれば幸いだ。


前提条件

  • macOS
  • Dropoverインストール済み
  • Homebrewでawsインストール済み
  • Cloudflare R2でカスタムドメイン・バケットが作成済み

やりたかったこと

  1. シェルフ上のファイルを R2 のバケットに置く(Dropoverでアップロード)
  2. ブラウザで開きたい URL は https://(カスタムドメイン)/オブジェクトキー(バケット名はパスに含めない)
  3. その URL を クリップボードに入れる(Dropover の「Shelf Interaction」などから bash を叩く)

Dropover 本体の S3 連携のままだと、コピーされるリンクをカスタムドメインに差し替える公式オプションはない。presigned URL ならホストだけ書き換えも基本できないので、アップロード経路ごと aws s3 cp に寄せる方針にした。

Does Dropover support custom domains?
Custom domains are not supported at this moment.

Can I customize the link format or domain of the generated URL?
Not at this time. Dropover generates public URLs based on the endpoint and bucket provided. If you want to use a custom domain, you’ll need to configure that within your S3 provider separately.


構成の概要

  • 実行場所: ~/Library/Application Scripts/me.damir.dropover-mac/r2-upload-aws.sh(Dropover が参照する Application Scripts 配下)
  • 設定: 同じディレクトリの .r2-upload.env(推奨)または ~/.r2-upload.env など
  • 処理の流れ: 環境ファイル読み込み → PATH 補正 → AWS 認証パス補正 → aws s3 cpPUBLIC_BASE とオブジェクトキーを結んで pbcopy

リポジトリ側の参照実装は scripts/r2-upload-aws.shscripts/r2-upload.env.example に置いてある(Dropover 用にコピーして使う想定)。


実行コード(セットアップと起動)

1. AWS CLI の導入(Homebrew)
brew update
brew install awscli
aws --version
2. スクリプトと設定ファイルの配置

Dropover から呼ぶパスにコピーする(バンドル ID は環境に合わせて読み替え)。

SCRIPT_DIR="$HOME/Library/Application Scripts/me.damir.dropover-mac"
mkdir -p "$SCRIPT_DIR"

# リポジトリからコピーする想定(パスは自分の clone に合わせる)
cp /path/to/repo/scripts/r2-upload-aws.sh "$SCRIPT_DIR/"
chmod +x "$SCRIPT_DIR/r2-upload-aws.sh"
3. .r2-upload.env(伏せ字・ダミー値)

機密は `` で伏せている。 実運用ではダッシュボードの値に置き換える。
Dropover からの起動では スクリプトと同じディレクトリに置くのが確実。

# ファイル: ~/Library/Application Scripts/me.damir.dropover-mac/.r2-upload.env
# 形式: KEY=value(= の前後に空白なし)。export は不要(スクリプト側で set -a して source する)。

# Cloudflare ダッシュボードの R2 概要に表示される Account ID(32 hex)
R2_ACCOUNT_ID=********************************

# R2 のバケット名
R2_BUCKET=********************************

# ブラウザで共有したい URL のオリジン(末尾スラッシュなし)。記事では架空の例。
PUBLIC_BASE=https://cdn.exampleblog.dev

# --- 認証(どれか一つ。Dropover だけ credentials が読めないときはキー直書き)---
# 名前付きプロファイルを使う場合のみ(未設定なら default / 環境変数)
# AWS_PROFILE=********************************

# サンドボックスで ~/.aws が読めないときの最終手段(リポジトリにコミットしないこと)
AWS_ACCESS_KEY_ID=********************
AWS_SECRET_ACCESS_KEY=****************************************

# 任意: オブジェクトキーを手動指定(未指定時は YYYYMMDD-元ファイル名)
# OBJECT_KEY_OVERRIDE=********************************
4. ターミナルからの試運転
"$HOME/Library/Application Scripts/me.damir.dropover-mac/r2-upload-aws.sh" \\
  "$HOME/Desktop/sample.jpg"

デバッグしたいとき(読み込んだ env・HOME などが表示される):

R2_UPLOAD_DEBUG=1 \\
  "$HOME/Library/Application Scripts/me.damir.dropover-mac/r2-upload-aws.sh" \\
  "$HOME/Desktop/sample.jpg"

成功すると標準出力に Uploaded & copied: と URL が出て、同じ URL がクリップボードに入る。

5. r2-upload-aws.sh 全文(AWS CLI / bash)

機密はスクリプトに書かず 環境変数と .r2-upload.env に寄せている。リポジトリの scripts/r2-upload-aws.sh と同一。

#!/usr/bin/env bash
# R2 アップロード (S3 互換 API) → 公開 URL は「カスタムドメイン + オブジェクトキー」のみ(バケット名は URL に含めない)
#
# Dropover 等 GUI から実行するときは ~/.r2-upload.env か ~/.config/r2-upload/env に
# R2_ACCOUNT_ID / R2_BUCKET / PUBLIC_BASE を書く(ターミナルの export は引き継がれない)。
# 任意: R2_UPLOAD_ENV=/path/to/file で別パスを指定。
#
# Dropover 等は HOME が /var/folders/... になり ~/.r2-upload.env が読めないことがある。
# その場合は「このスクリプトと同じフォルダ」に .r2-upload.env を置く(推奨)。
set -euo pipefail

_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

_mac_nfs_home() {
  local u
  u="$(id -un 2>/dev/null)" || return 1
  dscl . -read "/Users/${u}" NFSHomeDirectory 2>/dev/null | awk '{print $2; exit}'
}

# Dropover 等 GUI からだと PATH が狭く、Homebrew の aws が見えない
_ensure_path_for_gui() {
  local h prefixes
  h="$(_mac_nfs_home || true)"
  prefixes="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/local/aws-cli/v2/current/bin"
  [[ -n "${h}" ]] && prefixes="${prefixes}:${h}/.local/bin:${h}/bin"
  export PATH="${prefixes}:/usr/bin:/bin:${PATH:-}"
}
_ensure_path_for_gui

_load_r2_env() {
  local f _loaded="" _realhome=""
  local -a _candidates=()

  _realhome="$(_mac_nfs_home || true)"

  [[ -n "${R2_UPLOAD_ENV:-}" ]] && _candidates+=("${R2_UPLOAD_ENV}")
  _candidates+=(
    "${_script_dir}/.r2-upload.env"
    "${_script_dir}/r2-upload.env"
    "${HOME:-}/.r2-upload.env"
    "${HOME:-}/.config/r2-upload/env"
  )
  if [[ -n "${_realhome}" ]]; then
    _candidates+=("${_realhome}/.r2-upload.env" "${_realhome}/.config/r2-upload/env")
  fi

  for f in "${_candidates[@]}"; do
    [[ -n "${f}" && -f "${f}" ]] || continue
    set -a
    # shellcheck disable=SC1090
    source "${f}"
    set +a
    _loaded="${f}"
    break
  done

  if [[ -n "${R2_UPLOAD_DEBUG:-}" ]]; then
    echo "r2-upload-aws: HOME=${HOME:-}" >&2
    echo "r2-upload-aws: script_dir=${_script_dir}" >&2
    echo "r2-upload-aws: dscl_home=${_realhome:-}" >&2
    echo "r2-upload-aws: loaded_env=${_loaded:-none}" >&2
  fi
}
_load_r2_env

usage() {
  echo "Usage: $(basename "$0") <local-file>" >&2
  exit 1
}
[[ $# -ge 1 && -f "$1" ]] || usage

_diag_missing() {
  echo "r2-upload-aws: R2_ACCOUNT_ID / R2_BUCKET / PUBLIC_BASE が未設定です。" >&2
  echo "  1) このスクリプトと同じフォルダに .r2-upload.env を置く(Dropover 推奨)" >&2
  echo "     例: ${_script_dir}/.r2-upload.env" >&2
  echo "  2) または実ユーザのホームに ~/.r2-upload.env(現在 HOME=${HOME:-空})" >&2
  echo "  内容は KEY=value(=の前後に空白なし)。例は scripts/r2-upload.env.example" >&2
  echo "  デバッグ: 環境に R2_UPLOAD_DEBUG=1 を付けて再実行" >&2
}

# --- 設定(環境変数で上書き可。上記ファイルで読み込み可)---
if [[ -z "${R2_ACCOUNT_ID:-}" || -z "${R2_BUCKET:-}" || -z "${PUBLIC_BASE:-}" ]]; then
  _diag_missing
  exit 1
fi

# AWS_PROFILE は任意。未設定なら --profile を付けず default / 環境変数認証を使う。
# (set -u では空配列の "${arr[@]}" が環境によって unbound になるため、分岐で付ける)
ENDPOINT_URL="<https://$>{R2_ACCOUNT_ID}.r2.cloudflarestorage.com"

# オブジェクトキー = URL 上のパス(バケット名は含めない)
# 既定: YYYYMMDD-ファイル名(例: 20260502-photo.jpg)。同日同名は上書き。
base="$(basename "$1")"
OBJECT_KEY="${OBJECT_KEY_OVERRIDE:-$(date +%Y%m%d)-${base}}"
# 先頭スラッシュを除く(URL 結合のため)
OBJECT_KEY="${OBJECT_KEY#/}"

if ! command -v aws >/dev/null 2>&1; then
  echo "r2-upload-aws: aws が見つかりません(brew install awscli)。PATH=${PATH}" >&2
  exit 127
fi

# Dropover 等サンドボックスアプリの子プロセスでは HOME が Containers/... になり、
# ~/.aws/credentials が「コンテナ内のホーム」しか見えず Unable to locate credentials になる。
# 環境変数にキーが無いときだけ、dscl の実ホームの ~/.aws を指す。
_point_aws_sdk_to_user_credentials() {
  local h cred cfg
  if [[ -n "${AWS_ACCESS_KEY_ID:-}" && -n "${AWS_SECRET_ACCESS_KEY:-}" ]]; then
    return 0
  fi
  h="$(_mac_nfs_home || true)"
  [[ -z "${h}" ]] && return 0
  cred="${h}/.aws/credentials"
  cfg="${h}/.aws/config"
  if [[ -z "${AWS_SHARED_CREDENTIALS_FILE:-}" && -f "${cred}" ]]; then
    export AWS_SHARED_CREDENTIALS_FILE="${cred}"
  fi
  if [[ -z "${AWS_CONFIG_FILE:-}" && -f "${cfg}" ]]; then
    export AWS_CONFIG_FILE="${cfg}"
  fi
  export AWS_EC2_METADATA_DISABLED="${AWS_EC2_METADATA_DISABLED:-true}"
  if [[ -n "${R2_UPLOAD_DEBUG:-}" ]]; then
    echo "r2-upload-aws: AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE:-unset}" >&2
    echo "r2-upload-aws: AWS_CONFIG_FILE=${AWS_CONFIG_FILE:-unset}" >&2
  fi
}
_point_aws_sdk_to_user_credentials

if [[ -n "${AWS_PROFILE:-}" ]]; then
  aws s3 cp --profile "${AWS_PROFILE}" "$1" "s3://${R2_BUCKET}/${OBJECT_KEY}" \\
    --endpoint-url "${ENDPOINT_URL}" \\
    --region auto
else
  aws s3 cp "$1" "s3://${R2_BUCKET}/${OBJECT_KEY}" \\
    --endpoint-url "${ENDPOINT_URL}" \\
    --region auto
fi

pub="${PUBLIC_BASE%/}"
URL="${pub}/${OBJECT_KEY}"
printf '%s' "${URL}" | pbcopy
printf 'Uploaded & copied:\\n%s\\n' "${URL}"

結果の URL 例(架空ドメイン・ダミー日付):

<https://cdn.exampleblog.dev/20260502-sample.jpg>

Dropoverにて設定

📄Arrow icon of a page linkDropoverシェルフ内の画像をWebPに変換 と同様に、Shelf Interaction 項目の Custom scriptsにて設定し、Instant actionで設定するだけです。

現在の新しいバージョンでは、少し設定場所が変更されていますので、注意してください。

💡
バージョンによっては項目や設定が変更になっている場合があります

ハマりどころ(時系列メモ)

1. R2_ACCOUNT_ID: set R2_ACCOUNT_ID(環境変数が空)

GUI から起動したスクリプトは、ターミナルで export した変数を 引き継がない~/.r2-upload.envKEY=value を書いて source する必要がある。

さらに Dropover は App Sandbox 付きのコンテナで動くことがあり、HOME~/Library/Containers/me.damir.dropover-mac/Data/... 側になる。その状態だと ~/.r2-upload.env が「コンテナ内のホーム」を指してしまい」、ユーザーが編集した実ホームの ~/.r2-upload.env が読まれない

対策: dscl で実ユーザの NFS ホームを取り、そのパスの .r2-upload.env も候補に入れる。確実なのは r2-upload-aws.sh と同じフォルダに .r2-upload.env を置くこと(Application Scripts 配下)。

2. aws: command not found

GUI 子プロセスの PATH が狭く、/opt/homebrew/bin に届かない。

対策: スクリプト先頭で /opt/homebrew/bin/usr/local/bin・公式 AWS CLI v2 のパスなどを PATH の先頭に足す。

3. The config profile (r2) could not be found

スクリプト側で AWS_PROFILE 未指定でも r2 固定にしていたのが原因。

対策: AWS_PROFILE書いたときだけ --profile を付ける。未指定なら default プロファイルや環境変数認証に任せる。

4. _aws_extra[@]: unbound variable

set -u空配列の "${arr[@]}" の組み合わせで、Bash によっては落ちる。

対策: --profile の有無で aws s3 cp を if/else の二段に分け、空配列展開をやめる。

5. Unable to locate credentials

コンテナ実行時、aws が見に行く ~/.aws/credentialsコンテナの HOME 基準になり、実ホームの資格情報に届かない。

対策: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY が未設定のとき、dscl で得たホームの ~/.aws/credentials(と config)へ AWS_SHARED_CREDENTIALS_FILE / AWS_CONFIG_FILE を向ける。それでも読めない(サンドボックス拒否)場合は .r2-upload.env にキーを直書きするしかない。そのファイルは絶対に git に載せない。

6. URL が長すぎる

当初は uploads/日付/uuid-ファイル名 のようなキーにしていたが、公開 URL が冗長になる。

対策: デフォルトのオブジェクトキーを YYYYMMDD-元ファイル名 に変更。同日・同名は 上書きになる点だけ注意。別ルールにしたい場合は OBJECT_KEY_OVERRIDE で上書き可能。


AWS CLI を選んだ理由(この記事の主軸)

R2 は S3 互換 API を公開しており、Cloudflare も AWS CLI / SDK での利用を案内している。認証は R2 の S3 API 用アクセスキー~/.aws/credentials(または環境変数)。Terraform・他ツール・本文の bash と 同じ操作モデルで揃えやすい。

Dropover からの実行では PATH を広げることと AWS_SHARED_CREDENTIALS_FILE で実ホームの credentials を指すことがポイントになった(詳細は「ハマりどころ」)。

(参考)Cloudflare Wrangler

同じリポジトリに scripts/r2-upload-wrangler.sh もあるが、記事の手順は AWS CLI 版がメインWrangler は Cloudflare 公式 CLI で wrangler r2 object put のように R2 に PUT できる。認証は wrangler login や API トークン寄りで、Workers / Pages と CLI を一本化したい場合に向く。Dropover 連携でも PATH 補正は同様に必要になる。

試すだけなら:

brew install cloudflare-wrangler
chmod +x /path/to/repo/scripts/r2-upload-wrangler.sh
/path/to/repo/scripts/r2-upload-wrangler.sh "$HOME/Desktop/sample.jpg"

.r2-upload.envAWS 版と同じく R2_BUCKETPUBLIC_BASE があればよい(R2_ACCOUNT_ID は Wrangler スクリプト側では未使用)。


セキュリティで特に気をつける項目(伏せ字の意味)

項目 注意
R2 の S3 互換アクセスキー .r2-upload.env やログに平文が残りやすい。リポジトリ・スクショ・記事に載せない。 漏れたらダッシュボードでローテーション。
シークレットアクセスキー 上に同じ。共有ドライブや同期フォルダに置かない。
Account ID / バケット名 機密度はキーより低いが、記事や gist では伏せ字またはダミーにするとよい(推測・スキャン対策)。
.r2-upload.env 全体 .gitignore 必須。バックアップツールの公開範囲にも注意。
presigned URL クエリ付き URL はそのまま他人に渡さない設計にする。

Cloudflare / URL まわりの注意

  • カスタムドメインは R2 ダッシュボードや DNS で別途設定する。スクリプトは PUBLIC_BASE + オブジェクトキーを文字列結合しているだけで、実際にその URL で GET できるかは R2 の公開設定・カスタムドメインのマッピング次第。
  • presigned URL を使う運用では、ホストだけ差し替えても 署名が合わず 403 になりがち。今回のような「固定の公開ベース URL」を組み立てる方式とは前提が違う。
  • オブジェクトキーを短くした結果、同名上書きに注意。

Dropover 公式ドキュメント

  • Using S3 in Dropover — S3 / S3 互換の設定、Instant Actions、カスタムドメイン未対応の FAQ など。

まとめ

  • Dropover 標準の「Copy S3 Link」だけでは カスタムドメイン URL にしたいニーズをそのまま満たしにくい。
  • Custom Script + AWS CLI(r2-upload-aws.sh 全文は上記)+ .r2-upload.env(スクリプト同階層推奨) で、PATH・HOME・認証のコンテナ問題を潰せば、Dropover のシェルフから 意図した公開 URL を pbcopy まで持っていける。
  • 別ルートとして Wrangler もあるが、本記事では AWS CLI を主にした。

このメモは作業ログを整理したものです。文中の cdn.exampleblog.dev や `*` は架空・伏せ字であり、実サービスとは無関係です。ツールや Dropover のバージョンによって挙動が変わる場合があります。

最後に、このような複雑な設定はしたくない方は、そのまま標準のS3にてアップロードし、下記のような置換するコードをシェルスクリプトやショートカットキーで代用しても結果的には同じ事ですね。

pbpaste | sed 's#https://[^.]*\.r2\.cloudflarestorage\.com/your-bucket#https://cdn.example.com#g' | pbcopy