mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 19:16:10 +02:00
commit
ac835bb655
25 changed files with 232 additions and 485 deletions
|
@ -6,4 +6,6 @@ enableImmutableInstalls: false
|
||||||
|
|
||||||
enableTelemetry: false
|
enableTelemetry: false
|
||||||
|
|
||||||
|
httpTimeout: 600000
|
||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
|
@ -160,7 +160,6 @@ available_commands = (
|
||||||
"delete-profile",
|
"delete-profile",
|
||||||
"search-profile",
|
"search-profile",
|
||||||
"derive-password",
|
"derive-password",
|
||||||
"migrate-components-v2",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
@ -233,7 +232,4 @@ elif args.action == "search-profile":
|
||||||
|
|
||||||
search_profile(email)
|
search_profile(email)
|
||||||
|
|
||||||
elif args.action == "migrate-components-v2":
|
|
||||||
migrate_components_v2()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ export PENPOT_FLAGS="\
|
||||||
enable-file-validation \
|
enable-file-validation \
|
||||||
enable-file-schema-validation";
|
enable-file-schema-validation";
|
||||||
|
|
||||||
|
# Default deletion delay for devenv
|
||||||
|
export PENPOT_DELETION_DELAY="24h"
|
||||||
|
|
||||||
# Setup default upload media file size to 100MiB
|
# Setup default upload media file size to 100MiB
|
||||||
export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
||||||
|
|
|
@ -18,7 +18,9 @@ if [ -f ./environ ]; then
|
||||||
source ./environ
|
source ./environ
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-preview $JVM_OPTS"
|
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow -Dpolyglot.engine.WarnInterpreterOnly=false --enable-preview $JVM_OPTS"
|
||||||
|
|
||||||
set -x
|
ENTRYPOINT=${1:-app.main};
|
||||||
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main
|
|
||||||
|
set -ex
|
||||||
|
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m $ENTRYPOINT
|
||||||
|
|
|
@ -32,35 +32,18 @@ export OPTIONS="
|
||||||
-J-XX:+UnlockDiagnosticVMOptions \
|
-J-XX:+UnlockDiagnosticVMOptions \
|
||||||
-J-XX:+DebugNonSafepoints"
|
-J-XX:+DebugNonSafepoints"
|
||||||
|
|
||||||
|
# Default deletion delay for devenv
|
||||||
|
export PENPOT_DELETION_DELAY="24h"
|
||||||
|
|
||||||
# Setup default upload media file size to 100MiB
|
# Setup default upload media file size to 100MiB
|
||||||
export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
||||||
|
|
||||||
# Setup default multipart upload size to 300MiB
|
# Setup default multipart upload size to 300MiB
|
||||||
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
|
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
|
||||||
|
|
||||||
# Setup HEAP
|
|
||||||
# export OPTIONS="$OPTIONS -J-Xms50m -J-Xmx1024m"
|
|
||||||
# export OPTIONS="$OPTIONS -J-Xms1100m -J-Xmx1100m -J-XX:+AlwaysPreTouch"
|
|
||||||
|
|
||||||
# Increase virtual thread pool size
|
|
||||||
# export OPTIONS="$OPTIONS -J-Djdk.virtualThreadScheduler.parallelism=16"
|
|
||||||
|
|
||||||
# Disable C2 Compiler
|
|
||||||
# export OPTIONS="$OPTIONS -J-XX:TieredStopAtLevel=1"
|
|
||||||
|
|
||||||
# Disable all compilers
|
|
||||||
# export OPTIONS="$OPTIONS -J-Xint"
|
|
||||||
|
|
||||||
# Setup GC
|
|
||||||
# export OPTIONS="$OPTIONS -J-XX:+UseG1GC"
|
|
||||||
|
|
||||||
# Setup GC
|
|
||||||
# export OPTIONS="$OPTIONS -J-XX:+UseZGC"
|
|
||||||
|
|
||||||
# Enable ImageMagick v7.x support
|
# Enable ImageMagick v7.x support
|
||||||
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
|
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
|
||||||
|
|
||||||
|
|
||||||
# Initialize MINIO config
|
# Initialize MINIO config
|
||||||
mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin -q
|
mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin -q
|
||||||
mc admin user add penpot-s3 penpot-devenv penpot-devenv -q
|
mc admin user add penpot-s3 penpot-devenv penpot-devenv -q
|
||||||
|
@ -76,24 +59,8 @@ export PENPOT_ASSETS_STORAGE_BACKEND=assets-s3
|
||||||
export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000
|
export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000
|
||||||
export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot
|
export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot
|
||||||
|
|
||||||
if [ "$1" = "--watch" ]; then
|
entrypoint=${1:-app.main};
|
||||||
trap "exit" INT TERM ERR
|
|
||||||
trap "kill 0" EXIT
|
|
||||||
|
|
||||||
echo "Start Watch..."
|
set -ex
|
||||||
|
|
||||||
clojure $OPTIONS -A:dev -M -m app.main &
|
clojure $OPTIONS -A:dev -M -m $entrypoint;
|
||||||
|
|
||||||
npx nodemon \
|
|
||||||
--watch src \
|
|
||||||
--watch ../common \
|
|
||||||
--ext "clj" \
|
|
||||||
--signal SIGKILL \
|
|
||||||
--exec 'echo "(app.main/stop)\n\r(repl/refresh)\n\r(app.main/start)\n" | nc -N localhost 6062'
|
|
||||||
|
|
||||||
wait;
|
|
||||||
|
|
||||||
else
|
|
||||||
set -x
|
|
||||||
clojure $OPTIONS -A:dev -M -m app.main;
|
|
||||||
fi
|
|
||||||
|
|
|
@ -101,6 +101,8 @@
|
||||||
(s/def ::audit-log-archive-uri ::us/string)
|
(s/def ::audit-log-archive-uri ::us/string)
|
||||||
(s/def ::audit-log-http-handler-concurrency ::us/integer)
|
(s/def ::audit-log-http-handler-concurrency ::us/integer)
|
||||||
|
|
||||||
|
(s/def ::deletion-delay ::dt/duration)
|
||||||
|
|
||||||
(s/def ::admins ::us/set-of-valid-emails)
|
(s/def ::admins ::us/set-of-valid-emails)
|
||||||
(s/def ::file-change-snapshot-every ::us/integer)
|
(s/def ::file-change-snapshot-every ::us/integer)
|
||||||
(s/def ::file-change-snapshot-timeout ::dt/duration)
|
(s/def ::file-change-snapshot-timeout ::dt/duration)
|
||||||
|
@ -214,6 +216,7 @@
|
||||||
(s/keys :opt-un [::secret-key
|
(s/keys :opt-un [::secret-key
|
||||||
::flags
|
::flags
|
||||||
::admins
|
::admins
|
||||||
|
::deletion-delay
|
||||||
::allow-demo-users
|
::allow-demo-users
|
||||||
::audit-log-archive-uri
|
::audit-log-archive-uri
|
||||||
::audit-log-http-handler-concurrency
|
::audit-log-http-handler-concurrency
|
||||||
|
@ -335,7 +338,8 @@
|
||||||
:enable-backend-openapi-doc
|
:enable-backend-openapi-doc
|
||||||
:enable-backend-worker
|
:enable-backend-worker
|
||||||
:enable-secure-session-cookies
|
:enable-secure-session-cookies
|
||||||
:enable-email-verification])
|
:enable-email-verification
|
||||||
|
:enable-v2-migration])
|
||||||
|
|
||||||
(defn- parse-flags
|
(defn- parse-flags
|
||||||
[config]
|
[config]
|
||||||
|
@ -380,7 +384,8 @@
|
||||||
(defonce ^:dynamic flags (parse-flags config))
|
(defonce ^:dynamic flags (parse-flags config))
|
||||||
|
|
||||||
(def deletion-delay
|
(def deletion-delay
|
||||||
(dt/duration {:days 7}))
|
(or (c/get config :deletion-delay)
|
||||||
|
(dt/duration {:days 7})))
|
||||||
|
|
||||||
(defn get
|
(defn get
|
||||||
"A configuration getter. Helps code be more testable."
|
"A configuration getter. Helps code be more testable."
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
[app.storage.tmp :as tmp]
|
[app.storage.tmp :as tmp]
|
||||||
[app.svgo :as svgo]
|
[app.svgo :as svgo]
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.events :as events]
|
|
||||||
[app.util.pointer-map :as pmap]
|
[app.util.pointer-map :as pmap]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[buddy.core.codecs :as bc]
|
[buddy.core.codecs :as bc]
|
||||||
|
@ -1196,9 +1195,6 @@
|
||||||
add-instance-grid
|
add-instance-grid
|
||||||
(fn [fdata frame-id grid assets]
|
(fn [fdata frame-id grid assets]
|
||||||
(reduce (fn [result [component position]]
|
(reduce (fn [result [component position]]
|
||||||
(events/tap :progress {:op :migrate-component
|
|
||||||
:id (:id component)
|
|
||||||
:name (:name component)})
|
|
||||||
(add-main-instance result component frame-id (gpt/add position
|
(add-main-instance result component frame-id (gpt/add position
|
||||||
(gpt/point grid-gap grid-gap))))
|
(gpt/point grid-gap grid-gap))))
|
||||||
fdata
|
fdata
|
||||||
|
@ -1518,9 +1514,6 @@
|
||||||
|
|
||||||
(->> (d/zip media-group grid)
|
(->> (d/zip media-group grid)
|
||||||
(reduce (fn [fdata [mobj position]]
|
(reduce (fn [fdata [mobj position]]
|
||||||
(events/tap :progress {:op :migrate-graphic
|
|
||||||
:id (:id mobj)
|
|
||||||
:name (:name mobj)})
|
|
||||||
(or (process fdata mobj position) fdata))
|
(or (process fdata mobj position) fdata))
|
||||||
(assoc-in fdata [:options :components-v2] true)))))
|
(assoc-in fdata [:options :components-v2] true)))))
|
||||||
|
|
||||||
|
@ -1759,11 +1752,6 @@
|
||||||
(let [file (get-file system file-id)
|
(let [file (get-file system file-id)
|
||||||
file (process-file! system file :validate? validate?)]
|
file (process-file! system file :validate? validate?)]
|
||||||
|
|
||||||
(events/tap :progress
|
|
||||||
{:op :migrate-file
|
|
||||||
:name (:name file)
|
|
||||||
:id (:id file)})
|
|
||||||
|
|
||||||
(persist-file! system file)))))
|
(persist-file! system file)))))
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
|
@ -1791,10 +1779,11 @@
|
||||||
(some-> *team-stats* (swap! update :processed-files (fnil inc 0)))))))))
|
(some-> *team-stats* (swap! update :processed-files (fnil inc 0)))))))))
|
||||||
|
|
||||||
(defn migrate-team!
|
(defn migrate-team!
|
||||||
[system team-id & {:keys [validate? skip-on-graphic-error? label]}]
|
[system team-id & {:keys [validate? rown skip-on-graphic-error? label]}]
|
||||||
|
|
||||||
(l/dbg :hint "migrate:team:start"
|
(l/dbg :hint "migrate:team:start"
|
||||||
:team-id (dm/str team-id))
|
:team-id (dm/str team-id)
|
||||||
|
:rown rown)
|
||||||
|
|
||||||
(let [tpoint (dt/tpoint)
|
(let [tpoint (dt/tpoint)
|
||||||
err (volatile! false)
|
err (volatile! false)
|
||||||
|
@ -1816,11 +1805,6 @@
|
||||||
(conj "layout/grid")
|
(conj "layout/grid")
|
||||||
(conj "styles/v2"))]
|
(conj "styles/v2"))]
|
||||||
|
|
||||||
(events/tap :progress
|
|
||||||
{:op :migrate-team
|
|
||||||
:name (:name team)
|
|
||||||
:id id})
|
|
||||||
|
|
||||||
(run! (partial migrate-file system)
|
(run! (partial migrate-file system)
|
||||||
(get-and-lock-team-files conn id))
|
(get-and-lock-team-files conn id))
|
||||||
|
|
||||||
|
@ -1849,6 +1833,7 @@
|
||||||
|
|
||||||
(l/dbg :hint "migrate:team:end"
|
(l/dbg :hint "migrate:team:end"
|
||||||
:team-id (dm/str team-id)
|
:team-id (dm/str team-id)
|
||||||
|
:rown rown
|
||||||
:files files
|
:files files
|
||||||
:components components
|
:components components
|
||||||
:graphics graphics
|
:graphics graphics
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
(= code :invalid-image)
|
(= code :invalid-image)
|
||||||
(binding [l/*context* (request->context request)]
|
(binding [l/*context* (request->context request)]
|
||||||
(let [cause (or parent-cause err)]
|
(let [cause (or parent-cause err)]
|
||||||
(l/error :hint "unexpected error on processing image" :cause cause)
|
(l/warn :hint "unexpected error on processing image" :cause cause)
|
||||||
{::rres/status 400 ::rres/body data}))
|
{::rres/status 400 ::rres/body data}))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
|
|
|
@ -23,17 +23,20 @@
|
||||||
|
|
||||||
(defn- send-mattermost-notification!
|
(defn- send-mattermost-notification!
|
||||||
[cfg {:keys [id public-uri] :as report}]
|
[cfg {:keys [id public-uri] :as report}]
|
||||||
|
|
||||||
|
|
||||||
(let [text (str "Exception: " public-uri "/dbg/error/" id " "
|
(let [text (str "Exception: " public-uri "/dbg/error/" id " "
|
||||||
(when-let [pid (:profile-id report)]
|
(when-let [pid (:profile-id report)]
|
||||||
(str "(pid: #uuid-" pid ")"))
|
(str "(pid: #uuid-" pid ")"))
|
||||||
"\n"
|
"\n"
|
||||||
"```\n"
|
"- host: #" (:host report) "\n"
|
||||||
"- host: `" (:host report) "`\n"
|
"- tenant: #" (:tenant report) "\n"
|
||||||
"- tenant: `" (:tenant report) "`\n"
|
"- logger: #" (:logger report) "\n"
|
||||||
"- request-path: `" (:request-path report) "`\n"
|
"- request-path: `" (:request-path report) "`\n"
|
||||||
"- frontend-version: `" (:frontend-version report) "`\n"
|
"- frontend-version: `" (:frontend-version report) "`\n"
|
||||||
"- backend-version: `" (:backend-version report) "`\n"
|
"- backend-version: `" (:backend-version report) "`\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
"```\n"
|
||||||
"Trace:\n"
|
"Trace:\n"
|
||||||
(:trace report)
|
(:trace report)
|
||||||
"```")
|
"```")
|
||||||
|
@ -60,6 +63,7 @@
|
||||||
:frontend-version (:version/frontend context)
|
:frontend-version (:version/frontend context)
|
||||||
:profile-id (:request/profile-id context)
|
:profile-id (:request/profile-id context)
|
||||||
:request-path (:request/path context)
|
:request-path (:request/path context)
|
||||||
|
:logger (::l/logger record)
|
||||||
:trace (ex/format-throwable cause :detail? false :header? false)})
|
:trace (ex/format-throwable cause :detail? false :header? false)})
|
||||||
|
|
||||||
(defn handle-event
|
(defn handle-event
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http.client :as http]
|
[app.http.client :as http]
|
||||||
[app.util.json :as json]
|
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[app.worker :as wrk]
|
[app.worker :as wrk]
|
||||||
|
[clojure.data.json :as json]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
@ -86,11 +86,9 @@
|
||||||
(declare interpret-exception)
|
(declare interpret-exception)
|
||||||
(declare interpret-response)
|
(declare interpret-response)
|
||||||
|
|
||||||
(def ^:private json-mapper
|
(def json-write-opts
|
||||||
(json/mapper
|
{:key-fn str/camel
|
||||||
{:encode-key-fn str/camel
|
:indent true})
|
||||||
:decode-key-fn (comp keyword str/kebab)
|
|
||||||
:pretty true}))
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::run-webhook-handler [_]
|
(defmethod ig/pre-init-spec ::run-webhook-handler [_]
|
||||||
(s/keys :req [::http/client ::db/pool]))
|
(s/keys :req [::http/client ::db/pool]))
|
||||||
|
@ -134,15 +132,15 @@
|
||||||
whook (::config props)
|
whook (::config props)
|
||||||
|
|
||||||
body (case (:mtype whook)
|
body (case (:mtype whook)
|
||||||
"application/json" (json/encode-str event json-mapper)
|
"application/json" (json/write-str event json-write-opts)
|
||||||
"application/transit+json" (t/encode-str event)
|
"application/transit+json" (t/encode-str event)
|
||||||
"application/x-www-form-urlencoded" (uri/map->query-string event))]
|
"application/x-www-form-urlencoded" (uri/map->query-string event))]
|
||||||
|
|
||||||
(l/debug :hint "run webhook"
|
(l/dbg :hint "run webhook"
|
||||||
:event-name (:name event)
|
:event-name (:name event)
|
||||||
:webhook-id (:id whook)
|
:webhook-id (:id whook)
|
||||||
:webhook-uri (:uri whook)
|
:webhook-uri (:uri whook)
|
||||||
:webhook-mtype (:mtype whook))
|
:webhook-mtype (:mtype whook))
|
||||||
|
|
||||||
(let [req {:uri (:uri whook)
|
(let [req {:uri (:uri whook)
|
||||||
:headers {"content-type" (:mtype whook)
|
:headers {"content-type" (:mtype whook)
|
||||||
|
@ -160,8 +158,8 @@
|
||||||
(report-delivery! whook req nil err)
|
(report-delivery! whook req nil err)
|
||||||
(update-webhook! whook err)
|
(update-webhook! whook err)
|
||||||
(when (= err "unknown")
|
(when (= err "unknown")
|
||||||
(l/error :hint "unknown error on webhook request"
|
(l/err :hint "unknown error on webhook request"
|
||||||
:cause cause))))))))))
|
:cause cause))))))))))
|
||||||
|
|
||||||
(defn interpret-response
|
(defn interpret-response
|
||||||
[{:keys [status] :as response}]
|
[{:keys [status] :as response}]
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
[app.loggers.webhooks :as-alias webhooks]
|
[app.loggers.webhooks :as-alias webhooks]
|
||||||
[app.metrics :as-alias mtx]
|
[app.metrics :as-alias mtx]
|
||||||
[app.metrics.definition :as-alias mdef]
|
[app.metrics.definition :as-alias mdef]
|
||||||
|
[app.migrations.v2 :as migrations.v2]
|
||||||
[app.msgbus :as-alias mbus]
|
[app.msgbus :as-alias mbus]
|
||||||
[app.redis :as-alias rds]
|
[app.redis :as-alias rds]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
@ -527,6 +528,15 @@
|
||||||
:worker? (contains? cf/flags :backend-worker)
|
:worker? (contains? cf/flags :backend-worker)
|
||||||
:version (:full cf/version)))
|
:version (:full cf/version)))
|
||||||
|
|
||||||
|
(defn start-custom
|
||||||
|
[config]
|
||||||
|
(ig/load-namespaces config)
|
||||||
|
(alter-var-root #'system (fn [sys]
|
||||||
|
(when sys (ig/halt! sys))
|
||||||
|
(-> config
|
||||||
|
(ig/prep)
|
||||||
|
(ig/init)))))
|
||||||
|
|
||||||
(defn stop
|
(defn stop
|
||||||
[]
|
[]
|
||||||
(alter-var-root #'system (fn [sys]
|
(alter-var-root #'system (fn [sys]
|
||||||
|
@ -573,6 +583,11 @@
|
||||||
(nrepl/start-server :bind "0.0.0.0" :port 6064 :handler cider-nrepl-handler))
|
(nrepl/start-server :bind "0.0.0.0" :port 6064 :handler cider-nrepl-handler))
|
||||||
|
|
||||||
(start)
|
(start)
|
||||||
|
|
||||||
|
(when (contains? cf/flags :v2-migration)
|
||||||
|
(px/sleep 5000)
|
||||||
|
(migrations.v2/migrate app.main/system))
|
||||||
|
|
||||||
(deref p))
|
(deref p))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(binding [*out* *err*]
|
(binding [*out* *err*]
|
||||||
|
|
104
backend/src/app/migrations/v2.clj
Normal file
104
backend/src/app/migrations/v2.clj
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.migrations.v2
|
||||||
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.features.components-v2 :as feat]
|
||||||
|
[app.setup :as setup]
|
||||||
|
[app.util.time :as dt]))
|
||||||
|
|
||||||
|
(def ^:private sql:get-teams
|
||||||
|
"SELECT id, features,
|
||||||
|
row_number() OVER (ORDER BY created_at DESC) AS rown
|
||||||
|
FROM team
|
||||||
|
WHERE deleted_at IS NULL
|
||||||
|
AND (features <@ '{components/v2}' OR features IS NULL)
|
||||||
|
ORDER BY created_at DESC")
|
||||||
|
|
||||||
|
(defn- get-teams
|
||||||
|
[conn]
|
||||||
|
(->> (db/cursor conn [sql:get-teams] {:chunk-size 1})
|
||||||
|
(map feat/decode-row)))
|
||||||
|
|
||||||
|
(defn- migrate-teams
|
||||||
|
[{:keys [::db/conn] :as system}]
|
||||||
|
;; Allow long running transaction for this connection
|
||||||
|
(db/exec-one! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
|
||||||
|
|
||||||
|
;; Do not allow other migration running in the same time
|
||||||
|
(db/xact-lock! conn 0)
|
||||||
|
|
||||||
|
;; Run teams migration
|
||||||
|
(run! (fn [{:keys [id rown]}]
|
||||||
|
(try
|
||||||
|
(-> (assoc system ::db/rollback true)
|
||||||
|
(feat/migrate-team! id
|
||||||
|
:rown rown
|
||||||
|
:label "v2-migration"
|
||||||
|
:validate? false
|
||||||
|
:skip-on-graphics-error? true))
|
||||||
|
(catch Throwable _
|
||||||
|
(swap! feat/*stats* update :errors (fnil inc 0))
|
||||||
|
(l/wrn :hint "error on migrating team (skiping)"))))
|
||||||
|
(get-teams conn))
|
||||||
|
|
||||||
|
(setup/set-prop! system :v2-migrated true))
|
||||||
|
|
||||||
|
(defn migrate
|
||||||
|
[system]
|
||||||
|
(let [tpoint (dt/tpoint)
|
||||||
|
stats (atom {})
|
||||||
|
migrated? (setup/get-prop system :v2-migrated false)]
|
||||||
|
|
||||||
|
(when-not migrated?
|
||||||
|
(l/inf :hint "v2 migration started"
|
||||||
|
:files (:processed-files stats))
|
||||||
|
(try
|
||||||
|
(binding [feat/*stats* stats]
|
||||||
|
(db/tx-run! system migrate-teams))
|
||||||
|
|
||||||
|
(let [stats (deref stats)
|
||||||
|
elapsed (dt/format-duration (tpoint))]
|
||||||
|
(l/inf :hint "v2 migration finished"
|
||||||
|
:files (:processed-files stats)
|
||||||
|
:teams (:processed-teams stats)
|
||||||
|
:errors (:errors stats)
|
||||||
|
:elapsed elapsed))
|
||||||
|
|
||||||
|
(catch Throwable cause
|
||||||
|
(l/err :hint "error on aplying v2 migration" :cause cause))))))
|
||||||
|
|
||||||
|
(def ^:private required-services
|
||||||
|
[[:app.main/assets :app.storage.s3/backend]
|
||||||
|
[:app.main/assets :app.storage.fs/backend]
|
||||||
|
:app.storage/storage
|
||||||
|
:app.db/pool
|
||||||
|
:app.setup/props
|
||||||
|
:app.svgo/optimizer
|
||||||
|
:app.metrics/metrics
|
||||||
|
:app.migrations/migrations
|
||||||
|
:app.http.client/client])
|
||||||
|
|
||||||
|
(defn -main
|
||||||
|
[& _args]
|
||||||
|
(try
|
||||||
|
(let [config-var (requiring-resolve 'app.main/system-config)
|
||||||
|
start-var (requiring-resolve 'app.main/start-custom)
|
||||||
|
stop-var (requiring-resolve 'app.main/stop)
|
||||||
|
system-var (requiring-resolve 'app.main/system)
|
||||||
|
config (select-keys @config-var required-services)]
|
||||||
|
|
||||||
|
(start-var config)
|
||||||
|
(migrate @system-var)
|
||||||
|
(stop-var)
|
||||||
|
(System/exit 0))
|
||||||
|
(catch Throwable cause
|
||||||
|
(ex/print-throwable cause)
|
||||||
|
(flush)
|
||||||
|
(System/exit -1))))
|
|
@ -7,6 +7,7 @@
|
||||||
(ns app.setup
|
(ns app.setup
|
||||||
"Initial data setup of instance."
|
"Initial data setup of instance."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
(bc/bytes->b64u)
|
(bc/bytes->b64u)
|
||||||
(bc/bytes->str)))
|
(bc/bytes->str)))
|
||||||
|
|
||||||
(defn- retrieve-all
|
(defn- get-all-props
|
||||||
[conn]
|
[conn]
|
||||||
(->> (db/query conn :server-prop {:preload true})
|
(->> (db/query conn :server-prop {:preload true})
|
||||||
(filter #(not= "secret-key" (:id %)))
|
(filter #(not= "secret-key" (:id %)))
|
||||||
|
@ -50,6 +51,28 @@
|
||||||
:cause cause))))
|
:cause cause))))
|
||||||
instance-id)))
|
instance-id)))
|
||||||
|
|
||||||
|
|
||||||
|
(def sql:add-prop
|
||||||
|
"INSERT INTO server_prop (id, content, preload)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (id)
|
||||||
|
DO UPDATE SET content=?, preload=?")
|
||||||
|
|
||||||
|
(defn get-prop
|
||||||
|
([system prop] (get-prop system prop nil))
|
||||||
|
([system prop default]
|
||||||
|
(let [prop (d/name prop)]
|
||||||
|
(db/run! system (fn [{:keys [::db/conn]}]
|
||||||
|
(or (db/get* conn :server-prop {:id prop})
|
||||||
|
default))))))
|
||||||
|
|
||||||
|
(defn set-prop!
|
||||||
|
[system prop value]
|
||||||
|
(let [value (db/tjson value)
|
||||||
|
prop (d/name prop)]
|
||||||
|
(db/run! system (fn [{:keys [::db/conn]}]
|
||||||
|
(db/exec-one! conn [sql:add-prop prop value false value false])))))
|
||||||
|
|
||||||
(s/def ::key ::us/string)
|
(s/def ::key ::us/string)
|
||||||
(s/def ::props (s/map-of ::us/keyword some?))
|
(s/def ::props (s/map-of ::us/keyword some?))
|
||||||
|
|
||||||
|
@ -67,7 +90,7 @@
|
||||||
"PENPOT_SECRET_KEY environment variable")))
|
"PENPOT_SECRET_KEY environment variable")))
|
||||||
|
|
||||||
(let [secret (or key (generate-random-key))]
|
(let [secret (or key (generate-random-key))]
|
||||||
(-> (retrieve-all conn)
|
(-> (get-all-props conn)
|
||||||
(assoc :secret-key secret)
|
(assoc :secret-key secret)
|
||||||
(assoc :tokens-key (keys/derive secret :salt "tokens"))
|
(assoc :tokens-key (keys/derive secret :salt "tokens"))
|
||||||
(update :instance-id handle-instance-id conn (db/read-only? pool))))))
|
(update :instance-id handle-instance-id conn (db/read-only? pool))))))
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.main :as main]
|
|
||||||
[app.rpc.commands.auth :as cmd.auth]
|
[app.rpc.commands.auth :as cmd.auth]
|
||||||
[app.srepl.components-v2 :refer [migrate-teams!]]
|
|
||||||
[app.util.events :as events]
|
|
||||||
[app.util.json :as json]
|
[app.util.json :as json]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -105,39 +102,6 @@
|
||||||
[{:keys [password]}]
|
[{:keys [password]}]
|
||||||
(auth/derive-password password))
|
(auth/derive-password password))
|
||||||
|
|
||||||
(defmethod exec-command :migrate-v2
|
|
||||||
[_]
|
|
||||||
(letfn [(on-progress-report [{:keys [elapsed completed errors]}]
|
|
||||||
(println (str/ffmt "-> Progress: completed: %, errors: %, elapsed: %"
|
|
||||||
completed errors elapsed)))
|
|
||||||
|
|
||||||
(on-progress [{:keys [op name]}]
|
|
||||||
(case op
|
|
||||||
:migrate-team
|
|
||||||
(println (str/ffmt "-> Migrating team: \"%\"" name))
|
|
||||||
:migrate-file
|
|
||||||
(println (str/ffmt "=> Migrating file: \"%\"" name))
|
|
||||||
nil))
|
|
||||||
|
|
||||||
(on-event [[type payload]]
|
|
||||||
(case type
|
|
||||||
:progress-report (on-progress-report payload)
|
|
||||||
:progress (on-progress payload)
|
|
||||||
:error (on-error payload)
|
|
||||||
nil))
|
|
||||||
|
|
||||||
(on-error [cause]
|
|
||||||
(println "EE:" (ex-message cause)))]
|
|
||||||
|
|
||||||
(println "The components/v2 migration started...")
|
|
||||||
|
|
||||||
(try
|
|
||||||
(let [result (-> (partial migrate-teams! main/system {:rollback? true})
|
|
||||||
(events/run-with! on-event))]
|
|
||||||
(println (str/ffmt "Migration process finished (elapsed: %)" (:elapsed result))))
|
|
||||||
(catch Throwable cause
|
|
||||||
(on-error cause)))))
|
|
||||||
|
|
||||||
(defmethod exec-command :default
|
(defmethod exec-command :default
|
||||||
[{:keys [::cmd]}]
|
[{:keys [::cmd]}]
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
|
|
|
@ -6,18 +6,15 @@
|
||||||
|
|
||||||
(ns app.srepl.components-v2
|
(ns app.srepl.components-v2
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.fressian :as fres]
|
[app.common.fressian :as fres]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.features.components-v2 :as feat]
|
[app.features.components-v2 :as feat]
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.srepl.helpers :as h]
|
[app.srepl.helpers :as h]
|
||||||
[app.svgo :as svgo]
|
|
||||||
[app.util.events :as events]
|
[app.util.events :as events]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[app.worker :as-alias wrk]
|
[app.worker :as-alias wrk]
|
||||||
[cuerdas.core :as str]
|
|
||||||
[datoteka.fs :as fs]
|
[datoteka.fs :as fs]
|
||||||
[datoteka.io :as io]
|
[datoteka.io :as io]
|
||||||
[promesa.exec :as px]
|
[promesa.exec :as px]
|
||||||
|
@ -31,86 +28,6 @@
|
||||||
;; PRIVATE HELPERS
|
;; PRIVATE HELPERS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn- report-progress-files
|
|
||||||
[tpoint]
|
|
||||||
(fn [_ _ oldv newv]
|
|
||||||
(when (or (not= (:processed-files oldv)
|
|
||||||
(:processed-files newv))
|
|
||||||
(not= (:errors oldv)
|
|
||||||
(:errors newv)))
|
|
||||||
(let [completed (:processed-files newv 0)
|
|
||||||
errors (:errors newv 0)
|
|
||||||
elapsed (dt/format-duration (tpoint))]
|
|
||||||
(events/tap :progress-report
|
|
||||||
{:elapsed elapsed
|
|
||||||
:completed completed
|
|
||||||
:errors errors})
|
|
||||||
(l/dbg :hint "progress"
|
|
||||||
:completed completed
|
|
||||||
:elapsed elapsed)))))
|
|
||||||
|
|
||||||
(defn- report-progress-teams
|
|
||||||
[tpoint]
|
|
||||||
(fn [_ _ oldv newv]
|
|
||||||
(when (or (not= (:processed-teams oldv)
|
|
||||||
(:processed-teams newv))
|
|
||||||
(not= (:errors oldv)
|
|
||||||
(:errors newv)))
|
|
||||||
(let [completed (:processed-teams newv 0)
|
|
||||||
errors (:errors newv 0)
|
|
||||||
elapsed (dt/format-duration (tpoint))]
|
|
||||||
(events/tap :progress-report
|
|
||||||
{:elapsed elapsed
|
|
||||||
:completed completed
|
|
||||||
:errors errors})
|
|
||||||
(l/dbg :hint "progress"
|
|
||||||
:completed completed
|
|
||||||
:elapsed elapsed)))))
|
|
||||||
|
|
||||||
(def ^:private sql:get-teams-by-created-at
|
|
||||||
"WITH teams AS (
|
|
||||||
SELECT id, features,
|
|
||||||
row_number() OVER (ORDER BY created_at) AS rown
|
|
||||||
FROM team
|
|
||||||
WHERE deleted_at IS NULL
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
) SELECT * FROM TEAMS %(pred)s")
|
|
||||||
|
|
||||||
(def ^:private sql:get-teams-by-graphics
|
|
||||||
"WITH teams AS (
|
|
||||||
SELECT t.id, t.features,
|
|
||||||
row_number() OVER (ORDER BY t.created_at) AS rown,
|
|
||||||
(SELECT count(*)
|
|
||||||
FROM file_media_object AS fmo
|
|
||||||
JOIN file AS f ON (f.id = fmo.file_id)
|
|
||||||
JOIN project AS p ON (p.id = f.project_id)
|
|
||||||
WHERE p.team_id = t.id
|
|
||||||
AND fmo.mtype = 'image/svg+xml'
|
|
||||||
AND fmo.is_local = false) AS graphics
|
|
||||||
FROM team AS t
|
|
||||||
WHERE t.deleted_at IS NULL
|
|
||||||
ORDER BY 3 ASC
|
|
||||||
)
|
|
||||||
SELECT * FROM teams %(pred)s")
|
|
||||||
|
|
||||||
(def ^:private sql:get-teams-by-activity
|
|
||||||
"WITH teams AS (
|
|
||||||
SELECT t.id, t.features,
|
|
||||||
row_number() OVER (ORDER BY t.created_at) AS rown,
|
|
||||||
(SELECT coalesce(max(date_trunc('month', f.modified_at)), date_trunc('month', t.modified_at))
|
|
||||||
FROM file AS f
|
|
||||||
JOIN project AS p ON (f.project_id = p.id)
|
|
||||||
WHERE p.team_id = t.id) AS updated_at,
|
|
||||||
(SELECT coalesce(count(*), 0)
|
|
||||||
FROM file AS f
|
|
||||||
JOIN project AS p ON (f.project_id = p.id)
|
|
||||||
WHERE p.team_id = t.id) AS total_files
|
|
||||||
FROM team AS t
|
|
||||||
WHERE t.deleted_at IS NULL
|
|
||||||
ORDER BY 3 DESC, 4 DESC
|
|
||||||
)
|
|
||||||
SELECT * FROM teams %(pred)s")
|
|
||||||
|
|
||||||
(def ^:private sql:get-files-by-created-at
|
(def ^:private sql:get-files-by-created-at
|
||||||
"SELECT id, features,
|
"SELECT id, features,
|
||||||
row_number() OVER (ORDER BY created_at DESC) AS rown
|
row_number() OVER (ORDER BY created_at DESC) AS rown
|
||||||
|
@ -118,87 +35,12 @@
|
||||||
WHERE deleted_at IS NULL
|
WHERE deleted_at IS NULL
|
||||||
ORDER BY created_at DESC")
|
ORDER BY created_at DESC")
|
||||||
|
|
||||||
(def ^:private sql:get-files-by-modified-at
|
|
||||||
"SELECT id, features
|
|
||||||
row_number() OVER (ORDER BY modified_at DESC) AS rown
|
|
||||||
FROM file
|
|
||||||
WHERE deleted_at IS NULL
|
|
||||||
ORDER BY modified_at DESC")
|
|
||||||
|
|
||||||
(def ^:private sql:get-files-by-graphics
|
|
||||||
"WITH files AS (
|
|
||||||
SELECT f.id, f.features,
|
|
||||||
row_number() OVER (ORDER BY modified_at) AS rown,
|
|
||||||
(SELECT count(*) FROM file_media_object AS fmo
|
|
||||||
WHERE fmo.mtype = 'image/svg+xml'
|
|
||||||
AND fmo.is_local = false
|
|
||||||
AND fmo.file_id = f.id) AS graphics
|
|
||||||
FROM file AS f
|
|
||||||
WHERE f.deleted_at IS NULL
|
|
||||||
ORDER BY 3 ASC
|
|
||||||
) SELECT * FROM files %(pred)s")
|
|
||||||
|
|
||||||
(defn- read-pred
|
|
||||||
[entries]
|
|
||||||
(let [entries (if (and (vector? entries)
|
|
||||||
(keyword? (first entries)))
|
|
||||||
[entries]
|
|
||||||
entries)]
|
|
||||||
(loop [params []
|
|
||||||
queries []
|
|
||||||
entries (seq entries)]
|
|
||||||
(if-let [[op val field] (first entries)]
|
|
||||||
(let [field (name field)
|
|
||||||
cond (case op
|
|
||||||
:lt (str/ffmt "% < ?" field)
|
|
||||||
:lte (str/ffmt "% <= ?" field)
|
|
||||||
:gt (str/ffmt "% > ?" field)
|
|
||||||
:gte (str/ffmt "% >= ?" field)
|
|
||||||
:eq (str/ffmt "% = ?" field))]
|
|
||||||
(recur (conj params val)
|
|
||||||
(conj queries cond)
|
|
||||||
(rest entries)))
|
|
||||||
|
|
||||||
(let [sql (apply str "WHERE " (str/join " AND " queries))]
|
|
||||||
(apply vector sql params))))))
|
|
||||||
|
|
||||||
(defn- get-teams
|
|
||||||
[conn query pred]
|
|
||||||
(let [query (d/nilv query :created-at)
|
|
||||||
sql (case query
|
|
||||||
:created-at sql:get-teams-by-created-at
|
|
||||||
:activity sql:get-teams-by-activity
|
|
||||||
:graphics sql:get-teams-by-graphics)
|
|
||||||
sql (if pred
|
|
||||||
(let [[pred-sql & pred-params] (read-pred pred)]
|
|
||||||
(apply vector
|
|
||||||
(str/format sql {:pred pred-sql})
|
|
||||||
pred-params))
|
|
||||||
[(str/format sql {:pred ""})])]
|
|
||||||
|
|
||||||
(->> (db/cursor conn sql {:chunk-size 500})
|
|
||||||
(map feat/decode-row)
|
|
||||||
(remove (fn [{:keys [features]}]
|
|
||||||
(contains? features "components/v2"))))))
|
|
||||||
|
|
||||||
(defn- get-files
|
(defn- get-files
|
||||||
[conn query pred]
|
[conn]
|
||||||
(let [query (d/nilv query :created-at)
|
(->> (db/cursor conn [sql:get-files-by-created-at] {:chunk-size 500})
|
||||||
sql (case query
|
(map feat/decode-row)
|
||||||
:created-at sql:get-files-by-created-at
|
(remove (fn [{:keys [features]}]
|
||||||
:modified-at sql:get-files-by-modified-at
|
(contains? features "components/v2")))))
|
||||||
:graphics sql:get-files-by-graphics)
|
|
||||||
sql (if pred
|
|
||||||
(let [[pred-sql & pred-params] (read-pred pred)]
|
|
||||||
(apply vector
|
|
||||||
(str/format sql {:pred pred-sql})
|
|
||||||
pred-params))
|
|
||||||
[(str/format sql {:pred ""})])]
|
|
||||||
|
|
||||||
(->> (db/cursor conn sql {:chunk-size 500})
|
|
||||||
(map feat/decode-row)
|
|
||||||
(remove (fn [{:keys [features]}]
|
|
||||||
(contains? features "components/v2"))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; PUBLIC API
|
;; PUBLIC API
|
||||||
|
@ -244,8 +86,6 @@
|
||||||
stats (atom {})
|
stats (atom {})
|
||||||
tpoint (dt/tpoint)]
|
tpoint (dt/tpoint)]
|
||||||
|
|
||||||
(add-watch stats :progress-report (report-progress-files tpoint))
|
|
||||||
|
|
||||||
(binding [feat/*stats* stats
|
(binding [feat/*stats* stats
|
||||||
feat/*cache* cache]
|
feat/*cache* cache]
|
||||||
(try
|
(try
|
||||||
|
@ -265,127 +105,6 @@
|
||||||
(let [elapsed (dt/format-duration (tpoint))]
|
(let [elapsed (dt/format-duration (tpoint))]
|
||||||
(l/dbg :hint "migrate:end" :rollback rollback? :elapsed elapsed)))))))
|
(l/dbg :hint "migrate:end" :rollback rollback? :elapsed elapsed)))))))
|
||||||
|
|
||||||
(defn migrate-teams!
|
|
||||||
"A REPL helper for migrate all teams.
|
|
||||||
|
|
||||||
This function starts multiple concurrent team migration processes
|
|
||||||
until the maximum number of jobs is reached which by default has the
|
|
||||||
value of `1`. This is controled with the `:max-jobs` option.
|
|
||||||
|
|
||||||
If you want to run this on multiple machines you will need to specify
|
|
||||||
the total number of partitions and the current partition.
|
|
||||||
|
|
||||||
In order to get the report table populated, you will need to provide
|
|
||||||
a correct `:label`. That label is also used for persist a file
|
|
||||||
snaphot before continue with the migration."
|
|
||||||
[& {:keys [max-jobs max-items max-time rollback? validate? query
|
|
||||||
pred max-procs cache skip-on-graphic-error?
|
|
||||||
label partitions current-partition]
|
|
||||||
:or {validate? false
|
|
||||||
rollback? true
|
|
||||||
max-jobs 1
|
|
||||||
current-partition 1
|
|
||||||
skip-on-graphic-error? true
|
|
||||||
max-items Long/MAX_VALUE}}]
|
|
||||||
|
|
||||||
(when (int? partitions)
|
|
||||||
(when-not (int? current-partition)
|
|
||||||
(throw (IllegalArgumentException. "missing `current-partition` parameter")))
|
|
||||||
(when-not (<= 0 current-partition partitions)
|
|
||||||
(throw (IllegalArgumentException. "invalid value on `current-partition` parameter"))))
|
|
||||||
|
|
||||||
(let [stats (atom {})
|
|
||||||
tpoint (dt/tpoint)
|
|
||||||
mtime (some-> max-time dt/duration)
|
|
||||||
|
|
||||||
factory (px/thread-factory :virtual false :prefix "penpot/migration/")
|
|
||||||
executor (px/cached-executor :factory factory)
|
|
||||||
|
|
||||||
max-procs (or max-procs max-jobs)
|
|
||||||
sjobs (ps/create :permits max-jobs)
|
|
||||||
sprocs (ps/create :permits max-procs)
|
|
||||||
|
|
||||||
migrate-team
|
|
||||||
(fn [team-id]
|
|
||||||
(try
|
|
||||||
(db/tx-run! (assoc main/system ::db/rollback rollback?)
|
|
||||||
(fn [system]
|
|
||||||
(db/exec-one! system ["SET LOCAL idle_in_transaction_session_timeout = 0"])
|
|
||||||
(feat/migrate-team! system team-id
|
|
||||||
:label label
|
|
||||||
:validate? validate?
|
|
||||||
:skip-on-graphic-error? skip-on-graphic-error?)))
|
|
||||||
|
|
||||||
(catch Throwable cause
|
|
||||||
(l/wrn :hint "unexpected error on processing team (skiping)"
|
|
||||||
:team-id (str team-id))
|
|
||||||
|
|
||||||
(events/tap :error
|
|
||||||
(ex-info "unexpected error on processing team (skiping)"
|
|
||||||
{:team-id team-id}
|
|
||||||
cause))
|
|
||||||
|
|
||||||
(swap! stats update :errors (fnil inc 0)))
|
|
||||||
|
|
||||||
(finally
|
|
||||||
(ps/release! sjobs))))
|
|
||||||
|
|
||||||
process-team
|
|
||||||
(fn [team-id]
|
|
||||||
(ps/acquire! sjobs)
|
|
||||||
(let [ts (tpoint)]
|
|
||||||
(if (and mtime (neg? (compare mtime ts)))
|
|
||||||
(do
|
|
||||||
(l/inf :hint "max time constraint reached"
|
|
||||||
:team-id (str team-id)
|
|
||||||
:elapsed (dt/format-duration ts))
|
|
||||||
(ps/release! sjobs)
|
|
||||||
(reduced nil))
|
|
||||||
|
|
||||||
(px/run! executor (partial migrate-team team-id)))))]
|
|
||||||
|
|
||||||
(l/dbg :hint "migrate:start"
|
|
||||||
:label label
|
|
||||||
:rollback rollback?
|
|
||||||
:max-jobs max-jobs
|
|
||||||
:max-items max-items)
|
|
||||||
|
|
||||||
(add-watch stats :progress-report (report-progress-teams tpoint))
|
|
||||||
|
|
||||||
(binding [feat/*stats* stats
|
|
||||||
feat/*cache* cache
|
|
||||||
svgo/*semaphore* sprocs]
|
|
||||||
(try
|
|
||||||
(db/tx-run! main/system
|
|
||||||
(fn [{:keys [::db/conn] :as system}]
|
|
||||||
(db/exec! conn ["SET LOCAL statement_timeout = 0"])
|
|
||||||
(db/exec! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
|
|
||||||
|
|
||||||
(run! process-team
|
|
||||||
(->> (get-teams conn query pred)
|
|
||||||
(filter (fn [{:keys [rown]}]
|
|
||||||
(if (int? partitions)
|
|
||||||
(= current-partition (inc (mod rown partitions)))
|
|
||||||
true)))
|
|
||||||
(map :id)
|
|
||||||
(take max-items)))
|
|
||||||
|
|
||||||
;; Close and await tasks
|
|
||||||
(pu/close! executor)))
|
|
||||||
|
|
||||||
(-> (deref stats)
|
|
||||||
(assoc :elapsed (dt/format-duration (tpoint))))
|
|
||||||
|
|
||||||
(catch Throwable cause
|
|
||||||
(l/dbg :hint "migrate:error" :cause cause)
|
|
||||||
(events/tap :error cause))
|
|
||||||
|
|
||||||
(finally
|
|
||||||
(let [elapsed (dt/format-duration (tpoint))]
|
|
||||||
(l/dbg :hint "migrate:end"
|
|
||||||
:rollback rollback?
|
|
||||||
:elapsed elapsed)))))))
|
|
||||||
|
|
||||||
(defn migrate-files!
|
(defn migrate-files!
|
||||||
"A REPL helper for migrate all files.
|
"A REPL helper for migrate all files.
|
||||||
|
|
||||||
|
@ -399,8 +118,8 @@
|
||||||
In order to get the report table populated, you will need to provide
|
In order to get the report table populated, you will need to provide
|
||||||
a correct `:label`. That label is also used for persist a file
|
a correct `:label`. That label is also used for persist a file
|
||||||
snaphot before continue with the migration."
|
snaphot before continue with the migration."
|
||||||
[& {:keys [max-jobs max-items max-time rollback? validate? query
|
[& {:keys [max-jobs max-items rollback? validate?
|
||||||
pred max-procs cache skip-on-graphic-error?
|
cache skip-on-graphic-error?
|
||||||
label partitions current-partition]
|
label partitions current-partition]
|
||||||
:or {validate? false
|
:or {validate? false
|
||||||
rollback? true
|
rollback? true
|
||||||
|
@ -417,14 +136,10 @@
|
||||||
|
|
||||||
(let [stats (atom {})
|
(let [stats (atom {})
|
||||||
tpoint (dt/tpoint)
|
tpoint (dt/tpoint)
|
||||||
mtime (some-> max-time dt/duration)
|
|
||||||
|
|
||||||
factory (px/thread-factory :virtual false :prefix "penpot/migration/")
|
factory (px/thread-factory :virtual false :prefix "penpot/migration/")
|
||||||
executor (px/cached-executor :factory factory)
|
executor (px/cached-executor :factory factory)
|
||||||
|
|
||||||
max-procs (or max-procs max-jobs)
|
|
||||||
sjobs (ps/create :permits max-jobs)
|
sjobs (ps/create :permits max-jobs)
|
||||||
sprocs (ps/create :permits max-procs)
|
|
||||||
|
|
||||||
migrate-file
|
migrate-file
|
||||||
(fn [file-id rown]
|
(fn [file-id rown]
|
||||||
|
@ -455,16 +170,7 @@
|
||||||
process-file
|
process-file
|
||||||
(fn [{:keys [id rown]}]
|
(fn [{:keys [id rown]}]
|
||||||
(ps/acquire! sjobs)
|
(ps/acquire! sjobs)
|
||||||
(let [ts (tpoint)]
|
(px/run! executor (partial migrate-file id rown)))]
|
||||||
(if (and mtime (neg? (compare mtime ts)))
|
|
||||||
(do
|
|
||||||
(l/inf :hint "max time constraint reached"
|
|
||||||
:file-id (str id)
|
|
||||||
:elapsed (dt/format-duration ts))
|
|
||||||
(ps/release! sjobs)
|
|
||||||
(reduced nil))
|
|
||||||
|
|
||||||
(px/run! executor (partial migrate-file id rown)))))]
|
|
||||||
|
|
||||||
(l/dbg :hint "migrate:start"
|
(l/dbg :hint "migrate:start"
|
||||||
:label label
|
:label label
|
||||||
|
@ -472,11 +178,8 @@
|
||||||
:max-jobs max-jobs
|
:max-jobs max-jobs
|
||||||
:max-items max-items)
|
:max-items max-items)
|
||||||
|
|
||||||
(add-watch stats :progress-report (report-progress-files tpoint))
|
|
||||||
|
|
||||||
(binding [feat/*stats* stats
|
(binding [feat/*stats* stats
|
||||||
feat/*cache* cache
|
feat/*cache* cache]
|
||||||
svgo/*semaphore* sprocs]
|
|
||||||
(try
|
(try
|
||||||
(db/tx-run! main/system
|
(db/tx-run! main/system
|
||||||
(fn [{:keys [::db/conn] :as system}]
|
(fn [{:keys [::db/conn] :as system}]
|
||||||
|
@ -484,7 +187,7 @@
|
||||||
(db/exec! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
|
(db/exec! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
|
||||||
|
|
||||||
(run! process-file
|
(run! process-file
|
||||||
(->> (get-files conn query pred)
|
(->> (get-files conn)
|
||||||
(filter (fn [{:keys [rown] :as row}]
|
(filter (fn [{:keys [rown] :as row}]
|
||||||
(if (int? partitions)
|
(if (int? partitions)
|
||||||
(= current-partition (inc (mod rown partitions)))
|
(= current-partition (inc (mod rown partitions)))
|
||||||
|
@ -601,17 +304,3 @@
|
||||||
(let [elapsed (dt/format-duration (tpoint))]
|
(let [elapsed (dt/format-duration (tpoint))]
|
||||||
(l/dbg :hint "populate:end"
|
(l/dbg :hint "populate:end"
|
||||||
:elapsed elapsed))))))
|
:elapsed elapsed))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; FILE PROCESS HELPERS
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn delete-broken-files
|
|
||||||
[{:keys [id data] :as file}]
|
|
||||||
(if (-> data :options :components-v2 true?)
|
|
||||||
(do
|
|
||||||
(l/wrn :hint "found old components-v2 format"
|
|
||||||
:file-id (str id)
|
|
||||||
:file-name (:name file))
|
|
||||||
(assoc file :deleted-at (dt/now)))
|
|
||||||
file))
|
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
FROM file AS f
|
FROM file AS f
|
||||||
WHERE f.has_media_trimmed IS false
|
WHERE f.has_media_trimmed IS false
|
||||||
AND f.modified_at < now() - ?::interval
|
AND f.modified_at < now() - ?::interval
|
||||||
|
AND f.deleted_at IS NULL
|
||||||
ORDER BY f.modified_at DESC
|
ORDER BY f.modified_at DESC
|
||||||
FOR UPDATE
|
FOR UPDATE
|
||||||
SKIP LOCKED")
|
SKIP LOCKED")
|
||||||
|
|
|
@ -210,6 +210,9 @@
|
||||||
:project-id (str project-id)
|
:project-id (str project-id)
|
||||||
:deleted-at (dt/format-instant deleted-at))
|
:deleted-at (dt/format-instant deleted-at))
|
||||||
|
|
||||||
|
;; NOTE: fragments not handled here because they have
|
||||||
|
;; cascade.
|
||||||
|
|
||||||
;; And finally, permanently delete the file.
|
;; And finally, permanently delete the file.
|
||||||
(db/delete! conn :file {:id id})
|
(db/delete! conn :file {:id id})
|
||||||
|
|
||||||
|
@ -230,7 +233,6 @@
|
||||||
(inc total))
|
(inc total))
|
||||||
0)))
|
0)))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private sql:get-file-thumbnails
|
(def ^:private sql:get-file-thumbnails
|
||||||
"SELECT file_id, revn, media_id, deleted_at
|
"SELECT file_id, revn, media_id, deleted_at
|
||||||
FROM file_thumbnail
|
FROM file_thumbnail
|
||||||
|
|
|
@ -612,7 +612,7 @@
|
||||||
(t/is (fn? result))
|
(t/is (fn? result))
|
||||||
|
|
||||||
(let [events (th/consume-sse result)]
|
(let [events (th/consume-sse result)]
|
||||||
(t/is (= 6 (count events)))
|
(t/is (= 5 (count events)))
|
||||||
(t/is (= :end (first (last events))))))))
|
(t/is (= :end (first (last events))))))))
|
||||||
|
|
||||||
(t/deftest get-list-of-buitin-templates
|
(t/deftest get-list-of-buitin-templates
|
||||||
|
|
|
@ -50,12 +50,8 @@
|
||||||
"styles/v2"
|
"styles/v2"
|
||||||
"layout/grid"})
|
"layout/grid"})
|
||||||
|
|
||||||
;; A set of features enabled by default for each file, they are
|
;; A set of features enabled by default
|
||||||
;; implicit and are enabled by default and can't be disabled. The
|
(def default-features
|
||||||
;; features listed in this set are mainly freatures addedby file
|
|
||||||
;; migrations process, so all features referenced in migrations should
|
|
||||||
;; be here.
|
|
||||||
(def default-enabled-features
|
|
||||||
#{"fdata/shape-data-type"
|
#{"fdata/shape-data-type"
|
||||||
"styles/v2"
|
"styles/v2"
|
||||||
"layout/grid"
|
"layout/grid"
|
||||||
|
@ -81,7 +77,8 @@
|
||||||
(def no-migration-features
|
(def no-migration-features
|
||||||
(-> #{"fdata/objects-map"
|
(-> #{"fdata/objects-map"
|
||||||
"fdata/pointer-map"
|
"fdata/pointer-map"
|
||||||
"layout/grid"}
|
"layout/grid"
|
||||||
|
"fdata/shape-data-type"}
|
||||||
(into frontend-only-features)))
|
(into frontend-only-features)))
|
||||||
|
|
||||||
(sm/def! ::features
|
(sm/def! ::features
|
||||||
|
@ -132,7 +129,7 @@
|
||||||
(defn get-enabled-features
|
(defn get-enabled-features
|
||||||
"Get the globally enabled fratures set."
|
"Get the globally enabled fratures set."
|
||||||
[flags]
|
[flags]
|
||||||
(into default-enabled-features xf-flag-to-feature flags))
|
(into default-features xf-flag-to-feature flags))
|
||||||
|
|
||||||
(defn get-team-enabled-features
|
(defn get-team-enabled-features
|
||||||
"Get the team enabled features.
|
"Get the team enabled features.
|
||||||
|
@ -144,7 +141,6 @@
|
||||||
team-features (into #{} xf-remove-ephimeral (:features team))]
|
team-features (into #{} xf-remove-ephimeral (:features team))]
|
||||||
(-> enabled-features
|
(-> enabled-features
|
||||||
(set/intersection no-migration-features)
|
(set/intersection no-migration-features)
|
||||||
(set/union default-enabled-features)
|
|
||||||
(set/union team-features))))
|
(set/union team-features))))
|
||||||
|
|
||||||
(defn check-client-features!
|
(defn check-client-features!
|
||||||
|
@ -247,7 +243,7 @@
|
||||||
(let [not-supported (-> (or source-features #{})
|
(let [not-supported (-> (or source-features #{})
|
||||||
(set/difference destination-features)
|
(set/difference destination-features)
|
||||||
(set/difference no-migration-features)
|
(set/difference no-migration-features)
|
||||||
(set/difference default-enabled-features)
|
(set/difference default-features)
|
||||||
(seq))]
|
(seq))]
|
||||||
(when not-supported
|
(when not-supported
|
||||||
(ex/raise :type :restriction
|
(ex/raise :type :restriction
|
||||||
|
@ -259,7 +255,7 @@
|
||||||
(let [not-supported (-> (or destination-features #{})
|
(let [not-supported (-> (or destination-features #{})
|
||||||
(set/difference source-features)
|
(set/difference source-features)
|
||||||
(set/difference no-migration-features)
|
(set/difference no-migration-features)
|
||||||
(set/difference default-enabled-features)
|
(set/difference default-features)
|
||||||
(seq))]
|
(seq))]
|
||||||
(when not-supported
|
(when not-supported
|
||||||
(ex/raise :type :restriction
|
(ex/raise :type :restriction
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
data data]
|
data data]
|
||||||
(if-let [[to-version migrate-fn] (first migrations)]
|
(if-let [[to-version migrate-fn] (first migrations)]
|
||||||
(let [migrate-fn (or migrate-fn identity)]
|
(let [migrate-fn (or migrate-fn identity)]
|
||||||
(l/inf :hint "migrate file"
|
(l/trc :hint "migrate file"
|
||||||
:op (if (>= from-version to-version) "down" "up")
|
:op (if (>= from-version to-version) "down" "up")
|
||||||
:file-id (str (:id data))
|
:file-id (str (:id data))
|
||||||
:version to-version)
|
:version to-version)
|
||||||
|
|
|
@ -143,23 +143,23 @@ RUN set -eux; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.xz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.xz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "Unsupported arch: ${ARCH}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
;; \
|
;; \
|
||||||
esac; \
|
esac; \
|
||||||
curl -LfsSo /tmp/nodejs.tar.xz ${BINARY_URL}; \
|
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
|
||||||
mkdir -p /usr/local/nodejs; \
|
mkdir -p /usr/local/nodejs; \
|
||||||
cd /usr/local/nodejs; \
|
cd /usr/local/nodejs; \
|
||||||
tar -xf /tmp/nodejs.tar.xz --strip-components=1; \
|
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||||
chown -R root /usr/local/nodejs; \
|
chown -R root /usr/local/nodejs; \
|
||||||
corepack enable; \
|
corepack enable; \
|
||||||
rm -rf /tmp/nodejs.tar.xz;
|
rm -rf /tmp/nodejs.tar.gz;
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
|
|
|
@ -40,16 +40,12 @@ RUN set -eux; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
ESUM='1c4be9aa173cb0deb0d215643d9509c8900e5497290b29eee4bee335fa57984f'; \
|
ESUM='3ce6a2b357e2ef45fd6b53d6587aa05bfec7771e7fb982f2c964f6b771b7526a'; \
|
||||||
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_aarch64_linux_hotspot_19.0.2_7.tar.gz'; \
|
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.2_13.tar.gz'; \
|
||||||
;; \
|
|
||||||
armhf|armv7l) \
|
|
||||||
ESUM='6a51cb3868b5a3b81848a0d276267230ff3f8639f20ba9ae9ef1d386440bf1fd'; \
|
|
||||||
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_arm_linux_hotspot_19.0.2_7.tar.gz'; \
|
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
ESUM='3a3ba7a3f8c3a5999e2c91ea1dca843435a0d1c43737bd2f6822b2f02fc52165'; \
|
ESUM='454bebb2c9fe48d981341461ffb6bf1017c7b7c6e15c6b0c29b959194ba3aaa5'; \
|
||||||
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_x64_linux_hotspot_19.0.2_7.tar.gz'; \
|
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz'; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "Unsupported arch: ${ARCH}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
|
@ -63,7 +59,6 @@ RUN set -eux; \
|
||||||
tar -xf /tmp/openjdk.tar.gz --strip-components=1; \
|
tar -xf /tmp/openjdk.tar.gz --strip-components=1; \
|
||||||
rm -rf /tmp/openjdk.tar.gz;
|
rm -rf /tmp/openjdk.tar.gz;
|
||||||
|
|
||||||
|
|
||||||
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/
|
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/
|
||||||
|
|
||||||
USER penpot:penpot
|
USER penpot:penpot
|
||||||
|
|
|
@ -3,7 +3,7 @@ LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
|
||||||
|
|
||||||
ENV LANG=en_US.UTF-8 \
|
ENV LANG=en_US.UTF-8 \
|
||||||
LC_ALL=en_US.UTF-8 \
|
LC_ALL=en_US.UTF-8 \
|
||||||
NODE_VERSION=v18.15.0 \
|
NODE_VERSION=v20.11.1 \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
PATH=/opt/node/bin:$PATH
|
PATH=/opt/node/bin:$PATH
|
||||||
|
|
||||||
|
@ -75,26 +75,23 @@ RUN set -eux; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.xz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||||
;; \
|
|
||||||
armhf|armv7l) \
|
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-armv7l.tar.xz"; \
|
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.xz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "Unsupported arch: ${ARCH}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
;; \
|
;; \
|
||||||
esac; \
|
esac; \
|
||||||
curl -LfsSo /tmp/nodejs.tar.xz ${BINARY_URL}; \
|
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
|
||||||
mkdir -p /opt/node; \
|
mkdir -p /opt/node; \
|
||||||
cd /opt/node; \
|
cd /opt/node; \
|
||||||
tar -xf /tmp/nodejs.tar.xz --strip-components=1; \
|
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||||
chown -R root /opt/node; \
|
chown -R root /opt/node; \
|
||||||
npm install -g yarn; \
|
corepack enable; \
|
||||||
rm -rf /tmp/nodejs.tar.xz; \
|
rm -rf /tmp/nodejs.tar.gz; \
|
||||||
mkdir -p /opt/penpot; \
|
mkdir -p /opt/penpot; \
|
||||||
chown -R penpot:penpot /opt/penpot;
|
chown -R penpot:penpot /opt/penpot;
|
||||||
|
|
||||||
|
@ -104,7 +101,7 @@ WORKDIR /opt/penpot/exporter
|
||||||
USER penpot:penpot
|
USER penpot:penpot
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
yarn --network-timeout 1000000; \
|
yarn install; \
|
||||||
yarn --network-timeout 1000000 run playwright install chromium;
|
yarn run playwright install chromium;
|
||||||
|
|
||||||
CMD ["node", "app.js"]
|
CMD ["node", "app.js"]
|
||||||
|
|
|
@ -16,6 +16,7 @@ clojure -J-Xms100M -J-Xmx1000M -J-XX:+UseSerialGC -M:dev:shadow-cljs release mai
|
||||||
rm -rf target/app;
|
rm -rf target/app;
|
||||||
|
|
||||||
# Copy package*.json files
|
# Copy package*.json files
|
||||||
|
cp ../.yarnrc.yml target/;
|
||||||
cp yarn.lock target/;
|
cp yarn.lock target/;
|
||||||
cp package.json target/;
|
cp package.json target/;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
(-> global-enabled-features
|
(-> global-enabled-features
|
||||||
(set/union (:features/runtime state #{}))
|
(set/union (:features/runtime state #{}))
|
||||||
(set/intersection cfeat/no-migration-features)
|
(set/intersection cfeat/no-migration-features)
|
||||||
(set/union cfeat/default-enabled-features)
|
|
||||||
(set/union (:features/team state #{}))))
|
(set/union (:features/team state #{}))))
|
||||||
|
|
||||||
(def features-ref
|
(def features-ref
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue