mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
⏪ Backport .github and docker/ changes from develop
This commit is contained in:
parent
021492a577
commit
2df5f417a0
9
.github/CODEOWNERS
vendored
Normal file
9
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Changes to workflows, CI configuration and repository automation need
|
||||
# review from the ci-maintainers team. Issue and PR templates do not.
|
||||
/.github/workflows/ @penpot/ci-maintainers
|
||||
/.github/actions/ @penpot/ci-maintainers
|
||||
/.github/scripts/ @penpot/ci-maintainers
|
||||
/.github/dependabot.yml @penpot/ci-maintainers
|
||||
/.github/zizmor.yml @penpot/ci-maintainers
|
||||
/.github/actionlint.yaml @penpot/ci-maintainers
|
||||
/.github/CODEOWNERS @penpot/ci-maintainers
|
||||
14
.github/actionlint.yaml
vendored
Normal file
14
.github/actionlint.yaml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# actionlint configuration — https://github.com/rhysd/actionlint/blob/main/docs/config.md
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- penpot-standar-runner
|
||||
- penpot-extended-runner
|
||||
|
||||
paths:
|
||||
.github/workflows/**/*.{yml,yaml}:
|
||||
ignore:
|
||||
# shellcheck style/info notices that are pervasive in the existing
|
||||
# scripts (unquoted `>> $GITHUB_OUTPUT`, repeated redirects). Real
|
||||
# shellcheck warnings and errors are still reported.
|
||||
- 'SC2086:info'
|
||||
- 'SC2129:style'
|
||||
25
.github/workflows/auto-label.yml
vendored
25
.github/workflows/auto-label.yml
vendored
@ -1,25 +1,42 @@
|
||||
name: Auto Label and Add to Project
|
||||
|
||||
# pull_request_target is required so the GitHub App secrets are available
|
||||
# for PRs from forks. It is safe here: the job never checks out or runs PR
|
||||
# code, it only reads ids from the event payload.
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
# The Renovate Dependency Dashboard and Renovate's GitHub Actions PRs do
|
||||
# not need triage. Other Renovate PRs (base images, Node.js, pnpm) do.
|
||||
if: >-
|
||||
!(github.event_name == 'issues'
|
||||
&& github.event.issue.user.login == 'penpot-renovate[bot]')
|
||||
&& !(github.event_name == 'pull_request_target'
|
||||
&& github.event.pull_request.user.login == 'penpot-renovate[bot]'
|
||||
&& github.event.pull_request.head.ref == 'renovate-github-actions')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: triage-app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
with:
|
||||
app-id: ${{ secrets.TRIAGE_APP_ID }}
|
||||
client-id: ${{ secrets.TRIAGE_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.TRIAGE_APP_PRIVATE_KEY }}
|
||||
owner: penpot
|
||||
# No owner/repositories: the token is scoped to this repository.
|
||||
# Organization permissions (the project board) still apply.
|
||||
permission-issues: write
|
||||
permission-pull-requests: write
|
||||
permission-organization-projects: write
|
||||
|
||||
- name: Process Issue or PR
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.triage-app-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
29
.github/workflows/build-adhoc.yml
vendored
29
.github/workflows/build-adhoc.yml
vendored
@ -20,18 +20,39 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: ${{ inputs.gh_ref }}
|
||||
force: ${{ inputs.force }}
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: ${{ inputs.gh_ref }}
|
||||
# Pin build-docker to the commit build-bundle actually bundled.
|
||||
@ -39,8 +60,10 @@ jobs:
|
||||
force: ${{ inputs.force }}
|
||||
|
||||
build-docker-admin-console:
|
||||
permissions: {}
|
||||
uses: ./.github/workflows/build-docker-admin-console.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
ORG_WORKFLOW_TOKEN: ${{ secrets.ORG_WORKFLOW_TOKEN }}
|
||||
with:
|
||||
gh_ref: ${{ inputs.nitrate_ref || inputs.gh_ref }}
|
||||
force: ${{ inputs.force }}
|
||||
|
||||
52
.github/workflows/build-bundle.yml
vendored
52
.github/workflows/build-bundle.yml
vendored
@ -20,7 +20,6 @@ on:
|
||||
description: 'Name of the branch or ref'
|
||||
type: string
|
||||
required: true
|
||||
default: 'develop'
|
||||
force:
|
||||
description: 'Rebuild and overwrite even if this version already exists in S3'
|
||||
type: boolean
|
||||
@ -30,6 +29,17 @@ on:
|
||||
sha:
|
||||
description: 'Bundled commit, full 40-char SHA.'
|
||||
value: ${{ jobs.check.outputs.sha }}
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID:
|
||||
required: false
|
||||
AWS_REGION:
|
||||
required: false
|
||||
AWS_SECRET_ACCESS_KEY:
|
||||
required: false
|
||||
MATTERMOST_WEBHOOK:
|
||||
required: false
|
||||
S3_BUCKET:
|
||||
required: false
|
||||
|
||||
# Literal group name: under `workflow_call`, `github.workflow` resolves to the
|
||||
# caller's workflow, which put this workflow and the other reusable one called
|
||||
@ -39,6 +49,9 @@ concurrency:
|
||||
group: build-bundle-${{ inputs.gh_ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── 1. Decide whether there is anything to build ───────────────────────
|
||||
check:
|
||||
@ -55,15 +68,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
|
||||
run: |
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
|
||||
echo "bundle_version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
@ -79,8 +95,12 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
SHORT_SHA: ${{ steps.vars.outputs.short_sha }}
|
||||
BUNDLE_VERSION: ${{ steps.vars.outputs.bundle_version }}
|
||||
run: |
|
||||
if [ "${{ inputs.force }}" = "true" ]; then
|
||||
if [ "$FORCE" = "true" ]; then
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo "### 🔁 Bundle build forced"
|
||||
@ -91,8 +111,8 @@ jobs:
|
||||
fi
|
||||
|
||||
BUNDLE_EXISTS=$(aws s3api head-object \
|
||||
--bucket ${{ secrets.S3_BUCKET }} \
|
||||
--key "penpot-sha-${{ steps.vars.outputs.short_sha }}.zip" \
|
||||
--bucket "$S3_BUCKET" \
|
||||
--key "penpot-sha-${SHORT_SHA}.zip" \
|
||||
> /dev/null 2>&1 && echo "true" || echo "false")
|
||||
|
||||
if [ "$BUNDLE_EXISTS" = "true" ]; then
|
||||
@ -100,7 +120,7 @@ jobs:
|
||||
{
|
||||
echo "### ⏭️ Bundle build skipped"
|
||||
echo ""
|
||||
echo "The bundle in S3 was already built from \`sha-${{ steps.vars.outputs.short_sha }}\` (\`${{ steps.vars.outputs.bundle_version }}\`)."
|
||||
echo "The bundle in S3 was already built from \`sha-${SHORT_SHA}\` (\`${BUNDLE_VERSION}\`)."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
@ -116,8 +136,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
@ -142,18 +163,25 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
SHORT_SHA: ${{ needs.check.outputs.short_sha }}
|
||||
BUNDLE_VERSION: ${{ needs.check.outputs.bundle_version }}
|
||||
run: |
|
||||
aws s3 cp zips/penpot.zip \
|
||||
s3://${{ secrets.S3_BUCKET }}/penpot-sha-${{ needs.check.outputs.short_sha }}.zip \
|
||||
--metadata bundle-version=${{ needs.check.outputs.bundle_version }}
|
||||
"s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" \
|
||||
--metadata "bundle-version=${BUNDLE_VERSION}"
|
||||
|
||||
- name: Write step summary
|
||||
env:
|
||||
SHORT_SHA: ${{ needs.check.outputs.short_sha }}
|
||||
BUNDLE_VERSION: ${{ needs.check.outputs.bundle_version }}
|
||||
COMMIT_TITLE: ${{ needs.check.outputs.commit_title }}
|
||||
run: |
|
||||
{
|
||||
echo "### ✅ Bundle built"
|
||||
echo ""
|
||||
echo "- Version: \`${{ needs.check.outputs.bundle_version }}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${{ needs.check.outputs.short_sha }}\`](https://github.com/${{ github.repository }}/commit/${{ needs.check.outputs.short_sha }}) — ${{ needs.check.outputs.commit_title }}"
|
||||
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
|
||||
echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
|
||||
29
.github/workflows/build-develop.yml
vendored
29
.github/workflows/build-develop.yml
vendored
@ -18,18 +18,39 @@ concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "develop"
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "develop"
|
||||
# Pin build-docker to the commit build-bundle actually bundled.
|
||||
@ -37,8 +58,10 @@ jobs:
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker-admin-console:
|
||||
permissions: {}
|
||||
uses: ./.github/workflows/build-docker-admin-console.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
ORG_WORKFLOW_TOKEN: ${{ secrets.ORG_WORKFLOW_TOKEN }}
|
||||
with:
|
||||
gh_ref: "develop"
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
22
.github/workflows/build-docker-admin-console.yml
vendored
22
.github/workflows/build-docker-admin-console.yml
vendored
@ -39,6 +39,8 @@ on:
|
||||
description: 'Token with Actions write access on penpot-nitrate'
|
||||
required: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-nitrate-docker:
|
||||
runs-on: ubuntu-latest
|
||||
@ -48,22 +50,23 @@ jobs:
|
||||
WORKFLOW: build-docker-admin-console.yml
|
||||
GH_REF: ${{ inputs.gh_ref }}
|
||||
DISPATCH_REF: ${{ inputs.dispatch_ref }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
steps:
|
||||
- name: Trigger nitrate docker build
|
||||
id: dispatch
|
||||
run: |
|
||||
DISTINCT_ID="${{ github.run_id }}-${{ github.run_attempt }}"
|
||||
CALLER_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
DISTINCT_ID="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
CALLER_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
|
||||
gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$DISPATCH_REF" \
|
||||
-f gh_ref="$GH_REF" \
|
||||
-f force="${{ inputs.force }}" \
|
||||
-f force="$FORCE" \
|
||||
-f caller_run_id="$DISTINCT_ID" \
|
||||
-f caller_run_url="$CALLER_URL"
|
||||
|
||||
# Locate the dispatched run using the correlation id embedded in its run-name
|
||||
RUN_ID=""
|
||||
for i in $(seq 1 24); do
|
||||
for _ in $(seq 1 24); do
|
||||
sleep 5
|
||||
RUN_ID=$(gh run list --repo "$REPO" --workflow "$WORKFLOW" \
|
||||
--limit 10 --json databaseId,displayTitle \
|
||||
@ -83,20 +86,25 @@ jobs:
|
||||
echo "::notice title=Nitrate docker build::$RUN_URL"
|
||||
|
||||
- name: Wait for nitrate docker build
|
||||
env:
|
||||
RUN_ID: ${{ steps.dispatch.outputs.run_id }}
|
||||
run: |
|
||||
gh run watch "${{ steps.dispatch.outputs.run_id }}" \
|
||||
gh run watch "$RUN_ID" \
|
||||
--repo "$REPO" \
|
||||
--interval 30 \
|
||||
--exit-status
|
||||
|
||||
- name: Report result
|
||||
if: always() && steps.dispatch.outputs.run_id != ''
|
||||
env:
|
||||
RUN_ID: ${{ steps.dispatch.outputs.run_id }}
|
||||
RUN_URL: ${{ steps.dispatch.outputs.run_url }}
|
||||
run: |
|
||||
CONCLUSION=$(gh run view "${{ steps.dispatch.outputs.run_id }}" \
|
||||
CONCLUSION=$(gh run view "$RUN_ID" \
|
||||
--repo "$REPO" --json conclusion --jq '.conclusion')
|
||||
{
|
||||
echo "### 🐳 Nitrate docker build"
|
||||
echo ""
|
||||
echo "- Result: \`${CONCLUSION:-in_progress}\`"
|
||||
echo "- Run: ${{ steps.dispatch.outputs.run_url }}"
|
||||
echo "- Run: ${RUN_URL}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
42
.github/workflows/build-docker-devenv.yml
vendored
42
.github/workflows/build-docker-devenv.yml
vendored
@ -3,40 +3,55 @@ name: DevEnv Docker Image Builder
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: build-docker-devenv
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and push DevEnv Docker image
|
||||
runs-on: penpot-standar-runner
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
DOCKER_IMAGE: 'penpotapp/devenv'
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
# Each job execution will use its own docker configuration.
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
echo "DOCKER_CONFIG=${RUNNER_TEMP}/.docker-${GITHUB_RUN_ID}-${GITHUB_JOB}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Login to Docker Registry (push destination)
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Docker Hardened Images registry (base image pull)
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push DevEnv Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
env:
|
||||
DOCKER_IMAGE: 'penpotapp/devenv'
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: ./docker/devenv/
|
||||
file: ./docker/devenv/Dockerfile
|
||||
@ -56,4 +71,13 @@ jobs:
|
||||
TEXT: |
|
||||
🚀 *[PENPOT] New devenv available*
|
||||
📄 You may want to update your devenv.
|
||||
@alvaro
|
||||
|
||||
- name: Notify Mattermost (developers)
|
||||
uses: mattermost/action-mattermost-notify@ae31bb6f9e26a54336e79696f108a2c91cf55b4e # v2.1.0
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: penpotdev
|
||||
TEXT: |
|
||||
🚀 *[PENPOT] New devenv available*
|
||||
📄 You may want to update your devenv.
|
||||
(\cc @alvaro)
|
||||
|
||||
134
.github/workflows/build-docker.yml
vendored
134
.github/workflows/build-docker.yml
vendored
@ -19,7 +19,6 @@ on:
|
||||
description: 'Name of the branch or ref'
|
||||
type: string
|
||||
required: true
|
||||
default: 'develop'
|
||||
sha:
|
||||
description: >-
|
||||
Exact commit to check out (full 40-char SHA, e.g. from
|
||||
@ -33,6 +32,27 @@ on:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID:
|
||||
required: false
|
||||
AWS_REGION:
|
||||
required: false
|
||||
AWS_SECRET_ACCESS_KEY:
|
||||
required: false
|
||||
DOCKER_PASSWORD:
|
||||
required: false
|
||||
DOCKER_REGISTRY:
|
||||
required: false
|
||||
DOCKER_USERNAME:
|
||||
required: false
|
||||
MATTERMOST_WEBHOOK:
|
||||
required: false
|
||||
PUB_DOCKER_PASSWORD:
|
||||
required: false
|
||||
PUB_DOCKER_USERNAME:
|
||||
required: false
|
||||
S3_BUCKET:
|
||||
required: false
|
||||
|
||||
# Literal group name: under `workflow_call`, `github.workflow` resolves to the
|
||||
# caller's workflow, which put this workflow and the other reusable one called
|
||||
@ -50,6 +70,9 @@ env:
|
||||
# missing (e.g. if runners ever move to separate machines).
|
||||
BUNDLE_CACHE: /var/tmp/penpot-bundle-cache
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── 1. Resolve the build key and check the whole set at once ───────────
|
||||
prepare:
|
||||
@ -58,6 +81,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
gh_ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
ref_slug: ${{ steps.vars.outputs.ref_slug }}
|
||||
bundle_version: ${{ steps.vars.outputs.bundle_version }}
|
||||
sha: ${{ steps.vars.outputs.sha }}
|
||||
short_sha: ${{ steps.vars.outputs.short_sha }}
|
||||
@ -66,16 +90,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.sha != '' && inputs.sha || inputs.gh_ref }}
|
||||
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
|
||||
run: |
|
||||
GH_REF="${{ inputs.gh_ref || github.ref_name }}"
|
||||
# A git ref accepts characters that a Docker tag does not.
|
||||
REF_SLUG=$(printf '%s' "$GH_REF" | tr -c 'A-Za-z0-9._-' '-' | cut -c1-100)
|
||||
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
|
||||
echo "ref_slug=$REF_SLUG" >> $GITHUB_OUTPUT
|
||||
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "commit_title=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT
|
||||
@ -92,13 +121,16 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
SHORT_SHA: ${{ steps.vars.outputs.short_sha }}
|
||||
run: |
|
||||
if [ "${{ inputs.force }}" = "true" ]; then
|
||||
if [ "$FORCE" = "true" ]; then
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
mkdir -p "$BUNDLE_CACHE"
|
||||
find "$BUNDLE_CACHE" -type f -mtime +1 -delete || true
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${{ steps.vars.outputs.short_sha }}.zip"
|
||||
aws s3 cp "s3://${{ secrets.S3_BUCKET }}/penpot-sha-${{ steps.vars.outputs.short_sha }}.zip" "$ZIP.$$.tmp"
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
|
||||
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
|
||||
mv "$ZIP.$$.tmp" "$ZIP"
|
||||
{
|
||||
echo "### 🔁 Image set build forced"
|
||||
@ -109,14 +141,14 @@ jobs:
|
||||
fi
|
||||
|
||||
if aws s3api head-object \
|
||||
--bucket ${{ secrets.S3_BUCKET }} \
|
||||
--key "markers/images-sha-${{ steps.vars.outputs.short_sha }}" \
|
||||
--bucket "$S3_BUCKET" \
|
||||
--key "markers/images-sha-${SHORT_SHA}" \
|
||||
> /dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo "### ⏭️ Image set build skipped"
|
||||
echo ""
|
||||
echo "The whole set was already built and promoted for \`sha-${{ steps.vars.outputs.short_sha }}\`."
|
||||
echo "The whole set was already built and promoted for \`sha-${SHORT_SHA}\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
@ -126,9 +158,9 @@ jobs:
|
||||
# prune stale bundles while at it.
|
||||
mkdir -p "$BUNDLE_CACHE"
|
||||
find "$BUNDLE_CACHE" -type f -mtime +1 -delete || true
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${{ steps.vars.outputs.short_sha }}.zip"
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
|
||||
if [ ! -f "$ZIP" ]; then
|
||||
aws s3 cp "s3://${{ secrets.S3_BUCKET }}/penpot-sha-${{ steps.vars.outputs.short_sha }}.zip" "$ZIP.$$.tmp"
|
||||
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
|
||||
mv "$ZIP.$$.tmp" "$ZIP"
|
||||
fi
|
||||
fi
|
||||
@ -156,12 +188,13 @@ jobs:
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}-${{ matrix.image }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.sha != '' && inputs.sha || inputs.gh_ref }}
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
@ -171,7 +204,7 @@ jobs:
|
||||
# images from DockerHub for unregistered users.
|
||||
# https://docs.docker.com/docker-hub/usage/
|
||||
- name: Login to DockerHub Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
@ -183,7 +216,7 @@ jobs:
|
||||
# reuses the same PUB_DOCKER_* credentials as the DockerHub
|
||||
# login above.
|
||||
- name: Login to Docker Hardened Images registry (base image pull)
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
@ -197,38 +230,41 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
IMAGE: ${{ matrix.image }}
|
||||
run: |
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${{ needs.prepare.outputs.short_sha }}.zip"
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
|
||||
if [ ! -f "$ZIP" ]; then
|
||||
echo "Bundle not found in host cache; falling back to S3."
|
||||
mkdir -p "$BUNDLE_CACHE"
|
||||
aws s3 cp "s3://${{ secrets.S3_BUCKET }}/penpot-sha-${{ needs.prepare.outputs.short_sha }}.zip" "$ZIP.$$.tmp"
|
||||
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
|
||||
mv "$ZIP.$$.tmp" "$ZIP"
|
||||
fi
|
||||
# Extract only the bundle this job needs.
|
||||
pushd docker/images
|
||||
unzip -q "$ZIP" "penpot/${{ matrix.image }}/*"
|
||||
mv "penpot/${{ matrix.image }}" "bundle-${{ matrix.image }}"
|
||||
unzip -q "$ZIP" "penpot/${IMAGE}/*"
|
||||
mv "penpot/${IMAGE}" "bundle-${IMAGE}"
|
||||
popd
|
||||
|
||||
- name: Set up QEMU (stable)
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ matrix.image }}
|
||||
labels: |
|
||||
bundle_version=${{ needs.prepare.outputs.bundle_version }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: ./docker/images/
|
||||
file: ./docker/images/Dockerfile.${{ matrix.image }}
|
||||
@ -256,22 +292,26 @@ jobs:
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Point branch tags to the new build key
|
||||
env:
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
run: |
|
||||
set -e
|
||||
for image in $ALL_IMAGES; do
|
||||
docker buildx imagetools create \
|
||||
-t "${{ secrets.DOCKER_REGISTRY }}/$image:${{ needs.prepare.outputs.gh_ref }}" \
|
||||
"${{ secrets.DOCKER_REGISTRY }}/$image:sha-${{ needs.prepare.outputs.short_sha }}"
|
||||
-t "${DOCKER_REGISTRY}/${image}:${REF_SLUG}" \
|
||||
"${DOCKER_REGISTRY}/${image}:sha-${SHORT_SHA}"
|
||||
done
|
||||
|
||||
# The marker is written LAST: its presence certifies that all five
|
||||
@ -281,20 +321,27 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
run: |
|
||||
echo "${{ github.run_id }}" | aws s3 cp - \
|
||||
"s3://${{ secrets.S3_BUCKET }}/markers/images-sha-${{ needs.prepare.outputs.short_sha }}"
|
||||
echo "$GITHUB_RUN_ID" | aws s3 cp - \
|
||||
"s3://${S3_BUCKET}/markers/images-sha-${SHORT_SHA}"
|
||||
|
||||
- name: Write step summary
|
||||
env:
|
||||
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
BUNDLE_VERSION: ${{ needs.prepare.outputs.bundle_version }}
|
||||
COMMIT_TITLE: ${{ needs.prepare.outputs.commit_title }}
|
||||
run: |
|
||||
{
|
||||
echo "### ✅ Image set promoted"
|
||||
echo ""
|
||||
echo "- Version: \`${{ needs.prepare.outputs.bundle_version }}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${{ needs.prepare.outputs.short_sha }}\`](https://github.com/${{ github.repository }}/commit/${{ needs.prepare.outputs.short_sha }}) — ${{ needs.prepare.outputs.commit_title }}"
|
||||
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
|
||||
echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo ""
|
||||
echo "All \`:${{ needs.prepare.outputs.gh_ref }}\` tags now point to \`sha-${{ needs.prepare.outputs.short_sha }}\`."
|
||||
echo "All \`:${REF_SLUG}\` tags now point to \`sha-${SHORT_SHA}\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# ── 3b. Skip path: make sure THIS ref's tags point to the existing
|
||||
@ -314,34 +361,43 @@ jobs:
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Point branch tags to the existing build key
|
||||
env:
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
run: |
|
||||
set -e
|
||||
for image in $ALL_IMAGES; do
|
||||
docker buildx imagetools create \
|
||||
-t "${{ secrets.DOCKER_REGISTRY }}/$image:${{ needs.prepare.outputs.gh_ref }}" \
|
||||
"${{ secrets.DOCKER_REGISTRY }}/$image:sha-${{ needs.prepare.outputs.short_sha }}"
|
||||
-t "${DOCKER_REGISTRY}/${image}:${REF_SLUG}" \
|
||||
"${DOCKER_REGISTRY}/${image}:sha-${SHORT_SHA}"
|
||||
done
|
||||
|
||||
- name: Write step summary
|
||||
env:
|
||||
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
BUNDLE_VERSION: ${{ needs.prepare.outputs.bundle_version }}
|
||||
COMMIT_TITLE: ${{ needs.prepare.outputs.commit_title }}
|
||||
run: |
|
||||
{
|
||||
echo "### ✅ Image set already built (branch tags ensured)"
|
||||
echo ""
|
||||
echo "- Version: \`${{ needs.prepare.outputs.bundle_version }}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${{ needs.prepare.outputs.short_sha }}\`](https://github.com/${{ github.repository }}/commit/${{ needs.prepare.outputs.short_sha }}) — ${{ needs.prepare.outputs.commit_title }}"
|
||||
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
|
||||
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
|
||||
echo "- Checked at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo ""
|
||||
echo "All \`:${{ needs.prepare.outputs.gh_ref }}\` tags now point to \`sha-${{ needs.prepare.outputs.short_sha }}\`."
|
||||
echo "All \`:${REF_SLUG}\` tags now point to \`sha-${SHORT_SHA}\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# ── 4. Single failure notification for the whole workflow ─────────────
|
||||
|
||||
256
.github/workflows/build-renovate.yml
vendored
Normal file
256
.github/workflows/build-renovate.yml
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
name: Renovate Docker Image Check
|
||||
|
||||
# Triggered by pushes to Renovate branches, not by pull_request: this is a
|
||||
# public repository and these jobs run on the self-hosted runners, so a PR
|
||||
# from a fork must never be able to start them. Only users with write
|
||||
# access can push here.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'renovate-*'
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: renovate-image-check-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BUNDLE_CACHE: /var/tmp/penpot-bundle-cache
|
||||
|
||||
jobs:
|
||||
# ── 1. Pick an existing develop bundle and detect what changed ────────
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: penpot-standar-runner
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
short_sha: ${{ steps.bundle.outputs.short_sha }}
|
||||
images: ${{ steps.changes.outputs.images }}
|
||||
devenv: ${{ steps.changes.outputs.devenv }}
|
||||
imagemagick: ${{ steps.changes.outputs.imagemagick }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Find the develop commit this branch is based on
|
||||
id: base
|
||||
run: |
|
||||
echo "sha=$(git merge-base HEAD origin/develop)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Detect changed images
|
||||
id: changes
|
||||
env:
|
||||
BASE_SHA: ${{ steps.base.outputs.sha }}
|
||||
run: |
|
||||
changed() {
|
||||
git diff --quiet "$BASE_SHA" HEAD -- "$@" && echo false || echo true
|
||||
}
|
||||
{
|
||||
# Anything outside .github can affect the production images.
|
||||
echo "images=$(changed . ':!.github')"
|
||||
echo "devenv=$(changed docker/devenv)"
|
||||
echo "imagemagick=$(changed docker/imagemagick)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Renovate only changes Dockerfiles and package manager versions, so
|
||||
# the most recent develop bundle is enough to build the images.
|
||||
- name: Find the latest develop bundle
|
||||
id: bundle
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
BASE_SHA: ${{ steps.base.outputs.sha }}
|
||||
run: |
|
||||
for SHA in $(git rev-list --first-parent --max-count=50 "$BASE_SHA"); do
|
||||
SHORT_SHA=$(git rev-parse --short=12 "$SHA")
|
||||
if aws s3api head-object --bucket "$S3_BUCKET" \
|
||||
--key "penpot-sha-${SHORT_SHA}.zip" > /dev/null 2>&1; then
|
||||
echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "Using bundle \`penpot-sha-${SHORT_SHA}.zip\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
echo "::error::No bundle found for the last 50 develop commits"
|
||||
exit 1
|
||||
|
||||
# ── 2. Production images, built from the PR Dockerfiles ───────────────
|
||||
images:
|
||||
name: Build ${{ matrix.image }}
|
||||
runs-on: penpot-standar-runner
|
||||
timeout-minutes: 60
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.images == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 3
|
||||
matrix:
|
||||
image: [backend, frontend, exporter, storybook, mcp]
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
env:
|
||||
MATRIX_IMAGE: ${{ matrix.image }}
|
||||
run: |
|
||||
echo "DOCKER_CONFIG=${RUNNER_TEMP}/.docker-${GITHUB_RUN_ID}-${GITHUB_JOB}-${MATRIX_IMAGE}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to DockerHub Registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Docker Hardened Images registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Prepare Penpot bundle
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
|
||||
IMAGE: ${{ matrix.image }}
|
||||
run: |
|
||||
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
|
||||
if [ ! -f "$ZIP" ]; then
|
||||
mkdir -p "$BUNDLE_CACHE"
|
||||
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
|
||||
mv "$ZIP.$$.tmp" "$ZIP"
|
||||
fi
|
||||
pushd docker/images
|
||||
unzip -q "$ZIP" "penpot/${IMAGE}/*"
|
||||
mv "penpot/${IMAGE}" "bundle-${IMAGE}"
|
||||
popd
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: ./docker/images/
|
||||
file: ./docker/images/Dockerfile.${{ matrix.image }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: false
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:buildcache
|
||||
|
||||
# ── 3. Devenv, only when the PR touches it ────────────────────────────
|
||||
devenv:
|
||||
name: Build devenv
|
||||
runs-on: penpot-standar-runner
|
||||
timeout-minutes: 90
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.devenv == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
echo "DOCKER_CONFIG=${RUNNER_TEMP}/.docker-${GITHUB_RUN_ID}-${GITHUB_JOB}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub Registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Docker Hardened Images registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Build devenv image
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: ./docker/devenv/
|
||||
file: ./docker/devenv/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: false
|
||||
cache-from: type=registry,ref=penpotapp/devenv:buildcache
|
||||
|
||||
# ── 4. ImageMagick, only when the PR touches it ───────────────────────
|
||||
# amd64 only: compiling ImageMagick under QEMU takes too long for a check.
|
||||
imagemagick:
|
||||
name: Build imagemagick
|
||||
runs-on: penpot-standar-runner
|
||||
timeout-minutes: 60
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.imagemagick == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
echo "DOCKER_CONFIG=${RUNNER_TEMP}/.docker-${GITHUB_RUN_ID}-${GITHUB_JOB}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to Docker Hardened Images registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Build imagemagick image
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: ./docker/imagemagick/
|
||||
file: ./docker/imagemagick/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
29
.github/workflows/build-staging.yml
vendored
29
.github/workflows/build-staging.yml
vendored
@ -18,18 +18,39 @@ concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "staging"
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "staging"
|
||||
# Pin build-docker to the commit build-bundle actually bundled.
|
||||
@ -37,8 +58,10 @@ jobs:
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker-admin-console:
|
||||
permissions: {}
|
||||
uses: ./.github/workflows/build-docker-admin-console.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
ORG_WORKFLOW_TOKEN: ${{ secrets.ORG_WORKFLOW_TOKEN }}
|
||||
with:
|
||||
gh_ref: "staging"
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
41
.github/workflows/build-tag.yml
vendored
41
.github/workflows/build-tag.yml
vendored
@ -21,31 +21,55 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: ${{ github.ref_name }}
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: ${{ github.ref_name }}
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
build-docker-admin-console:
|
||||
permissions: {}
|
||||
uses: ./.github/workflows/build-docker-admin-console.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
ORG_WORKFLOW_TOKEN: ${{ secrets.ORG_WORKFLOW_TOKEN }}
|
||||
with:
|
||||
gh_ref: ${{ github.ref_name }}
|
||||
force: ${{ inputs.force || false }}
|
||||
|
||||
notify:
|
||||
name: Notifications
|
||||
permissions: {}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-docker
|
||||
@ -66,7 +90,16 @@ jobs:
|
||||
needs:
|
||||
- build-docker
|
||||
- build-docker-admin-console
|
||||
permissions:
|
||||
contents: write
|
||||
uses: ./.github/workflows/release.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
with:
|
||||
gh_ref: ${{ github.ref_name }}
|
||||
|
||||
29
.github/workflows/build-tmp-tokens.yml
vendored
29
.github/workflows/build-tmp-tokens.yml
vendored
@ -2,23 +2,44 @@ name: _TMP TOKENS
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '46 5-20 * * 1-5'
|
||||
# schedule:
|
||||
# - cron: '46 5-20 * * 1-5'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "hiru-tokens-in-libs"
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
with:
|
||||
gh_ref: "hiru-tokens-in-libs"
|
||||
|
||||
15
.github/workflows/commit-checker.yml
vendored
15
.github/workflows/commit-checker.yml
vendored
@ -7,19 +7,16 @@ on:
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- staging
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-commit-message:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -27,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Commit Type
|
||||
uses: gsactions/commit-message-checker@v2
|
||||
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee # v2.0.0
|
||||
with:
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
|
||||
flags: 'gm'
|
||||
@ -59,7 +56,7 @@ jobs:
|
||||
# always the trusted version: PR commits are only read, never
|
||||
# executed. On push events this resolves to the pushed sha.
|
||||
- name: Checkout trusted code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.base.sha || github.sha }}
|
||||
|
||||
78
.github/workflows/lint-workflows.yml
vendored
Normal file
78
.github/workflows/lint-workflows.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
name: "CI: Workflows"
|
||||
|
||||
# Static analysis of the GitHub Actions workflows themselves:
|
||||
# - actionlint: syntax, expressions, runner labels, job/step references,
|
||||
# and shellcheck on every `run:` block.
|
||||
# - zizmor: security audits (template injection, dangerous triggers,
|
||||
# credential persistence, ...). Configured in .github/zizmor.yml.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/actionlint.yaml'
|
||||
- '.github/zizmor.yml'
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/actionlint.yaml'
|
||||
- '.github/zizmor.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: lint-workflows-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
ACTIONLINT_VERSION: '1.7.12'
|
||||
ZIZMOR_VERSION: '1.31.1'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint workflows
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
# PR code is untrusted: never run this on the self-hosted runners.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install actionlint
|
||||
run: |
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
|
||||
| tar -xz -C "$RUNNER_TEMP" actionlint
|
||||
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
||||
|
||||
# Each finding is emitted as a `::error` annotation on the PR diff.
|
||||
- name: Run actionlint
|
||||
run: |
|
||||
# shellcheck disable=SC2016 # Go template, not shell expansion.
|
||||
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}},title=actionlint ({{$err.Kind}})::{{$err.Message}}\n{{end}}'
|
||||
|
||||
- name: Run zizmor
|
||||
if: ${{ !cancelled() }}
|
||||
env:
|
||||
# Enables the online audits (known-vulnerable-actions,
|
||||
# impostor-commit, ref-confusion, ...).
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
pipx run "zizmor==${ZIZMOR_VERSION}" \
|
||||
--config .github/zizmor.yml \
|
||||
--format github \
|
||||
.github/workflows
|
||||
38
.github/workflows/plugins-deploy-api-doc.yml
vendored
38
.github/workflows/plugins-deploy-api-doc.yml
vendored
@ -33,43 +33,32 @@ jobs:
|
||||
steps:
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
|
||||
run: |
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
# No dependency cache here: a deploy must not restore a cache that
|
||||
# other workflows can write to.
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
uses: pnpm/setup@a0881977cd8ea73b1ed212236c99ca10dcc80cf4 # v3.1.1
|
||||
with:
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
runtime: node@24.22.1
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
# END: Setup Node and PNPM enabling cache
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile;
|
||||
pnpm install --frozen-lockfile;
|
||||
pnpm add -D -w wrangler@latest;
|
||||
|
||||
- name: Build docs
|
||||
@ -78,8 +67,9 @@ jobs:
|
||||
run: pnpm run build:doc
|
||||
|
||||
- name: Select Worker name
|
||||
env:
|
||||
REF: ${{ steps.vars.outputs.gh_ref }}
|
||||
run: |
|
||||
REF="${{ steps.vars.outputs.gh_ref }}"
|
||||
case "$REF" in
|
||||
main)
|
||||
echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV
|
||||
@ -97,7 +87,7 @@ jobs:
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-api-doc.toml
|
||||
sed -i "s/WORKER_URI/${WORKER_URI}/g" wrangler-penpot-plugins-api-doc.toml
|
||||
|
||||
- name: Add noindex header and robots.txt files for non-production environments
|
||||
if: ${{ steps.vars.outputs.gh_ref != 'main' }}
|
||||
@ -117,7 +107,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
|
||||
with:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
81
.github/workflows/plugins-deploy-package.yml
vendored
81
.github/workflows/plugins-deploy-package.yml
vendored
@ -23,11 +23,17 @@ on:
|
||||
description: 'Name of the branch'
|
||||
type: string
|
||||
required: true
|
||||
default: 'develop'
|
||||
plugin_name:
|
||||
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID:
|
||||
required: false
|
||||
CLOUDFLARE_API_TOKEN:
|
||||
required: false
|
||||
MATTERMOST_WEBHOOK:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -35,72 +41,71 @@ permissions:
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: penpot-standar-runner
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
# Persistent pnpm store shared by all runner instances on the host.
|
||||
# pnpm's content-addressable store is safe for concurrent access.
|
||||
- /var/cache/github-runner/pnpm-store:/root/.local/share/pnpm/store
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref }}
|
||||
PLUGIN_NAME: ${{ inputs.plugin_name }}
|
||||
steps:
|
||||
# plugin_name is a free-form string that ends up in paths, worker names
|
||||
# and GITHUB_ENV; reject anything that is not a plain slug before it is
|
||||
# used anywhere.
|
||||
- name: Validate inputs
|
||||
run: |
|
||||
if ! [[ "$PLUGIN_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
||||
echo "::error::Invalid plugin_name: must match ^[a-z0-9][a-z0-9-]*$"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
with:
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
# END: Setup Node and PNPM enabling cache
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile;
|
||||
pnpm install --frozen-lockfile;
|
||||
pnpm add -D -w wrangler@latest;
|
||||
|
||||
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: pnpm --filter ${{ inputs.plugin_name }}-plugin build
|
||||
run: pnpm --filter "${PLUGIN_NAME}-plugin" build
|
||||
|
||||
- name: Select Worker name
|
||||
run: |
|
||||
REF="${{ inputs.gh_ref }}"
|
||||
case "$REF" in
|
||||
case "$GH_REF" in
|
||||
main)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
|
||||
echo "WORKER_NAME=${PLUGIN_NAME}-plugin-pro" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${PLUGIN_NAME}.plugins.penpot.app" >> $GITHUB_ENV ;;
|
||||
staging)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
||||
echo "WORKER_NAME=${PLUGIN_NAME}-plugin-pre" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${PLUGIN_NAME}.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
||||
develop)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
||||
echo "WORKER_NAME=${PLUGIN_NAME}-plugin-hourly" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${PLUGIN_NAME}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
||||
*) echo "Unsupported branch ${GH_REF}" && exit 1 ;;
|
||||
esac
|
||||
|
||||
- name: Set the custom url
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
|
||||
sed -i "s/WORKER_URI/${WORKER_URI}/g" "apps/${PLUGIN_NAME}-plugin/wrangler.toml"
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
|
||||
with:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
44
.github/workflows/plugins-deploy-packages.yml
vendored
44
.github/workflows/plugins-deploy-packages.yml
vendored
@ -21,6 +21,9 @@ on:
|
||||
- staging
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
@ -36,9 +39,11 @@ jobs:
|
||||
# [For new plugins]
|
||||
# Add more outputs here
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- id: filter
|
||||
uses: dorny/paths-filter@v4
|
||||
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
|
||||
with:
|
||||
filters: |
|
||||
colors_to_tokens:
|
||||
@ -72,7 +77,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: colors-to-tokens
|
||||
@ -81,7 +89,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: contrast
|
||||
@ -90,7 +101,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: create-palette
|
||||
@ -99,7 +113,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: icons
|
||||
@ -108,7 +125,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: lorem-ipsum
|
||||
@ -117,7 +137,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: rename-layers
|
||||
@ -126,7 +149,10 @@ jobs:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MATTERMOST_WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: table
|
||||
|
||||
38
.github/workflows/plugins-deploy-styles-doc.yml
vendored
38
.github/workflows/plugins-deploy-styles-doc.yml
vendored
@ -31,43 +31,32 @@ jobs:
|
||||
steps:
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
|
||||
run: |
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
# No dependency cache here: a deploy must not restore a cache that
|
||||
# other workflows can write to.
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
uses: pnpm/setup@a0881977cd8ea73b1ed212236c99ca10dcc80cf4 # v3.1.1
|
||||
with:
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
runtime: node@24.22.1
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
# END: Setup Node and PNPM enabling cache
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile;
|
||||
pnpm install --frozen-lockfile;
|
||||
pnpm add -D -w wrangler@latest;
|
||||
|
||||
- name: Build styles
|
||||
@ -76,8 +65,9 @@ jobs:
|
||||
run: pnpm run build:styles-example
|
||||
|
||||
- name: Select Worker name
|
||||
env:
|
||||
REF: ${{ steps.vars.outputs.gh_ref }}
|
||||
run: |
|
||||
REF="${{ steps.vars.outputs.gh_ref }}"
|
||||
case "$REF" in
|
||||
main)
|
||||
echo "WORKER_NAME=penpot-plugins-styles-doc-pro" >> $GITHUB_ENV
|
||||
@ -95,7 +85,7 @@ jobs:
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-styles-doc.toml
|
||||
sed -i "s/WORKER_URI/${WORKER_URI}/g" wrangler-penpot-plugins-styles-doc.toml
|
||||
|
||||
- name: Add noindex header and robots.txt files for non-production environments
|
||||
if: ${{ steps.vars.outputs.gh_ref != 'main' }}
|
||||
@ -115,7 +105,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
|
||||
with:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@ -13,6 +13,21 @@ on:
|
||||
description: 'Tag to release'
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
AWS_REGION:
|
||||
required: false
|
||||
DOCKER_PASSWORD:
|
||||
required: false
|
||||
DOCKER_REGISTRY:
|
||||
required: false
|
||||
DOCKER_USERNAME:
|
||||
required: false
|
||||
MATTERMOST_WEBHOOK:
|
||||
required: false
|
||||
PUB_DOCKER_PASSWORD:
|
||||
required: false
|
||||
PUB_DOCKER_USERNAME:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@ -26,18 +41,21 @@ jobs:
|
||||
steps:
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
env:
|
||||
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
|
||||
run: |
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# --- Publicly release the docker images ---
|
||||
- name: Configure ECR credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.DOCKER_USERNAME }}
|
||||
aws-secret-access-key: ${{ secrets.DOCKER_PASSWORD }}
|
||||
@ -92,14 +110,19 @@ jobs:
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# --- Create GitHub release ---
|
||||
# Updates the release if it already exists, so re-running is safe.
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.vars.outputs.gh_ref }}
|
||||
name: ${{ steps.vars.outputs.gh_ref }}
|
||||
body: ${{ steps.extract_release_notes.outputs.release_notes }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
TAG: ${{ steps.vars.outputs.gh_ref }}
|
||||
NOTES: ${{ steps.extract_release_notes.outputs.release_notes }}
|
||||
run: |
|
||||
if gh release view "$TAG" > /dev/null 2>&1; then
|
||||
gh release edit "$TAG" --title "$TAG" --notes "$NOTES"
|
||||
else
|
||||
gh release create "$TAG" --verify-tag --title "$TAG" --notes "$NOTES"
|
||||
fi
|
||||
|
||||
- name: Notify Mattermost
|
||||
if: failure()
|
||||
|
||||
11
.github/workflows/tests-backend.yml
vendored
11
.github/workflows/tests-backend.yml
vendored
@ -28,6 +28,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -60,7 +63,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Fmt
|
||||
working-directory: ./backend
|
||||
@ -75,6 +80,10 @@ jobs:
|
||||
- name: Tests
|
||||
working-directory: ./backend
|
||||
env:
|
||||
# `clojure.core/assert` is elided at compile time, so without this
|
||||
# flag the precondition asserts do not exist in the compiled code
|
||||
# and the suite silently stops checking them.
|
||||
PENPOT_FLAGS: "enable-backend-asserts"
|
||||
PENPOT_TEST_DATABASE_URI: "postgresql://postgres/penpot_test"
|
||||
PENPOT_TEST_DATABASE_USERNAME: penpot_test
|
||||
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
|
||||
|
||||
7
.github/workflows/tests-common.yml
vendored
7
.github/workflows/tests-common.yml
vendored
@ -26,6 +26,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-common:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -39,7 +42,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Lint
|
||||
working-directory: ./common
|
||||
|
||||
46
.github/workflows/tests-e2e.yml
vendored
46
.github/workflows/tests-e2e.yml
vendored
@ -86,6 +86,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── 1. Build the frontend bundle once ──────────────────────────────────
|
||||
build-bundle:
|
||||
@ -107,8 +110,9 @@ jobs:
|
||||
# An empty `ref` makes checkout fall back to its default (the PR merge
|
||||
# ref on pull_request, the pushed ref on push).
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
# The cache key must come from the SHA actually checked out: on a manual
|
||||
@ -120,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Restore Bundle Cache
|
||||
id: restore
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: ${{ steps.vars.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
@ -133,7 +137,7 @@ jobs:
|
||||
|
||||
- name: Store Bundle Cache
|
||||
if: steps.restore.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: ${{ steps.vars.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
@ -163,12 +167,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
@ -190,16 +195,18 @@ jobs:
|
||||
working-directory: ./frontend
|
||||
env:
|
||||
WORKERS: ${{ inputs.workers }}
|
||||
SHARD: ${{ matrix.shard }}
|
||||
SHARD_TOTAL: ${{ strategy.job-total }}
|
||||
run: |
|
||||
WORKERS=${WORKERS:-2}
|
||||
echo "Running shard ${{ matrix.shard }}/${{ strategy.job-total }} with $WORKERS workers"
|
||||
echo "Running shard ${SHARD}/${SHARD_TOTAL} with $WORKERS workers"
|
||||
pnpm exec playwright test --project default \
|
||||
--workers="$WORKERS" \
|
||||
--shard=${{ matrix.shard }}/${{ strategy.job-total }} \
|
||||
--shard="${SHARD}/${SHARD_TOTAL}" \
|
||||
--reporter=blob
|
||||
|
||||
- name: Upload blob report
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: integration-blob-report-${{ matrix.shard }}
|
||||
@ -208,7 +215,7 @@ jobs:
|
||||
retention-days: 3
|
||||
|
||||
- name: Upload test result
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: integration-tests-result-${{ matrix.shard }}
|
||||
@ -232,11 +239,13 @@ jobs:
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# The driver serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
@ -270,11 +279,13 @@ jobs:
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Mocked mode serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
@ -316,8 +327,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
- name: Install deps
|
||||
@ -326,7 +338,7 @@ jobs:
|
||||
pnpm install --frozen-lockfile;
|
||||
|
||||
- name: Download blob reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
path: frontend/all-blob-reports
|
||||
pattern: integration-blob-report-*
|
||||
@ -355,7 +367,7 @@ jobs:
|
||||
>> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload JSON report
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: integration-json-report
|
||||
@ -365,7 +377,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: integration-html-report
|
||||
|
||||
14
.github/workflows/tests-exporter.yml
vendored
14
.github/workflows/tests-exporter.yml
vendored
@ -9,6 +9,7 @@ on:
|
||||
paths:
|
||||
- 'exporter/**'
|
||||
- 'common/**'
|
||||
- 'render-wasm/**'
|
||||
|
||||
types:
|
||||
- opened
|
||||
@ -23,11 +24,15 @@ on:
|
||||
paths:
|
||||
- 'exporter/**'
|
||||
- 'common/**'
|
||||
- 'render-wasm/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-exporter:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -41,7 +46,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Lint
|
||||
working-directory: ./exporter
|
||||
@ -54,3 +61,8 @@ jobs:
|
||||
working-directory: ./exporter
|
||||
run: |
|
||||
./scripts/test
|
||||
|
||||
- name: Check no invoke_* in exporter JS glue
|
||||
working-directory: ./render-wasm
|
||||
run: |
|
||||
./check-no-invoke-v export
|
||||
|
||||
7
.github/workflows/tests-frontend.yml
vendored
7
.github/workflows/tests-frontend.yml
vendored
@ -30,6 +30,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-frontend:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -43,7 +46,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Lint
|
||||
working-directory: ./frontend
|
||||
|
||||
7
.github/workflows/tests-library.yml
vendored
7
.github/workflows/tests-library.yml
vendored
@ -28,6 +28,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-library:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -41,7 +44,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Lint
|
||||
working-directory: ./library
|
||||
|
||||
7
.github/workflows/tests-mcp.yml
vendored
7
.github/workflows/tests-mcp.yml
vendored
@ -24,6 +24,9 @@ on:
|
||||
paths:
|
||||
- 'mcp/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-mcp:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -33,7 +36,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
working-directory: ./mcp
|
||||
|
||||
7
.github/workflows/tests-plugins.yml
vendored
7
.github/workflows/tests-plugins.yml
vendored
@ -26,6 +26,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-plugins:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -38,7 +41,9 @@ jobs:
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
|
||||
17
.github/workflows/tests-wasm.yml
vendored
17
.github/workflows/tests-wasm.yml
vendored
@ -26,6 +26,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-render-wasm:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
@ -39,7 +42,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Format
|
||||
working-directory: ./render-wasm
|
||||
@ -55,3 +60,13 @@ jobs:
|
||||
working-directory: ./render-wasm
|
||||
run: |
|
||||
./test
|
||||
|
||||
- name: Build
|
||||
working-directory: ./render-wasm
|
||||
run: |
|
||||
NODE_ENV=production ./build frontend
|
||||
|
||||
- name: Check no invoke_*V in JS glue
|
||||
working-directory: ./render-wasm
|
||||
run: |
|
||||
./check-no-invoke-v
|
||||
|
||||
39
.github/zizmor.yml
vendored
Normal file
39
.github/zizmor.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# zizmor configuration — https://docs.zizmor.sh/configuration/
|
||||
#
|
||||
# Every audit is enforced for every workflow. New workflows are checked with
|
||||
# no exceptions. The entries below are the only remaining ones, and each is
|
||||
# marked as either a pending clean-up or an accepted risk with its reason.
|
||||
# Remove a file from a list in the same PR that fixes it, and the whole rule
|
||||
# entry once its list is empty.
|
||||
rules:
|
||||
# Accepted: wrangler is added with `pnpm add wrangler@latest` before each
|
||||
# deploy instead of being a dependency of the plugins workspace. Only used
|
||||
# to publish to Cloudflare; not worth adding to the workspace.
|
||||
adhoc-packages:
|
||||
ignore:
|
||||
- plugins-deploy-api-doc.yml
|
||||
- plugins-deploy-package.yml
|
||||
- plugins-deploy-styles-doc.yml
|
||||
# Accepted: auto-label.yml needs pull_request_target so the GitHub App
|
||||
# secrets are available for PRs from forks. It never checks out or runs
|
||||
# PR code, it only reads ids from the event payload.
|
||||
dangerous-triggers:
|
||||
ignore:
|
||||
- auto-label.yml
|
||||
# Style nudge towards the `$/...` syntax; not worth enforcing.
|
||||
self-repository:
|
||||
disable: true
|
||||
# Accepted: penpotapp/devenv is our own image, built and pushed by
|
||||
# build-docker-devenv.yml, so the floating tag is intentional.
|
||||
unpinned-images:
|
||||
ignore:
|
||||
- plugins-deploy-package.yml
|
||||
- tests-backend.yml
|
||||
- tests-common.yml
|
||||
- tests-e2e.yml
|
||||
- tests-exporter.yml
|
||||
- tests-frontend.yml
|
||||
- tests-library.yml
|
||||
- tests-mcp.yml
|
||||
- tests-plugins.yml
|
||||
- tests-wasm.yml
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev AS base
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev@sha256:9b025768946856e44f9775005eb199f2e1820484ea27bb5c8bcbb187b80464dc AS base
|
||||
|
||||
ENV LANG='C.UTF-8' \
|
||||
LC_ALL='C.UTF-8' \
|
||||
@ -66,8 +66,8 @@ RUN set -ex; \
|
||||
|
||||
FROM base AS setup-node
|
||||
|
||||
ENV NODE_VERSION=v24.21.0 \
|
||||
PNPM_VERSION=12.5.1 \
|
||||
ENV NODE_VERSION=v24.22.1 \
|
||||
PNPM_VERSION=12.15.0 \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
@ -76,14 +76,16 @@ RUN set -eux; \
|
||||
aarch64|arm64) \
|
||||
OPENSSL_ARCH='linux-aarch64'; \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||
NODE_SHA256='33c37be234ba8bee041961047b19160f4d7d0f164e8e246f78bdd10feff148b3'; \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
PNPM_SHA256='d7ce93a2c7d847d3a34bd3f559b60c14f578b1320c17cc352a2c5c5fdf956117'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
OPENSSL_ARCH='linux-x86_64'; \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||
NODE_SHA256='eb6728e77c576a35162c122c45ccadf224d89ff00abc8dae46cc2897c6002d7c'; \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
PNPM_SHA256='55b5ed0092bd11176c1669598e231ae52e7cedac59de6efd0d415730f37d0598'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -91,6 +93,7 @@ RUN set -eux; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
|
||||
echo "${NODE_SHA256} */tmp/nodejs.tar.gz" | sha256sum -c -; \
|
||||
mkdir -p /opt/node; \
|
||||
cd /opt/node; \
|
||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||
@ -103,72 +106,13 @@ RUN set -eux; \
|
||||
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
|
||||
rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz;
|
||||
|
||||
################################################################################
|
||||
## OPENCODE SETUP
|
||||
################################################################################
|
||||
|
||||
FROM base AS setup-opencode
|
||||
|
||||
ENV OPENCODE_VERSION=1.18.31
|
||||
|
||||
RUN set -ex; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://github.com/anomalyco/opencode/releases/download/v${OPENCODE_VERSION}/opencode-linux-arm64.tar.gz"; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://github.com/anomalyco/opencode/releases/download/v${OPENCODE_VERSION}/opencode-linux-x64.tar.gz"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -fsSL ${BINARY_URL} -o /tmp/opencode.tar.gz; \
|
||||
mkdir -p /tmp/opencode; \
|
||||
tar -xzf /tmp/opencode.tar.gz -C /tmp/opencode; \
|
||||
chmod +x /tmp/opencode/opencode; \
|
||||
rm -f /tmp/opencode.tar.gz;
|
||||
|
||||
################################################################################
|
||||
## OPENCODE2 SETUP (V2, side-by-side with V1 `opencode`)
|
||||
################################################################################
|
||||
|
||||
FROM base AS setup-opencode2
|
||||
|
||||
ENV OPENCODE2_VERSION=2.0.12
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE2_VERSION}.tgz"; \
|
||||
ESUM='33f0dde9f0f055b6a3665d290371bcfc8c63daceade7a535c4b94d386f2bd917'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE2_VERSION}.tgz"; \
|
||||
ESUM='2a79beb2e24382cb2bdbb709237dbc22f498cf52a106d029a6a3a3243760c78b'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/opencode2.tgz "${BINARY_URL}"; \
|
||||
echo "${ESUM} */tmp/opencode2.tgz" | sha256sum -c -; \
|
||||
mkdir -p /tmp/opencode2; \
|
||||
tar -xzf /tmp/opencode2.tgz -C /tmp/opencode2 --strip-components=2 package/bin; \
|
||||
mv /tmp/opencode2/opencode /tmp/opencode2/opencode2; \
|
||||
chmod +x /tmp/opencode2/opencode2; \
|
||||
rm -f /tmp/opencode2.tgz;
|
||||
|
||||
|
||||
################################################################################
|
||||
## CADDYSERVER SETUP
|
||||
################################################################################
|
||||
|
||||
FROM base AS setup-caddy
|
||||
|
||||
ENV CADDY_VERSION=2.11.4
|
||||
ENV CADDY_VERSION=2.11.7
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
@ -282,13 +226,13 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-utils
|
||||
|
||||
ENV CLJKONDO_VERSION=2026.08.04 \
|
||||
BABASHKA_VERSION=1.13.223 \
|
||||
CLJFMT_VERSION=0.16.5 \
|
||||
PIXI_VERSION=0.81.0 \
|
||||
GITHUB_CLI_VERSION=2.101.0 \
|
||||
AWS_CLI_VERSION=2.36.49 \
|
||||
UV_VERSION=0.12.17 \
|
||||
ENV CLJKONDO_VERSION=2026.10.20 \
|
||||
BABASHKA_VERSION=1.13.226 \
|
||||
CLJFMT_VERSION=0.16.6 \
|
||||
PIXI_VERSION=0.83.0 \
|
||||
GITHUB_CLI_VERSION=2.103.0 \
|
||||
AWS_CLI_VERSION=2.38.5 \
|
||||
UV_VERSION=0.13.5 \
|
||||
UV_TOOL_DIR=/opt/uv/tools \
|
||||
UV_TOOL_BIN_DIR=/opt/utils/bin \
|
||||
UV_PYTHON_INSTALL_DIR=/opt/uv/python \
|
||||
@ -403,11 +347,11 @@ RUN set -ex; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_ARCH="aarch64"; \
|
||||
BINARY_SHA256="2dec26fdaff2f5e34961ea98f456297104f73d8409fc3599c8f2909c6e27a07e"; \
|
||||
BINARY_SHA256="07711a936e9d271045ab937d2c2c5390c25f419e4ba9e25adb9e996031e3f6be"; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_ARCH="x86_64"; \
|
||||
BINARY_SHA256="6b51b62d232217350e72d8f2493f47d3461239d011d1d5eef960dfcd8cb07069"; \
|
||||
BINARY_SHA256="5def7d1b7e55d892f9a381bb2cb2081a685712464be0d36d5d706af0ac48d0d3"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -476,6 +420,7 @@ RUN set -ex; \
|
||||
nginx \
|
||||
fd-find \
|
||||
bat \
|
||||
gettext \
|
||||
\
|
||||
fontconfig \
|
||||
woff-tools \
|
||||
@ -525,7 +470,6 @@ RUN set -ex; \
|
||||
libjpeg62-turbo \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libopenexr-3-1-30 \
|
||||
libfreetype6 \
|
||||
libfontconfig1 \
|
||||
libglib2.0-0 \
|
||||
@ -575,7 +519,7 @@ ENV LANG='C.UTF-8' \
|
||||
SERENA_CONTEXT="claude-code" \
|
||||
PATH="/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
|
||||
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-32 /opt/imagick /opt/imagick
|
||||
COPY --from=setup-jvm /opt/jdk /opt/jdk
|
||||
COPY --from=setup-jvm /opt/clojure /opt/clojure
|
||||
COPY --from=setup-node /opt/node /opt/node
|
||||
@ -586,8 +530,6 @@ COPY --from=setup-rust /opt/cargo /opt/cargo
|
||||
COPY --from=setup-rust /opt/rustup /opt/rustup
|
||||
COPY --from=setup-rust /opt/emsdk /opt/emsdk
|
||||
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
|
||||
COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode
|
||||
COPY --from=setup-opencode2 /tmp/opencode2/opencode2 /opt/utils/bin/opencode2
|
||||
|
||||
COPY files/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY files/nginx-mime.types /etc/nginx/mime.types
|
||||
|
||||
@ -15,7 +15,7 @@ volumes:
|
||||
|
||||
services:
|
||||
rustfs:
|
||||
image: "rustfs/rustfs:1.0.0"
|
||||
image: "rustfs/rustfs:1.0.3"
|
||||
command: /data
|
||||
|
||||
volumes:
|
||||
@ -68,7 +68,7 @@ services:
|
||||
- postgres
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8.1
|
||||
image: valkey/valkey:8.1.10
|
||||
restart: always
|
||||
command: valkey-server --save 120 1 --loglevel warning
|
||||
volumes:
|
||||
@ -79,7 +79,7 @@ services:
|
||||
- valkey
|
||||
|
||||
mailer:
|
||||
image: axllent/mailpit:v1.31.1
|
||||
image: axllent/mailpit:v1.32.0
|
||||
restart: always
|
||||
volumes:
|
||||
- "mailpit_data:/data"
|
||||
|
||||
@ -81,6 +81,11 @@ services:
|
||||
- PENPOT_DATABASE_PASSWORD=${PENPOT_DATABASE_PASSWORD}
|
||||
- PENPOT_DATABASE_MAX_POOL_SIZE=${PENPOT_DATABASE_MAX_POOL_SIZE}
|
||||
- PENPOT_REDIS_URI=${PENPOT_REDIS_URI}
|
||||
# Per-instance backend-test config. Values come from
|
||||
# instance-env-overrides in manage.sh; the main database above stays
|
||||
# shared by all instances on purpose.
|
||||
- PENPOT_TEST_DATABASE_URI=${PENPOT_TEST_DATABASE_URI}
|
||||
- PENPOT_TEST_REDIS_URI=${PENPOT_TEST_REDIS_URI}
|
||||
- PENPOT_OBJECTS_STORAGE_BACKEND=${PENPOT_OBJECTS_STORAGE_BACKEND}
|
||||
- PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}
|
||||
- PENPOT_OBJECTS_STORAGE_S3_BUCKET=${PENPOT_OBJECTS_STORAGE_S3_BUCKET}
|
||||
@ -93,7 +98,7 @@ services:
|
||||
|
||||
# Agentic devenv: set to a PyPI release version/tag to update Serena on startup,
|
||||
# leave empty to skip update and use the version baked into the image.
|
||||
- SERENA_UPDATE_VERSION=1.6.1
|
||||
- SERENA_UPDATE_VERSION=
|
||||
- SHADOW_SERVER_URL=${SHADOW_SERVER_URL}
|
||||
|
||||
networks:
|
||||
|
||||
@ -27,7 +27,7 @@ fi
|
||||
# pnpm
|
||||
export PNPM_HOME="/home/penpot/.local/share/pnpm"
|
||||
case ":$PATH:" in
|
||||
*":$PNPM_HOME:"*) ;;
|
||||
*) export PATH="$PNPM_HOME:$PATH" ;;
|
||||
*":$PNPM_HOME/bin:"*) ;;
|
||||
*) export PATH="$PNPM_HOME/bin:$PATH" ;;
|
||||
esac
|
||||
# pnpm end
|
||||
|
||||
@ -10,6 +10,10 @@ cp /root/.bashrc /home/penpot/.bashrc
|
||||
cp /root/.vimrc /home/penpot/.vimrc
|
||||
cp /root/.tmux.conf /home/penpot/.tmux.conf
|
||||
|
||||
chown -R penpot:penpot /home/penpot/.bashrc
|
||||
chown -R penpot:penpot /home/penpot/.vimrc
|
||||
chown -R penpot:penpot /home/penpot/.tmux.conf
|
||||
|
||||
# Seed SERENA_HOME with default config on first run
|
||||
mkdir -p ${SERENA_HOME}
|
||||
if [ ! -f "${SERENA_HOME}/serena_config.yml" ]; then
|
||||
@ -21,6 +25,16 @@ chown penpot:users /home/penpot
|
||||
# we need to be able to install rust-analyzer and possibly other dependencies with rustup
|
||||
chown -R penpot:penpot /opt/rustup
|
||||
|
||||
# pnpm keeps its global dir (bin, global, store, package-manager-store)
|
||||
# under ~/.local and its metadata cache under ~/.cache; opencode and other
|
||||
# user-space tools write there too. Hand both trees back to penpot so
|
||||
# packages, and pnpm itself, can be updated without root. Needed because the
|
||||
# UID remap above can leave everything the image created owned by the old
|
||||
# UID. ~/.config is deliberately excluded: the opencode overlay bind-mounts
|
||||
# a host directory inside it.
|
||||
mkdir -p /home/penpot/.local /home/penpot/.cache
|
||||
chown -R penpot:penpot /home/penpot/.local /home/penpot/.cache
|
||||
|
||||
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
|
||||
|
||||
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true"
|
||||
|
||||
@ -58,6 +58,13 @@ http {
|
||||
'' close;
|
||||
}
|
||||
|
||||
# Link preview crawlers; their requests for the application root
|
||||
# are served with dynamic Open Graph metadata from the backend.
|
||||
map $http_user_agent $penpot_link_preview_agent {
|
||||
default 0;
|
||||
"~*(slackbot|discordbot|twitterbot|facebookexternalhit|facebookcatalog|whatsapp|telegrambot|linkedinbot|skypeuripreview|pinterestbot|redditbot|embedly|iframely|mastodon|bluesky|applebot|googlebot|bingbot|bingpreview|duckduckbot|mattermost-bot|opengraph[.]xyz|twitter card validator)" 1;
|
||||
}
|
||||
|
||||
proxy_cache_path /tmp/cache/ levels=2:2 keys_zone=penpot:20m;
|
||||
proxy_cache_methods GET HEAD;
|
||||
proxy_cache_valid any 48h;
|
||||
@ -125,6 +132,10 @@ http {
|
||||
add_header x-internal-redirect "$upstream_http_x_accel_redirect";
|
||||
}
|
||||
|
||||
location = /link-preview {
|
||||
proxy_pass http://127.0.0.1:6060/link-preview$is_args$args;
|
||||
}
|
||||
|
||||
# On production, this is controlled by ELB
|
||||
location /api/export {
|
||||
proxy_pass http://127.0.0.1:6061;
|
||||
@ -170,11 +181,6 @@ http {
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /mcp/sse {
|
||||
proxy_pass http://127.0.0.1:4401/sse;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /admin {
|
||||
proxy_pass http://127.0.0.1:6063/admin;
|
||||
}
|
||||
@ -301,6 +307,10 @@ http {
|
||||
return 301 " /404";
|
||||
}
|
||||
|
||||
if ($penpot_link_preview_agent) {
|
||||
rewrite ^/$ /link-preview last;
|
||||
}
|
||||
|
||||
include /home/penpot/penpot/docker/devenv/files/nginx-security-headers.conf;
|
||||
add_header Cache-Control "no-store" always;
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev AS build
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev@sha256:9b025768946856e44f9775005eb199f2e1820484ea27bb5c8bcbb187b80464dc AS build
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG='C.UTF-8' \
|
||||
@ -6,11 +6,10 @@ ENV LANG='C.UTF-8' \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
TZ=Etc/UTC
|
||||
|
||||
ARG IMAGEMAGICK_VERSION=7.1.2-27
|
||||
ARG IMAGEMAGICK_VERSION=7.1.2-32
|
||||
|
||||
RUN set -e; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq upgrade; \
|
||||
apt-get -qqy --no-install-recommends install init-system-helpers; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
autoconf \
|
||||
@ -24,7 +23,6 @@ RUN set -e; \
|
||||
liblcms2-dev \
|
||||
libltdl-dev \
|
||||
liblzma-dev \
|
||||
libopenexr-dev \
|
||||
libxml2-dev \
|
||||
libpng-dev \
|
||||
librsvg2-dev \
|
||||
@ -39,7 +37,7 @@ RUN set -e; \
|
||||
mkdir -p /tmp/magick; \
|
||||
cd /tmp/magick; \
|
||||
tar -xf /tmp/magick.tar.gz --strip-components=1; \
|
||||
./configure --prefix=/opt/imagick; \
|
||||
./configure --prefix=/opt/imagick --without-openexr; \
|
||||
make -j 2; \
|
||||
make install; \
|
||||
rm -rf /opt/imagick/lib/libMagick++*; \
|
||||
@ -58,7 +56,6 @@ RUN set -ex; \
|
||||
libheif1 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libopenexr-3-1-30 \
|
||||
libopenjp2-7 \
|
||||
libpng16-16 \
|
||||
librsvg2-2 \
|
||||
@ -77,7 +74,7 @@ RUN set -ex; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
FROM dhi.io/debian-base:trixie-debian13 AS image
|
||||
FROM dhi.io/debian-base:trixie-debian13@sha256:de80b468d8c492508f24b30e529accfaa8c59fe4505ddd72e969b324d276ad21 AS image
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG='C.UTF-8' \
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev AS build
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev@sha256:9b025768946856e44f9775005eb199f2e1820484ea27bb5c8bcbb187b80464dc AS build
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG='C.UTF-8' \
|
||||
@ -9,11 +9,11 @@ ENV LANG='C.UTF-8' \
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
binutils \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gzip \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@ -27,12 +27,12 @@ RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
ESUM='5b222fce0b7076a10ac7ae3b1009a6c2caf4f35bc4e81de72010af6750c5e146'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_aarch64.tar.gz'; \
|
||||
ESUM='8ff07320636ef5b30f0d7e16687932df52255c3ef3ffd920a6aae3723511ce7f'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.203-ca-jdk26.0.2.1-linux_aarch64.tar.gz'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
ESUM='4b7c114917aebd0fc6284fc7111245d7747a4d9603bd12d86b384b1abc9d575d'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_x64.tar.gz'; \
|
||||
ESUM='c6b810ca659e3293205444f47ac4af163396fd6d025c51fd5f767e628b2ceede'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.203-ca-jdk26.0.2.1-linux_x64.tar.gz'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -53,7 +53,7 @@ RUN set -eux; \
|
||||
--output /opt/jre;
|
||||
|
||||
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev AS image
|
||||
FROM dhi.io/debian-base:trixie-debian13-dev@sha256:9b025768946856e44f9775005eb199f2e1820484ea27bb5c8bcbb187b80464dc AS image
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG='C.UTF-8' \
|
||||
@ -68,7 +68,6 @@ RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install passwd; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
ca-certificates \
|
||||
curl \
|
||||
@ -81,7 +80,6 @@ RUN set -ex; \
|
||||
libheif1 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libopenexr-3-1-30 \
|
||||
libopenjp2-7 \
|
||||
libpng16-16 \
|
||||
librsvg2-2 \
|
||||
@ -108,7 +106,7 @@ RUN set -ex; \
|
||||
chown -R penpot:penpot /opt/data;
|
||||
|
||||
COPY --from=build /opt/jre /opt/jre
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-32 /opt/imagick /opt/imagick
|
||||
|
||||
COPY files/imagemagick-policy.xml /opt/imagick/etc/ImageMagick-7/policy.xml
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/node:24.20.0-debian13-dev
|
||||
FROM dhi.io/node:24.22.1-debian13-dev@sha256:20cf36e12660c68b1e28887d1e71a7f2494bcea9d034709368e0afce77728eb0
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
@ -11,12 +11,12 @@ RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install passwd; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
curl \
|
||||
tzdata \
|
||||
locales \
|
||||
ca-certificates \
|
||||
gzip \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
@ -26,7 +26,7 @@ RUN set -ex; \
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
\
|
||||
xvfb \
|
||||
fonts-noto-color-emoji \
|
||||
@ -37,10 +37,12 @@ RUN set -ex; \
|
||||
xfonts-scalable \
|
||||
fonts-liberation \
|
||||
fonts-ipafont-gothic \
|
||||
fonts-ipafont-mincho \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-tlwg-loma-otf \
|
||||
fonts-freefont-ttf \
|
||||
poppler-utils \
|
||||
poppler-data \
|
||||
\
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
@ -68,7 +70,6 @@ RUN set -ex; \
|
||||
libheif1 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libopenexr-3-1-30 \
|
||||
libopenjp2-7 \
|
||||
libpng16-16 \
|
||||
librsvg2-2 \
|
||||
@ -87,24 +88,24 @@ RUN set -ex; \
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-exporter/"
|
||||
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-32 /opt/imagick /opt/imagick
|
||||
|
||||
WORKDIR /opt/penpot/exporter
|
||||
|
||||
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||
# docker/devenv/Dockerfile); the generated ./setup expects it on PATH.
|
||||
# Corepack is gone from Node 25+, so nothing here may use it.
|
||||
ARG PNPM_VERSION=12.5.1
|
||||
ARG PNPM_VERSION=12.15.0
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
PNPM_SHA256='d7ce93a2c7d847d3a34bd3f559b60c14f578b1320c17cc352a2c5c5fdf956117'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
PNPM_SHA256='55b5ed0092bd11176c1669598e231ae52e7cedac59de6efd0d415730f37d0598'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/nginx:1.31.1-debian13-dev
|
||||
FROM dhi.io/nginx:1.31.7-debian13-dev@sha256:940ee353dd807f5c5e32720923bb9b34ef9e631ae567320f3eaea7797d6c7964
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
@ -7,7 +7,6 @@ USER root
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
|
||||
apt-get -qqy -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --no-install-recommends install bash gettext-base passwd; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
groupadd -f -g 1001 penpot; \
|
||||
@ -27,11 +26,20 @@ COPY ./files/nginx.conf.template /tmp/nginx.conf.template
|
||||
COPY ./files/nginx-resolvers.conf.template /tmp/resolvers.conf.template
|
||||
COPY ./files/nginx-admin-console-locations.conf.template /tmp/nginx-admin-console-locations.conf.template
|
||||
COPY ./files/nginx-mcp-locations.conf.template /tmp/nginx-mcp-locations.conf.template
|
||||
COPY ./files/nginx-security-headers.conf /etc/nginx/nginx-security-headers.conf
|
||||
COPY ./files/nginx-security-headers.conf.template /tmp/nginx-security-headers.conf.template
|
||||
COPY ./files/nginx-mime.types /etc/nginx/mime.types
|
||||
COPY ./files/nginx-external-locations.conf /etc/nginx/overrides/location.d/external-locations.conf
|
||||
COPY ./files/nginx-entrypoint.sh /entrypoint.sh
|
||||
|
||||
# The CSP hashes of the inline scripts of index.html are emitted by the
|
||||
# frontend build. Move them out of the document root: nginx must read them,
|
||||
# the browser has no reason to.
|
||||
RUN if [ -f /var/www/app/csp-script-hashes.txt ]; then \
|
||||
mv /var/www/app/csp-script-hashes.txt /etc/nginx/csp-script-hashes.txt; \
|
||||
else \
|
||||
echo "WARNING: the frontend bundle does not provide csp-script-hashes.txt" >&2; \
|
||||
fi
|
||||
|
||||
RUN chown -R 1001:0 /var/cache/nginx; \
|
||||
chmod -R g+w /var/cache/nginx; \
|
||||
chown -R 1001:0 /etc/nginx; \
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/node:24.20.0-debian13-dev AS build
|
||||
FROM dhi.io/node:24.22.1-debian13-dev@sha256:20cf36e12660c68b1e28887d1e71a7f2494bcea9d034709368e0afce77728eb0 AS build
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
@ -11,7 +11,7 @@ WORKDIR /opt/penpot/mcp
|
||||
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||
# docker/devenv/Dockerfile); ./setup expects it on PATH.
|
||||
# Corepack is gone from Node 25+, so nothing here may use it.
|
||||
ARG PNPM_VERSION=12.5.1
|
||||
ARG PNPM_VERSION=12.15.0
|
||||
RUN set -eux; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install curl ca-certificates gzip; \
|
||||
@ -20,11 +20,11 @@ RUN set -eux; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
PNPM_SHA256='d7ce93a2c7d847d3a34bd3f559b60c14f578b1320c17cc352a2c5c5fdf956117'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
PNPM_SHA256='55b5ed0092bd11176c1669598e231ae52e7cedac59de6efd0d415730f37d0598'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -42,7 +42,7 @@ RUN set -eux; \
|
||||
RUN ./setup
|
||||
|
||||
|
||||
FROM dhi.io/node:24.20.0-debian13 AS image
|
||||
FROM dhi.io/node:24.22.1-debian13@sha256:32a2fa6af5209698a9354cef0b19ada3dbfac3090b1010b70afa9f1a298ba2d9 AS image
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
FROM ubuntu:26.04
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM dhi.io/node:24.22.1-debian13-dev@sha256:20cf36e12660c68b1e28887d1e71a7f2494bcea9d034709368e0afce77728eb0
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
NODE_VERSION=v24.20.0 \
|
||||
PNPM_VERSION=12.5.1 \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# passwd provides useradd, not preinstalled on the DHI base image.
|
||||
RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
mkdir -p /etc/resolvconf/resolv.conf.d; \
|
||||
echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
|
||||
apt-get -qqy --no-install-recommends install passwd; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
curl \
|
||||
tzdata \
|
||||
@ -35,9 +32,8 @@ RUN set -ex; \
|
||||
\
|
||||
libgomp1 \
|
||||
libheif1 \
|
||||
libjpeg-turbo8 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libopenexr-3-1-30 \
|
||||
libopenjp2-7 \
|
||||
libpng16-16 \
|
||||
librsvg2-2 \
|
||||
@ -45,44 +41,12 @@ RUN set -ex; \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
libxml2-16 \
|
||||
libxml2 \
|
||||
libzip5 \
|
||||
libzstd1 \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
|
||||
mkdir -p /opt/node; \
|
||||
cd /opt/node; \
|
||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||
chown -R root /opt/node; \
|
||||
rm -rf /tmp/nodejs.tar.gz; \
|
||||
PNPM_URL="https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "${PNPM_URL}"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /opt/node/bin pnpm; \
|
||||
chmod 755 /opt/node/bin/pnpm; \
|
||||
rm -f /tmp/pnpm.tar.gz; \
|
||||
pnpm --version; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
mkdir -p /opt/penpot; \
|
||||
chown -R penpot:penpot /opt/penpot;
|
||||
|
||||
@ -90,8 +54,42 @@ ARG BUNDLE_PATH="./bundle-media-processor/"
|
||||
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/media-processor/
|
||||
|
||||
WORKDIR /opt/penpot/media-processor
|
||||
|
||||
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||
# docker/devenv/Dockerfile); ./setup expects it on PATH.
|
||||
# Corepack is gone from Node 25+, so nothing here may use it.
|
||||
ARG PNPM_VERSION=12.15.0
|
||||
RUN set -eux; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install gzip; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='d7ce93a2c7d847d3a34bd3f559b60c14f578b1320c17cc352a2c5c5fdf956117'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='55b5ed0092bd11176c1669598e231ae52e7cedac59de6efd0d415730f37d0598'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
mkdir -p /usr/local/bin; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /usr/local/bin pnpm; \
|
||||
chmod 755 /usr/local/bin/pnpm; \
|
||||
rm -f /tmp/pnpm.tar.gz; \
|
||||
pnpm --version;
|
||||
|
||||
# Runs as root: this base image installs Node system-wide (symlinked into
|
||||
# /usr/bin), so ./setup needs write access there.
|
||||
RUN ./setup && chown -R penpot:penpot /opt/penpot/media-processor
|
||||
|
||||
USER penpot:penpot
|
||||
|
||||
RUN ./setup
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM dhi.io/nginx:1.31.1-alpine3.22
|
||||
FROM dhi.io/nginx:1.31.7-alpine3.24@sha256:6d3811fbc76c5d0652752b39a003769759ccfeb2c1e874bacf99e3d44e5da3b9
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-storybook/"
|
||||
|
||||
@ -26,6 +26,9 @@
|
||||
x-flags: &penpot-flags
|
||||
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-mcp enable-admin-console
|
||||
|
||||
## Public URI where Penpot is reached. To serve Penpot from a subpath, include
|
||||
## it with a trailing slash, e.g. https://example.com/penpot/. A reverse proxy in
|
||||
## front must forward the subpath unchanged, without rewriting it.
|
||||
x-uri: &penpot-public-uri
|
||||
PENPOT_PUBLIC_URI: http://localhost:9001
|
||||
|
||||
@ -38,7 +41,7 @@ x-body-size: &penpot-http-body-size
|
||||
## Penpot SECRET KEY. It serves as a master key from which other keys for subsystems
|
||||
## (eg http sessions, or invitations) are derived.
|
||||
##
|
||||
## We recommend to use a trully randomly generated
|
||||
## We recommend to use a truly randomly generated
|
||||
## 512 bits base64 encoded string here. You can generate one with:
|
||||
##
|
||||
## python3 -c "import secrets; print(secrets.token_urlsafe(64))"
|
||||
@ -58,6 +61,7 @@ networks:
|
||||
volumes:
|
||||
penpot_postgres_v15:
|
||||
penpot_assets:
|
||||
# penpot_mailpit_data:
|
||||
# penpot_traefik:
|
||||
|
||||
services:
|
||||
@ -109,6 +113,8 @@ services:
|
||||
# ## internet with HTTPS using traefik.
|
||||
|
||||
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`)"
|
||||
# ## When Penpot is served from a subpath, match it too:
|
||||
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`) && PathPrefix(`/<SUBPATH>`)"
|
||||
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
|
||||
# - "traefik.http.routers.penpot-https.tls.certresolver=letsencrypt"
|
||||
# - "traefik.http.routers.penpot-https.tls=true"
|
||||
@ -139,6 +145,9 @@ services:
|
||||
networks:
|
||||
- penpot
|
||||
|
||||
## Configuration environment variables for the backend container.
|
||||
|
||||
|
||||
environment:
|
||||
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key, *penpot-database]
|
||||
|
||||
@ -169,13 +178,13 @@ services:
|
||||
PENPOT_TELEMETRY_ENABLED: "true"
|
||||
PENPOT_TELEMETRY_REFERER: compose
|
||||
|
||||
## Example SMTP/Email configuration. By default, emails are sent to the mailcatch
|
||||
## Example SMTP/Email configuration. By default, emails are sent to the Mailpit
|
||||
## service, but for production usage it is recommended to setup a real SMTP
|
||||
## provider. Emails are used to confirm user registrations & invitations. Look below
|
||||
## how the mailcatch service is configured.
|
||||
## how the Mailpit service is configured.
|
||||
PENPOT_SMTP_DEFAULT_FROM: no-reply@example.com
|
||||
PENPOT_SMTP_DEFAULT_REPLY_TO: no-reply@example.com
|
||||
PENPOT_SMTP_HOST: penpot-mailcatch
|
||||
PENPOT_SMTP_HOST: penpot-mailpit
|
||||
PENPOT_SMTP_PORT: 1025
|
||||
PENPOT_SMTP_USERNAME:
|
||||
PENPOT_SMTP_PASSWORD:
|
||||
@ -224,9 +233,19 @@ services:
|
||||
# communicate with the frontend.
|
||||
PENPOT_INTERNAL_URI: http://penpot-frontend:8080
|
||||
|
||||
## Valkey (or previously Redis) is used for the websockets notifications.
|
||||
## Valkey (or previously Redis) is used for the websockets notifications
|
||||
## and for storing the state export jobs
|
||||
PENPOT_REDIS_URI: redis://penpot-valkey/0
|
||||
|
||||
# PENPOT_EXPORTER_MAX_CONCURRENT_JOBS: 4
|
||||
# PENPOT_EXPORTER_MAX_JOBS_PER_PROFILE: 2
|
||||
# PENPOT_EXPORTER_QUEUE_MAX: 64
|
||||
# PENPOT_EXPORTER_JOB_TTL: 3600
|
||||
# PENPOT_WASM_WORKER_POOL_MAX: 2
|
||||
# PENPOT_WASM_WORKER_POOL_MIN: 1
|
||||
# PENPOT_WASM_WORKER_IDLE_TIMEOUT: 300
|
||||
# PENPOT_WASM_WORKER_IMAGE_CACHE_SIZE: 134217728
|
||||
|
||||
penpot-postgres:
|
||||
image: "postgres:15"
|
||||
restart: always
|
||||
@ -270,16 +289,22 @@ services:
|
||||
# although this should not be necessary.
|
||||
- VALKEY_EXTRA_FLAGS=--maxmemory 128mb --maxmemory-policy volatile-lfu
|
||||
|
||||
## A mailcatch service, used as temporal SMTP server. You can access via HTTP to the
|
||||
## port 1080 for read all emails the penpot platform has sent. Should be only used as a
|
||||
## temporal solution while no real SMTP provider is configured.
|
||||
## A Mailpit service, used as temporal SMTP server. You can access via HTTP to the
|
||||
## port 8025 to read all emails the penpot platform has sent. Should be only used as a
|
||||
## temporal solution while no real SMTP provider is configured. Messages are kept in
|
||||
## memory only and are lost when the container is recreated; uncomment the volume
|
||||
## below (and the one in the top-level volumes: section) to persist them instead.
|
||||
|
||||
penpot-mailcatch:
|
||||
image: sj26/mailcatcher:latest
|
||||
penpot-mailpit:
|
||||
image: axllent/mailpit:v1
|
||||
restart: always
|
||||
# volumes:
|
||||
# - penpot_mailpit_data:/data
|
||||
# environment:
|
||||
# - MP_DATABASE=/data/mailpit.db
|
||||
expose:
|
||||
- '1025'
|
||||
ports:
|
||||
- "1080:1080"
|
||||
- "8025:8025"
|
||||
networks:
|
||||
- penpot
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
# The Admin Console is served at the root of its own server. Keep proxy_pass
|
||||
# without a URI: building it from $request_uri escapes the path twice.
|
||||
location /admin-console {
|
||||
rewrite "^/admin-console(?:/(.*))?$" /$1 break;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $http_cf_connecting_ip;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass $PENPOT_ADMIN_CONSOLE_URI$request_uri;
|
||||
proxy_pass $PENPOT_ADMIN_CONSOLE_URI;
|
||||
}
|
||||
|
||||
@ -51,6 +51,23 @@ update_oidc_name /var/www/app/js/config.js
|
||||
## Nginx Config
|
||||
#########################################
|
||||
|
||||
# Path of PENPOT_PUBLIC_URI (e.g. /penpot/ for https://example.com/penpot/).
|
||||
# Requests below it are rewritten to the root, so the proxy in front of this
|
||||
# server forwards them unchanged.
|
||||
PENPOT_PUBLIC_PATH="$(sed -E 's|^[A-Za-z][A-Za-z0-9+.-]*://[^/]*||; s|[?#].*$||' <<< "${PENPOT_PUBLIC_URI:-}")"
|
||||
PENPOT_PUBLIC_PATH="/${PENPOT_PUBLIC_PATH#/}"
|
||||
export PENPOT_PUBLIC_PATH="${PENPOT_PUBLIC_PATH%/}/"
|
||||
|
||||
if [ "$PENPOT_PUBLIC_PATH" != "/" ]; then
|
||||
# shellcheck disable=SC2016 # literal regex metacharacters
|
||||
_public_path_regex="$(sed -E 's/[][\.*^$(){}+?|]/\\&/g' <<< "$PENPOT_PUBLIC_PATH")"
|
||||
export PENPOT_PUBLIC_PATH_DIRECTIVES="location = ${PENPOT_PUBLIC_PATH%/} { absolute_redirect off; return 301 ${PENPOT_PUBLIC_PATH}\$is_args\$args; }
|
||||
rewrite ^${_public_path_regex}(.*)\$ /\$1 last;"
|
||||
unset _public_path_regex
|
||||
else
|
||||
export PENPOT_PUBLIC_PATH_DIRECTIVES=""
|
||||
fi
|
||||
|
||||
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
||||
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
||||
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
|
||||
@ -58,7 +75,7 @@ export PENPOT_IPV6_LISTEN_DIRECTIVE=${PENPOT_IPV6_LISTEN_DIRECTIVE:-"listen [::]
|
||||
if is_truthy "${PENPOT_DISABLE_IPV6_LISTEN:-}"; then
|
||||
export PENPOT_IPV6_LISTEN_DIRECTIVE=""
|
||||
fi
|
||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE,\$PENPOT_IPV6_LISTEN_DIRECTIVE" \
|
||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE,\$PENPOT_IPV6_LISTEN_DIRECTIVE,\$PENPOT_PUBLIC_PATH,\$PENPOT_PUBLIC_PATH_DIRECTIVES" \
|
||||
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
if [[ $PENPOT_FLAGS == *"enable-admin-console"* ]]; then
|
||||
@ -84,4 +101,124 @@ export PENPOT_INTERNAL_RESOLVER=${PENPOT_INTERNAL_RESOLVER:-$PENPOT_DEFAULT_INTE
|
||||
envsubst "\$PENPOT_INTERNAL_RESOLVER" \
|
||||
< /tmp/resolvers.conf.template > /etc/nginx/overrides/http.d/resolvers.conf
|
||||
|
||||
#########################################
|
||||
## Security Headers Config
|
||||
#########################################
|
||||
|
||||
# The default policy describes what a stock Penpot deployment actually
|
||||
# needs: 'wasm-unsafe-eval' for the render engine, 'unsafe-inline' styles
|
||||
# for the inline style attributes emitted by the UI, and blob:/data: for
|
||||
# thumbnails, exports and font handling. Everything else is same-origin,
|
||||
# because the Google Fonts and GitHub templates endpoints are reverse
|
||||
# proxied by this very server.
|
||||
#
|
||||
# The hashes of the inline scripts of index.html are emitted by the frontend
|
||||
# build and moved to /etc/nginx at image build time. A bundle predating that
|
||||
# change simply yields no hashes, in which case those scripts would be
|
||||
# reported (or blocked under enforce) as before.
|
||||
#
|
||||
# It ships in report-only mode because deployments with plugins enabled still
|
||||
# report eval and remote fetch violations from the SES sandbox. Enforcing mode
|
||||
# stays opt-in until that is resolved.
|
||||
export PENPOT_CSP_MODE=${PENPOT_CSP_MODE:-report-only}
|
||||
|
||||
if [ -r /etc/nginx/csp-script-hashes.txt ]; then
|
||||
PENPOT_CSP_SCRIPT_HASHES=" $(tr -d '\n' < /etc/nginx/csp-script-hashes.txt)"
|
||||
else
|
||||
PENPOT_CSP_SCRIPT_HASHES=""
|
||||
fi
|
||||
|
||||
# Remember whether the policy comes from the deployment before the default
|
||||
# is applied, so the warning below only fires for the default one.
|
||||
if [ -n "${PENPOT_CSP_POLICY:-}" ]; then
|
||||
PENPOT_CSP_POLICY_IS_CUSTOM="true"
|
||||
else
|
||||
PENPOT_CSP_POLICY_IS_CUSTOM="false"
|
||||
fi
|
||||
|
||||
# Directives a deployment may extend. The default policy carries hashes that
|
||||
# change on every build, so a deployment that needs an extra origin cannot
|
||||
# hardcode the whole policy without recomputing them at each release. These
|
||||
# variables let it declare only what it adds.
|
||||
#
|
||||
# base-uri, form-action, object-src and frame-ancestors are deliberately not
|
||||
# extensible: there is no legitimate reason to relax them, and doing so
|
||||
# silently removes the protection they provide. A deployment that really
|
||||
# needs it can still set PENPOT_CSP_POLICY and own the whole policy.
|
||||
if [ "${PENPOT_CSP_POLICY_IS_CUSTOM}" = "false" ]; then
|
||||
export PENPOT_CSP_POLICY="default-src 'self'\
|
||||
; base-uri 'self'\
|
||||
; object-src 'none'\
|
||||
; frame-ancestors 'self'\
|
||||
; form-action 'self'\
|
||||
; manifest-src 'self'\
|
||||
; script-src 'self' 'wasm-unsafe-eval'${PENPOT_CSP_SCRIPT_HASHES}${PENPOT_CSP_SCRIPT_SRC_EXTRA:+ ${PENPOT_CSP_SCRIPT_SRC_EXTRA}}\
|
||||
; style-src 'self' 'unsafe-inline'${PENPOT_CSP_STYLE_SRC_EXTRA:+ ${PENPOT_CSP_STYLE_SRC_EXTRA}}\
|
||||
; img-src 'self' data: blob:${PENPOT_CSP_IMG_SRC_EXTRA:+ ${PENPOT_CSP_IMG_SRC_EXTRA}}\
|
||||
; font-src 'self'${PENPOT_CSP_FONT_SRC_EXTRA:+ ${PENPOT_CSP_FONT_SRC_EXTRA}}\
|
||||
; connect-src 'self' blob: data:${PENPOT_CSP_CONNECT_SRC_EXTRA:+ ${PENPOT_CSP_CONNECT_SRC_EXTRA}}\
|
||||
; frame-src 'self'${PENPOT_CSP_FRAME_SRC_EXTRA:+ ${PENPOT_CSP_FRAME_SRC_EXTRA}}\
|
||||
; worker-src 'self' blob:\
|
||||
; media-src 'self' blob:${PENPOT_CSP_REPORT_URI:+; report-uri ${PENPOT_CSP_REPORT_URI}}"
|
||||
else
|
||||
for _var in SCRIPT_SRC_EXTRA STYLE_SRC_EXTRA IMG_SRC_EXTRA FONT_SRC_EXTRA \
|
||||
CONNECT_SRC_EXTRA FRAME_SRC_EXTRA REPORT_URI; do
|
||||
eval "_value=\${PENPOT_CSP_${_var}:-}"
|
||||
if [ -n "${_value}" ]; then
|
||||
echo "penpot: WARNING: PENPOT_CSP_${_var} is ignored because PENPOT_CSP_POLICY defines the whole policy." >&2
|
||||
fi
|
||||
done
|
||||
unset _var _value
|
||||
export PENPOT_CSP_POLICY
|
||||
fi
|
||||
|
||||
case "${PENPOT_CSP_MODE}" in
|
||||
enforce)
|
||||
export PENPOT_CSP_DIRECTIVE="add_header Content-Security-Policy \"${PENPOT_CSP_POLICY}\" always;"
|
||||
# The plugin runtime initialises on every page load, whether or not a
|
||||
# plugin is opened, and its sandbox needs 'unsafe-eval'. Look at the
|
||||
# policy that will actually be served rather than at where it came
|
||||
# from, since it can be granted through the default policy, through
|
||||
# PENPOT_CSP_SCRIPT_SRC_EXTRA or through a policy of your own.
|
||||
case "${PENPOT_CSP_POLICY}" in
|
||||
*"'unsafe-eval'"*) ;;
|
||||
*)
|
||||
echo "penpot: WARNING: PENPOT_CSP_MODE=enforce degrades the plugin runtime." >&2
|
||||
echo "penpot: it initialises on every page load and needs 'unsafe-eval', which this policy does not grant." >&2
|
||||
echo "penpot: add it through PENPOT_CSP_SCRIPT_SRC_EXTRA if you need plugins, or keep report-only." >&2
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
report-only)
|
||||
export PENPOT_CSP_DIRECTIVE="add_header Content-Security-Policy-Report-Only \"${PENPOT_CSP_POLICY}\" always;"
|
||||
;;
|
||||
disabled)
|
||||
export PENPOT_CSP_DIRECTIVE=""
|
||||
;;
|
||||
*)
|
||||
echo "penpot: invalid PENPOT_CSP_MODE '${PENPOT_CSP_MODE}'; expected one of: enforce, report-only, disabled" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# HSTS is only meaningful when the deployment is served over HTTPS, so it
|
||||
# defaults to enabled when PENPOT_PUBLIC_URI declares an https scheme and
|
||||
# to disabled otherwise. Set PENPOT_HSTS_VALUE explicitly to override it,
|
||||
# for example to add includeSubDomains or preload, or to an empty value
|
||||
# to disable it on an https deployment.
|
||||
if [[ "${PENPOT_PUBLIC_URI:-}" == https://* ]]; then
|
||||
export PENPOT_HSTS_VALUE=${PENPOT_HSTS_VALUE-"max-age=31536000"}
|
||||
else
|
||||
export PENPOT_HSTS_VALUE=${PENPOT_HSTS_VALUE-""}
|
||||
fi
|
||||
|
||||
if [ -n "${PENPOT_HSTS_VALUE}" ]; then
|
||||
export PENPOT_HSTS_DIRECTIVE="add_header Strict-Transport-Security \"${PENPOT_HSTS_VALUE}\" always;"
|
||||
else
|
||||
export PENPOT_HSTS_DIRECTIVE=""
|
||||
fi
|
||||
|
||||
envsubst "\$PENPOT_CSP_DIRECTIVE,\$PENPOT_HSTS_DIRECTIVE" \
|
||||
< /tmp/nginx-security-headers.conf.template > /etc/nginx/nginx-security-headers.conf
|
||||
|
||||
exec "$@";
|
||||
|
||||
@ -7,6 +7,14 @@ location ~ ^/github/penpot-files/(.+)$ {
|
||||
proxy_set_header User-Agent "curl/8.5.0";
|
||||
proxy_set_header Host "raw.githubusercontent.com";
|
||||
proxy_set_header Accept "*/*";
|
||||
# A third-party upstream must not dictate the security policy of our own
|
||||
# origin: browsers enforce the intersection of every Content-Security-Policy
|
||||
# they receive, and any Strict-Transport-Security lands on this host.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Content-Security-Policy-Report-Only;
|
||||
|
||||
include /etc/nginx/nginx-security-headers.conf;
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
proxy_buffering off;
|
||||
}
|
||||
@ -31,6 +39,14 @@ location ~ ^/internal/gfonts/font/(?<font_file>.+) {
|
||||
|
||||
proxy_cache penpot;
|
||||
|
||||
# A third-party upstream must not dictate the security policy of our own
|
||||
# origin: browsers enforce the intersection of every Content-Security-Policy
|
||||
# they receive, and any Strict-Transport-Security lands on this host.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Content-Security-Policy-Report-Only;
|
||||
|
||||
include /etc/nginx/nginx-security-headers.conf;
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
add_header Cache-Control max-age=86400;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
@ -53,6 +69,14 @@ location ~ ^/internal/gfonts/css {
|
||||
|
||||
proxy_cache penpot;
|
||||
|
||||
# A third-party upstream must not dictate the security policy of our own
|
||||
# origin: browsers enforce the intersection of every Content-Security-Policy
|
||||
# they receive, and any Strict-Transport-Security lands on this host.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Content-Security-Policy-Report-Only;
|
||||
|
||||
include /etc/nginx/nginx-security-headers.conf;
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
add_header Cache-Control max-age=86400;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
|
||||
@ -11,9 +11,3 @@ location /mcp/stream {
|
||||
proxy_pass $mcp_stream_backend;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /mcp/sse {
|
||||
set $mcp_sse_backend $PENPOT_MCP_URI/sse$is_args$args;
|
||||
proxy_pass $mcp_sse_backend;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
6
docker/images/files/nginx-security-headers.conf.template
Normal file
6
docker/images/files/nginx-security-headers.conf.template
Normal file
@ -0,0 +1,6 @@
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
${PENPOT_CSP_DIRECTIVE}
|
||||
${PENPOT_HSTS_DIRECTIVE}
|
||||
@ -67,6 +67,13 @@ http {
|
||||
'' close;
|
||||
}
|
||||
|
||||
# Link preview crawlers; their requests for the application root
|
||||
# are served with dynamic Open Graph metadata from the backend.
|
||||
map $http_user_agent $penpot_link_preview_agent {
|
||||
default 0;
|
||||
"~*(slackbot|discordbot|twitterbot|facebookexternalhit|facebookcatalog|whatsapp|telegrambot|linkedinbot|skypeuripreview|pinterestbot|redditbot|embedly|iframely|mastodon|bluesky|applebot|googlebot|bingbot|bingpreview|duckduckbot|mattermost-bot|opengraph[.]xyz|twitter card validator)" 1;
|
||||
}
|
||||
|
||||
proxy_cache_path /tmp/cache/ levels=2:2 keys_zone=penpot:20m;
|
||||
proxy_cache_methods GET HEAD;
|
||||
proxy_cache_valid any 48h;
|
||||
@ -95,6 +102,8 @@ http {
|
||||
|
||||
root /var/www/app/;
|
||||
|
||||
${PENPOT_PUBLIC_PATH_DIRECTIVES}
|
||||
|
||||
location @handle_redirect {
|
||||
set $redirect_uri "$upstream_http_location";
|
||||
set $redirect_host "$upstream_http_x_host";
|
||||
@ -137,6 +146,10 @@ http {
|
||||
add_header x-internal-redirect "$upstream_http_x_accel_redirect";
|
||||
}
|
||||
|
||||
location = /link-preview {
|
||||
proxy_pass $PENPOT_BACKEND_URI/link-preview$is_args$args;
|
||||
}
|
||||
|
||||
location /api/export {
|
||||
proxy_set_header Host $proxy_host;
|
||||
proxy_pass $PENPOT_EXPORTER_URI;
|
||||
@ -156,7 +169,7 @@ http {
|
||||
location /readyz {
|
||||
access_log off;
|
||||
proxy_set_header Host $proxy_host;
|
||||
proxy_pass $PENPOT_BACKEND_URI$request_uri;
|
||||
proxy_pass $PENPOT_BACKEND_URI$uri$is_args$args;
|
||||
}
|
||||
|
||||
location /ws/notifications {
|
||||
@ -187,7 +200,11 @@ http {
|
||||
}
|
||||
|
||||
location ~ ^/[^/]+/(.*)$ {
|
||||
return 301 " /404";
|
||||
return 301 " ${PENPOT_PUBLIC_PATH}404";
|
||||
}
|
||||
|
||||
if ($penpot_link_preview_agent) {
|
||||
rewrite ^/$ /link-preview last;
|
||||
}
|
||||
|
||||
include /etc/nginx/nginx-security-headers.conf;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user