├── .air.toml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 功能需求.md │ └── 反馈bug.md └── workflows │ └── release.yml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── Dockerfile-lite ├── LICENSE ├── Makefile ├── README.md ├── README_EN.md ├── SECURITY.md ├── app ├── application │ ├── command │ │ └── test.go │ ├── http │ │ ├── controller │ │ │ ├── compose-container.go │ │ │ ├── compose.go │ │ │ ├── container-backup.go │ │ │ ├── container-stat.go │ │ │ ├── container-upgrade.go │ │ │ ├── container.go │ │ │ ├── explorer.go │ │ │ ├── home.go │ │ │ ├── image-build.go │ │ │ ├── image-buildx.go │ │ │ ├── image-tag.go │ │ │ ├── image-template.go │ │ │ ├── image.go │ │ │ ├── network.go │ │ │ ├── run-log.go │ │ │ ├── site-cert.go │ │ │ ├── site-domain.go │ │ │ ├── site.go │ │ │ └── volume.go │ │ └── middleware │ │ │ └── home.go │ ├── logic │ │ ├── compose.go │ │ ├── docker-container-task.go │ │ ├── docker-image-task.go │ │ ├── docker-task.go │ │ ├── image-template.go │ │ ├── image.go │ │ └── site.go │ └── provider.go ├── common │ ├── command │ │ └── test.go │ ├── http │ │ ├── controller │ │ │ ├── attach.go │ │ │ ├── cron.go │ │ │ ├── env.go │ │ │ ├── event.go │ │ │ ├── explorer.go │ │ │ ├── home.go │ │ │ ├── notice.go │ │ │ ├── registry.go │ │ │ ├── setting.go │ │ │ ├── store.go │ │ │ ├── tag.go │ │ │ └── user.go │ │ └── middleware │ │ │ └── home.go │ ├── logic │ │ ├── cron-template.go │ │ ├── cron.go │ │ ├── docker-env.go │ │ ├── event.go │ │ ├── notice.go │ │ ├── setting.go │ │ ├── stat.go │ │ ├── store.go │ │ └── user.go │ └── provider.go └── ctrl │ ├── command │ ├── compose │ │ └── deploy.go │ ├── container │ │ ├── backup.go │ │ └── upgrade.go │ ├── store │ │ └── sync.go │ └── user │ │ └── reset.go │ ├── provider.go │ └── sdk │ ├── proxy │ ├── app-compose.go │ ├── app-container.go │ ├── app-image.go │ ├── client.go │ ├── common-env.go │ └── common-store.go │ ├── types │ ├── app │ │ ├── compose.go │ │ ├── container.go │ │ └── image.go │ ├── common │ │ ├── env.go │ │ └── store.go │ └── proxy.go │ └── utils │ └── result.go ├── asset ├── index.html ├── nginx │ └── vhost.tpl ├── plugin │ ├── backup │ │ └── compose.yaml │ ├── explorer │ │ ├── compose.yaml │ │ ├── image-amd64.tar │ │ ├── image-arm.tar │ │ └── image-arm64.tar │ └── webshell │ │ └── compose.yaml └── static │ ├── 110.f3d64f88.async.js │ ├── 1239.2ff603f1.async.js │ ├── 1406.45baa800.async.js │ ├── 1482.90431ac7.async.js │ ├── 1604.fa24433c.async.js │ ├── 1876.a4999f7b.async.js │ ├── 2131.0302f810.async.js │ ├── 2136.0a6722b9.async.js │ ├── 2181.253c402d.async.js │ ├── 2391.a5e58e5d.async.js │ ├── 2398.a49ab4c8.async.js │ ├── 2406.eed6584f.async.js │ ├── 2849.165921b2.async.js │ ├── 3079.836d90d4.async.js │ ├── 3158.881c78d5.async.js │ ├── 3170.43ab622e.async.js │ ├── 3203.36b9d233.async.js │ ├── 3348.cac7a5b0.async.js │ ├── 3379.3175fc32.chunk.css │ ├── 3379.54aa12e8.async.js │ ├── 3635.a0cd4d5e.async.js │ ├── 3831.70763dc3.async.js │ ├── 4262.1600f41e.async.js │ ├── 4323.e1b6579d.async.js │ ├── 4393.8eef8d84.async.js │ ├── 4774.35c5d20a.async.js │ ├── 4997.8576b40a.async.js │ ├── 5083.a5b4b7f4.async.js │ ├── 5137.752e3bc0.async.js │ ├── 5139.ad8591f7.async.js │ ├── 5145.05f1be4e.async.js │ ├── 5213.1376d91a.async.js │ ├── 5265.0a1d0132.async.js │ ├── 5333.965a8877.async.js │ ├── 5529.4ab750cf.async.js │ ├── 5658.597d519d.async.js │ ├── 5843.e9e1b99b.async.js │ ├── 5976.67835c8c.async.js │ ├── 6121.48085b3f.async.js │ ├── 6153.7095f2d4.async.js │ ├── 6196.47b7f2c7.async.js │ ├── 6299.3b4ecac4.async.js │ ├── 6357.d2b2e36f.async.js │ ├── 6390.e3bcb4e0.async.js │ ├── 6442.7ea6b515.async.js │ ├── 6635.11aa525a.async.js │ ├── 6845.62bd4d90.async.js │ ├── 690.a621bd0c.async.js │ ├── 7142.36cf0473.async.js │ ├── 7143.91d5e9fc.async.js │ ├── 7234.5f787c7f.async.js │ ├── 78.d7d7fec2.async.js │ ├── 8010.8777bf08.async.js │ ├── 8171.f0ace9c2.async.js │ ├── 8174.3da9d177.async.js │ ├── 8195.efde9d4d.async.js │ ├── 8345.c41706b6.async.js │ ├── 8373.bb573054.async.js │ ├── 8428.6a72bae3.async.js │ ├── 8631.7a8bbcb7.async.js │ ├── 8636.a92238c8.async.js │ ├── 8762.a152ca56.async.js │ ├── 9170.ab354367.async.js │ ├── 9296.fbb1e23d.async.js │ ├── 940.f2775de8.async.js │ ├── 9490.1baf440f.async.js │ ├── 9607.7968ff15.async.js │ ├── 9831.f360be3b.async.js │ ├── 9994.dacae9b4.async.js │ ├── dpanel.ico │ ├── index.html │ ├── layouts__index.386958dc.async.js │ ├── logo-small.png │ ├── logo.png │ ├── p__app__backup.0f553844.async.js │ ├── p__app__create__image.b72be267.async.js │ ├── p__app__cron__index.d68c764f.async.js │ ├── p__app__cron__list.a3c29192.async.js │ ├── p__app__cron__log.77bd83d4.async.js │ ├── p__app__detail__edit.00bc1dd6.chunk.css │ ├── p__app__detail__edit.6eab49ee.async.js │ ├── p__app__detail__file.00bc1dd6.chunk.css │ ├── p__app__detail__file.0f48eee0.async.js │ ├── p__app__detail__index.fb3d00d5.async.js │ ├── p__app__detail__log.00bc1dd6.chunk.css │ ├── p__app__detail__log.af395098.async.js │ ├── p__app__detail__network.3531e5be.async.js │ ├── p__app__detail__stat.9f339f6b.async.js │ ├── p__app__domain__cert.00409a36.async.js │ ├── p__app__domain__cert.00bc1dd6.chunk.css │ ├── p__app__domain__index.8bcd0540.async.js │ ├── p__app__domain__list.00bc1dd6.chunk.css │ ├── p__app__domain__list.30943b4a.async.js │ ├── p__app__index.36c4b625.async.js │ ├── p__app__list.00bc1dd6.chunk.css │ ├── p__app__list.b05c94e8.async.js │ ├── p__app__recycle.f1f91c8c.async.js │ ├── p__compose__appstore.11c6deb9.async.js │ ├── p__compose__create.d0d0c47f.async.js │ ├── p__compose__index.17135f09.async.js │ ├── p__compose__list.00bc1dd6.chunk.css │ ├── p__compose__list.da32de81.async.js │ ├── p__console__container-log.00bc1dd6.chunk.css │ ├── p__console__container-log.f84011ac.async.js │ ├── p__console__container.00bc1dd6.chunk.css │ ├── p__console__container.0d9d9060.async.js │ ├── p__console__host.00bc1dd6.chunk.css │ ├── p__console__host.481d979d.async.js │ ├── p__docker__index.3b1c9bfc.async.js │ ├── p__docker__network.925abd8d.async.js │ ├── p__docker__volume.bdad06e9.async.js │ ├── p__image__create.00bc1dd6.chunk.css │ ├── p__image__create.2b69e9b2.async.js │ ├── p__image__index.e0ed1996.async.js │ ├── p__image__list.af95d92f.async.js │ ├── p__image__recycle.00bc1dd6.chunk.css │ ├── p__image__recycle.163d6f97.async.js │ ├── p__image__registry.f03db23d.async.js │ ├── p__system__appstore.9b4460b2.async.js │ ├── p__system__basic.fd523ef2.async.js │ ├── p__system__env.737d2f3d.async.js │ ├── p__system__event.11873484.async.js │ ├── p__system__home__host.00bc1dd6.chunk.css │ ├── p__system__home__host.276876dc.async.js │ ├── p__system__home__overview.917ca295.async.js │ ├── p__system__home__tag.00bc1dd6.chunk.css │ ├── p__system__home__tag.65fb462d.async.js │ ├── p__system__index.e144e227.async.js │ ├── p__system__notice.38bf44c2.async.js │ ├── p__system__upgrade.b9ebfc6f.async.js │ ├── p__system__user.7d8b0a4a.async.js │ ├── p__user__login__index.401a1582.async.js │ ├── p__user__oauth__callback.92e13b57.async.js │ ├── p__user__register__index.bf6ff728.async.js │ ├── t__plugin-layout__Layout.3a1d5bb6.async.js │ ├── t__plugin-layout__Layout.5012e1ab.chunk.css │ ├── umi.277b1e59.css │ └── umi.c6ba5b11.js ├── common ├── accessor │ ├── backup_setting_option.go │ ├── compose_setting_option.go │ ├── cron_log_value_option.go │ ├── cron_setting_option.go │ ├── image_info_option.go │ ├── image_setting_option.go │ ├── permission_value_option.go │ ├── registry_setting_option.go │ ├── setting_value_option.go │ ├── site_container_info_option.go │ ├── site_domain_setting_option.go │ ├── site_env_option.go │ └── store_setting_option.go ├── dao │ ├── gen.go │ ├── ims_backup.gen.go │ ├── ims_compose.gen.go │ ├── ims_cron.gen.go │ ├── ims_cron_log.gen.go │ ├── ims_event.gen.go │ ├── ims_image.gen.go │ ├── ims_notice.gen.go │ ├── ims_registry.gen.go │ ├── ims_setting.gen.go │ ├── ims_site.gen.go │ ├── ims_site_domain.gen.go │ ├── ims_store.gen.go │ └── ims_user_permission.gen.go ├── entity │ ├── ims_backup.gen.go │ ├── ims_compose.gen.go │ ├── ims_cron.gen.go │ ├── ims_cron_log.gen.go │ ├── ims_event.gen.go │ ├── ims_image.gen.go │ ├── ims_notice.gen.go │ ├── ims_registry.gen.go │ ├── ims_setting.gen.go │ ├── ims_site.gen.go │ ├── ims_site_domain.gen.go │ ├── ims_store.gen.go │ └── ims_user_permission.gen.go ├── function │ ├── .keep │ ├── array.go │ ├── byte.go │ ├── date.go │ ├── encrypt.go │ ├── error.go │ ├── map.go │ ├── ptr.go │ ├── strings.go │ ├── struct.go │ ├── util.go │ └── yaml.go ├── middleware │ ├── auth.go │ ├── cache.go │ └── cors.go ├── migrate │ ├── upgrade-20240909.go │ ├── upgrade-20250106.go │ ├── upgrade-20250113.go │ ├── upgrade-20250401.go │ ├── upgrade-20250521.go │ └── upgrade.go ├── service │ ├── acme │ │ ├── acme.go │ │ └── option.go │ ├── compose │ │ ├── compose.go │ │ ├── create.go │ │ ├── extension.go │ │ ├── placehold.go │ │ ├── task.go │ │ └── types.go │ ├── crontab │ │ ├── job.go │ │ └── wrapper.go │ ├── docker │ │ ├── backup │ │ │ ├── builder.go │ │ │ ├── option.go │ │ │ ├── reader.go │ │ │ └── writer.go │ │ ├── container.go │ │ ├── container │ │ │ ├── builder.go │ │ │ └── option.go │ │ ├── docker.go │ │ ├── exec.go │ │ ├── image.go │ │ ├── image │ │ │ ├── builder.go │ │ │ └── option.go │ │ ├── import.go │ │ ├── network.go │ │ ├── types.go │ │ └── utils.go │ ├── exec │ │ ├── command.go │ │ ├── option.go │ │ └── result.go │ ├── family │ │ ├── ce.go │ │ ├── ee.go │ │ ├── pe.go │ │ ├── util.go │ │ └── xk.go │ ├── fs │ │ ├── dockerfs │ │ │ ├── docker.go │ │ │ ├── file.go │ │ │ └── option.go │ │ └── fs.go │ ├── notice │ │ └── message.go │ ├── plugin │ │ ├── plugin.go │ │ └── wrapper.go │ ├── registry │ │ ├── option.go │ │ ├── registry.go │ │ ├── repository.go │ │ ├── types.go │ │ └── util.go │ ├── ssh │ │ ├── client.go │ │ ├── host.go │ │ ├── option.go │ │ └── types.go │ ├── storage │ │ ├── cache.go │ │ └── local.go │ └── ws │ │ ├── client.go │ │ ├── collection.go │ │ ├── option.go │ │ ├── progress.go │ │ └── types.go └── types │ ├── define │ └── message.go │ ├── event │ ├── Image_registry.go │ ├── compose.go │ ├── container.go │ ├── env.go │ ├── permission.go │ └── store.go │ ├── family.go │ ├── fs │ └── file_info.go │ └── permission.go ├── config.yaml ├── database └── gen-model.yaml ├── docker ├── entrypoint.sh ├── nginx │ ├── dpanel.conf │ ├── include │ │ ├── assets.conf │ │ ├── block-exploits.conf │ │ ├── challenge.conf │ │ └── force-ssl.conf │ └── nginx.conf └── script │ ├── clear-upload │ └── data.yaml │ ├── compose-upgrade │ └── data.yaml │ ├── container-backup │ └── data.yaml │ ├── container-clear │ └── data.yaml │ ├── container-restart │ └── data.yaml │ ├── container-upgrade │ └── data.yaml │ ├── host-backup │ └── data.yaml │ ├── mysql-backup │ └── data.yaml │ ├── rclone-backup │ └── data.yaml │ └── store-sync │ └── data.yaml ├── go.mod ├── go.sum ├── main.go └── reload.sh /.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "runtime" 4 | 5 | [build] 6 | args_bin = ["-f", "config.yaml"] 7 | bin = "./runtime/main" 8 | cmd = "go build -o ./runtime/main ." 9 | delay = 0 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"] 18 | include_file = [] 19 | kill_delay = "3s" 20 | log = "build-errors.log" 21 | rerun = false 22 | rerun_delay = 500 23 | send_interrupt = true 24 | stop_on_error = false 25 | 26 | [color] 27 | app = "" 28 | build = "yellow" 29 | main = "magenta" 30 | runner = "green" 31 | watcher = "cyan" 32 | 33 | [log] 34 | main_only = false 35 | time = false 36 | 37 | [misc] 38 | clean_on_exit = false 39 | 40 | [screen] 41 | clear_on_rebuild = false 42 | keep_scroll = true 43 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: 16 | - https://afdian.com/a/dpanel 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/功能需求.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能需求 3 | about: 提交新功能需求 4 | title: "[feature]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 完整填写以下信息,有助于快速理解新需求功能。请认真填写。 11 | 12 | **需求的出发点是什么?或是为了解决什么问题?** 13 | 14 | **详细描述功能需求?** 15 | 16 | **开发建议(示例、库、相关项目等)?** 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/反馈bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 反馈bug 3 | about: 反馈bug模板 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 完整填写以下信息,有助于快速定位和排查问题。请认真填写。 11 | 12 | **当前使用的dpanel版本?** 13 | 14 | 通过 【概览】- 【系统信息】- 【面板信息】 15 | 16 | 17 | ** 错误日志 ** 18 | 19 | 【容器管理】 - 【容器列表】-【dpanel】-【运行日志】 或 docker logs dpanel 20 | 21 | ** 复现方式 ** 22 | 23 | 描述一下出现问题的操作流程 24 | 25 | ** Q群反馈 ** 26 | 27 | Q群:837583876 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .settings/ 3 | .project 4 | *.patch 5 | .idea/ 6 | .git/ 7 | .env* 8 | .DS_Store 9 | LICENSE 10 | tests/.phpunit.result.cache 11 | .phpunit.result.cache 12 | runtime/logs 13 | runtime/main 14 | runtime/ 15 | bin/ 16 | data 17 | tests 18 | app/pro/**/.DS_Store 19 | docker/**/*.log 20 | .run/**.* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "app/pro"] 2 | path = app/pro 3 | url = https://github.com/donknap/dpanel-pro.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ARG APP_VERSION 4 | ARG TARGETARCH 5 | ARG APP_FAMILY 6 | ARG PROXY="proxy=0" 7 | 8 | ENV APP_NAME=dpanel 9 | ENV APP_ENV=production 10 | ENV APP_FAMILY=$APP_FAMILY 11 | ENV APP_VERSION=$APP_VERSION 12 | ENV APP_SERVER_PORT=8080 13 | 14 | ENV DOCKER_HOST=unix:///var/run/docker.sock 15 | ENV STORAGE_LOCAL_PATH=/dpanel 16 | ENV DB_DATABASE=${STORAGE_LOCAL_PATH}/dpanel.db 17 | ENV TZ=Asia/Shanghai 18 | ENV ACME_OVERRIDE_CONFIG_HOME=/dpanel/acme 19 | 20 | COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf 21 | COPY ./docker/nginx/include /etc/nginx/conf.d/include 22 | COPY ./docker/script /app/script 23 | 24 | COPY ./runtime/dpanel${APP_FAMILY:+"-${APP_FAMILY}"}-musl-${TARGETARCH} /app/server/dpanel 25 | COPY ./runtime/config.yaml /app/server/config.yaml 26 | 27 | COPY ./docker/entrypoint.sh /docker/entrypoint.sh 28 | 29 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \ 30 | apk add --no-cache --update nginx musl docker-compose curl openssl tzdata git && \ 31 | mkdir -p /tmp/nginx/body /var/lib/nginx/cache/public /var/lib/nginx/cache/private && \ 32 | export ${PROXY} && curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --config-home /dpanel/acme && \ 33 | chmod 755 /docker/entrypoint.sh 34 | 35 | WORKDIR /app/server 36 | VOLUME [ "/dpanel" ] 37 | 38 | EXPOSE 443 39 | EXPOSE 80 40 | EXPOSE 8080 41 | 42 | ENTRYPOINT [ "/docker/entrypoint.sh" ] -------------------------------------------------------------------------------- /Dockerfile-lite: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ARG APP_VERSION 4 | ARG TARGETARCH 5 | ARG APP_FAMILY 6 | 7 | ENV APP_NAME=dpanel 8 | ENV APP_ENV=lite 9 | ENV APP_VERSION=$APP_VERSION 10 | ENV APP_FAMILY=$APP_FAMILY 11 | ENV APP_SERVER_PORT=8080 12 | 13 | ENV DOCKER_HOST=unix:///var/run/docker.sock 14 | ENV STORAGE_LOCAL_PATH=/dpanel 15 | ENV DB_DATABASE=${STORAGE_LOCAL_PATH}/dpanel.db 16 | ENV TZ=Asia/Shanghai 17 | ENV ACME_OVERRIDE_CONFIG_HOME=/dpanel/acme 18 | 19 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \ 20 | apk add --no-cache --update musl docker-compose tzdata git 21 | 22 | COPY ./runtime/dpanel${APP_FAMILY:+"-${APP_FAMILY}"}-musl-${TARGETARCH} /app/server/dpanel 23 | COPY ./runtime/config.yaml /app/server/config.yaml 24 | COPY ./docker/script /app/script 25 | 26 | WORKDIR /app/server 27 | VOLUME [ "/dpanel" ] 28 | 29 | EXPOSE 8080 30 | 31 | ENTRYPOINT [ "sh", "-c", "/app/server/dpanel server:start -f /app/server/config.yaml" ] -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | All security bugs should be reported to the contact as below: 12 | 13 | - email: work.donknap@qq.com 14 | - qq: 914417117 15 | 16 | Thanks for your support! 17 | -------------------------------------------------------------------------------- /app/application/command/test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "github.com/gookit/color" 5 | "github.com/spf13/cobra" 6 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 7 | ) 8 | 9 | type Test struct { 10 | console.Abstract 11 | } 12 | 13 | func (test Test) GetName() string { 14 | return "application:test" 15 | } 16 | 17 | func (test Test) GetDescription() string { 18 | return "application command" 19 | } 20 | 21 | func (test Test) Handle(cmd *cobra.Command, args []string) { 22 | color.Infoln("application test") 23 | } 24 | -------------------------------------------------------------------------------- /app/application/http/controller/container-stat.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | "github.com/donknap/dpanel/common/service/ws" 7 | "github.com/gin-gonic/gin" 8 | "io" 9 | "time" 10 | ) 11 | 12 | func (self Container) GetStatInfo(http *gin.Context) { 13 | type ParamsValidate struct { 14 | Id string `json:"id" binding:"required"` 15 | } 16 | params := ParamsValidate{} 17 | if !self.Validate(http, ¶ms) { 18 | return 19 | } 20 | progress, err := ws.NewFdProgressPip(http, fmt.Sprintf(ws.MessageTypeContainerStat, params.Id)) 21 | if err != nil { 22 | self.JsonResponseWithError(http, err, 500) 23 | return 24 | } 25 | response, err := docker.Sdk.Client.ContainerStats(progress.Context(), params.Id, true) 26 | if err != nil { 27 | self.JsonResponseWithError(http, err, 500) 28 | return 29 | } 30 | lastSendTime := time.Now() 31 | 32 | progress.OnWrite = func(p string) error { 33 | if time.Now().Sub(lastSendTime) < time.Second*2 { 34 | return nil 35 | } 36 | lastSendTime = time.Now() 37 | progress.BroadcastMessage(p) 38 | return nil 39 | } 40 | _, err = io.Copy(progress, response.Body) 41 | self.JsonSuccessResponse(http) 42 | return 43 | } 44 | 45 | func (self Container) GetProcessInfo(http *gin.Context) { 46 | type ParamsValidate struct { 47 | Id string `json:"id" binding:"required"` 48 | } 49 | params := ParamsValidate{} 50 | if !self.Validate(http, ¶ms) { 51 | return 52 | } 53 | 54 | psInfo, err := docker.Sdk.Client.ContainerTop(docker.Sdk.Ctx, params.Id, nil) 55 | if err != nil { 56 | self.JsonResponseWithError(http, err, 500) 57 | return 58 | } 59 | self.JsonResponseWithoutError(http, gin.H{ 60 | "list": psInfo, 61 | }) 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /app/application/http/controller/home.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller" 6 | ) 7 | 8 | type Home struct { 9 | controller.Abstract 10 | } 11 | 12 | func (home Home) Index(ctx *gin.Context) { 13 | home.JsonResponseWithoutError(ctx, "hello world!") 14 | } 15 | -------------------------------------------------------------------------------- /app/application/http/controller/image-build.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "github.com/donknap/dpanel/common/dao" 6 | registry2 "github.com/donknap/dpanel/common/service/registry" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func (self Image) GetBuildTask(http *gin.Context) { 11 | type ParamsValidate struct { 12 | Id int32 `form:"id" binding:"required"` 13 | } 14 | params := ParamsValidate{} 15 | if !self.Validate(http, ¶ms) { 16 | return 17 | } 18 | imageRow, _ := dao.Image.Where(dao.Image.ID.Eq(params.Id)).First() 19 | if imageRow == nil { 20 | self.JsonResponseWithError(http, errors.New("构建任务不存在"), 500) 21 | return 22 | } 23 | tagDetail := registry2.GetImageTagDetail(imageRow.Tag) 24 | imageRow.Tag = tagDetail.ImageName 25 | 26 | self.JsonResponseWithoutError(http, gin.H{ 27 | "task": imageRow, 28 | }) 29 | return 30 | } 31 | 32 | func (self Image) DeleteBuildTask(http *gin.Context) { 33 | type ParamsValidate struct { 34 | Id []int32 `form:"id" binding:"required"` 35 | } 36 | params := ParamsValidate{} 37 | if !self.Validate(http, ¶ms) { 38 | return 39 | } 40 | _, err := dao.Image.Where(dao.Image.ID.In(params.Id...)).Delete() 41 | if err != nil { 42 | self.JsonResponseWithError(http, err, 500) 43 | return 44 | } 45 | self.JsonSuccessResponse(http) 46 | return 47 | } 48 | 49 | func (self Image) GetListBuild(http *gin.Context) { 50 | type ParamsValidate struct { 51 | Page int `json:"page,default=1" binding:"omitempty,gt=0"` 52 | PageSize int `json:"pageSize" binding:"omitempty,gt=1"` 53 | All bool `json:"all"` 54 | } 55 | params := ParamsValidate{} 56 | if !self.Validate(http, ¶ms) { 57 | return 58 | } 59 | if params.Page < 1 { 60 | params.Page = 1 61 | } 62 | if params.PageSize < 1 { 63 | params.PageSize = 10 64 | } 65 | 66 | query := dao.Image.Order(dao.Image.ID.Desc()) 67 | if !params.All { 68 | query = query.Where(dao.Image.BuildType.Neq("pull")) 69 | } 70 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize) 71 | self.JsonResponseWithoutError(http, gin.H{ 72 | "total": total, 73 | "page": params.Page, 74 | "list": list, 75 | }) 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /app/application/http/controller/image-buildx.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func (self Image) CreateByBuildX(http *gin.Context) { 6 | type ParamsValidate struct { 7 | Id int32 `json:"id" binding:"required"` 8 | Platform []string `json:"platform" binding:"required"` 9 | ClearContainer bool `json:"clearContainer" binding:"required"` 10 | ClearCache bool `json:"clearCache" binding:"required"` 11 | Registry string `json:"registry"` 12 | } 13 | 14 | params := ParamsValidate{} 15 | if !self.Validate(http, ¶ms) { 16 | return 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/application/http/controller/image-template.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/application/logic" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func (self Image) GetTemplateList(http *gin.Context) { 9 | self.JsonResponseWithoutError(http, logic.ImageTemplate{}.GetSupportEnv()) 10 | return 11 | } 12 | 13 | func (self Image) GetTemplateDockerfile(http *gin.Context) { 14 | type ParamsValidate struct { 15 | Language string `json:"language" binding:"required"` 16 | Version string `json:"version" binding:"required"` 17 | } 18 | params := ParamsValidate{} 19 | if !self.Validate(http, ¶ms) { 20 | return 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/application/http/controller/run-log.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/docker/docker/api/types/container" 7 | "github.com/docker/docker/pkg/stdcopy" 8 | "github.com/donknap/dpanel/common/service/docker" 9 | "github.com/donknap/dpanel/common/service/ws" 10 | "github.com/gin-gonic/gin" 11 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller" 12 | "io" 13 | "log/slog" 14 | "strconv" 15 | ) 16 | 17 | type RunLog struct { 18 | controller.Abstract 19 | } 20 | 21 | func (self RunLog) Run(http *gin.Context) { 22 | type ParamsValidate struct { 23 | Id string `json:"id" binding:"required"` 24 | LineTotal int `json:"lineTotal" binding:"required,number,oneof=50 100 200 500 1000 5000 -1"` 25 | Download bool `json:"download"` 26 | ShowTime bool `json:"showTime"` 27 | } 28 | 29 | params := ParamsValidate{} 30 | if !self.Validate(http, ¶ms) { 31 | return 32 | } 33 | option := container.LogsOptions{ 34 | ShowStderr: true, 35 | ShowStdout: true, 36 | Follow: !params.Download, 37 | Timestamps: params.ShowTime, 38 | } 39 | if params.LineTotal > 0 { 40 | option.Tail = strconv.Itoa(params.LineTotal) 41 | } 42 | progress, err := ws.NewFdProgressPip(http, fmt.Sprintf(ws.MessageTypeContainerLog, params.Id)) 43 | if err != nil { 44 | self.JsonResponseWithError(http, err, 500) 45 | return 46 | } 47 | 48 | if progress.IsShadow() { 49 | option.Follow = false 50 | } 51 | 52 | response, err := docker.Sdk.Client.ContainerLogs(docker.Sdk.Ctx, params.Id, option) 53 | if err != nil { 54 | self.JsonResponseWithError(http, err, 500) 55 | return 56 | } 57 | if params.Download { 58 | buffer, err := docker.GetContentFromStdFormat(response) 59 | _ = response.Close() 60 | if err != nil { 61 | self.JsonResponseWithError(http, err, 500) 62 | return 63 | } 64 | http.Header("Content-Type", "text/plain") 65 | http.Header("Content-Disposition", "attachment; filename="+params.Id+".log") 66 | http.Data(200, "text/plain", buffer.Bytes()) 67 | return 68 | } 69 | 70 | progress.OnWrite = func(p string) error { 71 | newReader := bytes.NewReader([]byte(p)) 72 | stdout := new(bytes.Buffer) 73 | _, err = stdcopy.StdCopy(stdout, stdout, newReader) 74 | if err != nil { 75 | progress.BroadcastMessage(p) 76 | } else { 77 | progress.BroadcastMessage(stdout.String()) 78 | } 79 | return nil 80 | } 81 | 82 | go func() { 83 | if progress.IsShadow() { 84 | return 85 | } 86 | select { 87 | case <-progress.Done(): 88 | slog.Debug("container", "run log response close", fmt.Sprintf(ws.MessageTypeContainerLog, params.Id)) 89 | _ = response.Close() 90 | } 91 | }() 92 | 93 | _, err = io.Copy(progress, response) 94 | //if err != nil { 95 | // self.JsonResponseWithError(http, errors.New("读取日志失败"), 500) 96 | // return 97 | //} 98 | //newReader := bytes.NewReader(out.Bytes()) 99 | // 100 | //stdout := new(bytes.Buffer) 101 | //_, err = stdcopy.StdCopy(stdout, stdout, newReader) 102 | // 103 | //if err == nil { 104 | // out = stdout 105 | //} 106 | self.JsonResponseWithoutError(http, gin.H{ 107 | "log": "", 108 | }) 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /app/application/http/middleware/home.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware" 7 | "time" 8 | ) 9 | 10 | type Home struct { 11 | middleware.Abstract 12 | } 13 | 14 | func (home Home) Process(ctx *gin.Context) { 15 | log, _ := facade.GetLoggerFactory().Channel("default") 16 | log.Info("route middleware test, req time: " + time.Now().String()) 17 | 18 | ctx.Next() 19 | } 20 | -------------------------------------------------------------------------------- /app/application/logic/docker-task.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/accessor" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | ) 7 | 8 | type CreateContainerOption struct { 9 | SiteId int32 `json:"id"` // 站点id 10 | SiteTitle string `json:"siteTitle" binding:"required"` 11 | SiteName string `json:"siteName" binding:"required"` 12 | ContainerId string `json:"containerId"` 13 | BuildParams *accessor.SiteEnvOption 14 | } 15 | 16 | type BuildImageOption struct { 17 | ZipPath string // 构建包 18 | DockerFileContent []byte // 自定义Dockerfile 19 | DockerFileInPath string // Dockerfile 所在路径 20 | GitUrl string 21 | Tag string // 镜像Tag 22 | ImageId int32 23 | Context string // Dockerfile 所在的目录 24 | Platform *docker.ImagePlatform 25 | } 26 | 27 | type ImageRemoteOption struct { 28 | Auth string 29 | Type string 30 | Tag string 31 | Platform string 32 | Proxy string 33 | } 34 | 35 | type NoticeOption struct { 36 | Message string 37 | } 38 | 39 | type DockerTask struct { 40 | sdk *docker.Builder 41 | } 42 | -------------------------------------------------------------------------------- /app/application/logic/image-template.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | type ImageTemplate struct { 4 | } 5 | 6 | type envItem struct { 7 | Name string `json:"name"` 8 | Version string `json:"version"` 9 | BaseImage string `json:"baseImage"` 10 | } 11 | 12 | type env struct { 13 | Name string `json:"name"` 14 | Env []envItem `json:"env"` 15 | Ext []string `json:"ext"` 16 | } 17 | 18 | func (self ImageTemplate) GetSupportEnv() map[string]env { 19 | supportEnv := make(map[string]env) 20 | 21 | supportEnv[LangPhp] = env{ 22 | Name: LangPhp, 23 | Env: []envItem{ 24 | { 25 | Name: "php-72", 26 | BaseImage: "dpanel/base-image:php-72|7", 27 | }, 28 | { 29 | Name: "php-74", 30 | BaseImage: "dpanel/base-image:php-74|7", 31 | }, 32 | { 33 | Name: "php-81", 34 | BaseImage: "dpanel/base-image:php-81|81", 35 | }, 36 | }, 37 | Ext: []string{ 38 | "intl", "pecl-apcu", "imap", "pecl-mongodb", "pdo_pgsql", 39 | }, 40 | } 41 | 42 | supportEnv[LangJava] = env{ 43 | Name: LangJava, 44 | Env: []envItem{ 45 | { 46 | Name: "jdk8", 47 | Version: "8", 48 | BaseImage: "dpanel/base-image:java-8", 49 | }, 50 | { 51 | Name: "jdk11", 52 | Version: "11", 53 | BaseImage: "dpanel/base-image:java-11", 54 | }, 55 | { 56 | Name: "jdk12", 57 | Version: "12", 58 | BaseImage: "dpanel/base-image:java-12", 59 | }, 60 | }, 61 | } 62 | 63 | supportEnv[LangGolang] = env{ 64 | Name: LangGolang, 65 | Env: []envItem{ 66 | { 67 | Name: "go1.21", 68 | Version: "1.21", 69 | BaseImage: "dpanel/base-image:go-1.21|1.21", 70 | }, 71 | }, 72 | } 73 | 74 | supportEnv[LangNode] = env{ 75 | Name: LangNode, 76 | Env: []envItem{ 77 | { 78 | Name: "node12", 79 | Version: "12", 80 | BaseImage: "dpanel/base-image:node-12", 81 | }, 82 | { 83 | Name: "node14", 84 | Version: "14", 85 | BaseImage: "dpanel/base-image:node-14", 86 | }, 87 | { 88 | Name: "node16", 89 | Version: "16", 90 | BaseImage: "dpanel/base-image:node-16", 91 | }, 92 | { 93 | Name: "node18", 94 | Version: "18", 95 | BaseImage: "dpanel/base-image:node-18", 96 | }, 97 | }, 98 | } 99 | 100 | supportEnv[LangHtml] = env{ 101 | Name: LangHtml, 102 | Env: []envItem{ 103 | { 104 | Name: "common", 105 | Version: "1.0.0", 106 | BaseImage: "dpanel/base-image:html-common", 107 | }, 108 | }, 109 | } 110 | return supportEnv 111 | } 112 | -------------------------------------------------------------------------------- /app/application/logic/image.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/donknap/dpanel/common/dao" 6 | "github.com/donknap/dpanel/common/function" 7 | "github.com/donknap/dpanel/common/service/docker" 8 | registry2 "github.com/donknap/dpanel/common/service/registry" 9 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 10 | "log/slog" 11 | ) 12 | 13 | type Image struct { 14 | } 15 | 16 | func (self Image) GetRegistryConfig(imageName string) registry2.Config { 17 | result := registry2.Config{ 18 | Proxy: make([]string, 0), 19 | } 20 | imageNameDetail := registry2.GetImageTagDetail(imageName) 21 | 22 | if docker.Sdk.Client != nil && imageNameDetail.Registry == registry2.DefaultRegistryDomain { 23 | // 获取docker 配置中的加速地址 24 | // 暂时注释掉,直接取 daemon.json 的镜像地址问题太多 25 | //if dockerInfo, err := docker.Sdk.Client.Info(docker.Sdk.Ctx); err == nil && dockerInfo.RegistryConfig != nil && !function.IsEmptyArray(dockerInfo.RegistryConfig.Mirrors) { 26 | // result.Proxy = dockerInfo.RegistryConfig.Mirrors 27 | //} 28 | } 29 | 30 | registryList, _ := dao.Registry.Where(dao.Registry.ServerAddress.Eq(imageNameDetail.Registry)).Find() 31 | for _, item := range registryList { 32 | if item.Setting == nil { 33 | continue 34 | } 35 | if item.Setting.EnableHttp { 36 | result.Proxy = append(result.Proxy, fmt.Sprintf("http://"+item.ServerAddress)) 37 | } 38 | if !function.IsEmptyArray(item.Setting.Proxy) { 39 | result.Proxy = append(result.Proxy, item.Setting.Proxy...) 40 | } 41 | if item.Setting.Username != "" && item.Setting.Password != "" { 42 | result.Username = item.Setting.Username 43 | password, err := function.AseDecode(facade.GetConfig().GetString("app.name"), item.Setting.Password) 44 | if err != nil { 45 | slog.Debug("image registry decode password", "error", err) 46 | } 47 | result.Password = password 48 | result.ExistsAuth = true 49 | } 50 | } 51 | 52 | if !function.InArray(result.Proxy, imageNameDetail.Registry) { 53 | result.Proxy = append(result.Proxy, imageNameDetail.Registry) 54 | } 55 | return result 56 | } 57 | -------------------------------------------------------------------------------- /app/common/command/test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "github.com/gookit/color" 5 | "github.com/spf13/cobra" 6 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 7 | ) 8 | 9 | type Test struct { 10 | console.Abstract 11 | } 12 | 13 | func (test Test) GetName() string { 14 | return "home:test" 15 | } 16 | 17 | func (test Test) GetDescription() string { 18 | return "test command" 19 | } 20 | 21 | func (self Test) Configure(command *cobra.Command) { 22 | command.Flags().String("name", "test", "test name params") 23 | } 24 | 25 | func (test Test) Handle(cmd *cobra.Command, args []string) { 26 | color.Infoln("home test") 27 | } 28 | -------------------------------------------------------------------------------- /app/common/http/controller/attach.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/donknap/dpanel/common/service/storage" 7 | "github.com/gin-gonic/gin" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller" 9 | "log/slog" 10 | "os" 11 | "path/filepath" 12 | ) 13 | 14 | type Attach struct { 15 | controller.Abstract 16 | } 17 | 18 | func (self Attach) Upload(http *gin.Context) { 19 | fileUploader, fileHeader, _ := http.Request.FormFile("file") 20 | if fileHeader == nil { 21 | self.JsonResponseWithError(http, errors.New("请指定上传文件"), 500) 22 | return 23 | } 24 | defer func() { 25 | _ = fileUploader.Close() 26 | }() 27 | file, err := storage.Local{}.CreateTempFile("") 28 | if err != nil { 29 | self.JsonResponseWithError(http, err, 500) 30 | return 31 | } 32 | slog.Debug("upload file", "path", file.Name()) 33 | err = http.SaveUploadedFile(fileHeader, file.Name()) 34 | if err != nil { 35 | self.JsonResponseWithError(http, err, 500) 36 | return 37 | } 38 | self.JsonResponseWithoutError(http, gin.H{ 39 | "path": filepath.Base(file.Name()), 40 | }) 41 | return 42 | } 43 | 44 | func (self Attach) Delete(http *gin.Context) { 45 | type ParamsValidate struct { 46 | Path string `form:"path" binding:"required"` 47 | } 48 | params := ParamsValidate{} 49 | if !self.Validate(http, ¶ms) { 50 | return 51 | } 52 | path := storage.Local{}.GetRealPath(params.Path) 53 | fmt.Printf("%v \n", path) 54 | _, err := os.Stat(path) 55 | if err == nil { 56 | os.Remove(path) 57 | } 58 | self.JsonSuccessResponse(http) 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /app/common/http/controller/event.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/dao" 5 | "github.com/gin-gonic/gin" 6 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 7 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller" 8 | ) 9 | 10 | type Event struct { 11 | controller.Abstract 12 | } 13 | 14 | func (self Event) GetList(http *gin.Context) { 15 | type ParamsValidate struct { 16 | Page int `form:"page,default=1" binding:"omitempty,gt=0"` 17 | PageSize int `form:"pageSize" binding:"omitempty"` 18 | Type string `form:"type" binding:"omitempty,oneof=builder config container daemon image network node plugin secret service volume"` 19 | } 20 | 21 | params := ParamsValidate{} 22 | if !self.Validate(http, ¶ms) { 23 | return 24 | } 25 | if params.Page < 1 { 26 | params.Page = 1 27 | } 28 | if params.PageSize < 1 { 29 | params.PageSize = 10 30 | } 31 | query := dao.Event.Order(dao.Event.ID.Desc()) 32 | if params.Type != "" { 33 | query = query.Where(dao.Event.Type.Eq(params.Type)) 34 | } 35 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize) 36 | self.JsonResponseWithoutError(http, gin.H{ 37 | "total": total, 38 | "page": params.Page, 39 | "list": list, 40 | }) 41 | return 42 | } 43 | 44 | func (self Event) Prune(http *gin.Context) { 45 | if oldRow, _ := dao.Event.Last(); oldRow != nil { 46 | _, _ = dao.Event.Where(dao.Event.ID.Lte(oldRow.ID)).Delete() 47 | db, err := facade.GetDbFactory().Channel("default") 48 | if err != nil { 49 | self.JsonResponseWithError(http, err, 500) 50 | return 51 | } 52 | db.Exec("vacuum") 53 | } 54 | self.JsonSuccessResponse(http) 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /app/common/http/controller/notice.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/dao" 5 | "github.com/donknap/dpanel/common/entity" 6 | "github.com/donknap/dpanel/common/function" 7 | "github.com/gin-gonic/gin" 8 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 9 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/controller" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | type Notice struct { 14 | controller.Abstract 15 | } 16 | 17 | func (self Notice) Unread(http *gin.Context) { 18 | type ParamsValidate struct { 19 | Action string `form:"action" binding:"required,oneof=new clear init"` 20 | } 21 | params := ParamsValidate{} 22 | if !self.Validate(http, ¶ms) { 23 | return 24 | } 25 | var list []*entity.Notice 26 | var total int64 27 | if params.Action == "init" { 28 | list, total, _ = dao.Notice.Order(dao.Notice.ID.Desc()).FindByPage(0, 5) 29 | } 30 | if function.IsEmptyArray(list) { 31 | list = make([]*entity.Notice, 0) 32 | } 33 | if params.Action == "clear" { 34 | db, err := facade.GetDbFactory().Channel("default") 35 | if err != nil { 36 | self.JsonResponseWithError(http, err, 500) 37 | return 38 | } 39 | db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&entity.Notice{}) 40 | } 41 | self.JsonResponseWithoutError(http, gin.H{ 42 | "list": list, 43 | "unreadTotal": total, 44 | }) 45 | return 46 | } 47 | 48 | func (self Notice) GetList(http *gin.Context) { 49 | type ParamsValidate struct { 50 | Page int `form:"page,default=1" binding:"omitempty,gt=0"` 51 | PageSize int `form:"pageSize" binding:"omitempty"` 52 | Type string `form:"type" binding:"omitempty"` 53 | } 54 | 55 | params := ParamsValidate{} 56 | if !self.Validate(http, ¶ms) { 57 | return 58 | } 59 | if params.Page < 1 { 60 | params.Page = 1 61 | } 62 | if params.PageSize < 1 { 63 | params.PageSize = 10 64 | } 65 | query := dao.Notice.Order(dao.Notice.ID.Desc()) 66 | if params.Type != "" { 67 | query = query.Where(dao.Notice.Title.Eq(params.Type)) 68 | } 69 | list, total, _ := query.FindByPage((params.Page-1)*params.PageSize, params.PageSize) 70 | self.JsonResponseWithoutError(http, gin.H{ 71 | "total": total, 72 | "page": params.Page, 73 | "list": list, 74 | }) 75 | return 76 | } 77 | 78 | func (self Notice) Delete(http *gin.Context) { 79 | type ParamsValidate struct { 80 | Id []int32 `form:"id" binding:"required"` 81 | } 82 | params := ParamsValidate{} 83 | if !self.Validate(http, ¶ms) { 84 | return 85 | } 86 | dao.Notice.Where(dao.Notice.ID.In(params.Id...)).Delete() 87 | self.JsonSuccessResponse(http) 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /app/common/http/middleware/home.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware" 7 | "time" 8 | ) 9 | 10 | type Home struct { 11 | middleware.Abstract 12 | } 13 | 14 | func (home Home) Process(ctx *gin.Context) { 15 | log, _ := facade.GetLoggerFactory().Channel("default") 16 | log.Info("route middleware test, req time: " + time.Now().String()) 17 | 18 | ctx.Next() 19 | } 20 | -------------------------------------------------------------------------------- /app/common/logic/cron-template.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/donknap/dpanel/common/function" 6 | "github.com/donknap/dpanel/common/service/docker" 7 | "gopkg.in/yaml.v3" 8 | "io/fs" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | type CronTemplateItem struct { 15 | Name string `json:"name"` 16 | Environment []docker.EnvItem `json:"environment"` 17 | Script string `json:"script"` 18 | Description string `json:"description"` 19 | Tag []string `json:"tag"` 20 | Project string `json:"project"` 21 | } 22 | 23 | type CronTemplate struct { 24 | } 25 | 26 | func (self CronTemplate) Template(dir string) ([]CronTemplateItem, error) { 27 | result := make([]CronTemplateItem, 0) 28 | err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { 29 | if strings.HasSuffix(path, "data.yaml") { 30 | content, err := os.ReadFile(path) 31 | if err != nil { 32 | return err 33 | } 34 | yamlData := new(function.YamlGetter) 35 | err = yaml.Unmarshal(content, &yamlData) 36 | if err != nil { 37 | return err 38 | } 39 | item := CronTemplateItem{ 40 | Name: yamlData.GetString("task.name"), 41 | Script: yamlData.GetString("task.script"), 42 | Environment: make([]docker.EnvItem, 0), 43 | Description: yamlData.GetString("task.descriptionZh"), 44 | Tag: yamlData.GetStringSlice("task.tag"), 45 | Project: "dpanel", 46 | } 47 | relPath, _ := filepath.Rel(dir, path) 48 | segments := strings.Split(filepath.Clean(relPath), string(filepath.Separator)) 49 | if len(segments) == 3 { 50 | item.Project = segments[0] 51 | } 52 | fields := yamlData.GetSliceStringMapString("task.environment") 53 | for index, field := range fields { 54 | envItem := docker.EnvItem{ 55 | Name: field["name"], 56 | Label: field["labelZh"], 57 | } 58 | envItem.Rule = Store{}.ParseSettingField(field, func(item *docker.ValueRuleItem) { 59 | if (item.Kind & docker.EnvValueTypeSelect) != 0 { 60 | item.Option = function.PluckArrayWalk( 61 | yamlData.GetSliceStringMapString(fmt.Sprintf("task.environment.%d.values", index)), 62 | func(i map[string]string) (docker.ValueItem, bool) { 63 | return docker.ValueItem{ 64 | Name: i["label"], 65 | Value: i["value"], 66 | }, true 67 | }, 68 | ) 69 | } 70 | }) 71 | item.Environment = append(item.Environment, envItem) 72 | } 73 | result = append(result, item) 74 | } 75 | return nil 76 | }) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return result, nil 81 | } 82 | -------------------------------------------------------------------------------- /app/common/logic/docker-env.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "errors" 5 | "github.com/donknap/dpanel/common/accessor" 6 | "github.com/donknap/dpanel/common/entity" 7 | "github.com/donknap/dpanel/common/service/docker" 8 | "golang.org/x/exp/maps" 9 | ) 10 | 11 | type DockerEnv struct { 12 | } 13 | 14 | func (self DockerEnv) UpdateEnv(data *docker.Client) { 15 | setting, err := Setting{}.GetValue(SettingGroupSetting, SettingGroupSettingDocker) 16 | if err != nil || setting.Value == nil || setting.Value.Docker == nil { 17 | setting = &entity.Setting{ 18 | GroupName: SettingGroupSetting, 19 | Name: SettingGroupSettingDocker, 20 | Value: &accessor.SettingValueOption{ 21 | Docker: make(map[string]*docker.Client, 0), 22 | }, 23 | } 24 | } 25 | dockerList := map[string]*docker.Client{ 26 | data.Name: data, 27 | } 28 | maps.Copy(setting.Value.Docker, dockerList) 29 | _ = Setting{}.Save(setting) 30 | return 31 | } 32 | 33 | func (self DockerEnv) GetEnvByName(name string) (*docker.Client, error) { 34 | dockerEnvSetting, err := Setting{}.GetValue(SettingGroupSetting, SettingGroupSettingDocker) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if dockerEnv, ok := dockerEnvSetting.Value.Docker[name]; ok { 39 | return dockerEnv, nil 40 | } else { 41 | return nil, errors.New("docker env not found") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/common/logic/event.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docker/docker/api/types/events" 6 | "github.com/donknap/dpanel/common/dao" 7 | "github.com/donknap/dpanel/common/entity" 8 | "github.com/donknap/dpanel/common/function" 9 | "github.com/donknap/dpanel/common/service/docker" 10 | "log/slog" 11 | "time" 12 | ) 13 | 14 | type EventLogic struct { 15 | } 16 | 17 | func (self EventLogic) MonitorLoop() { 18 | slog.Debug("Event Monitor Loop") 19 | messageChan, errorChan := docker.Sdk.Client.Events(docker.Sdk.Ctx, events.ListOptions{}) 20 | for { 21 | select { 22 | case <-docker.Sdk.Ctx.Done(): 23 | slog.Debug("event", "loop", "exit event loop") 24 | return 25 | case message := <-messageChan: 26 | msg := "" 27 | switch string(message.Type) + "/" + string(message.Action) { 28 | case "image/tag", "image/save", "image/push", "image/pull", "image/load", 29 | "image/import", "image/delete", 30 | "container/destroy", "container/create", 31 | "container/stop", "container/start", "container/restart", 32 | "container/kill", "container/die", 33 | "container/extract-to-dir": 34 | msg += message.Actor.Attributes["name"] 35 | case "container/resize": 36 | msg += fmt.Sprintf("%s: %s-%s", message.Actor.Attributes["name"], 37 | message.Actor.Attributes["width"], message.Actor.Attributes["height"]) 38 | case "volume/mount": 39 | msg += fmt.Sprintf("%s, %s:%s, %s", message.Actor.Attributes["container"], 40 | message.Actor.Attributes["driver"], message.Actor.Attributes["destination"], message.Actor.Attributes["read/write"]) 41 | case "volume/destroy": 42 | msg += fmt.Sprintf("%s", message.Actor.ID) 43 | case "network/disconnect", "network/connect": 44 | msg += fmt.Sprintf("%s %s", message.Actor.Attributes["name"], 45 | message.Actor.Attributes["type"]) 46 | } 47 | if msg != "" { 48 | eventRow := &entity.Event{ 49 | Type: string(message.Type), 50 | Action: string(message.Action), 51 | Message: msg, 52 | CreatedAt: time.Unix(message.Time, 0).In(time.Local).Format("2006-01-02 15:04:05"), 53 | } 54 | _ = dao.Event.Create(eventRow) 55 | time.Sleep(time.Second * 1) 56 | } 57 | case err := <-errorChan: 58 | if err != nil { 59 | _ = dao.Event.Create(&entity.Event{ 60 | Type: "error", 61 | Message: err.Error(), 62 | CreatedAt: time.Now().Format(function.ShowYmdHis), 63 | }) 64 | } 65 | time.Sleep(time.Second) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/common/logic/notice.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/donknap/dpanel/common/accessor" 6 | "github.com/jordan-wright/email" 7 | "log/slog" 8 | "net/smtp" 9 | "strconv" 10 | ) 11 | 12 | type Notice struct { 13 | } 14 | 15 | func (self Notice) Send(emailServer accessor.EmailServer, toEmail string, subject string, htmlContent string) error { 16 | e := email.NewEmail() 17 | e.From = emailServer.Email 18 | e.Subject = subject 19 | e.To = []string{toEmail} 20 | e.HTML = []byte(htmlContent) 21 | err := e.SendWithTLS( 22 | emailServer.Host+":"+strconv.Itoa(emailServer.Port), 23 | smtp.PlainAuth("", emailServer.Email, emailServer.Code, emailServer.Host), 24 | &tls.Config{ 25 | ServerName: emailServer.Host, // 必须与证书匹配的域名 26 | }, 27 | ) 28 | if err != nil { 29 | slog.Debug("email send", "error", err) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /app/common/logic/stat.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/docker/go-units" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type Stat struct { 11 | } 12 | 13 | type statItemResult struct { 14 | Cpu float64 `json:"cpu"` 15 | Memory ioItemResult `json:"memory"` 16 | BlockIO ioItemResult `json:"blockIO"` 17 | NetworkIO ioItemResult `json:"networkIO"` 18 | Name string `json:"name"` 19 | Container string `json:"container"` 20 | } 21 | 22 | type ioItemResult struct { 23 | In int64 `json:"in"` 24 | Out int64 `json:"out"` 25 | } 26 | 27 | func (self Stat) GetStat(response string) ([]*statItemResult, error) { 28 | result := make([]*statItemResult, 0) 29 | statJsonItem := struct { 30 | BlockIO string 31 | CPUPerc string 32 | MemPerc string 33 | MemUsage string 34 | NetIO string 35 | Name string 36 | Container string 37 | }{} 38 | if response == "" { 39 | return result, nil 40 | } 41 | for _, line := range strings.Split(response, "\n") { 42 | if line == "" || !strings.Contains(line, "\"Name\":") { 43 | continue 44 | } 45 | // 只取 {} 之间的数据 46 | start := strings.Index(line, "{") 47 | end := strings.LastIndex(line, "}") 48 | if start == -1 || end == -1 { 49 | continue 50 | } 51 | line = line[start : end+1] 52 | err := json.Unmarshal([]byte(line), &statJsonItem) 53 | if err != nil { 54 | return nil, err 55 | } 56 | r := &statItemResult{ 57 | Name: statJsonItem.Name, 58 | Container: statJsonItem.Container, 59 | } 60 | cpu, _ := strconv.ParseFloat(strings.TrimSuffix(statJsonItem.CPUPerc, "%"), 64) 61 | // 使用率超过100%时,代表该容器使用超过1核。需要将占用转换成100%之内的占用 62 | // 后端不进行转换,在前端计算在所有核心下的占用 63 | if cpu > 100 { 64 | //cpu = math.Round(cpu/float64(dockerInfo.NCPU)*100) / 100 65 | } 66 | r.Cpu += cpu 67 | if strings.Contains(statJsonItem.MemUsage, "/") { 68 | memory := strings.Split(statJsonItem.MemUsage, "/") 69 | use, _ := units.RAMInBytes(strings.TrimSpace(memory[0])) 70 | limit, _ := units.RAMInBytes(strings.TrimSpace(memory[1])) 71 | 72 | r.Memory.In = use 73 | r.Memory.Out = limit 74 | } 75 | if strings.Contains(statJsonItem.NetIO, "/") { 76 | networkIo := strings.Split(statJsonItem.NetIO, "/") 77 | in, _ := units.RAMInBytes(strings.TrimSpace(networkIo[0])) 78 | out, _ := units.RAMInBytes(strings.TrimSpace(networkIo[1])) 79 | 80 | r.NetworkIO.In = in 81 | r.NetworkIO.Out = out 82 | } 83 | if strings.Contains(statJsonItem.BlockIO, "/") { 84 | blockIo := strings.Split(statJsonItem.BlockIO, "/") 85 | in, _ := units.RAMInBytes(strings.TrimSpace(blockIo[0])) 86 | out, _ := units.RAMInBytes(strings.TrimSpace(blockIo[1])) 87 | 88 | r.BlockIO.In = in 89 | r.BlockIO.Out = out 90 | } 91 | result = append(result, r) 92 | } 93 | return result, nil 94 | } 95 | -------------------------------------------------------------------------------- /app/ctrl/command/container/backup.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app" 6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils" 7 | "github.com/spf13/cobra" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 9 | ) 10 | 11 | type Backup struct { 12 | console.Abstract 13 | } 14 | 15 | func (self Backup) GetName() string { 16 | return "container:backup" 17 | } 18 | 19 | func (self Backup) GetDescription() string { 20 | return "生成容器快照" 21 | } 22 | 23 | func (self Backup) Configure(command *cobra.Command) { 24 | command.Flags().String("name", "", "容器名称") 25 | command.Flags().Int("enable-image", 0, "是否快照容器镜像") 26 | command.Flags().String("docker-env", "", "指定 docker 环境") 27 | _ = command.MarkFlagRequired("name") 28 | } 29 | 30 | func (self Backup) Handle(cmd *cobra.Command, args []string) { 31 | name, _ := cmd.Flags().GetString("name") 32 | dockerEnv, _ := cmd.Flags().GetString("docker-env") 33 | enableImage, _ := cmd.Flags().GetInt("enable-image") 34 | 35 | proxyClient, err := proxy.NewProxyClient() 36 | if err != nil { 37 | utils.Result{}.Error(err) 38 | return 39 | } 40 | dockerEnvList, err := proxyClient.CommonEnvGetList() 41 | if err != nil { 42 | utils.Result{}.Error(err) 43 | return 44 | } 45 | if dockerEnv != "" && dockerEnv != dockerEnvList.CurrentName { 46 | err = proxyClient.CommonEnvSwitch(dockerEnv) 47 | if err != nil { 48 | utils.Result{}.Error(err) 49 | return 50 | } 51 | defer func() { 52 | _ = proxyClient.CommonEnvSwitch(dockerEnvList.CurrentName) 53 | }() 54 | } 55 | result, err := proxyClient.AppContainerBackupCreate(&app.ContainerBackupOption{ 56 | Id: name, 57 | EnableImage: enableImage > 0, 58 | EnableCommitImage: false, 59 | Volume: make([]string, 0), 60 | }) 61 | if err != nil { 62 | utils.Result{}.Error(err) 63 | return 64 | } 65 | utils.Result{}.Success(result) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /app/ctrl/command/container/upgrade.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app" 6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils" 7 | "github.com/spf13/cobra" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 9 | ) 10 | 11 | type Upgrade struct { 12 | console.Abstract 13 | } 14 | 15 | func (self Upgrade) GetName() string { 16 | return "container:upgrade" 17 | } 18 | 19 | func (self Upgrade) GetDescription() string { 20 | return "检测当前容器更新" 21 | } 22 | 23 | func (self Upgrade) Configure(command *cobra.Command) { 24 | command.Flags().String("name", "", "容器名称") 25 | command.Flags().String("docker-env", "", "指定 docker 环境") 26 | command.Flags().Int("upgrade", 0, "是否升级容器") 27 | _ = command.MarkFlagRequired("name") 28 | } 29 | 30 | func (self Upgrade) Handle(cmd *cobra.Command, args []string) { 31 | name, _ := cmd.Flags().GetString("name") 32 | dockerEnv, _ := cmd.Flags().GetString("docker-env") 33 | isUpgrade, _ := cmd.Flags().GetInt("upgrade") 34 | 35 | proxyClient, err := proxy.NewProxyClient() 36 | if err != nil { 37 | utils.Result{}.Error(err) 38 | return 39 | } 40 | dockerEnvList, err := proxyClient.CommonEnvGetList() 41 | if err != nil { 42 | utils.Result{}.Error(err) 43 | return 44 | } 45 | if dockerEnv != "" && dockerEnv != dockerEnvList.CurrentName { 46 | err = proxyClient.CommonEnvSwitch(dockerEnv) 47 | if err != nil { 48 | utils.Result{}.Error(err) 49 | return 50 | } 51 | defer func() { 52 | _ = proxyClient.CommonEnvSwitch(dockerEnvList.CurrentName) 53 | }() 54 | } 55 | containerInfo, err := proxyClient.AppContainerGetDetail(name) 56 | if err != nil { 57 | utils.Result{}.Error(err) 58 | return 59 | } 60 | result, err := proxyClient.AppImageCheckUpgrade(&app.ImageCheckUpgradeOption{ 61 | Tag: containerInfo.Info.Config.Image, 62 | Md5: containerInfo.Info.Image, 63 | CacheTime: 0, 64 | }) 65 | if err != nil { 66 | utils.Result{}.Error(err) 67 | return 68 | } 69 | if isUpgrade <= 0 { 70 | utils.Result{}.Success(result) 71 | return 72 | } 73 | _, err = proxyClient.AppImageTagRemote(&app.ImageTagRemoteOption{ 74 | Tag: containerInfo.Info.Config.Image, 75 | Type: "pull", 76 | }) 77 | if err != nil { 78 | utils.Result{}.Error(err) 79 | return 80 | } 81 | containerUpgradeResult, err := proxyClient.AppContainerUpgrade(&app.ContainerUpgradeOption{ 82 | Md5: containerInfo.Info.ID, 83 | EnableBak: true, 84 | }) 85 | 86 | if err != nil { 87 | utils.Result{}.Error(err) 88 | return 89 | } 90 | 91 | utils.Result{}.Success(containerUpgradeResult) 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /app/ctrl/command/store/sync.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/ctrl/sdk/proxy" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common" 6 | "github.com/donknap/dpanel/app/ctrl/sdk/utils" 7 | "github.com/donknap/dpanel/common/dao" 8 | "github.com/gin-gonic/gin" 9 | "github.com/spf13/cobra" 10 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 11 | ) 12 | 13 | type Sync struct { 14 | console.Abstract 15 | } 16 | 17 | func (self Sync) GetName() string { 18 | return "store:sync" 19 | } 20 | 21 | func (self Sync) GetDescription() string { 22 | return "同步应用商店数据" 23 | } 24 | 25 | func (self Sync) Configure(command *cobra.Command) { 26 | command.Flags().String("name", "", "商店标识") 27 | _ = command.MarkFlagRequired("name") 28 | } 29 | 30 | func (self Sync) Handle(cmd *cobra.Command, args []string) { 31 | name, _ := cmd.Flags().GetString("name") 32 | 33 | store, _ := dao.Store.Where(dao.Store.Name.Eq(name)).First() 34 | if store == nil { 35 | utils.Result{}.Errorf("%s 商店不存在,请先添加", name) 36 | return 37 | } 38 | proxyClient, err := proxy.NewProxyClient() 39 | if err != nil { 40 | utils.Result{}.Error(err) 41 | return 42 | } 43 | 44 | appList, err := proxyClient.CommonStoreSync(&common.StoreSyncOption{ 45 | Id: store.ID, 46 | Name: store.Name, 47 | Type: store.Setting.Type, 48 | Url: store.Setting.Url, 49 | }) 50 | if err != nil { 51 | utils.Result{}.Error(err) 52 | return 53 | } 54 | 55 | utils.Result{}.Success(gin.H{ 56 | "total": len(appList), 57 | }) 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /app/ctrl/command/user/reset.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/common/logic" 5 | "github.com/donknap/dpanel/common/accessor" 6 | "github.com/donknap/dpanel/common/dao" 7 | "github.com/donknap/dpanel/common/entity" 8 | "github.com/donknap/dpanel/common/function" 9 | "github.com/gookit/color" 10 | "github.com/spf13/cobra" 11 | "github.com/we7coreteam/w7-rangine-go/v2/src/console" 12 | ) 13 | 14 | type Reset struct { 15 | console.Abstract 16 | } 17 | 18 | func (self Reset) GetName() string { 19 | return "user:reset" 20 | } 21 | 22 | func (self Reset) GetDescription() string { 23 | return "重置面板用户名或是密码" 24 | } 25 | 26 | func (self Reset) Configure(command *cobra.Command) { 27 | command.Flags().String("password", "", "重置管理员密码") 28 | command.Flags().String("username", "", "重置管理员用户名") 29 | } 30 | 31 | func (self Reset) Handle(cmd *cobra.Command, args []string) { 32 | founder, _ := dao.Setting. 33 | Where(dao.Setting.GroupName.Eq(logic.SettingGroupUser)). 34 | Where(dao.Setting.Name.Eq(logic.SettingGroupUserFounder)).First() 35 | if founder == nil { 36 | founder = &entity.Setting{ 37 | GroupName: logic.SettingGroupUser, 38 | Name: logic.SettingGroupUserFounder, 39 | Value: &accessor.SettingValueOption{ 40 | Username: "", 41 | Password: "", 42 | }, 43 | } 44 | } 45 | username, err := cmd.Flags().GetString("username") 46 | if err != nil { 47 | color.Errorln("重置失败,", err.Error()) 48 | return 49 | } 50 | password, err := cmd.Flags().GetString("password") 51 | if err != nil { 52 | color.Errorln("重置失败,", err.Error()) 53 | return 54 | } 55 | if username == "" && password == "" { 56 | username = "admin" 57 | password = function.GetRandomString(10) 58 | } 59 | 60 | if username != "" && password == "" { 61 | color.Errorln("重置用户名时必须指定密码") 62 | return 63 | } 64 | 65 | if username != "" { 66 | founder.Value.Username = username 67 | } 68 | 69 | founder.Value.Password = logic.User{}.GetMd5Password(password, founder.Value.Username) 70 | 71 | err = dao.Setting.Save(founder) 72 | if err != nil { 73 | color.Errorln("重置失败,", err.Error()) 74 | return 75 | } 76 | color.Println("用户名: ", username) 77 | color.Println("密 码: ", password) 78 | color.Successln("用户名或是密码重置成功") 79 | } 80 | -------------------------------------------------------------------------------- /app/ctrl/provider.go: -------------------------------------------------------------------------------- 1 | package ctrl 2 | 3 | import ( 4 | "github.com/donknap/dpanel/app/ctrl/command/compose" 5 | "github.com/donknap/dpanel/app/ctrl/command/container" 6 | "github.com/donknap/dpanel/app/ctrl/command/store" 7 | "github.com/donknap/dpanel/app/ctrl/command/user" 8 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/console" 9 | ) 10 | 11 | type Provider struct { 12 | } 13 | 14 | func (provider *Provider) Register(console console.Console) { 15 | console.RegisterCommand(new(user.Reset)) 16 | console.RegisterCommand(new(store.Sync)) 17 | 18 | // 容器相关 19 | console.RegisterCommand(new(container.Upgrade)) 20 | console.RegisterCommand(new(container.Backup)) 21 | 22 | console.RegisterCommand(new(compose.Deploy)) 23 | } 24 | -------------------------------------------------------------------------------- /app/ctrl/sdk/proxy/app-compose.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func (self *Client) AppComposeDeploy(params *app.ComposeDeployOption) error { 10 | _, err := self.Post("/api/app/compose/container-deploy", params) 11 | if err != nil { 12 | return err 13 | } 14 | return nil 15 | } 16 | 17 | func (self *Client) AppComposeTask(name string) (result app.ComposeDetailResult, err error) { 18 | data, err := self.Post("/api/app/compose/get-task", gin.H{ 19 | "id": name, 20 | }) 21 | if err != nil { 22 | return result, err 23 | } 24 | err = json.NewDecoder(data).Decode(&result) 25 | return result, err 26 | } 27 | -------------------------------------------------------------------------------- /app/ctrl/sdk/proxy/app-container.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func (self *Client) AppContainerGetDetail(containerName string) (result app.ContainerDetailResult, err error) { 10 | data, err := self.Post("/api/app/container/get-detail", gin.H{ 11 | "md5": containerName, 12 | }) 13 | if err != nil { 14 | return result, err 15 | } 16 | err = json.NewDecoder(data).Decode(&result) 17 | return result, err 18 | } 19 | 20 | func (self *Client) AppContainerUpgrade(params *app.ContainerUpgradeOption) (result app.ContainerUpgradeResult, err error) { 21 | data, err := self.Post("/api/app/container/upgrade", params) 22 | if err != nil { 23 | return result, err 24 | } 25 | err = json.NewDecoder(data).Decode(&result) 26 | return result, err 27 | } 28 | 29 | func (self *Client) AppContainerBackupCreate(params *app.ContainerBackupOption) (result app.ContainerBackupResult, err error) { 30 | data, err := self.Post("/api/app/container-backup/create", params) 31 | if err != nil { 32 | return result, err 33 | } 34 | err = json.NewDecoder(data).Decode(&result) 35 | return result, err 36 | } 37 | -------------------------------------------------------------------------------- /app/ctrl/sdk/proxy/app-image.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/app" 6 | ) 7 | 8 | func (self *Client) AppImageCheckUpgrade(params *app.ImageCheckUpgradeOption) (result app.ImageCheckUpgradeResult, err error) { 9 | data, err := self.Post("/api/app/image/check-upgrade", params) 10 | if err != nil { 11 | return result, err 12 | } 13 | err = json.NewDecoder(data).Decode(&result) 14 | return result, err 15 | } 16 | 17 | func (self *Client) AppImageTagRemote(params *app.ImageTagRemoteOption) (result app.ImageTagRemoteResult, err error) { 18 | data, err := self.Post("/api/app/image/tag-remote", params) 19 | if err != nil { 20 | return result, err 21 | } 22 | err = json.NewDecoder(data).Decode(&result) 23 | return result, err 24 | } 25 | -------------------------------------------------------------------------------- /app/ctrl/sdk/proxy/common-env.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func (self *Client) CommonEnvGetList() (result common.EnvListResult, err error) { 10 | data, err := self.Post("/api/common/env/get-list", nil) 11 | if err != nil { 12 | return result, err 13 | } 14 | err = json.NewDecoder(data).Decode(&result) 15 | return result, err 16 | } 17 | 18 | func (self *Client) CommonEnvSwitch(name string) error { 19 | _, err := self.Post("/api/common/env/switch", gin.H{ 20 | "name": name, 21 | }) 22 | if err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /app/ctrl/sdk/proxy/common-store.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/donknap/dpanel/app/ctrl/sdk/types/common" 6 | "github.com/donknap/dpanel/common/accessor" 7 | ) 8 | 9 | func (self *Client) CommonStoreSync(option *common.StoreSyncOption) (list []accessor.StoreAppItem, err error) { 10 | data, err := self.Post("/api/common/store/sync", option) 11 | if err != nil { 12 | return list, err 13 | } 14 | result := struct { 15 | List []accessor.StoreAppItem `json:"list"` 16 | }{ 17 | List: make([]accessor.StoreAppItem, 0), 18 | } 19 | err = json.NewDecoder(data).Decode(&result) 20 | return result.List, err 21 | } 22 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/app/compose.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/entity" 5 | "github.com/donknap/dpanel/common/service/compose" 6 | "github.com/donknap/dpanel/common/service/docker" 7 | ) 8 | 9 | type ComposeDeployOption struct { 10 | Id string `json:"id" binding:"required"` 11 | Environment []docker.EnvItem `json:"environment"` 12 | DeployServiceName []string `json:"deployServiceName"` 13 | CreatePath bool `json:"createPath"` 14 | RemoveOrphans bool `json:"removeOrphans"` 15 | } 16 | 17 | type ComposeDetailResult struct { 18 | Detail *entity.Compose `json:"detail"` 19 | Yaml [2]string `json:"yaml"` 20 | ContainerList []*compose.ContainerResult `json:"containerList"` 21 | Project struct { 22 | Name string `json:"name"` 23 | Services map[string]struct { 24 | Image string `json:"image"` 25 | } `json:"services"` 26 | } `json:"project"` 27 | } 28 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/app/container.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/docker/docker/api/types/container" 5 | "github.com/donknap/dpanel/common/accessor" 6 | ) 7 | 8 | type ContainerDetailResult struct { 9 | Info container.InspectResponse `json:"info"` 10 | Ignore accessor.ContainerCheckIgnoreUpgrade `json:"ignore"` 11 | } 12 | 13 | type ContainerUpgradeOption struct { 14 | Md5 string `json:"md5" binding:"required"` 15 | ImageTag string `json:"imageTag"` 16 | EnableBak bool `json:"enableBak"` 17 | } 18 | 19 | type ContainerUpgradeResult struct { 20 | ContainerId string `json:"containerId"` 21 | } 22 | 23 | type ContainerBackupOption struct { 24 | Id string `json:"id"` 25 | EnableImage bool `json:"enableImage"` 26 | EnableCommitImage bool `json:"enableCommitImage"` 27 | Volume []string `json:"volume"` 28 | } 29 | 30 | type ContainerBackupResult struct { 31 | Path string `json:"path"` 32 | } 33 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/app/image.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | type ImageCheckUpgradeOption struct { 4 | Tag string `json:"tag"` // 要检测的镜像 tag 5 | Md5 string `json:"md5"` // 镜像 id 6 | CacheTime int `json:"cacheTime"` 7 | } 8 | 9 | type ImageCheckUpgradeResult struct { 10 | Upgrade bool `json:"upgrade"` 11 | Digest string `json:"digest"` 12 | DigestLocal []string `json:"digestLocal"` 13 | } 14 | 15 | type ImageTagRemoteOption struct { 16 | Tag string `json:"tag"` 17 | Type string `json:"type"` // pull or push 18 | Platform string `json:"platform"` 19 | } 20 | 21 | type ImageTagRemoteResult struct { 22 | Tag string `json:"tag"` 23 | ProxyUrl string `json:"proxyUrl"` 24 | } 25 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/common/env.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/donknap/dpanel/common/service/docker" 4 | 5 | type EnvListResult struct { 6 | CurrentName string `json:"currentName"` 7 | List []*docker.Client `json:"list"` 8 | } 9 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/common/store.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type StoreSyncOption struct { 4 | Id int32 `json:"id"` 5 | Name string `json:"name"` 6 | Type string `json:"type"` 7 | Url string `json:"url"` 8 | } 9 | -------------------------------------------------------------------------------- /app/ctrl/sdk/types/proxy.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Message struct { 4 | Code int `json:"code"` 5 | Error string `json:"error"` 6 | Data interface{} `json:"data"` 7 | } 8 | -------------------------------------------------------------------------------- /app/ctrl/sdk/utils/result.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Result struct{} 10 | 11 | func (self Result) Error(err error) { 12 | str, err := json.Marshal(gin.H{ 13 | "error": err.Error(), 14 | "code": 500, 15 | }) 16 | if err != nil { 17 | self.Error(err) 18 | return 19 | } 20 | 21 | fmt.Println(string(str)) 22 | return 23 | } 24 | 25 | func (self Result) Errorf(format string, a ...any) { 26 | self.Error(fmt.Errorf(format, a)) 27 | return 28 | } 29 | 30 | func (self Result) Success(data interface{}) { 31 | str, err := json.Marshal(data) 32 | if err != nil { 33 | self.Error(err) 34 | return 35 | } 36 | fmt.Println(string(str)) 37 | } 38 | -------------------------------------------------------------------------------- /asset/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangine Framework 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /asset/nginx/vhost.tpl: -------------------------------------------------------------------------------- 1 | map $scheme $hsts_header { 2 | https "max-age=63072000; preload"; 3 | } 4 | 5 | upstream {{.TargetName}} { 6 | server {{.ServerAddress}}:{{.Port}}; 7 | } 8 | 9 | {{if .EnableSSL}} 10 | server { 11 | listen 80; 12 | server_name {{.ServerName}} {{range $index, $value := .ServerNameAlias}}{{$value}} {{end}}; 13 | 14 | location / { 15 | return 301 https://$host$request_uri; 16 | } 17 | } 18 | {{end}} 19 | 20 | server { 21 | set $forward_scheme {{.ServerProtocol}}; 22 | {{if .EnableSSL}} 23 | listen 443 ssl; 24 | {{else}} 25 | listen 80; 26 | {{end}} 27 | 28 | server_name {{.ServerName}} {{range $index, $value := .ServerNameAlias}}{{$value}} {{end}}; 29 | 30 | {{if .EnableSSL}} 31 | ssl_certificate {{.SslCrt}}; 32 | ssl_certificate_key {{.SslKey}}; 33 | ssl_session_cache shared:SSL:1m; 34 | ssl_session_timeout 5m; 35 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; 36 | ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; 37 | ssl_prefer_server_ciphers on; 38 | {{end}} 39 | 40 | {{if .EnableAssetCache}} 41 | # Asset Caching 42 | include /etc/nginx/conf.d/include/assets.conf; 43 | {{end}} 44 | {{if .EnableBlockCommonExploits}} 45 | # Block Exploits 46 | include /etc/nginx/conf.d/include/block-exploits.conf; 47 | {{end}} 48 | 49 | {{if .EnableWs}} 50 | proxy_set_header Upgrade $http_upgrade; 51 | proxy_set_header Connection $http_connection; 52 | proxy_http_version 1.1; 53 | {{end}} 54 | 55 | {{.ExtraNginx}} 56 | 57 | location / { 58 | {{if .EnableWs}} 59 | proxy_set_header Upgrade $http_upgrade; 60 | proxy_set_header Connection $http_connection; 61 | proxy_http_version 1.1; 62 | {{end}} 63 | 64 | add_header X-Served-By $host; 65 | 66 | proxy_set_header Host $host; 67 | proxy_set_header X-Forwarded-Scheme $scheme; 68 | proxy_set_header X-Forwarded-Proto $scheme; 69 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 70 | proxy_set_header X-Real-IP $remote_addr; 71 | proxy_pass $forward_scheme://{{.TargetName}}$request_uri; 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /asset/plugin/backup/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | backup: 3 | image: dpanel/explorer 4 | container_name: dpanel-plugin-backup 5 | restart: no 6 | labels: 7 | - "com.dpanel.container.title=dpanel 备份助手" 8 | - "com.dpanel.container.auto_remove=true" 9 | command: 10 | #{{- range $key, $val := .backup.Command }} 11 | - {{ $val | unescaped }} 12 | #{{- end}} 13 | #{{if .backup.Volumes}} 14 | volumes: 15 | #{{- range $key, $val := .backup.Volumes }} 16 | - {{ $val }} 17 | #{{- end}} 18 | #{{- end}} 19 | x-dpanel-service: 20 | image_tar: 21 | amd64: asset/plugin/explorer/image-amd64.tar 22 | arm64: asset/plugin/explorer/image-arm64.tar 23 | arm: asset/plugin/explorer/image-arm.tar 24 | auto_remove: true 25 | external: 26 | volumes_from: 27 | #{{- range $key, $val := .backup.External.VolumesFrom}} 28 | - {{ $val }} 29 | #{{- end}} 30 | volumes: 31 | #{{- range $key, $val := .backup.External.Volumes}} 32 | - {{ $val }} 33 | #{{- end}} 34 | -------------------------------------------------------------------------------- /asset/plugin/explorer/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | explorer: 3 | image: dpanel/explorer 4 | container_name: dpanel-plugin-explorer 5 | restart: always 6 | privileged: false 7 | pid: host 8 | labels: 9 | - "com.dpanel.container.title=dpanel 文件管理助手" 10 | - "com.dpanel.container.auto_remove=true" 11 | x-dpanel-service: 12 | image_tar: 13 | amd64: asset/plugin/explorer/image-amd64.tar 14 | arm64: asset/plugin/explorer/image-arm64.tar 15 | arm: asset/plugin/explorer/image-arm.tar -------------------------------------------------------------------------------- /asset/plugin/explorer/image-amd64.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-amd64.tar -------------------------------------------------------------------------------- /asset/plugin/explorer/image-arm.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-arm.tar -------------------------------------------------------------------------------- /asset/plugin/explorer/image-arm64.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/plugin/explorer/image-arm64.tar -------------------------------------------------------------------------------- /asset/plugin/webshell/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | webshell: 3 | image: dpanel/explorer 4 | container_name: {{.webshell.ContainerName}} 5 | restart: no 6 | privileged: true 7 | pid: host 8 | labels: 9 | - "com.dpanel.container.title=dpanel webshell 助手" 10 | - "com.dpanel.container.auto_remove=true" 11 | command: 12 | - /bin/sh 13 | - "-c" 14 | - "nsenter --target 1 --net --mount" 15 | x-dpanel-service: 16 | image_tar: 17 | amd64: asset/plugin/explorer/image-amd64.tar 18 | arm64: asset/plugin/explorer/image-arm64.tar 19 | arm: asset/plugin/explorer/image-arm.tar 20 | -------------------------------------------------------------------------------- /asset/static/6390.e3bcb4e0.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6390],{96390:function(e,n,t){t.r(n),t.d(n,{default:function(){return s}});var o=t(67294),u=t(82346),E=t(85893);function s(){var O=(0,u.useOutletContext)();return(0,E.jsx)(u.Outlet,{context:O})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/8171.f0ace9c2.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8171],{82885:function(s,n,c){c.r(n),c.d(n,{rpmChanges:function(){return t},rpmSpec:function(){return g}});var o=/^-+$/,a=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /,i=/^[\w+.-]+@[\w.-]+/;const t={name:"rpmchanges",token:function(r){return r.sol()&&(r.match(o)||r.match(a))?"tag":r.match(i)?"string":(r.next(),null)}};var l=/^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/,u=/^[a-zA-Z0-9()]+:/,p=/^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/,f=/^%(ifnarch|ifarch|if)/,h=/^%(else|endif)/,d=/^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/;const g={name:"rpmspec",startState:function(){return{controlFlow:!1,macroParameters:!1,section:!1}},token:function(r,e){var m=r.peek();if(m=="#")return r.skipToEnd(),"comment";if(r.sol()){if(r.match(u))return"header";if(r.match(p))return"atom"}if(r.match(/^\$\w+/)||r.match(/^\$\{\w+\}/))return"def";if(r.match(h))return"keyword";if(r.match(f))return e.controlFlow=!0,"keyword";if(e.controlFlow){if(r.match(d))return"operator";if(r.match(/^(\d+)/))return"number";r.eol()&&(e.controlFlow=!1)}if(r.match(l))return r.eol()&&(e.controlFlow=!1),"number";if(r.match(/^%[\w]+/))return r.match("(")&&(e.macroParameters=!0),"keyword";if(e.macroParameters){if(r.match(/^\d+/))return"number";if(r.match(")"))return e.macroParameters=!1,"keyword"}return r.match(/^%\{\??[\w \-\:\!]+\}/)?(r.eol()&&(e.controlFlow=!1),"def"):(r.next(),null)}}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/dpanel.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/dpanel.ico -------------------------------------------------------------------------------- /asset/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /asset/static/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/logo-small.png -------------------------------------------------------------------------------- /asset/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/asset/static/logo.png -------------------------------------------------------------------------------- /asset/static/p__app__cron__index.d68c764f.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[306],{26910:function(O,t,n){n.r(t),n.d(t,{default:function(){return s}});var u=n(82346),f=n(67294),E=n(85893);function s(){return(0,E.jsx)(u.Outlet,{})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__app__domain__index.8bcd0540.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4388],{4037:function(D,t,n){n.r(t),n.d(t,{default:function(){return s}});var u=n(82346),O=n(67294),E=n(85893);function s(){return(0,E.jsx)(u.Outlet,{})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__app__index.36c4b625.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8115],{35880:function(u,n,e){var r=e(67294),t=(0,r.createContext)({});n.Z=t},54823:function(u,n,e){e.r(n),e.d(n,{default:function(){return R}});var r=e(82346),t=e(67294),f=e(35880),a=e(85893);function R(){var s=(0,t.useRef)(),_=(0,t.useRef)(),v=(0,t.useRef)(),E=(0,t.useRef)(),c=(0,t.useRef)();return(0,a.jsx)(a.Fragment,{children:(0,a.jsx)(f.Z.Provider,{value:{createFormRef:s,volumeListRef:_,domainRef:v,createEnvRef:c,createLinkRef:E},children:(0,a.jsx)(r.Outlet,{})})})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__compose__index.17135f09.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6889],{96781:function(_,t,e){var a=e(67294),n=(0,a.createContext)({});t.Z=n},24943:function(_,t,e){e.r(t),e.d(t,{default:function(){return E}});var a=e(82346),n=e(67294),r=e(96781),s=e(85893);function E(){var f=(0,n.useRef)(),o=(0,a.useAccess)(),c=(0,a.useNavigate)(),u=(0,a.useLocation)();return(0,n.useEffect)(function(){u.pathname=="/compose"&&(!o.canSeeFounder&&!o.canSeeManage&&o.canSeeComposeStore?c("/compose/appstore"):c("/compose/list"))},[u.pathname]),(0,s.jsx)(r.Z.Provider,{value:{listTableRef:f},children:(0,s.jsx)(a.Outlet,{})})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__console__host.481d979d.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6856],{49610:function(C,u,e){var d=e(5574),l=e.n(d),a=e(67294),O=e(52626),f=e(78267),D=e.n(f),M=e(12323),g=e(87662),m=e(4244),W=e.n(m),T=e(24078),h=e(85893),t,o,n,i,U=(0,a.forwardRef)(function(B,I){var y,L=(0,a.useState)(),b=l()(L,2),S=b[0],j=b[1],K=(0,a.useState)(""),R=l()(K,2),v=R[0],x=R[1];(0,a.useImperativeHandle)(I,function(){return{start:function(c,_){P(),o.fit(),_.width=t.cols,_.height=t.rows;var A=_?Object.keys(_).filter(function(r){return _&&_[r]}).map(function(r){if(_&&_[r])return"".concat(r,"=").concat(_[r])}):[];n=new WebSocket((0,O.R)(c+"?"+A.join("&"))),n.addEventListener("message",function(r){var E=JSON.parse(r.data),p=E.type;if(p==c&&(t.write(E.data),E.data.indexOf("exec failed")!=-1)){P();return}}),i=t.onData(function(r){var E={content:{command:r},type:c};n&&n.send(JSON.stringify(E))}),x(c),j(_)},close:function(){P()},fit:function(){return o.fit(),{width:t.cols,height:t.rows}},run:function(c){S&&c&&c.forEach(function(_){var A={content:{command:_+` 2 | `},type:v};n&&n.send(JSON.stringify(A))})}}}),(0,a.useEffect)(function(){return t=new m.Terminal(M.X),o=new f.FitAddon,t.loadAddon(o),t.open(document.getElementById("terminal-container-console")),o.fit(),(0,g.pe)({groupName:"setting",name:"themeConsoleConfig"}).then(function(s){s.data&&s.data.fontSize&&Object.assign(t.options,s.data)}),function(){P()}},[]),(0,a.useEffect)(function(){window.addEventListener("resize",function(){o.fit();var s={size:{width:t.cols,height:t.rows},type:v};v&&n&&n.send(JSON.stringify(s))})},[v]);function P(){i&&(t.reset(),o.fit(),t.focus(),i.dispose()),n&&(n.close(),n=null)}return(0,h.jsx)("div",{style:{height:(y=B.height)!==null&&y!==void 0?y:"100vh"},id:"terminal-container-console"})});u.Z=U},12323:function(C,u,e){e.d(u,{X:function(){return d},n:function(){return l}});var d={convertEol:!0,fontFamily:'Menlo, Monaco, "Courier New", monospace',fontWeight:400,fontSize:16,lineHeight:1,cursorStyle:"block",cursorInactiveStyle:"outline",cursorBlink:!0,letterSpacing:0,theme:{foreground:"#bfbfbf",cursor:"gray",selectionForeground:"#ffffff"}},l=["black","red","green","yellow","blue","magenta","cyan","white","brightBlack","brightRed","brightGreen","brightYellow","brightBlue","brightMagenta","brightCyan","brightWhite"]},67359:function(C,u,e){e.r(u),e.d(u,{default:function(){return g}});var d=e(5574),l=e.n(d),a=e(49610),O=e(87662),f=e(82346),D=e(67294),M=e(85893);function g(){var m=(0,f.useParams)(),W=(0,f.useSearchParams)(),T=l()(W,2),h=T[0],t=T[1],o=(0,D.useRef)();return(0,D.useEffect)(function(){m.id&&(0,O.K3)({name:m.id}).then(function(n){var i;window.document.title="".concat(n.data.name," - console"),(i=o.current)===null||i===void 0||i.start("/console/host/".concat(n.data.name),{cmd:h.get("cmd"),workDir:h.get("workDir")})}),document.body.style.overflow="hidden"},[]),(0,M.jsx)(a.Z,{ref:o})}}}]); 3 | -------------------------------------------------------------------------------- /asset/static/p__docker__index.3b1c9bfc.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9290],{64464:function(f,a,e){e.r(a),e.d(a,{default:function(){return r}});var n=e(82346),o=e(67294),s=e(85893);function r(){var t=(0,n.useAccess)(),c=(0,n.useNavigate)(),u=(0,n.useLocation)();return(0,o.useEffect)(function(){u.pathname=="/docker"&&(t.canSeeDockerVolume&&c("/docker/volume"),t.canSeeDockerNetwork&&c("/docker/network"))},[u.pathname]),(0,s.jsx)(n.Outlet,{})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__image__index.e0ed1996.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1764],{15888:function(f,n,u){u.r(n),u.d(n,{default:function(){return a}});var t=u(82346),s=u(85893);function a(){return(0,s.jsx)(t.Outlet,{})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__system__index.e144e227.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5071],{44775:function(f,o,a){a.r(o),a.d(o,{default:function(){return c}});var t=a(82346),m=a(67294),i=a(85893);function c(){var s=(0,t.useAccess)(),e=(0,t.useNavigate)(),n=(0,t.useLocation)();return(0,m.useEffect)(function(){(n.pathname=="/"||n.pathname=="/home")&&(s.canSeeEnableHomeTag?e("/home/tag"):s.canSeeHome?e("/home/overview"):s.canSeeComposeStore?e("/compose/appstore"):s.canSeeComposeList?e("/compose/list"):s.canSeeContainerList?e("/app/list"):e("/home/overview")),n.pathname=="/system"&&(s.canSeeSystemBasic?e("/system/basic"):s.canSeeSystemEnv?e("/system/env"):s.canSeeSystemAppstore?e("/system/appstore"):e("/"))},[n.pathname]),(0,i.jsx)(t.Outlet,{})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/p__user__oauth__callback.92e13b57.async.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2822],{76951:function(f,u,e){e.r(u),e.d(u,{default:function(){return P}});var l=e(5574),d=e.n(l),i=e(68602),s=e(82346),E=e(22181),m=e(28036),c=e(67294),_=e(85893);function P(){var B=(0,s.useSearchParams)(),r=d()(B,2),h=r[0],O=r[1],D=(0,s.useParams)(),a=(0,s.useModel)("@@initialState"),v=a.initialState,A=a.loading,T=a.error,M=a.refresh,C=a.setInitialState;return(0,c.useEffect)(function(){var t=D.name,n=h.get("code");!t||!n||(0,i.X6)({name:t,code:n}).then(function(o){if(o.data.accessToken){localStorage.setItem("token",o.data.accessToken),M(),window.location.href="/dpanel/ui";return}else return!1})},[]),(0,_.jsx)(E.ZP,{title:"\u200B\u200B\u60A8\u6B63\u5728\u6388\u6743\u5BB9\u5668\u7BA1\u7406\u5E94\u7528\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u200B\u200B",extra:(0,_.jsx)(m.ZP,{type:"primary",loading:!0,children:"\u6B63\u5728\u767B\u5F55\u4E2D..."},"console")})}}}]); 2 | -------------------------------------------------------------------------------- /asset/static/t__plugin-layout__Layout.5012e1ab.chunk.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 480px){.umi-plugin-layout-container{width:100%!important}.umi-plugin-layout-container>*{border-radius:0!important}}.umi-plugin-layout-menu .anticon{margin-right:8px}.umi-plugin-layout-menu .ant-dropdown-menu-item{min-width:160px}.umi-plugin-layout-right{display:flex!important;float:right;height:100%;margin-left:auto;overflow:hidden}.umi-plugin-layout-right .umi-plugin-layout-action{display:flex;align-items:center;height:100%;padding:0 12px;cursor:pointer;transition:all .3s}.umi-plugin-layout-right .umi-plugin-layout-action>i{color:#ffffffd9;vertical-align:middle}.umi-plugin-layout-right .umi-plugin-layout-action:hover,.umi-plugin-layout-right .umi-plugin-layout-action.opened{background:#00000006}.umi-plugin-layout-right .umi-plugin-layout-search{padding:0 12px}.umi-plugin-layout-right .umi-plugin-layout-search:hover{background:transparent}.umi-plugin-layout-name{margin-left:8px}.umi-plugin-layout-name.umi-plugin-layout-hide-avatar-img{margin-left:0} 2 | -------------------------------------------------------------------------------- /asset/static/umi.277b1e59.css: -------------------------------------------------------------------------------- 1 | html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6} 2 | -------------------------------------------------------------------------------- /common/accessor/backup_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | type BackupSettingOption struct { 4 | BackupTargetType string `json:"backupTargetType"` // 兼容旧的数据,将来统一都是 snapshot 类型 5 | BackupTar string `json:"backupTar,omitempty"` // tar 包的位置 6 | BackupPath string `json:"backupPath,omitempty"` // 废弃,快照默认放到 /dpanel/backup 目录下,不支持保存至主机 7 | VolumePathList []string `json:"volumePathList"` 8 | Size int64 `json:"size"` 9 | Error string `json:"error,omitempty"` 10 | Status int `json:"status,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /common/accessor/compose_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/service/docker" 5 | "github.com/donknap/dpanel/common/service/storage" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | const ( 11 | ComposeTypeText = "text" 12 | ComposeTypeRemoteUrl = "remoteUrl" 13 | ComposeTypeStoragePath = "storagePath" 14 | ComposeTypeOutPath = "outPath" 15 | ComposeTypeDangling = "dangling" 16 | ComposeTypeStore = "store" 17 | ComposeStatusWaiting = "waiting" 18 | ComposeStatusDeploying = "deploying" 19 | ComposeStatusError = "error" 20 | ) 21 | 22 | type ComposeSettingOption struct { 23 | Status string `json:"status,omitempty"` 24 | Type string `json:"type"` 25 | Uri []string `json:"uri,omitempty"` 26 | RemoteUrl string `json:"remoteUrl,omitempty"` 27 | Store string `json:"store,omitempty"` 28 | Environment []docker.EnvItem `json:"environment,omitempty"` 29 | DockerEnvName string `json:"dockerEnvName,omitempty"` 30 | DeployServiceName []string `json:"deployServiceName,omitempty"` 31 | CreatedAt string `json:"createdAt,omitempty"` 32 | UpdatedAt string `json:"updatedAt,omitempty"` 33 | Message string `json:"message,omitempty"` 34 | } 35 | 36 | func (self ComposeSettingOption) GetUriFilePath() string { 37 | if self.Type == ComposeTypeOutPath { 38 | return self.Uri[0] 39 | } 40 | return filepath.Join(self.GetWorkingDir(), self.Uri[0]) 41 | } 42 | 43 | func (self ComposeSettingOption) GetWorkingDir() string { 44 | if self.DockerEnvName == docker.DefaultClientName { 45 | return storage.Local{}.GetComposePath() 46 | } else { 47 | return filepath.Join(filepath.Dir(storage.Local{}.GetComposePath()), "compose-"+self.DockerEnvName) 48 | } 49 | } 50 | 51 | func (self ComposeSettingOption) GetYaml() ([2]string, error) { 52 | yaml := [2]string{ 53 | "", "", 54 | } 55 | for i, uri := range self.Uri { 56 | yamlFilePath := "" 57 | if self.Type == ComposeTypeOutPath { 58 | // 外部路径分两种,一种是原目录挂载,二是将Yaml文件放置到存储目录中 59 | if filepath.IsAbs(uri) { 60 | yamlFilePath = uri 61 | } else { 62 | yamlFilePath = filepath.Join(self.GetWorkingDir(), uri) 63 | } 64 | } else { 65 | yamlFilePath = filepath.Join(self.GetWorkingDir(), uri) 66 | } 67 | content, err := os.ReadFile(yamlFilePath) 68 | if err == nil { 69 | yaml[i] = string(content) 70 | } 71 | } 72 | return yaml, nil 73 | } 74 | -------------------------------------------------------------------------------- /common/accessor/cron_log_value_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import "time" 4 | 5 | type CronLogValueOption struct { 6 | Error string `json:"error,omitempty"` 7 | Message string `json:"message,omitempty"` 8 | RunTime time.Time `json:"runTime,omitempty"` 9 | UseTime float64 `json:"useTime,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /common/accessor/cron_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "fmt" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | "github.com/robfig/cron/v3" 7 | "time" 8 | ) 9 | 10 | const ( 11 | CronUnitPreWeek = "preWeek" 12 | CronUnitPreMonth = "preMonth" 13 | CronUnitPreDay = "preDay" 14 | CronUnitPreHour = "preHour" 15 | CronUnitPreAtDay = "preAtDay" 16 | CronUnitPreAtHour = "preAtHour" 17 | CronUnitPreAtMinute = "preAtMinute" 18 | CronUnitPreAtSecond = "preAtSecond" 19 | CronUnitCode = "code" 20 | ) 21 | 22 | type CronSettingExpression struct { 23 | Unit string `json:"unit"` 24 | Code string `json:"code,omitempty"` 25 | Seconds string `json:"seconds,omitempty"` 26 | Minutes string `json:"minutes,omitempty"` 27 | Hours string `json:"hours,omitempty"` 28 | DayOfMonth string `json:"dayOfMonth,omitempty"` 29 | Month string `json:"month,omitempty"` 30 | DayOfWeek string `json:"dayOfWeek,omitempty"` 31 | } 32 | 33 | func (self CronSettingExpression) ToString() string { 34 | switch self.Unit { 35 | case CronUnitPreWeek: 36 | return fmt.Sprintf("0 %s %s * * %s", self.Minutes, self.Hours, self.DayOfWeek) 37 | case CronUnitPreMonth: 38 | return fmt.Sprintf("0 %s %s %s * *", self.Minutes, self.Hours, self.DayOfMonth) 39 | case CronUnitPreDay: 40 | return fmt.Sprintf("0 %s %s * * *", self.Minutes, self.Hours) 41 | case CronUnitPreHour: 42 | return fmt.Sprintf("0 %s * * * *", self.Minutes) 43 | case CronUnitPreAtDay: 44 | return fmt.Sprintf("0 %s %s */%s * *", self.Minutes, self.Hours, self.DayOfMonth) 45 | case CronUnitPreAtHour: 46 | return fmt.Sprintf("0 %s 0-23/%s * * *", self.Minutes, self.Hours) 47 | case CronUnitPreAtMinute: 48 | return fmt.Sprintf("0 */%s * * * *", self.Minutes) 49 | case CronUnitPreAtSecond: 50 | return fmt.Sprintf("*/%s * * * * *", self.Seconds) 51 | case CronUnitCode: 52 | return self.Code 53 | } 54 | return "0 0 0 * * *" 55 | } 56 | 57 | type CronSettingOption struct { 58 | NextRunTime []time.Time `json:"nextRunTime,omitempty"` 59 | Expression []CronSettingExpression `json:"expression,omitempty"` 60 | ContainerName string `json:"containerName,omitempty"` 61 | Script string `json:"script,omitempty"` 62 | JobIds []cron.EntryID `json:"jobIds,omitempty"` 63 | Environment []docker.EnvItem `json:"environment,omitempty"` 64 | EnableRunBlock bool `json:"enableRunBlock,omitempty"` 65 | KeepLogTotal int `json:"keepLogTotal,omitempty"` 66 | Disable bool `json:"disable,omitempty"` 67 | DockerEnvName string `json:"dockerEnvName,omitempty"` 68 | } 69 | -------------------------------------------------------------------------------- /common/accessor/image_info_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "github.com/docker/docker/api/types/image" 7 | "github.com/donknap/dpanel/common/service/docker" 8 | "log/slog" 9 | ) 10 | 11 | type ImageInfoOption struct { 12 | Id string `json:"id"` 13 | Info image.InspectResponse `json:"info"` 14 | } 15 | 16 | func (c ImageInfoOption) Value() (driver.Value, error) { 17 | return c.Id, nil 18 | } 19 | 20 | func (c *ImageInfoOption) Scan(value interface{}) error { 21 | if value == nil { 22 | return nil 23 | } 24 | id, ok := value.(string) 25 | if !ok { 26 | return fmt.Errorf("value is not string id, value: %v", value) 27 | } 28 | if id == "" { 29 | slog.Debug("tag not found") 30 | return nil 31 | } 32 | c.Id = id 33 | imageInfo, err := docker.Sdk.Client.ImageInspect(docker.Sdk.Ctx, id) 34 | if err != nil { 35 | slog.Debug(err.Error()) 36 | return nil 37 | } 38 | c.Info = imageInfo 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /common/accessor/image_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | type ImageSettingOption struct { 4 | BuildGit string `json:"buildGit"` 5 | BuildDockerfile string `json:"buildDockerfile"` 6 | BuildRoot string `json:"buildRoot"` 7 | Platform string `json:"platform"` 8 | Registry string `json:"registry"` 9 | } 10 | -------------------------------------------------------------------------------- /common/accessor/permission_value_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | type DataPermissionItem struct { 4 | Name string `json:"name"` 5 | Uri string `json:"uri" binding:"required"` 6 | Key string `json:"key,omitempty" binding:"required"` 7 | Value []interface{} `json:"value,omitempty" binding:"required"` 8 | ShowKey string `json:"showKey,omitempty"` 9 | } 10 | 11 | type ResourcePermissionItem struct { 12 | Name string `json:"name" binding:"required"` 13 | Uri string `json:"uri" binding:"required"` 14 | Limit int64 `json:"limit" binding:"required"` 15 | Created int64 `json:"created"` 16 | } 17 | 18 | type MenuPermission struct { 19 | Permissions map[string]struct{} `json:"permissions,omitempty"` 20 | Uris map[string]struct{} `json:"uris,omitempty"` 21 | } 22 | 23 | type DataPermission struct { 24 | Permissions map[string]DataPermissionItem `json:"permissions,omitempty"` 25 | } 26 | 27 | type ResourcesPermission struct { 28 | Permissions map[string]ResourcePermissionItem `json:"permissions,omitempty"` 29 | } 30 | 31 | type PermissionValueOption struct { 32 | MenuPermission *MenuPermission `json:"menuPermission,omitempty"` 33 | DataPermission *DataPermission `json:"dataPermission,omitempty"` 34 | ResourcesPermission *ResourcesPermission `json:"resources,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /common/accessor/registry_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | type RegistrySettingOption struct { 4 | Username string `json:"username"` 5 | Password string `json:"password"` 6 | Email string `json:"email"` 7 | Proxy []string `json:"proxy"` 8 | EnableHttp bool `json:"enableHttp"` 9 | } 10 | -------------------------------------------------------------------------------- /common/accessor/site_container_info_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "github.com/docker/docker/api/types/container" 5 | ) 6 | 7 | type SiteContainerInfoOption struct { 8 | Id string 9 | Info container.InspectResponse 10 | } 11 | -------------------------------------------------------------------------------- /common/accessor/site_domain_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "html/template" 5 | ) 6 | 7 | type SiteDomainSettingOption struct { 8 | ServerName string `json:"serverName"` 9 | ServerNameAlias []string `json:"serverNameAlias,omitempty"` 10 | ServerAddress string `json:"serverAddress"` 11 | ServerProtocol string `json:"serverProtocol"` 12 | TargetName string `json:"targetName"` 13 | Port int32 `json:"port"` 14 | EnableBlockCommonExploits bool `json:"enableBlockCommonExploits"` 15 | EnableAssetCache bool `json:"enableAssetCache"` 16 | EnableWs bool `json:"enableWs"` 17 | EnableSSL bool `json:"enableSSL"` 18 | ExtraNginx template.HTML `json:"extraNginx,omitempty"` 19 | Email string `json:"email,omitempty"` 20 | SslCrt string `json:"sslCrt,omitempty"` 21 | SslKey string `json:"sslKey,omitempty"` 22 | CertName string `json:"certName,omitempty"` 23 | } 24 | -------------------------------------------------------------------------------- /common/accessor/site_env_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/service/docker" 5 | ) 6 | 7 | type SiteEnvOption struct { 8 | DockerEnvName string `json:"dockerEnvName,omitempty"` 9 | Name string `json:"name,omitempty"` 10 | Hostname string `json:"hostname,omitempty"` 11 | ContainerName string `json:"containerName,omitempty"` 12 | Environment []docker.EnvItem `json:"environment,omitempty"` 13 | Links []docker.LinkItem `json:"links,omitempty"` 14 | Ports []docker.PortItem `json:"ports,omitempty"` 15 | Volumes []docker.VolumeItem `json:"volumes,omitempty"` 16 | Network []docker.NetworkItem `json:"network,omitempty"` 17 | ImageName string `json:"imageName,omitempty"` // 非表单提交 18 | ImageId string `json:"imageId,omitempty"` // 非表单提交 19 | Privileged bool `json:"privileged,omitempty"` 20 | AutoRemove bool `json:"autoRemove,omitempty"` 21 | Restart string `json:"restart,omitempty"` 22 | Cpus float32 `json:"cpus,omitempty"` 23 | Memory int `json:"memory,omitempty"` 24 | ShmSize string `json:"shmsize,omitempty"` 25 | WorkDir string `json:"workDir,omitempty"` 26 | User string `json:"user,omitempty"` 27 | Command string `json:"command,omitempty"` 28 | Entrypoint string `json:"entrypoint,omitempty"` 29 | UseHostNetwork bool `json:"useHostNetwork,omitempty"` 30 | BindIpV6 bool `json:"bindIpV6,omitempty"` 31 | Log *docker.LogDriverItem `json:"log,omitempty"` 32 | Dns []string `json:"dns,omitempty"` 33 | Label []docker.ValueItem `json:"label,omitempty"` 34 | PublishAllPorts bool `json:"publishAllPorts,omitempty"` 35 | ExtraHosts []docker.ValueItem `json:"extraHosts,omitempty"` 36 | IpV4 *docker.NetworkCreateItem `json:"ipV4,omitempty"` 37 | IpV6 *docker.NetworkCreateItem `json:"ipV6,omitempty"` 38 | Device []docker.DeviceItem `json:"device,omitempty"` 39 | Gpus *docker.GpusItem `json:"gpus,omitempty"` 40 | Hook *docker.HookItem `json:"hook,omitempty"` 41 | Healthcheck *docker.HealthcheckItem `json:"healthcheck,omitempty"` 42 | HostPid bool `json:"hostPid,omitempty"` 43 | CapAdd []string `json:"capAdd,omitempty"` 44 | } 45 | -------------------------------------------------------------------------------- /common/accessor/store_setting_option.go: -------------------------------------------------------------------------------- 1 | package accessor 2 | 3 | import "github.com/donknap/dpanel/common/service/docker" 4 | 5 | var ( 6 | StoreTypeOnePanel = "1panel" 7 | StoreTypeOnePanelLocal = "1panel-local" 8 | StoreTypePortainer = "portainer" 9 | StoreTypeCasaOs = "casaos" 10 | ) 11 | 12 | type StoreAppItem struct { 13 | Title string `json:"title"` 14 | Name string `json:"name"` 15 | Logo string `json:"logo"` 16 | Content string `json:"content"` 17 | Description string `json:"description"` 18 | Tag []string `json:"tag"` 19 | Website string `json:"website"` 20 | Version map[string]StoreAppVersionItem `json:"version"` 21 | } 22 | 23 | type StoreAppVersionScriptItem struct { 24 | Install string `json:"install,omitempty"` 25 | Uninstall string `json:"uninstall,omitempty"` 26 | Upgrade string `json:"upgrade,omitempty"` 27 | } 28 | 29 | type StoreAppVersionItem struct { 30 | Name string `json:"name"` 31 | ComposeFile string `json:"composeFile" yaml:"composeFile"` 32 | Environment []docker.EnvItem `json:"environment,omitempty"` 33 | Script *StoreAppVersionScriptItem `json:"script,omitempty"` 34 | } 35 | 36 | type StoreSettingOption struct { 37 | Type string `json:"type,omitempty"` 38 | Url string `json:"url,omitempty"` 39 | Apps []StoreAppItem `json:"apps,omitempty"` 40 | UpdatedAt int64 `json:"updatedAt,omitempty"` 41 | } 42 | -------------------------------------------------------------------------------- /common/entity/ims_backup.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/donknap/dpanel/common/accessor" 11 | ) 12 | 13 | const TableNameBackup = "ims_backup" 14 | 15 | // Backup mapped from table 16 | type Backup struct { 17 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 18 | ContainerID string `gorm:"column:container_id" json:"containerId"` 19 | Setting *accessor.BackupSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 20 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` 21 | } 22 | 23 | // TableName Backup's table name 24 | func (*Backup) TableName() string { 25 | return TableNameBackup 26 | } 27 | -------------------------------------------------------------------------------- /common/entity/ims_compose.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameCompose = "ims_compose" 12 | 13 | // Compose mapped from table 14 | type Compose struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Name string `gorm:"column:name" json:"name"` 17 | Title string `gorm:"column:title" json:"title"` 18 | Setting *accessor.ComposeSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 19 | } 20 | 21 | // TableName Compose's table name 22 | func (*Compose) TableName() string { 23 | return TableNameCompose 24 | } 25 | -------------------------------------------------------------------------------- /common/entity/ims_cron.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameCron = "ims_cron" 12 | 13 | // Cron mapped from table 14 | type Cron struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Title string `gorm:"column:title" json:"title"` 17 | Setting *accessor.CronSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 18 | } 19 | 20 | // TableName Cron's table name 21 | func (*Cron) TableName() string { 22 | return TableNameCron 23 | } 24 | -------------------------------------------------------------------------------- /common/entity/ims_cron_log.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameCronLog = "ims_cron_log" 12 | 13 | // CronLog mapped from table 14 | type CronLog struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | CronID int32 `gorm:"column:cron_id" json:"cronId"` 17 | Value *accessor.CronLogValueOption `gorm:"column:value;serializer:json" json:"value"` 18 | } 19 | 20 | // TableName CronLog's table name 21 | func (*CronLog) TableName() string { 22 | return TableNameCronLog 23 | } 24 | -------------------------------------------------------------------------------- /common/entity/ims_event.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | const TableNameEvent = "ims_event" 8 | 9 | // Event mapped from table 10 | type Event struct { 11 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 12 | Type string `gorm:"column:type" json:"type"` 13 | Action string `gorm:"column:action" json:"action"` 14 | Message string `gorm:"column:message" json:"message"` 15 | CreatedAt string `gorm:"column:created_at" json:"createdAt"` 16 | } 17 | 18 | // TableName Event's table name 19 | func (*Event) TableName() string { 20 | return TableNameEvent 21 | } 22 | -------------------------------------------------------------------------------- /common/entity/ims_image.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameImage = "ims_image" 12 | 13 | // Image mapped from table 14 | type Image struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Tag string `gorm:"column:tag" json:"tag"` 17 | Title string `gorm:"column:title" json:"title"` 18 | Setting *accessor.ImageSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 19 | ImageInfo *accessor.ImageInfoOption `gorm:"column:image_info" json:"imageInfo"` 20 | BuildType string `gorm:"column:build_type" json:"buildType"` 21 | Status int32 `gorm:"column:status" json:"status"` 22 | Message string `gorm:"column:message" json:"message"` 23 | } 24 | 25 | // TableName Image's table name 26 | func (*Image) TableName() string { 27 | return TableNameImage 28 | } 29 | -------------------------------------------------------------------------------- /common/entity/ims_notice.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | const TableNameNotice = "ims_notice" 12 | 13 | // Notice mapped from table 14 | type Notice struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Type string `gorm:"column:type" json:"type"` 17 | Title string `gorm:"column:title" json:"title"` 18 | Message string `gorm:"column:message" json:"message"` 19 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` 20 | } 21 | 22 | // TableName Notice's table name 23 | func (*Notice) TableName() string { 24 | return TableNameNotice 25 | } 26 | -------------------------------------------------------------------------------- /common/entity/ims_registry.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameRegistry = "ims_registry" 12 | 13 | // Registry mapped from table 14 | type Registry struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Title string `gorm:"column:title" json:"title"` 17 | ServerAddress string `gorm:"column:server_address" json:"serverAddress"` 18 | Setting *accessor.RegistrySettingOption `gorm:"column:setting;serializer:json" json:"setting"` 19 | } 20 | 21 | // TableName Registry's table name 22 | func (*Registry) TableName() string { 23 | return TableNameRegistry 24 | } 25 | -------------------------------------------------------------------------------- /common/entity/ims_setting.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameSetting = "ims_setting" 12 | 13 | // Setting mapped from table 14 | type Setting struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | GroupName string `gorm:"column:group_name" json:"groupName"` 17 | Name string `gorm:"column:name" json:"name"` 18 | Value *accessor.SettingValueOption `gorm:"column:value;serializer:json" json:"value"` 19 | } 20 | 21 | // TableName Setting's table name 22 | func (*Setting) TableName() string { 23 | return TableNameSetting 24 | } 25 | -------------------------------------------------------------------------------- /common/entity/ims_site.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | const TableNameSite = "ims_site" 13 | 14 | // Site mapped from table 15 | type Site struct { 16 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 17 | SiteTitle string `gorm:"column:site_title" json:"siteTitle"` 18 | SiteName string `gorm:"column:site_name" json:"siteName"` 19 | Env *accessor.SiteEnvOption `gorm:"column:env;serializer:json" json:"env"` 20 | ContainerInfo *accessor.SiteContainerInfoOption `gorm:"column:container_info;serializer:json" json:"containerInfo"` 21 | Status int32 `gorm:"column:status" json:"status"` 22 | StatusStep string `gorm:"column:status_step" json:"statusStep"` 23 | Message string `gorm:"column:message" json:"message"` 24 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deletedAt"` 25 | } 26 | 27 | // TableName Site's table name 28 | func (*Site) TableName() string { 29 | return TableNameSite 30 | } 31 | -------------------------------------------------------------------------------- /common/entity/ims_site_domain.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/donknap/dpanel/common/accessor" 11 | ) 12 | 13 | const TableNameSiteDomain = "ims_site_domain" 14 | 15 | // SiteDomain mapped from table 16 | type SiteDomain struct { 17 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 18 | ContainerID string `gorm:"column:container_id" json:"containerId"` 19 | ServerName string `gorm:"column:server_name" json:"serverName"` 20 | Setting *accessor.SiteDomainSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 21 | CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` 22 | } 23 | 24 | // TableName SiteDomain's table name 25 | func (*SiteDomain) TableName() string { 26 | return TableNameSiteDomain 27 | } 28 | -------------------------------------------------------------------------------- /common/entity/ims_store.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameStore = "ims_store" 12 | 13 | // Store mapped from table 14 | type Store struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Title string `gorm:"column:title" json:"title"` 17 | Name string `gorm:"column:name" json:"name"` 18 | Setting *accessor.StoreSettingOption `gorm:"column:setting;serializer:json" json:"setting"` 19 | } 20 | 21 | // TableName Store's table name 22 | func (*Store) TableName() string { 23 | return TableNameStore 24 | } 25 | -------------------------------------------------------------------------------- /common/entity/ims_user_permission.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/donknap/dpanel/common/accessor" 9 | ) 10 | 11 | const TableNameUserPermission = "ims_user_permission" 12 | 13 | // UserPermission mapped from table 14 | type UserPermission struct { 15 | ID int32 `gorm:"column:id;primaryKey" json:"id"` 16 | Username string `gorm:"column:username" json:"username"` 17 | Value *accessor.PermissionValueOption `gorm:"column:value;serializer:json" json:"value"` 18 | } 19 | 20 | // TableName UserPermission's table name 21 | func (*UserPermission) TableName() string { 22 | return TableNameUserPermission 23 | } 24 | -------------------------------------------------------------------------------- /common/function/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donknap/dpanel/bc799e806157a4a843f130e63e7c3689235b5ab5/common/function/.keep -------------------------------------------------------------------------------- /common/function/array.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "cmp" 5 | "reflect" 6 | ) 7 | 8 | func IsEmptyArray[T interface{}](v []T) bool { 9 | if v == nil { 10 | return true 11 | } 12 | if len(v) == 0 { 13 | return true 14 | } 15 | return false 16 | } 17 | 18 | func InArray[T cmp.Ordered](v []T, item T) bool { 19 | if v == nil { 20 | return false 21 | } 22 | for _, t := range v { 23 | if t == item { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | func InArrayArray[T cmp.Ordered](v []T, item ...T) bool { 31 | if v == nil { 32 | return false 33 | } 34 | for _, t := range item { 35 | if found := InArray(v, t); found { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | func InArrayWalk[T interface{}](v []T, walk func(i T) bool) bool { 43 | exists, _ := IndexArrayWalk(v, walk) 44 | return exists 45 | } 46 | 47 | func IndexArrayWalk[T interface{}](v []T, walk func(i T) bool) (exists bool, index int) { 48 | if v == nil { 49 | return false, 0 50 | } 51 | for i, t := range v { 52 | if walk(t) { 53 | return true, i 54 | } 55 | } 56 | return false, 0 57 | } 58 | 59 | func PluckArrayWalk[T interface{}, R interface{}](v []T, walk func(i T) (R, bool)) []R { 60 | result := make([]R, 0) 61 | for _, item := range v { 62 | newItem, ok := walk(item) 63 | if ok { 64 | result = append(result, newItem) 65 | } 66 | } 67 | return result 68 | } 69 | 70 | func FindArrayValueIndex(items interface{}, value ...interface{}) (exists bool, pos []int) { 71 | pos = make([]int, 0) 72 | some := reflect.ValueOf(items) 73 | 74 | switch some.Kind() { 75 | case reflect.Slice, reflect.Array: 76 | for i := 0; i < some.Len(); i++ { 77 | if len(value) == 1 { 78 | if reflect.DeepEqual(some.Index(i).Interface(), value[0]) { 79 | pos = append(pos, i) 80 | } 81 | } else { 82 | someStruct := some.Index(i) 83 | if someStruct.Kind() == reflect.Ptr { 84 | someStruct = someStruct.Elem() 85 | } 86 | if someStruct.Kind() == reflect.Struct { 87 | fieldName, ok := value[0].(string) 88 | someValue := someStruct.FieldByName(fieldName) 89 | if ok { 90 | if someValue.Kind() == reflect.Slice || someValue.Kind() == reflect.Array { 91 | exists1, _ := FindArrayValueIndex(someValue.Interface(), value[1:]...) 92 | if exists1 { 93 | return exists1, append(pos, i) 94 | } 95 | } else { 96 | for j := 1; j < len(value)-1; j++ { 97 | fieldName, ok = value[j].(string) 98 | someValue = someValue.FieldByName(fieldName) 99 | } 100 | if reflect.DeepEqual(someValue.Interface(), value[len(value)-1]) { 101 | pos = append(pos, i) 102 | } 103 | } 104 | } else { 105 | return false, pos 106 | } 107 | } else { 108 | return false, pos 109 | } 110 | } 111 | } 112 | default: 113 | return false, pos 114 | } 115 | if len(pos) > 0 { 116 | return true, pos 117 | } else { 118 | return false, nil 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /common/function/byte.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | // 根据回调函数清除bytes中的字符 4 | 5 | func BytesCleanFunc[T byte | rune](data []T, callback func(b T) bool) []T { 6 | var result []T 7 | for _, b := range data { 8 | if !callback(b) { 9 | result = append(result, b) 10 | } 11 | } 12 | return result 13 | } 14 | -------------------------------------------------------------------------------- /common/function/date.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | var ( 4 | ShowYmdHis = "2006-01-02 15:04:05" 5 | YmdHis = "20060102150405" 6 | ) 7 | -------------------------------------------------------------------------------- /common/function/encrypt.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/hex" 8 | "fmt" 9 | ) 10 | 11 | const CommonKey = "DPanelCommonAseKey20231208" 12 | 13 | func AseEncode(key string, str string) (result string, err error) { 14 | key = GetMd5(CommonKey + key) 15 | block, err := aes.NewCipher([]byte(key)) 16 | if err != nil { 17 | return result, err 18 | } 19 | blockSize := block.BlockSize() 20 | origStr := PKCS5Padding([]byte(str), blockSize) 21 | blockMode := cipher.NewCBCEncrypter(block, []byte(key)[:blockSize]) 22 | crypted := make([]byte, len(origStr)) 23 | blockMode.CryptBlocks(crypted, origStr) 24 | return hex.EncodeToString(crypted), nil 25 | } 26 | 27 | func AseDecode(key string, str string) (result string, err error) { 28 | decodeStr, err := hex.DecodeString(str) 29 | key = GetMd5(CommonKey + key) 30 | block, err := aes.NewCipher([]byte(key)) 31 | if err != nil { 32 | return result, err 33 | } 34 | blockSize := block.BlockSize() 35 | blockMode := cipher.NewCBCDecrypter(block, []byte(key)[:blockSize]) 36 | origData := make([]byte, len(decodeStr)) 37 | blockMode.CryptBlocks(origData, decodeStr) 38 | origData = PKCS5UnPadding(origData) 39 | return string(origData), nil 40 | } 41 | 42 | func PKCS5Padding(plaintext []byte, blockSize int) []byte { 43 | padding := blockSize - len(plaintext)%blockSize 44 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 45 | return append(plaintext, padtext...) 46 | } 47 | 48 | func PKCS5UnPadding(origData []byte) []byte { 49 | length := len(origData) 50 | unpadding := int(origData[length-1]) 51 | return origData[:(length - unpadding)] 52 | } 53 | 54 | func URIEncodeComponent(s string, excluded ...[]byte) string { 55 | var b bytes.Buffer 56 | written := 0 57 | for i, n := 0, len(s); i < n; i++ { 58 | c := s[i] 59 | switch c { 60 | case '-', '_', '.', '!', '~', '*', '\'', '(', ')': 61 | continue 62 | default: 63 | // Unreserved according to RFC 3986 sec 2.3 64 | if 'a' <= c && c <= 'z' { 65 | continue 66 | } 67 | if 'A' <= c && c <= 'Z' { 68 | continue 69 | } 70 | if '0' <= c && c <= '9' { 71 | continue 72 | } 73 | if len(excluded) > 0 { 74 | conti := false 75 | for _, ch := range excluded[0] { 76 | if ch == c { 77 | conti = true 78 | break 79 | } 80 | } 81 | if conti { 82 | continue 83 | } 84 | } 85 | } 86 | b.WriteString(s[written:i]) 87 | fmt.Fprintf(&b, "%%%02X", c) 88 | written = i + 1 89 | } 90 | if written == 0 { 91 | return s 92 | } 93 | b.WriteString(s[written:]) 94 | return b.String() 95 | } 96 | -------------------------------------------------------------------------------- /common/function/error.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/gin-gonic/gin" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func ErrorHasKeyword(e error, keyword ...string) bool { 12 | for _, k := range keyword { 13 | if strings.Contains(e.Error(), k) { 14 | return true 15 | } 16 | } 17 | return false 18 | } 19 | 20 | func ErrorMessage(title string, message ...string) error { 21 | jsonMessage, _ := json.Marshal(message) 22 | row := &gin.H{ 23 | "title": title, 24 | "message": string(jsonMessage), 25 | "type": "error", 26 | "createdAt": time.Now().Local(), 27 | } 28 | result, _ := json.Marshal(row) 29 | return errors.New(string(result)) 30 | } 31 | -------------------------------------------------------------------------------- /common/function/map.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "cmp" 5 | "sort" 6 | ) 7 | 8 | func IsEmptyMap[K cmp.Ordered, V interface{}](v map[K]V) bool { 9 | if v == nil { 10 | return true 11 | } 12 | if len(v) == 0 { 13 | return true 14 | } 15 | return false 16 | } 17 | 18 | func PluckMapWalkArray[K cmp.Ordered, U interface{}, R interface{}](m map[K]U, walk func(k K, v U) (R, bool)) []R { 19 | var keys []K 20 | for key := range m { 21 | keys = append(keys, key) 22 | } 23 | sort.Slice(keys, func(i, j int) bool { 24 | return keys[i] < keys[j] 25 | }) 26 | result := make([]R, 0) 27 | for _, key := range keys { 28 | newItem, ok := walk(key, m[key]) 29 | if ok { 30 | result = append(result, newItem) 31 | } 32 | } 33 | return result 34 | } 35 | -------------------------------------------------------------------------------- /common/function/ptr.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import "time" 4 | 5 | func PtrTime(v time.Time) *time.Time { 6 | return &v 7 | } 8 | 9 | func PtrString(str string) *string { 10 | return &str 11 | } 12 | 13 | func PtrBool(b bool) *bool { 14 | return &b 15 | } 16 | -------------------------------------------------------------------------------- /common/function/strings.go: -------------------------------------------------------------------------------- 1 | package function 2 | -------------------------------------------------------------------------------- /common/function/struct.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import "encoding/json" 4 | 5 | func StructToMap(obj interface{}) map[string]interface{} { 6 | b, _ := json.Marshal(obj) 7 | var m map[string]interface{} 8 | _ = json.Unmarshal(b, &m) 9 | return m 10 | } 11 | -------------------------------------------------------------------------------- /common/function/util.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha256" 6 | "errors" 7 | "fmt" 8 | "math/rand" 9 | "net" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func GetRandomString(n int) string { 15 | str := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789" 16 | bytes := []byte(str) 17 | var result []byte 18 | for i := 0; i < n; i++ { 19 | result = append(result, bytes[rand.Intn(len(bytes))]) 20 | } 21 | return string(result) 22 | } 23 | 24 | func GetMd5(str string) string { 25 | return fmt.Sprintf("%x", md5.Sum([]byte(str))) 26 | } 27 | 28 | type pathInfoOut struct { 29 | DirName string 30 | BaseName string 31 | Extension string 32 | Filename string 33 | } 34 | 35 | func GetPathInfo(path string) *pathInfoOut { 36 | filename := filepath.Base(path) 37 | ext := filepath.Ext(filename) 38 | 39 | dirname, basename := filepath.Split(path) 40 | basename = basename[:len(basename)-len(ext)] 41 | result := &pathInfoOut{} 42 | result.DirName = dirname 43 | result.BaseName = basename 44 | result.Extension = ext 45 | result.Filename = filename 46 | return result 47 | } 48 | 49 | func CheckFileAllowUpload(filename string) bool { 50 | allowFileExt := []string{ 51 | ".zip", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".cvs", 52 | ".jpg", ".png", ".jpeg", ".gif", 53 | } 54 | for _, s := range allowFileExt { 55 | if strings.HasSuffix(filename, s) { 56 | return true 57 | } 58 | } 59 | return false 60 | } 61 | 62 | func GetRootPath() string { 63 | rootPath, _ := filepath.Abs("./") 64 | return rootPath 65 | } 66 | 67 | func IpInSubnet(ipAddress, subnetAddress string) (bool, error) { 68 | ip := net.ParseIP(ipAddress) 69 | if ip == nil { 70 | return false, errors.New("错误的 ip 地址: " + ipAddress) 71 | } 72 | _, subnet, err := net.ParseCIDR(subnetAddress) 73 | if err != nil { 74 | return false, errors.New("错误的子网 CIDR 地址: " + subnetAddress) 75 | } 76 | 77 | if subnetAddress != subnet.String() { 78 | return false, errors.New("错误的子网 CIDR 地址, 应为: " + subnet.String()) 79 | } 80 | if !subnet.Contains(ip) { 81 | return false, errors.New("ip 地址与子网地址不匹配") 82 | } 83 | return true, nil 84 | } 85 | 86 | func GetSha256(str []byte) string { 87 | hash := sha256.New() 88 | hash.Write(str) 89 | return fmt.Sprintf("sha256:%x", hash.Sum(nil)) 90 | } 91 | -------------------------------------------------------------------------------- /common/function/yaml.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cast" 6 | "strings" 7 | ) 8 | 9 | type YamlGetter map[string]interface{} 10 | 11 | func (self YamlGetter) GetString(path string) string { 12 | return cast.ToString(self.getValueInterface(path)) 13 | } 14 | 15 | // GetStringSlice 16 | // 获取一个数组类型的 yaml 字段 tag 17 | // tag: 18 | // - a 19 | // - b 20 | func (self YamlGetter) GetStringSlice(path string) []string { 21 | return cast.ToStringSlice(self.toSlice(self.getValueInterface(path))) 22 | } 23 | 24 | // GetSliceStringMapString 25 | // 获取一个键值对数组 tag or tag.2.values 26 | // tag: 27 | // - name: a 28 | // age: 1 29 | // - name: b 30 | // age: 2 31 | // - name: c 32 | // age: 3 33 | // values: 34 | // - a 35 | // - b 36 | func (self YamlGetter) GetSliceStringMapString(path string) []map[string]string { 37 | result := make([]map[string]string, 0) 38 | slice := cast.ToSlice(self.toSlice(self.getValueInterface(path))) 39 | for _, item := range slice { 40 | temp := make(map[string]string) 41 | for key, value := range item.(YamlGetter) { 42 | temp[key] = cast.ToString(value) 43 | } 44 | result = append(result, temp) 45 | } 46 | return result 47 | } 48 | 49 | // GetStringMapString 50 | // 获取一个键值对 51 | // tag: 52 | // 53 | // name: a 54 | // age: 1 55 | func (self YamlGetter) GetStringMapString(path string) map[string]string { 56 | result := make(map[string]string) 57 | for key, value := range self.getValueInterface(path).(YamlGetter) { 58 | result[key] = cast.ToString(value) 59 | } 60 | return result 61 | } 62 | 63 | func (self YamlGetter) getValueInterface(path string) interface{} { 64 | if self == nil { 65 | return interface{}(nil) 66 | } 67 | 68 | current := self 69 | pathList := strings.Split(path, ".") 70 | pathLen := len(pathList) 71 | 72 | for i := 0; i < pathLen; i++ { 73 | switch t := current[pathList[i]].(type) { 74 | case []interface{}: 75 | // 断言是数组类型时,需要转换成 map 再继续下一步 76 | temp := make(YamlGetter) 77 | for j, v := range t { 78 | temp[fmt.Sprintf("%d", j)] = v 79 | } 80 | current = temp 81 | case YamlGetter: 82 | current = t 83 | default: 84 | // 类型非 map 或是 数组,直接返回数据上层再进行转换 85 | return t 86 | } 87 | if i == pathLen-1 { 88 | return current 89 | } 90 | } 91 | return interface{}(nil) 92 | } 93 | 94 | func (self YamlGetter) toSlice(data interface{}) []interface{} { 95 | if temp, ok := data.(YamlGetter); ok { 96 | result := make([]interface{}, len(temp)) 97 | for key, value := range temp { 98 | k := cast.ToInt(key) 99 | result[k] = value 100 | } 101 | return result 102 | } else { 103 | return make([]interface{}, 0) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /common/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/donknap/dpanel/app/common/logic" 7 | "github.com/donknap/dpanel/common/service/storage" 8 | "github.com/gin-gonic/gin" 9 | "github.com/golang-jwt/jwt/v5" 10 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware" 11 | "log/slog" 12 | "strings" 13 | ) 14 | 15 | type AuthMiddleware struct { 16 | middleware.Abstract 17 | } 18 | 19 | var ( 20 | ErrLogin = errors.New("请先登录") 21 | ) 22 | 23 | func (self AuthMiddleware) Process(http *gin.Context) { 24 | currentUrlPath := http.Request.URL.Path 25 | if strings.Contains(currentUrlPath, "/api/common/user/login") || 26 | strings.Contains(currentUrlPath, "/pro/home/login-info") || 27 | strings.Contains(currentUrlPath, "/api/common/user/create-founder") || 28 | strings.Contains(currentUrlPath, "/pro/user/reset-info") || 29 | strings.Contains(currentUrlPath, "/xk/user/oauth/callback") || 30 | (!strings.HasPrefix(currentUrlPath, "/api") && !strings.HasPrefix(currentUrlPath, "/ws")) { 31 | http.Next() 32 | return 33 | } 34 | 35 | var authToken = "" 36 | if strings.HasPrefix(currentUrlPath, "/ws/") { 37 | authToken = "Bearer " + http.Query("token") 38 | } else { 39 | authToken = http.GetHeader("Authorization") 40 | } 41 | 42 | if authToken == "" { 43 | self.JsonResponseWithError(http, ErrLogin, 401) 44 | http.AbortWithStatus(401) 45 | return 46 | } 47 | authCode := strings.Split(authToken, "Bearer ") 48 | if len(authCode) != 2 { 49 | self.JsonResponseWithError(http, ErrLogin, 401) 50 | http.AbortWithStatus(401) 51 | return 52 | } 53 | 54 | myUserInfo := logic.UserInfo{} 55 | jwtSecret := logic.User{}.GetJwtSecret() 56 | token, err := jwt.ParseWithClaims(authCode[1], &myUserInfo, func(t *jwt.Token) (interface{}, error) { 57 | return jwtSecret, nil 58 | }, jwt.WithValidMethods([]string{"HS256"})) 59 | if err != nil { 60 | self.JsonResponseWithError(http, ErrLogin, 401) 61 | http.AbortWithStatus(401) 62 | return 63 | } 64 | if token.Valid { 65 | if myUserInfo.AutoLogin { 66 | if _, err := new(logic.Setting).GetValueById(myUserInfo.UserId); err == nil { 67 | myUserInfo.Fd = http.GetHeader("AuthorizationFd") 68 | http.Set("userInfo", myUserInfo) 69 | http.Next() 70 | return 71 | } 72 | } else { 73 | if v, ok := storage.Cache.Get(fmt.Sprintf(storage.CacheKeyCommonUserInfo, myUserInfo.UserId)); ok { 74 | if _, ok := v.(logic.UserInfo); ok { 75 | myUserInfo.Fd = http.GetHeader("AuthorizationFd") 76 | http.Set("userInfo", myUserInfo) 77 | http.Next() 78 | return 79 | } 80 | } 81 | } 82 | slog.Debug("auth get cache user error", "jwt", authToken, "userInfo", myUserInfo) 83 | self.JsonResponseWithError(http, ErrLogin, 401) 84 | http.AbortWithStatus(401) 85 | return 86 | } 87 | self.JsonResponseWithError(http, ErrLogin, 401) 88 | http.AbortWithStatus(401) 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /common/middleware/cache.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware" 6 | http2 "net/http" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type CacheMiddleware struct { 13 | middleware.Abstract 14 | } 15 | 16 | func (self CacheMiddleware) Process(http *gin.Context) { 17 | if strings.HasPrefix(http.Request.URL.Path, "/dpanel/static") { 18 | defaultMaxAge := 604800 19 | http.Writer.Header().Add("Cache-Control", "public, max-age="+strconv.Itoa(defaultMaxAge)) 20 | http.Writer.Header().Add("Expires", time.Now().Add(time.Duration(defaultMaxAge)*time.Second).UTC().Format(http2.TimeFormat)) 21 | http.Writer.WriteHeader(304) 22 | } 23 | http.Next() 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /common/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 6 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/middleware" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | type CorsMiddleware struct { 12 | middleware.Abstract 13 | } 14 | 15 | func (self CorsMiddleware) Process(ctx *gin.Context) { 16 | if host, ok := self.isAllow(ctx); ok { 17 | ctx.Header("Access-Control-Allow-Origin", host) 18 | ctx.Header("Access-Control-Allow-Headers", "Content-Type, AccessToken, X-CSRF-Token, Authorization, ") 19 | ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") 20 | ctx.Header("Access-Control-Expose-Headers", self.getAllowHeader()) 21 | ctx.Header("Access-Control-Allow-Credentials", "true") 22 | if ctx.Request.Method == "OPTIONS" { 23 | ctx.AbortWithStatus(http.StatusNoContent) 24 | } 25 | } 26 | ctx.Next() 27 | } 28 | 29 | func (self CorsMiddleware) isAllow(ctx *gin.Context) (string, bool) { 30 | host := ctx.Request.Header.Get("origin") 31 | if host == "" { 32 | host = ctx.Request.Header.Get("referer") 33 | } 34 | if host == "" { 35 | return "", false 36 | } 37 | allowUrl := facade.GetConfig().GetStringSlice("app.cors") 38 | for _, value := range allowUrl { 39 | if value == host { 40 | return host, true 41 | } 42 | } 43 | return "", false 44 | } 45 | 46 | func (self CorsMiddleware) getAllowHeader() string { 47 | allowHeader := []string{ 48 | "Content-Length", 49 | "Content-Type", 50 | "X-Auth-Token", 51 | "Origin", 52 | "Authorization", 53 | "X-Requested-With", 54 | "x-requested-with", 55 | "x-xsrf-token", 56 | "x-csrf-token", 57 | "x-w7-from", 58 | "access-token", 59 | "Api-Version", 60 | "Access-Control-Allow-Origin", 61 | "Access-Control-Allow-Headers", 62 | "Access-Control-Allow-Methods", 63 | "authority", 64 | "uid", 65 | "uuid", 66 | } 67 | return strings.Join(allowHeader, ",") 68 | } 69 | -------------------------------------------------------------------------------- /common/migrate/upgrade-20240909.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/dao" 5 | ) 6 | 7 | type Upgrade20240909 struct{} 8 | 9 | func (self Upgrade20240909) Version() string { 10 | return "1.1.1" 11 | } 12 | 13 | func (self Upgrade20240909) Upgrade() error { 14 | composeList, _ := dao.Compose.Find() 15 | for _, compose := range composeList { 16 | if compose.Setting == nil || compose.Setting.Status == "" { 17 | continue 18 | } 19 | compose.Setting.Status = "" 20 | err := dao.Compose.Save(compose) 21 | if err != nil { 22 | return err 23 | } 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /common/migrate/upgrade-20250106.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/dao" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | "gorm.io/datatypes" 7 | "gorm.io/gen" 8 | "log/slog" 9 | ) 10 | 11 | type Upgrade20250106 struct{} 12 | 13 | func (self Upgrade20250106) Version() string { 14 | return "1.5.2" 15 | } 16 | 17 | func (self Upgrade20250106) Upgrade() error { 18 | composeList, _ := dao.Compose.Find() 19 | for _, compose := range composeList { 20 | if compose.Setting == nil || compose.Setting.DockerEnvName != "" { 21 | continue 22 | } 23 | compose.Setting.DockerEnvName = docker.DefaultClientName 24 | err := dao.Compose.Save(compose) 25 | if err != nil { 26 | return err 27 | } 28 | } 29 | query := dao.Compose.Where(gen.Cond( 30 | datatypes.JSONQuery("setting").Equals("outPath", "type"), 31 | )...) 32 | if list, err := query.Find(); err == nil && list != nil && len(list) > 0 { 33 | ids := make([]int32, 0) 34 | for _, compose := range list { 35 | ids = append(ids, compose.ID) 36 | } 37 | slog.Debug("清理 outPath 数据", "ids", ids) 38 | _, err := dao.Compose.Where(dao.Compose.ID.In(ids...)).Delete() 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /common/migrate/upgrade-20250113.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/dao" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | ) 7 | 8 | type Upgrade20250113 struct{} 9 | 10 | func (self Upgrade20250113) Version() string { 11 | return "1.5.3" 12 | } 13 | 14 | func (self Upgrade20250113) Upgrade() error { 15 | list, _ := dao.Cron.Find() 16 | for _, item := range list { 17 | if item.Setting == nil || item.Setting.DockerEnvName != "" { 18 | continue 19 | } 20 | item.Setting.DockerEnvName = docker.DefaultClientName 21 | err := dao.Cron.Save(item) 22 | if err != nil { 23 | return err 24 | } 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /common/migrate/upgrade-20250401.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/function" 5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 6 | ) 7 | 8 | type Upgrade20250401 struct{} 9 | 10 | func (self Upgrade20250401) Version() string { 11 | return "1.6.3" 12 | } 13 | 14 | func (self Upgrade20250401) Upgrade() error { 15 | // 如果获取不到数据,尝试将 container_info 中的数据变更成 json 串 16 | db, err := facade.GetDbFactory().Channel("default") 17 | if err != nil { 18 | return err 19 | } 20 | 21 | var ids []int32 22 | db.Table("ims_site").Select("id").Where("LENGTH(container_info) = ?", 64).Pluck("id", &ids) 23 | if !function.IsEmptyArray(ids) { 24 | db.Exec(`UPDATE ims_site SET container_info = CONCAT('{"Id": "', container_info, '"}') WHERE id IN (?)`, ids) 25 | } 26 | 27 | db.Table("ims_site").Select("id").Where("LENGTH(container_info) = ?", 0).Pluck("id", &ids) 28 | if !function.IsEmptyArray(ids) { 29 | db.Exec(`UPDATE ims_site SET container_info = '{}' WHERE id IN (?)`, ids) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /common/migrate/upgrade-20250521.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/function" 5 | "github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade" 6 | ) 7 | 8 | type Upgrade20250521 struct{} 9 | 10 | func (self Upgrade20250521) Version() string { 11 | return "1.7.0" 12 | } 13 | 14 | func (self Upgrade20250521) Upgrade() error { 15 | // 如果获取不到数据,尝试将 container_info 中的数据变更成 json 串 16 | db, err := facade.GetDbFactory().Channel("default") 17 | if err != nil { 18 | return err 19 | } 20 | 21 | replaceName := []map[string]string{ 22 | { 23 | "old": "Tag", 24 | "new": "tag", 25 | }, 26 | { 27 | "old": "checkContainerAllUpgrade", 28 | "new": "containerCheckAllUpgrade", 29 | }, 30 | { 31 | "old": "checkContainerIgnore", 32 | "new": "containerCheckIgnoreUpgrade", 33 | }, 34 | } 35 | 36 | for _, item := range replaceName { 37 | var ids []int32 38 | db.Table("ims_setting").Where("group_name = ? AND name = ?", "setting", item["old"]).Pluck("id", &ids) 39 | if !function.IsEmptyArray(ids) { 40 | db.Exec(`UPDATE ims_setting SET name = ? WHERE id IN (?)`, item["new"], ids) 41 | } 42 | } 43 | 44 | replaceFieldName := []map[string]string{ 45 | { 46 | "old": "themeUser", 47 | "new": "themeUserConfig", 48 | }, 49 | { 50 | "old": "theme", 51 | "new": "themeConfig", 52 | }, 53 | { 54 | "old": "ignoreCheckUpgrade", 55 | "new": "containerCheckIgnoreUpgrade", 56 | }, 57 | } 58 | 59 | for _, item := range replaceFieldName { 60 | var ids []int32 61 | db.Table("ims_setting").Where("group_name = ? AND name = ?", "setting", item["new"]).Pluck("id", &ids) 62 | if !function.IsEmptyArray(ids) { 63 | db.Exec(`UPDATE ims_setting SET value = REPLACE(value, ?, ?) WHERE id IN (?)`, item["old"], item["new"], ids) 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /common/migrate/upgrade.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | type Updater interface { 4 | Upgrade() error 5 | Version() string 6 | } 7 | -------------------------------------------------------------------------------- /common/service/acme/option.go: -------------------------------------------------------------------------------- 1 | package acme 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/function" 5 | ) 6 | 7 | type Option func(self *Acme) error 8 | 9 | func WithDomain(list ...string) Option { 10 | if function.IsEmptyArray(list) { 11 | return nil 12 | } 13 | return func(self *Acme) error { 14 | for _, d := range list { 15 | self.argv = append(self.argv, "--domain", d) 16 | } 17 | return nil 18 | } 19 | } 20 | 21 | func WithCertServer(server string) Option { 22 | if server == "" { 23 | server = "letsencrypt" 24 | } 25 | return func(self *Acme) error { 26 | self.argv = append(self.argv, "--server", server) 27 | return nil 28 | } 29 | } 30 | 31 | func WithEmail(email string) Option { 32 | return func(self *Acme) error { 33 | self.argv = append(self.argv, "--email", email) 34 | return nil 35 | } 36 | } 37 | 38 | func WithAutoUpgrade(b bool) Option { 39 | return func(self *Acme) error { 40 | if b { 41 | self.argv = append(self.argv, "--auto-upgrade", "1") 42 | } else { 43 | self.argv = append(self.argv, "--auto-upgrade", "0") 44 | } 45 | return nil 46 | } 47 | } 48 | 49 | func WithForce() Option { 50 | return func(self *Acme) error { 51 | self.argv = append(self.argv, "--force") 52 | return nil 53 | } 54 | } 55 | 56 | func WithDnsNginx() Option { 57 | return func(self *Acme) error { 58 | self.argv = append(self.argv, "--nginx") 59 | return nil 60 | } 61 | } 62 | 63 | func WithRenew() Option { 64 | return func(self *Acme) error { 65 | self.argv = append(self.argv, "--renew") 66 | return nil 67 | } 68 | } 69 | 70 | func WithIssue() Option { 71 | return func(self *Acme) error { 72 | self.argv = append(self.argv, "--issue") 73 | return nil 74 | } 75 | } 76 | 77 | func WithCertRootPath(path string) Option { 78 | return func(self *Acme) error { 79 | self.argv = append(self.argv, "--key-file", path, "--fullchain-file", path) 80 | return nil 81 | } 82 | } 83 | 84 | func WithDnsApi(apiType string, env []string) Option { 85 | return func(self *Acme) error { 86 | self.argv = append(self.argv, "--dns", apiType) 87 | self.env = append(self.env, env...) 88 | return nil 89 | } 90 | } 91 | 92 | func WithConfigHomePath(path string) Option { 93 | return func(self *Acme) error { 94 | self.argv = append(self.argv, "--config-home", path) 95 | return nil 96 | } 97 | } 98 | 99 | func WithDebug() Option { 100 | return func(self *Acme) error { 101 | self.argv = append(self.argv, "--debug") 102 | return nil 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /common/service/compose/compose.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "github.com/compose-spec/compose-go/v2/types" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type Wrapper struct { 10 | Project *types.Project 11 | } 12 | 13 | // 区别于 Project.GetService 方法,此方法会将扩展信息一起返回 14 | func (self Wrapper) GetService(name string) (types.ServiceConfig, ExtService, error) { 15 | service, err := self.Project.GetService(name) 16 | if err != nil { 17 | return types.ServiceConfig{}, ExtService{}, err 18 | } 19 | 20 | ext := ExtService{} 21 | exists, err := service.Extensions.Get(ExtensionServiceName, &ext) 22 | if err == nil && exists { 23 | return service, ext, nil 24 | } 25 | return service, ExtService{}, nil 26 | } 27 | 28 | func (self Wrapper) GetBaseCommand() []string { 29 | cmd := make([]string, 0) 30 | for _, file := range self.Project.ComposeFiles { 31 | cmd = append(cmd, "-f", file) 32 | } 33 | cmd = append(cmd, "-p", self.Project.Name) 34 | for _, envFileName := range []string{ 35 | ".env", 36 | } { 37 | envFilePath := filepath.Join(self.Project.WorkingDir, envFileName) 38 | _, err := os.Stat(envFilePath) 39 | if err == nil { 40 | cmd = append(cmd, "--env-file", envFilePath) 41 | } 42 | } 43 | return cmd 44 | } 45 | -------------------------------------------------------------------------------- /common/service/compose/extension.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import "github.com/compose-spec/compose-go/v2/types" 4 | 5 | const ExtensionServiceName = "x-dpanel-service" 6 | const ExtensionName = "x-dpanel" 7 | 8 | const VolumeTypeDPanel = "dpanel" 9 | 10 | type ExternalItem struct { 11 | VolumesFrom []string `yaml:"volumes_from,omitempty" json:"volumes_from"` 12 | Volumes []string `yaml:"volumes,omitempty" json:"volumes"` 13 | Networks map[string]*types.ServiceNetworkConfig `yaml:"networks,omitempty" json:"networks"` 14 | } 15 | 16 | type PortsItem struct { 17 | BindIPV6 bool `yaml:"bind_ipv6,omitempty" json:"bind_ipv6"` 18 | PublishAll bool `yaml:"publish_all,omitempty" json:"publish_all"` 19 | } 20 | type ExtService struct { 21 | ImageTar map[string]string `yaml:"image_tar,omitempty" json:"image_tar"` 22 | AutoRemove bool `yaml:"auto_remove,omitempty" json:"auto_remove"` 23 | External ExternalItem `yaml:"external,omitempty" json:"external"` // 关联外部容器资源 24 | Ports PortsItem `yaml:"ports,omitempty" json:"ports"` 25 | } 26 | 27 | type Ext struct { 28 | DisabledServices []string `yaml:"disabledServices,omitempty" json:"disabledServices"` // 过滤掉不需要部署的服务 29 | } 30 | -------------------------------------------------------------------------------- /common/service/compose/placehold.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "errors" 5 | "github.com/donknap/dpanel/common/function" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // 仅在应用商店中的配置文件 data.yml 中支持 11 | const ( 12 | ContainerDefaultName = "%CONTAINER_DEFAULT_NAME%" 13 | CurrentUsername = "%CURRENT_USERNAME%" 14 | TaskIndex = "%TASK_INDEX%" 15 | CurrentDate = "%CURRENT_DATE%" 16 | ) 17 | 18 | type ReplaceFunc func(placeholder string) (string, error) 19 | type ReplaceTable map[string]ReplaceFunc 20 | 21 | func NewReplaceTable(rt ...ReplaceTable) ReplaceTable { 22 | defaultTable := ReplaceTable{ 23 | ContainerDefaultName: func(placeholder string) (string, error) { 24 | return "", nil 25 | }, 26 | CurrentUsername: func(placeholder string) (string, error) { 27 | return "", errors.New("not implemented") 28 | }, 29 | CurrentDate: func(placeholder string) (string, error) { 30 | return time.Now().Format(function.YmdHis), nil 31 | }, 32 | } 33 | for _, item := range rt { 34 | for k, v := range item { 35 | defaultTable[k] = v 36 | } 37 | } 38 | 39 | return defaultTable 40 | } 41 | 42 | func (self ReplaceTable) Replace(replace *string) error { 43 | var err error 44 | for key, replaceFunc := range self { 45 | if v, err := replaceFunc(key); err == nil { 46 | *replace = strings.Replace(*replace, key, v, -1) 47 | } 48 | } 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /common/service/compose/types.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "github.com/compose-spec/compose-go/v2/types" 5 | ) 6 | 7 | type Service struct { 8 | types.ServiceConfig 9 | XDPanelService ExtService `yaml:"x-dpanel-service,omitempty" json:"x-dpanel-service,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /common/service/crontab/job.go: -------------------------------------------------------------------------------- 1 | package crontab 2 | 3 | import ( 4 | "log/slog" 5 | ) 6 | 7 | type RunFunc func() error 8 | 9 | type Option func(job *Job) 10 | 11 | func WithRunFunc(callback RunFunc) Option { 12 | return func(job *Job) { 13 | job.runFunc = append(job.runFunc, callback) 14 | } 15 | } 16 | 17 | func WithName(name string) Option { 18 | return func(job *Job) { 19 | job.Name = name 20 | } 21 | } 22 | 23 | func New(opts ...Option) *Job { 24 | c := &Job{ 25 | runFunc: make([]RunFunc, 0), 26 | } 27 | for _, opt := range opts { 28 | opt(c) 29 | } 30 | return c 31 | } 32 | 33 | type Job struct { 34 | Name string 35 | runFunc []RunFunc 36 | } 37 | 38 | func (self Job) Run() { 39 | if self.runFunc != nil { 40 | for _, runFunc := range self.runFunc { 41 | err := runFunc() 42 | if err != nil { 43 | slog.Debug("crontab crash ", "err", err.Error()) 44 | return 45 | } 46 | } 47 | } else { 48 | slog.Debug("invalid crontab job") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /common/service/crontab/wrapper.go: -------------------------------------------------------------------------------- 1 | package crontab 2 | 3 | import ( 4 | "errors" 5 | "github.com/robfig/cron/v3" 6 | "log/slog" 7 | "os" 8 | "time" 9 | ) 10 | 11 | var ( 12 | Wrapper = NewCronWrapper() 13 | ) 14 | 15 | func NewCronWrapper() *wrapper { 16 | timeLocation := time.Local 17 | if os.Getenv("TZ") != "" { 18 | timeLocation, _ = time.LoadLocation(os.Getenv("TZ")) 19 | } 20 | specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) 21 | cronWrapper := &wrapper{ 22 | Cron: cron.New( 23 | cron.WithParser(specParser), 24 | cron.WithLocation(timeLocation), 25 | ), 26 | parser: specParser, 27 | } 28 | 29 | cronWrapper.Cron.Start() 30 | return cronWrapper 31 | } 32 | 33 | type wrapper struct { 34 | Cron *cron.Cron 35 | parser cron.Parser 36 | } 37 | 38 | func (self wrapper) CheckExpression(expression []string) error { 39 | for _, exp := range expression { 40 | _, err := self.parser.Parse(exp) 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | return nil 46 | } 47 | 48 | func (self wrapper) AddJob(job *Job, expression ...string) ([]cron.EntryID, error) { 49 | if job == nil { 50 | return nil, errors.New("invalid job") 51 | } 52 | ids := make([]cron.EntryID, 0) 53 | for _, item := range expression { 54 | id, err := self.Cron.AddJob(item, job) 55 | ids = append(ids, id) 56 | if err != nil { 57 | self.RemoveJob(ids...) 58 | return nil, err 59 | } 60 | } 61 | slog.Debug("cron add job", "name", job.Name, "next run time", self.GetNextRunTime(ids...)) 62 | return ids, nil 63 | } 64 | 65 | func (self wrapper) RemoveJob(ids ...cron.EntryID) { 66 | for _, entryID := range ids { 67 | self.Cron.Remove(entryID) 68 | } 69 | } 70 | 71 | func (self wrapper) GetNextRunTime(ids ...cron.EntryID) []time.Time { 72 | result := make([]time.Time, 0) 73 | for _, entryID := range ids { 74 | result = append(result, self.Cron.Entry(entryID).Next) 75 | } 76 | return result 77 | } 78 | -------------------------------------------------------------------------------- /common/service/docker/backup/builder.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | "github.com/docker/docker/api/types" 5 | "github.com/donknap/dpanel/common/entity" 6 | "log/slog" 7 | ) 8 | 9 | func New(opts ...Option) (*Builder, error) { 10 | var err error 11 | c := &Builder{} 12 | 13 | for _, opt := range opts { 14 | err = opt(c) 15 | if err != nil { 16 | return nil, err 17 | } 18 | } 19 | 20 | return c, nil 21 | } 22 | 23 | type Builder struct { 24 | tarFilePath string 25 | tarPathPrefix string 26 | Writer *writer 27 | Reader *reader 28 | } 29 | 30 | func (self Builder) Close() (err error) { 31 | defer func() { 32 | if e := recover(); e != nil { 33 | err = e.(error) 34 | } 35 | if self.Writer != nil { 36 | _ = self.Writer.tarWriter.Close() 37 | } 38 | if self.Reader != nil { 39 | _ = self.Reader.file.Close() 40 | } 41 | }() 42 | if self.Writer != nil { 43 | _ = self.Writer.tarWriter.Close() 44 | _ = self.Writer.file.Close() 45 | } 46 | if self.Reader != nil { 47 | err = self.Reader.file.Close() 48 | if err != nil { 49 | slog.Warn("container backup reader close file", "error", err) 50 | } 51 | } 52 | return err 53 | } 54 | 55 | type Manifest struct { 56 | Config string 57 | Image string 58 | Volume []string 59 | Network []string 60 | } 61 | 62 | type Info struct { 63 | Docker types.Version 64 | Backup *entity.Backup 65 | } 66 | -------------------------------------------------------------------------------- /common/service/docker/backup/option.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | "archive/tar" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | type Option func(self *Builder) error 12 | 13 | func WithTarPathPrefix(prefix string) Option { 14 | return func(self *Builder) error { 15 | if prefix != "" { 16 | self.tarPathPrefix = strings.TrimLeft(prefix, "/") 17 | } 18 | return nil 19 | } 20 | } 21 | 22 | func WithPath(path string) Option { 23 | return func(self *Builder) error { 24 | if path == "" { 25 | return errors.New("invalid path") 26 | } 27 | self.tarFilePath = path 28 | return nil 29 | } 30 | } 31 | 32 | func WithWriter() Option { 33 | return func(self *Builder) error { 34 | dir := filepath.Dir(self.tarFilePath) 35 | if _, err := os.Stat(dir); err != nil { 36 | _ = os.MkdirAll(dir, os.ModePerm) 37 | } 38 | file, err := os.OpenFile(self.tarFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644) 39 | if err != nil { 40 | return err 41 | } 42 | self.Writer = &writer{ 43 | tarPathPrefix: self.tarPathPrefix, 44 | file: file, 45 | } 46 | self.Writer.tarWriter = tar.NewWriter(file) 47 | return nil 48 | } 49 | } 50 | 51 | func WithReader() Option { 52 | return func(self *Builder) error { 53 | var err error 54 | if _, err := os.Stat(self.tarFilePath); err != nil { 55 | return err 56 | } 57 | file, err := os.OpenFile(self.tarFilePath, os.O_RDWR, 0o644) 58 | if err != nil { 59 | return err 60 | } 61 | self.Reader = &reader{ 62 | file: file, 63 | } 64 | if err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /common/service/docker/backup/reader.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "encoding/json" 7 | "errors" 8 | "github.com/donknap/dpanel/common/function" 9 | "io" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type blobItem struct { 15 | Name string 16 | Offset int64 17 | } 18 | 19 | type reader struct { 20 | file *os.File 21 | blobs []blobItem 22 | } 23 | 24 | func (self *reader) Info() (*Info, error) { 25 | tarReader := tar.NewReader(self.file) 26 | info := &Info{} 27 | for { 28 | header, err := tarReader.Next() 29 | if err != nil { 30 | break 31 | } 32 | headerName := strings.TrimLeft(header.Name, "/") 33 | if strings.HasSuffix(headerName, "info.json") { 34 | content, err := io.ReadAll(tarReader) 35 | if err != nil { 36 | return nil, err 37 | } 38 | err = json.Unmarshal(content, &info) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return info, nil 43 | } 44 | } 45 | return nil, errors.New("info file not found in archive") 46 | } 47 | 48 | func (self *reader) Manifest() ([]Manifest, error) { 49 | var offset int64 50 | tarReader := tar.NewReader(self.file) 51 | m := make([]Manifest, 0) 52 | for { 53 | header, err := tarReader.Next() 54 | if err != nil { 55 | break 56 | } 57 | if strings.HasSuffix(header.Name, "manifest.json") { 58 | content, err := io.ReadAll(tarReader) 59 | if err != nil { 60 | return nil, err 61 | } 62 | err = json.Unmarshal(content, &m) 63 | if err != nil { 64 | return nil, err 65 | } 66 | } 67 | if strings.Contains(header.Name, "blobs/sha256/") { 68 | offset, _ = self.file.Seek(0, io.SeekCurrent) 69 | self.blobs = append(self.blobs, blobItem{ 70 | Name: header.Name, 71 | Offset: offset - 512, 72 | }) 73 | } 74 | } 75 | if function.IsEmptyArray(m) { 76 | return nil, errors.New("manifest file not found in archive") 77 | } 78 | return m, nil 79 | } 80 | 81 | func (self *reader) ReadBlobs(fileName string) (io.Reader, error) { 82 | var index int 83 | var ok bool 84 | if ok, index = function.IndexArrayWalk(self.blobs, func(i blobItem) bool { 85 | return strings.HasSuffix(i.Name, fileName) 86 | }); !ok { 87 | return nil, errors.New("blob file not found in archive") 88 | } 89 | _, err := self.file.Seek(self.blobs[index].Offset, io.SeekStart) 90 | if err != nil { 91 | return nil, err 92 | } 93 | tarReader := tar.NewReader(self.file) 94 | _, err = tarReader.Next() 95 | if err != nil { 96 | return nil, err 97 | } 98 | return tarReader, nil 99 | } 100 | 101 | func (self *reader) ReadBlobsContent(fileName string) ([]byte, error) { 102 | out, err := self.ReadBlobs(fileName) 103 | if err != nil { 104 | return nil, err 105 | } 106 | gzReader, err := gzip.NewReader(out) 107 | if err != nil { 108 | return nil, err 109 | } 110 | defer func() { 111 | _ = gzReader.Close() 112 | }() 113 | return io.ReadAll(gzReader) 114 | } 115 | -------------------------------------------------------------------------------- /common/service/docker/container/builder.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/docker/docker/api/types/container" 5 | "github.com/docker/docker/api/types/network" 6 | "github.com/docker/go-connections/nat" 7 | "github.com/donknap/dpanel/common/service/docker" 8 | v1 "github.com/opencontainers/image-spec/specs-go/v1" 9 | ) 10 | 11 | func New(opts ...Option) (*Builder, error) { 12 | var err error 13 | c := &Builder{ 14 | containerConfig: &container.Config{ 15 | ExposedPorts: make(nat.PortSet), 16 | Labels: map[string]string{ 17 | "maintainer": docker.BuilderAuthor, 18 | "com.dpanel.description": docker.BuildDesc, 19 | "com.dpanel.website": docker.BuildWebSite, 20 | }, 21 | }, 22 | hostConfig: &container.HostConfig{ 23 | PortBindings: make(nat.PortMap), 24 | NetworkMode: "default", 25 | }, 26 | platform: &v1.Platform{}, 27 | networkingConfig: &network.NetworkingConfig{ 28 | EndpointsConfig: map[string]*network.EndpointSettings{}, 29 | }, 30 | } 31 | for _, opt := range opts { 32 | err = opt(c) 33 | if err != nil { 34 | return nil, err 35 | } 36 | } 37 | return c, nil 38 | } 39 | 40 | type Builder struct { 41 | containerConfig *container.Config 42 | hostConfig *container.HostConfig 43 | networkingConfig *network.NetworkingConfig 44 | platform *v1.Platform 45 | containerName string 46 | } 47 | 48 | func (self *Builder) Execute() (response container.CreateResponse, err error) { 49 | return docker.Sdk.Client.ContainerCreate( 50 | docker.Sdk.Ctx, 51 | self.containerConfig, 52 | self.hostConfig, 53 | self.networkingConfig, 54 | self.platform, 55 | self.containerName, 56 | ) 57 | } 58 | 59 | func (self *Builder) GetConfig() (*container.Config, *container.HostConfig, *network.NetworkingConfig) { 60 | return self.containerConfig, self.hostConfig, self.networkingConfig 61 | } 62 | -------------------------------------------------------------------------------- /common/service/docker/exec.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/function" 5 | "github.com/donknap/dpanel/common/service/exec" 6 | exec2 "os/exec" 7 | ) 8 | 9 | func (self Builder) GetRunCmd(command ...string) []exec.Option { 10 | return []exec.Option{ 11 | exec.WithCommandName("docker"), 12 | exec.WithArgs(append( 13 | self.runParams, 14 | command..., 15 | )...), 16 | } 17 | } 18 | 19 | func (self Builder) GetComposeCmd(command ...string) []exec.Option { 20 | if _, err := exec2.LookPath("docker-compose"); err == nil { 21 | return []exec.Option{ 22 | exec.WithCommandName("docker-compose"), 23 | exec.WithArgs(command...), 24 | exec.WithEnv(self.runEnv), 25 | } 26 | } else { 27 | return []exec.Option{ 28 | exec.WithCommandName("docker"), 29 | exec.WithArgs(append(append(self.runParams, "compose"), command...)...), 30 | } 31 | } 32 | } 33 | 34 | func (self Builder) ExecCleanResult(str []byte) string { 35 | // 执行命令时返回的结果应该以 utf8 字符返回,并过滤掉不可见字符 36 | out := function.BytesCleanFunc(str, func(b byte) bool { 37 | return b < 32 && b != '\n' && b != '\r' && b != '\t' 38 | }) 39 | return string(out) 40 | } 41 | -------------------------------------------------------------------------------- /common/service/docker/image/builder.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "github.com/docker/docker/api/types" 5 | "github.com/donknap/dpanel/common/service/docker" 6 | "io" 7 | ) 8 | 9 | func New(opts ...Option) (*Builder, error) { 10 | var err error 11 | c := &Builder{ 12 | imageBuildOption: types.ImageBuildOptions{ 13 | Dockerfile: "Dockerfile", // 默认在根目录 14 | Remove: true, 15 | NoCache: true, 16 | Labels: map[string]string{ 17 | "BuildAuthor": docker.BuilderAuthor, 18 | "BuildDesc": docker.BuildDesc, 19 | "BuildWebSite": docker.BuildWebSite, 20 | "buildVersion": docker.BuildVersion, 21 | }, 22 | BuildArgs: map[string]*string{}, 23 | }, 24 | } 25 | for _, opt := range opts { 26 | err = opt(c) 27 | if err != nil { 28 | return nil, err 29 | } 30 | } 31 | return c, nil 32 | } 33 | 34 | type Builder struct { 35 | imageBuildOption types.ImageBuildOptions 36 | buildContext io.Reader 37 | } 38 | 39 | func (self *Builder) Execute() (response types.ImageBuildResponse, err error) { 40 | response, err = docker.Sdk.Client.ImageBuild( 41 | docker.Sdk.Ctx, 42 | self.buildContext, 43 | self.imageBuildOption, 44 | ) 45 | if err != nil { 46 | return response, err 47 | } 48 | return response, nil 49 | } 50 | -------------------------------------------------------------------------------- /common/service/docker/image/option.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "archive/tar" 5 | "archive/zip" 6 | "bytes" 7 | "errors" 8 | "github.com/donknap/dpanel/common/function" 9 | "github.com/donknap/dpanel/common/service/docker" 10 | "io" 11 | "os" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type Option func(builder *Builder) error 17 | 18 | func WithDockerFileContent(content []byte) Option { 19 | return func(self *Builder) error { 20 | if content == nil { 21 | return nil 22 | } 23 | buf := new(bytes.Buffer) 24 | tarWriter := tar.NewWriter(buf) 25 | defer func() { 26 | _ = tarWriter.Close() 27 | }() 28 | if err := tarWriter.WriteHeader(&tar.Header{ 29 | Name: "Dockerfile", 30 | Size: int64(len(content)), 31 | Mode: int64(os.ModePerm), 32 | ModTime: time.Now(), 33 | }); err != nil { 34 | return err 35 | } 36 | if _, err := tarWriter.Write(content); err != nil { 37 | return err 38 | } 39 | self.buildContext = buf 40 | return nil 41 | } 42 | } 43 | 44 | func WithGitUrl(url string) Option { 45 | return func(self *Builder) error { 46 | if url == "" { 47 | return nil 48 | } 49 | self.imageBuildOption.RemoteContext = url 50 | return nil 51 | } 52 | } 53 | 54 | func WithDockerFilePath(path string) Option { 55 | return func(self *Builder) error { 56 | if path == "" { 57 | return nil 58 | } 59 | self.imageBuildOption.Dockerfile = path 60 | return nil 61 | } 62 | } 63 | 64 | func WithTag(name ...string) Option { 65 | return func(self *Builder) error { 66 | if name == nil { 67 | return errors.New("tag name is required") 68 | } 69 | self.imageBuildOption.Tags = append(self.imageBuildOption.Tags, name...) 70 | return nil 71 | } 72 | } 73 | 74 | func WithPlatform(item *docker.ImagePlatform) Option { 75 | return func(self *Builder) error { 76 | if item == nil { 77 | return nil 78 | } 79 | self.imageBuildOption.Platform = item.Type 80 | self.imageBuildOption.BuildArgs["TARGETARCH"] = function.PtrString(item.Arch) 81 | return nil 82 | } 83 | } 84 | 85 | func WithZipFilePath(path string) Option { 86 | return func(self *Builder) error { 87 | if path == "" { 88 | return nil 89 | } 90 | zipArchive, err := zip.OpenReader(path) 91 | if err != nil { 92 | return err 93 | } 94 | defer func() { 95 | _ = zipArchive.Close() 96 | _ = os.Remove(path) 97 | }() 98 | 99 | buf := new(bytes.Buffer) 100 | tarWriter := tar.NewWriter(buf) 101 | defer func() { 102 | _ = tarWriter.Close() 103 | }() 104 | 105 | for _, zipFile := range zipArchive.File { 106 | if strings.HasPrefix(zipFile.Name, "__MACOSX") { 107 | continue 108 | } 109 | fileInfoHeader, err := tar.FileInfoHeader(zipFile.FileInfo(), "") 110 | fileInfoHeader.Name = zipFile.Name 111 | 112 | if err != nil { 113 | return err 114 | } 115 | err = tarWriter.WriteHeader(fileInfoHeader) 116 | if err != nil { 117 | return err 118 | } 119 | zipFileReader, err := zipFile.Open() 120 | _, err = io.Copy(tarWriter, zipFileReader) 121 | if err != nil { 122 | return err 123 | } 124 | } 125 | self.buildContext = buf 126 | return nil 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /common/service/docker/network.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/docker/docker/api/types/network" 7 | "github.com/donknap/dpanel/common/function" 8 | "strings" 9 | ) 10 | 11 | func (self Builder) NetworkRemove(ctx context.Context, networkName string) error { 12 | if networkRow, err := self.Client.NetworkInspect(ctx, networkName, network.InspectOptions{}); err == nil { 13 | for _, item := range networkRow.Containers { 14 | err = self.Client.NetworkDisconnect(ctx, networkName, item.Name, true) 15 | } 16 | if err != nil { 17 | return err 18 | } 19 | return self.Client.NetworkRemove(ctx, networkName) 20 | } 21 | return nil 22 | } 23 | 24 | func (self Builder) NetworkCreate(ctx context.Context, networkName string, ipV4, ipV6 *NetworkCreateItem) (string, error) { 25 | option := network.CreateOptions{ 26 | Driver: "bridge", 27 | Options: map[string]string{ 28 | "name": networkName, 29 | }, 30 | EnableIPv6: function.PtrBool(false), 31 | IPAM: &network.IPAM{ 32 | Driver: "default", 33 | Options: map[string]string{}, 34 | Config: []network.IPAMConfig{}, 35 | }, 36 | } 37 | if ipV4 != nil && ipV4.Gateway != "" && ipV4.Subnet != "" { 38 | option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{ 39 | Subnet: ipV4.Subnet, 40 | Gateway: ipV4.Gateway, 41 | }) 42 | } 43 | if ipV6 != nil && ipV6.Gateway != "" && ipV6.Subnet != "" { 44 | option.EnableIPv6 = function.PtrBool(true) 45 | option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{ 46 | Subnet: ipV6.Subnet, 47 | Gateway: ipV6.Gateway, 48 | }) 49 | } 50 | response, err := self.Client.NetworkCreate(ctx, networkName, option) 51 | if err != nil { 52 | return "", err 53 | } 54 | return response.ID, nil 55 | } 56 | 57 | func (self Builder) NetworkConnect(ctx context.Context, networkRow NetworkItem, containerName string) error { 58 | // 关联网络时,重新退出加入 59 | _ = self.Client.NetworkDisconnect(ctx, networkRow.Name, containerName, true) 60 | 61 | if networkRow.Alise == nil { 62 | networkRow.Alise = make([]string, 0) 63 | } 64 | dpanelHostName := fmt.Sprintf("%s.pod.dpanel.local", strings.TrimLeft(containerName, "/")) 65 | if !function.InArray(networkRow.Alise, dpanelHostName) { 66 | networkRow.Alise = append(networkRow.Alise, dpanelHostName) 67 | } 68 | endpointSetting := &network.EndpointSettings{ 69 | Aliases: networkRow.Alise, 70 | IPAMConfig: &network.EndpointIPAMConfig{}, 71 | DNSNames: make([]string, 0), 72 | } 73 | if networkRow.IpV4 != "" { 74 | endpointSetting.IPAMConfig.IPv4Address = networkRow.IpV4 75 | } 76 | if networkRow.IpV6 != "" { 77 | endpointSetting.IPAMConfig.IPv6Address = networkRow.IpV6 78 | } 79 | if !function.IsEmptyArray(networkRow.DnsName) { 80 | endpointSetting.DNSNames = networkRow.DnsName 81 | } 82 | if networkRow.MacAddress != "" { 83 | endpointSetting.MacAddress = networkRow.MacAddress 84 | } 85 | return self.Client.NetworkConnect(ctx, networkRow.Name, containerName, endpointSetting) 86 | } 87 | -------------------------------------------------------------------------------- /common/service/docker/utils.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "bytes" 5 | "github.com/docker/docker/api/types/container" 6 | "github.com/docker/docker/pkg/stdcopy" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | func GetContentFromStdFormat(reader io.Reader) (*bytes.Buffer, error) { 12 | buffer := new(bytes.Buffer) 13 | _, err := io.Copy(buffer, reader) 14 | if err != nil { 15 | return nil, err 16 | } 17 | newReader := bytes.NewReader(buffer.Bytes()) 18 | stdout := new(bytes.Buffer) 19 | _, err = stdcopy.StdCopy(stdout, stdout, newReader) 20 | if err == nil { 21 | return stdout, nil 22 | } else { 23 | return buffer, nil 24 | } 25 | } 26 | 27 | func GetRestartPolicyByString(restartType string) (mode container.RestartPolicyMode) { 28 | restartPolicyMap := map[string]container.RestartPolicyMode{ 29 | "always": container.RestartPolicyAlways, 30 | "no": container.RestartPolicyDisabled, 31 | "unless-stopped": container.RestartPolicyUnlessStopped, 32 | "on-failure": container.RestartPolicyOnFailure, 33 | } 34 | if mode, ok := restartPolicyMap[restartType]; ok { 35 | return mode 36 | } else { 37 | return container.RestartPolicyDisabled 38 | } 39 | } 40 | 41 | func CommandSplit(cmd string) []string { 42 | result := make([]string, 0) 43 | field := "" 44 | ignoreSpace := false 45 | for _, s := range strings.Split(cmd, "") { 46 | if s == " " && !ignoreSpace { 47 | result = append(result, field) 48 | field = "" 49 | continue 50 | } 51 | if s == "\"" || s == "'" { 52 | ignoreSpace = !ignoreSpace 53 | continue 54 | } 55 | field += s 56 | } 57 | if field != "" { 58 | result = append(result, field) 59 | } 60 | return result 61 | } 62 | 63 | func NewValueItemFromMap(maps map[string]string) (r []ValueItem) { 64 | for name, value := range maps { 65 | r = append(r, ValueItem{ 66 | Name: name, 67 | Value: value, 68 | }) 69 | } 70 | return r 71 | } 72 | 73 | func DefaultCapabilities() []string { 74 | return []string{ 75 | "CAP_CHOWN", 76 | "CAP_DAC_OVERRIDE", 77 | "CAP_FSETID", 78 | "CAP_FOWNER", 79 | "CAP_MKNOD", 80 | "CAP_NET_RAW", 81 | "CAP_SETGID", 82 | "CAP_SETUID", 83 | "CAP_SETFCAP", 84 | "CAP_SETPCAP", 85 | "CAP_NET_BIND_SERVICE", 86 | "CAP_SYS_CHROOT", 87 | "CAP_KILL", 88 | "CAP_AUDIT_WRITE", 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /common/service/exec/command.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/creack/pty" 7 | "io" 8 | "log/slog" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | ) 13 | 14 | var cmd *exec.Cmd 15 | 16 | func New(opts ...Option) (*Command, error) { 17 | var err error 18 | c := &Command{ 19 | cmd: &exec.Cmd{}, 20 | } 21 | 22 | for _, opt := range opts { 23 | err = opt(c) 24 | if err != nil { 25 | return nil, err 26 | } 27 | } 28 | 29 | slog.Debug("run command", "args", c.cmd.Args, "env", c.cmd.Env) 30 | 31 | if c.cmd.Cancel == nil { 32 | // 没有配置超时时间,则先杀掉上一个进程 33 | // 如果配置了超时时间,自行处理命令的终止问题,不会在下次执行命令时被清理掉 34 | _ = Kill() 35 | cmd = c.cmd 36 | } 37 | 38 | return c, nil 39 | } 40 | 41 | type Command struct { 42 | cmd *exec.Cmd 43 | } 44 | 45 | func (self Command) RunInTerminal(size *pty.Winsize) (io.ReadCloser, error) { 46 | var out *os.File 47 | var err error 48 | 49 | if runtime.GOOS == "windows" { 50 | // 不支持 Pty,利用管道模拟读取 51 | return self.RunInPip() 52 | } 53 | 54 | out, err = pty.StartWithSize(self.cmd, size) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return TerminalResult{ 59 | Conn: out, 60 | cmd: self.cmd, 61 | }, err 62 | } 63 | 64 | func (self Command) RunInPip() (stdout io.ReadCloser, err error) { 65 | stdout, err = self.cmd.StdoutPipe() 66 | if err != nil { 67 | return nil, err 68 | } 69 | self.cmd.Stderr = self.cmd.Stdout 70 | if err = self.cmd.Start(); err != nil { 71 | return nil, err 72 | } 73 | return stdout, nil 74 | } 75 | 76 | func (self Command) Run() (io.Reader, error) { 77 | out := new(bytes.Buffer) 78 | self.cmd.Stdout = out 79 | self.cmd.Stderr = out 80 | err := self.cmd.Run() 81 | if err != nil { 82 | return nil, errors.Join(err, errors.New(out.String())) 83 | } 84 | return out, nil 85 | } 86 | 87 | func (self Command) RunWithResult() string { 88 | out, err := self.cmd.CombinedOutput() 89 | if err != nil { 90 | slog.Debug("run command with result", "arg", self.cmd.Args, "error", err.Error()) 91 | return string(out) 92 | } 93 | return string(out) 94 | } 95 | 96 | func Kill() error { 97 | var err error 98 | 99 | if cmd != nil && cmd.Process != nil && cmd.Process.Pid > 0 { 100 | err = cmd.Process.Kill() 101 | if err == nil { 102 | _, _ = cmd.Process.Wait() 103 | } 104 | slog.Debug("run command kill global cmd", "pid", cmd.Process.Pid, "name", cmd.String(), "error", err) 105 | } 106 | return nil 107 | } 108 | 109 | func (self Command) Cmd() *exec.Cmd { 110 | return self.cmd 111 | } 112 | 113 | func (self Command) Close() { 114 | var err error 115 | slog.Debug("run command kill self cmd", "cmd", self.cmd) 116 | if self.cmd != nil && self.cmd.Process != nil && self.cmd.Process.Pid > 0 { 117 | err = self.cmd.Process.Kill() 118 | if err == nil { 119 | _, _ = self.cmd.Process.Wait() 120 | } 121 | slog.Debug("run command kill self cmd", "pid", self.cmd.Process.Pid, "name", self.cmd.String(), "error", err) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /common/service/exec/option.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/donknap/dpanel/common/function" 7 | "os/exec" 8 | ) 9 | 10 | type Option func(command *Command) error 11 | 12 | func WithArgs(args ...string) Option { 13 | return func(self *Command) error { 14 | if self.cmd.Path == "" { 15 | self.cmd = exec.Command(args[0], args[1:]...) 16 | } else { 17 | self.cmd = exec.Command(self.cmd.Path, args...) 18 | } 19 | return nil 20 | } 21 | } 22 | 23 | func WithCommandName(commandName string) Option { 24 | return func(self *Command) error { 25 | if commandName == "" { 26 | return nil 27 | } 28 | if function.IsEmptyArray(self.cmd.Args) { 29 | self.cmd = exec.Command(commandName) 30 | } else { 31 | self.cmd = exec.Command(commandName, self.cmd.Args...) 32 | } 33 | return nil 34 | } 35 | } 36 | 37 | func WithDir(dir string) Option { 38 | return func(self *Command) error { 39 | if dir == "" { 40 | return nil 41 | } 42 | self.cmd.Dir = dir 43 | return nil 44 | } 45 | } 46 | 47 | func WithEnv(env []string) Option { 48 | return func(self *Command) error { 49 | self.cmd.Env = env 50 | return nil 51 | } 52 | } 53 | 54 | func WithCtx(ctx context.Context) Option { 55 | return func(self *Command) error { 56 | if function.IsEmptyArray(self.cmd.Args) { 57 | return errors.New("invalid arguments") 58 | } 59 | newCmd := exec.CommandContext(ctx, self.cmd.Args[0], self.cmd.Args[1:]...) 60 | newCmd.Env = self.cmd.Env 61 | newCmd.Dir = self.cmd.Dir 62 | self.cmd = newCmd 63 | return nil 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/service/exec/result.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | type TerminalResult struct { 10 | Conn *os.File 11 | cmd *exec.Cmd 12 | } 13 | 14 | func (self TerminalResult) Close() error { 15 | if self.Conn != nil { 16 | err := self.Conn.Close() 17 | if err != nil { 18 | slog.Debug("terminal result close", "error", err.Error()) 19 | } 20 | } 21 | if self.cmd != nil { 22 | err := self.cmd.Process.Kill() 23 | if err != nil { 24 | slog.Debug("terminal result close", "error", err.Error()) 25 | } else { 26 | return self.cmd.Wait() 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func (self TerminalResult) Read(p []byte) (n int, err error) { 33 | return self.Conn.Read(p) 34 | } 35 | -------------------------------------------------------------------------------- /common/service/family/ce.go: -------------------------------------------------------------------------------- 1 | //go:build !pe && !ee && !xk 2 | 3 | package family 4 | 5 | import ( 6 | "github.com/donknap/dpanel/common/function" 7 | "github.com/donknap/dpanel/common/types" 8 | "github.com/gin-gonic/gin" 9 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server" 10 | "log/slog" 11 | ) 12 | 13 | type Provider struct { 14 | } 15 | 16 | func (self Provider) Register(httpServer *server.Server) { 17 | slog.Debug("provider load community edition") 18 | httpServer.RegisterRouters(func(engine *gin.Engine) { 19 | engine.POST("/api/pro/*path", notSupportedApi) 20 | }) 21 | } 22 | 23 | func (self Provider) Feature() []string { 24 | return []string{ 25 | types.FeatureFamilyCe, 26 | } 27 | } 28 | 29 | func (self Provider) Check(name string) bool { 30 | return function.InArray(self.Feature(), name) 31 | } 32 | -------------------------------------------------------------------------------- /common/service/family/ee.go: -------------------------------------------------------------------------------- 1 | //go:build ee 2 | 3 | package family 4 | 5 | import ( 6 | "github.com/donknap/dpanel/app/pro/ee" 7 | "github.com/donknap/dpanel/common/function" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server" 9 | "log/slog" 10 | ) 11 | 12 | type Provider struct { 13 | } 14 | 15 | func (self *Provider) Register(httpServer *server.Server) { 16 | slog.Debug("provider load enterprise edition") 17 | new(ee.Provider).Register(httpServer) 18 | } 19 | 20 | func (self Provider) Feature() []string { 21 | return new(ee.Provider).Feature() 22 | } 23 | 24 | func (self Provider) Check(name string) bool { 25 | return function.InArray(self.Feature(), name) 26 | } 27 | -------------------------------------------------------------------------------- /common/service/family/pe.go: -------------------------------------------------------------------------------- 1 | //go:build pe 2 | 3 | package family 4 | 5 | import ( 6 | "github.com/donknap/dpanel/app/pro/pe" 7 | "github.com/donknap/dpanel/common/function" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server" 9 | "log/slog" 10 | ) 11 | 12 | type Provider struct { 13 | } 14 | 15 | func (self *Provider) Register(httpServer *server.Server) { 16 | slog.Debug("provider load professional edition") 17 | new(pe.Provider).Register(httpServer) 18 | } 19 | 20 | func (self Provider) Feature() []string { 21 | return new(pe.Provider).Feature() 22 | } 23 | 24 | func (self Provider) Check(name string) bool { 25 | return function.InArray(self.Feature(), name) 26 | } 27 | -------------------------------------------------------------------------------- /common/service/family/util.go: -------------------------------------------------------------------------------- 1 | package family 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/function" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func notSupportedApi(http *gin.Context) { 9 | http.JSON(500, map[string]interface{}{ 10 | "error": function.ErrorMessage(".proLicenseFileIsCorrect").Error(), 11 | "code": 500, 12 | }) 13 | http.Abort() 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /common/service/family/xk.go: -------------------------------------------------------------------------------- 1 | //go:build xk 2 | 3 | package family 4 | 5 | import ( 6 | "github.com/donknap/dpanel/app/pro/xk" 7 | "github.com/donknap/dpanel/common/function" 8 | "github.com/we7coreteam/w7-rangine-go/v2/src/http/server" 9 | "log/slog" 10 | ) 11 | 12 | type Provider struct { 13 | } 14 | 15 | func (self *Provider) Register(httpServer *server.Server) { 16 | slog.Debug("provider load xk edition") 17 | new(xk.Provider).Register(httpServer) 18 | } 19 | 20 | func (self Provider) Feature() []string { 21 | return new(xk.Provider).Feature() 22 | } 23 | 24 | func (self Provider) Check(name string) bool { 25 | return function.InArray(self.Feature(), name) 26 | } 27 | -------------------------------------------------------------------------------- /common/service/fs/dockerfs/file.go: -------------------------------------------------------------------------------- 1 | package dockerfs 2 | 3 | import ( 4 | "errors" 5 | "github.com/spf13/afero/mem" 6 | "os" 7 | ) 8 | 9 | type File struct { 10 | info os.FileInfo 11 | fd *mem.File 12 | fs *Fs 13 | } 14 | 15 | func (self *File) Close() error { 16 | if self.fd == nil { 17 | return nil 18 | } 19 | return self.fd.Close() 20 | } 21 | 22 | func (self *File) Read(p []byte) (n int, err error) { 23 | if self.fd == nil { 24 | return 0, &os.PathError{Op: "close", Path: self.info.Name(), Err: nil} 25 | } 26 | return self.fd.Read(p) 27 | } 28 | 29 | func (self *File) ReadAt(p []byte, off int64) (n int, err error) { 30 | return self.fd.ReadAt(p, off) 31 | } 32 | 33 | func (self *File) Seek(offset int64, whence int) (int64, error) { 34 | return self.fd.Seek(offset, whence) 35 | } 36 | 37 | func (self *File) Write(p []byte) (n int, err error) { 38 | return self.fd.Write(p) 39 | } 40 | 41 | func (self *File) WriteAt(p []byte, off int64) (n int, err error) { 42 | return self.fd.WriteAt(p, off) 43 | } 44 | 45 | func (self *File) Name() string { 46 | return self.fd.Name() 47 | } 48 | 49 | func (self *File) Readdir(count int) ([]os.FileInfo, error) { 50 | if self.info.Mode().IsRegular() { 51 | return nil, errors.New("targe is not a directory") 52 | } 53 | return self.fs.readDirFromContainer(self.info.Name()) 54 | } 55 | 56 | func (self *File) Readdirnames(n int) ([]string, error) { 57 | return self.fd.Readdirnames(n) 58 | } 59 | 60 | func (self *File) Stat() (os.FileInfo, error) { 61 | return self.fd.Stat() 62 | } 63 | 64 | func (self *File) Sync() error { 65 | return self.fd.Sync() 66 | } 67 | 68 | func (self *File) Truncate(size int64) error { 69 | return self.fd.Truncate(size) 70 | } 71 | 72 | func (self *File) WriteString(s string) (ret int, err error) { 73 | return self.fd.WriteString(s) 74 | } 75 | -------------------------------------------------------------------------------- /common/service/fs/dockerfs/option.go: -------------------------------------------------------------------------------- 1 | package dockerfs 2 | 3 | import "github.com/donknap/dpanel/common/service/docker" 4 | 5 | type Option func(self *Fs) error 6 | 7 | func WithTargetContainer(name, root string) Option { 8 | return func(self *Fs) error { 9 | self.targetContainerRootPath = root 10 | self.targetContainerName = name 11 | return nil 12 | } 13 | } 14 | 15 | func WithProxyContainer(name string) Option { 16 | return func(self *Fs) error { 17 | self.proxyContainerName = name 18 | return nil 19 | } 20 | } 21 | 22 | func WithDockerSdk(sdk *docker.Builder) Option { 23 | return func(self *Fs) error { 24 | self.sdk = sdk 25 | return nil 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/service/fs/fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | logic2 "github.com/donknap/dpanel/app/common/logic" 6 | "github.com/donknap/dpanel/common/function" 7 | "github.com/donknap/dpanel/common/service/docker" 8 | "github.com/donknap/dpanel/common/service/fs/dockerfs" 9 | "github.com/donknap/dpanel/common/service/plugin" 10 | "github.com/donknap/dpanel/common/service/ssh" 11 | "github.com/spf13/afero" 12 | "github.com/spf13/afero/sftpfs" 13 | ) 14 | 15 | func NewContainerExplorer(containerName string) (*afero.Afero, error) { 16 | o := &afero.Afero{} 17 | 18 | // todo 判断一下是否可以直接使用主机 sftp ,从而替换掉代理容器 19 | 20 | explorerPlugin, err := plugin.NewPlugin(plugin.PluginExplorer, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | pluginName, err := explorerPlugin.Create() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | containerInfo, err := docker.Sdk.Client.ContainerInspect(docker.Sdk.Ctx, containerName) 30 | if err != nil { 31 | return nil, err 32 | } 33 | if containerInfo.State.Pid == 0 { 34 | return nil, fmt.Errorf("the %s container does not exist or is not running", containerName) 35 | } 36 | 37 | dfs, err := dockerfs.New( 38 | dockerfs.WithDockerSdk(docker.Sdk), 39 | dockerfs.WithProxyContainer(pluginName), 40 | dockerfs.WithTargetContainer(containerName, fmt.Sprintf("/proc/%d/root", containerInfo.State.Pid)), 41 | ) 42 | if err != nil { 43 | return nil, err 44 | } 45 | o.Fs = dfs 46 | return o, nil 47 | } 48 | 49 | func NewSshExplorer(dockerEnvName string) (*ssh.Client, *afero.Afero, error) { 50 | dockerEnv, err := logic2.DockerEnv{}.GetEnvByName(dockerEnvName) 51 | if err != nil { 52 | return nil, nil, err 53 | } 54 | if !dockerEnv.EnableSSH || dockerEnv.SshServerInfo == nil { 55 | return nil, nil, function.ErrorMessage(".commonDataNotFoundOrDeleted") 56 | } 57 | option := []ssh.Option{ 58 | ssh.WithSftpClient(), 59 | } 60 | option = append(option, ssh.WithServerInfo(dockerEnv.SshServerInfo)...) 61 | sshClient, err := ssh.NewClient(option...) 62 | if err != nil { 63 | return nil, nil, err 64 | } 65 | afs := &afero.Afero{ 66 | Fs: sftpfs.New(sshClient.SftpConn), 67 | } 68 | return sshClient, afs, nil 69 | } 70 | -------------------------------------------------------------------------------- /common/service/notice/message.go: -------------------------------------------------------------------------------- 1 | package notice 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/donknap/dpanel/common/dao" 7 | "github.com/donknap/dpanel/common/entity" 8 | "github.com/donknap/dpanel/common/function" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | var ( 14 | QueueNoticePushMessage = make(chan *entity.Notice) 15 | ) 16 | 17 | const ( 18 | TypeError = "error" 19 | TypeInfo = "info" 20 | TypeSuccess = "success" 21 | ) 22 | 23 | type Message struct { 24 | } 25 | 26 | func (self Message) Error(title string, message ...string) error { 27 | return self.push(TypeError, title, message) 28 | } 29 | 30 | func (self Message) Info(title string, message ...string) error { 31 | return self.push(TypeInfo, title, message) 32 | } 33 | 34 | func (self Message) Success(title string, message ...string) error { 35 | return self.push(TypeSuccess, title, message) 36 | } 37 | 38 | func (self Message) push(level string, title string, message []string) error { 39 | jsonMessage, _ := json.Marshal(message) 40 | row := &entity.Notice{ 41 | Title: title, 42 | Message: string(jsonMessage), 43 | Type: level, 44 | CreatedAt: time.Now().Local(), 45 | } 46 | err := dao.Notice.Create(row) 47 | fmt.Printf("协程数,%v \n", runtime.NumGoroutine()) 48 | QueueNoticePushMessage <- row 49 | return err 50 | } 51 | 52 | func (self Message) New(title string, message ...string) error { 53 | return function.ErrorMessage(title, message...) 54 | } 55 | -------------------------------------------------------------------------------- /common/service/plugin/wrapper.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "github.com/donknap/dpanel/common/service/docker" 5 | ) 6 | 7 | type Wrapper struct { 8 | } 9 | 10 | type pluginItem struct { 11 | Name string `json:"name"` 12 | IsCreated bool `json:"isCreated"` 13 | ContainerId string `json:"containerId"` 14 | } 15 | 16 | func (self Wrapper) GetPluginList() map[string]pluginItem { 17 | pluginList := []string{ 18 | PluginExplorer, PluginBackup, 19 | } 20 | r := make(map[string]pluginItem) 21 | for _, name := range pluginList { 22 | containerRow, err := docker.Sdk.Client.ContainerInspect(docker.Sdk.Ctx, "dpanel-plugin-"+name) 23 | if err == nil { 24 | r[name] = pluginItem{ 25 | Name: name, 26 | IsCreated: true, 27 | ContainerId: containerRow.ID, 28 | } 29 | } else { 30 | r[name] = pluginItem{ 31 | Name: name, 32 | IsCreated: false, 33 | } 34 | } 35 | } 36 | return r 37 | } 38 | -------------------------------------------------------------------------------- /common/service/registry/option.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type Option func(*Registry) 11 | 12 | func WithCredentials(username, password string) Option { 13 | return func(registry *Registry) { 14 | registry.authString = base64.StdEncoding.EncodeToString([]byte( 15 | fmt.Sprintf("%s:%s", 16 | username, password, 17 | )), 18 | ) 19 | } 20 | } 21 | 22 | func WithCredentialsString(auth string) Option { 23 | return func(registry *Registry) { 24 | if auth != "" { 25 | registry.authString = auth 26 | } 27 | } 28 | } 29 | 30 | func WithRequestCacheTime(cacheTime time.Duration) Option { 31 | return func(registry *Registry) { 32 | registry.cacheTime = cacheTime 33 | } 34 | } 35 | 36 | func WithRegistryHost(host string) Option { 37 | return func(registry *Registry) { 38 | if strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(host, "http://"), "https://"), "/") == DefaultRegistryDomain { 39 | host = DefaultRegistryHost 40 | } 41 | registry.url = GetRegistryUrl(host) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/service/registry/repository.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/mcuadros/go-version" 8 | "io" 9 | "net/http" 10 | "sort" 11 | ) 12 | 13 | const ContentDigestHeader = "Docker-Content-Digest" 14 | 15 | type repository struct { 16 | registry *Registry 17 | } 18 | 19 | func (self repository) GetImageDigest(imageName string) (string, error) { 20 | imageDetail := GetImageTagDetail(imageName) 21 | 22 | u := self.registry.url.JoinPath(imageDetail.BaseName, "manifests", imageDetail.Version) 23 | req, err := http.NewRequest("HEAD", u.String(), nil) 24 | if err != nil { 25 | return "", err 26 | } 27 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json") 28 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json") 29 | req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v1+json") 30 | req.Header.Add("Accept", "application/vnd.oci.image.index.v1+json") 31 | 32 | res, err := self.registry.request(req, fmt.Sprintf(ScopeRepositoryPull, imageDetail.BaseName)) 33 | if err != nil { 34 | return "", err 35 | } 36 | defer func() { 37 | _ = res.Body.Close() 38 | }() 39 | 40 | return res.Header.Get(ContentDigestHeader), nil 41 | } 42 | 43 | func (self repository) GetImageTagList(basename string) ([]string, error) { 44 | u := self.registry.url.JoinPath(basename, "tags", "list") 45 | //if limit > 0 { 46 | // u.RawQuery = fmt.Sprintf("n=%d", limit) 47 | //} 48 | req, _ := http.NewRequest("GET", u.String(), nil) 49 | 50 | res, err := self.registry.request(req, fmt.Sprintf(ScopeRepositoryPull, basename)) 51 | if err != nil { 52 | return nil, err 53 | } 54 | defer res.Body.Close() 55 | 56 | buffer := new(bytes.Buffer) 57 | _, err = io.Copy(buffer, res.Body) 58 | if err != nil { 59 | return nil, err 60 | } 61 | result := ImageTagListResult{} 62 | err = json.Unmarshal(buffer.Bytes(), &result) 63 | if err != nil { 64 | return nil, err 65 | } 66 | sort.Slice(result.Tags, func(i, j int) bool { 67 | return version.CompareSimple(result.Tags[i], result.Tags[j]) == 1 68 | }) 69 | return result.Tags, nil 70 | } 71 | -------------------------------------------------------------------------------- /common/service/registry/types.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/docker/docker/api/types/registry" 7 | "log/slog" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | DefaultRegistryDomain = "docker.io" 14 | DefaultRegistryHost = "index.docker.io" 15 | ) 16 | 17 | type TokenResponse struct { 18 | Token string `json:"token"` 19 | AccessToken string `json:"access_token"` 20 | } 21 | 22 | // {registryUrl}/{namespace-可能有多个路径}/{imageName}:{version} 23 | type ImageTagDetail struct { 24 | Registry string 25 | Namespace string 26 | ImageName string 27 | Version string 28 | BaseName string 29 | } 30 | 31 | func (self ImageTagDetail) Uri() string { 32 | if self.Registry == "" { 33 | self.Registry = DefaultRegistryDomain 34 | } 35 | self.Registry = strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(self.Registry, "http://"), "https://"), "/") 36 | split := ":" 37 | if self.Namespace == "" { 38 | return fmt.Sprintf("%s/%s%s%s", self.Registry, self.ImageName, split, self.Version) 39 | } else { 40 | return fmt.Sprintf("%s/%s/%s%s%s", self.Registry, self.Namespace, self.ImageName, split, self.Version) 41 | } 42 | } 43 | 44 | type ImageTagListResult struct { 45 | Name string `json:"name"` 46 | Tags []string `json:"tags"` 47 | } 48 | 49 | type Config struct { 50 | Username string `json:"username"` 51 | Password string `json:"password"` 52 | Host string `json:"host"` 53 | Proxy []string `json:"proxy"` 54 | ExistsAuth bool `json:"existsAuth"` 55 | } 56 | 57 | func (self Config) GetAuthString() string { 58 | if self.Username == "" || self.Password == "" { 59 | authString, _ := registry.EncodeAuthConfig(registry.AuthConfig{ 60 | Username: "", 61 | Password: "", 62 | }) 63 | return authString 64 | } 65 | authString, err := registry.EncodeAuthConfig(registry.AuthConfig{ 66 | Username: self.Username, 67 | Password: self.Password, 68 | }) 69 | if err != nil { 70 | slog.Debug("get registry auth string", err.Error()) 71 | return "" 72 | } 73 | return authString 74 | } 75 | 76 | func (self Config) GetRegistryAuthString() string { 77 | if self.Username == "" || self.Password == "" { 78 | return "" 79 | } 80 | return base64.StdEncoding.EncodeToString([]byte( 81 | fmt.Sprintf("%s:%s", 82 | self.Username, self.Password, 83 | )), 84 | ) 85 | } 86 | 87 | type cacheItem struct { 88 | header http.Header 89 | body []byte 90 | } 91 | -------------------------------------------------------------------------------- /common/service/registry/util.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | func GetImageTagDetail(tag string) *ImageTagDetail { 10 | tag = strings.TrimPrefix(strings.TrimPrefix(tag, "http://"), "https://") 11 | result := &ImageTagDetail{} 12 | 13 | // 如果没有指定仓库地址,则默认为 docker.io 14 | noRegistryUrl := false 15 | temp := strings.Split(tag, "/") 16 | if !strings.Contains(temp[0], ".") || len(temp) == 1 { 17 | noRegistryUrl = true 18 | tag = DefaultRegistryDomain + "/" + tag 19 | } 20 | temp = strings.Split(tag, "/") 21 | // 先补齐 registry 地址后再判断是否有标签,仓库地址中可能包含端口号 22 | if !strings.Contains(strings.Join(temp[1:], "/"), ":") { 23 | tag += ":latest" 24 | } 25 | temp = strings.Split(tag, "/") 26 | result.Registry = temp[0] 27 | 28 | name := strings.Split(temp[len(temp)-1], ":") 29 | result.ImageName, result.Version = name[0], strings.Join(name[1:], ":") 30 | 31 | // 兼容使用 digest 标识版本号的情况 32 | if strings.Contains(result.Version, "@") { 33 | //result.Version = strings.Split(result.Version, "@")[1] 34 | } 35 | 36 | if len(temp) <= 2 { 37 | if noRegistryUrl { 38 | result.Namespace = "library" 39 | } 40 | } else { 41 | result.Namespace = strings.Join(temp[1:len(temp)-1], "/") 42 | } 43 | if result.Namespace != "" { 44 | result.BaseName = fmt.Sprintf("%s/%s", result.Namespace, result.ImageName) 45 | } else { 46 | result.BaseName = result.ImageName 47 | } 48 | 49 | return result 50 | } 51 | 52 | func GetRegistryUrl(address string) url.URL { 53 | host := strings.TrimSuffix(address, "/") 54 | protocol := "https" 55 | 56 | parseUrl, err := url.Parse(host) 57 | if err == nil && parseUrl.Host != "" { 58 | host = parseUrl.Host 59 | protocol = parseUrl.Scheme 60 | } 61 | 62 | return url.URL{ 63 | Scheme: protocol, 64 | Host: host, 65 | Path: "/v2/", 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/service/ssh/host.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "errors" 5 | "golang.org/x/crypto/ssh" 6 | "golang.org/x/crypto/ssh/knownhosts" 7 | "net" 8 | "os" 9 | ) 10 | 11 | var keyErr *knownhosts.KeyError 12 | 13 | type DefaultKnownHostsCallback struct { 14 | path string 15 | } 16 | 17 | func (self DefaultKnownHostsCallback) Handler(hostname string, remote net.Addr, key ssh.PublicKey) error { 18 | var ok bool 19 | var err error 20 | 21 | // 如果找到了并且有错才表示有问题,否则正常添加 host 22 | if ok, err = self.check(hostname, remote, key); ok && err != nil { 23 | return err 24 | } 25 | if !ok { 26 | err = self.add(hostname, remote, key) 27 | if err != nil { 28 | return err 29 | } 30 | } 31 | return nil 32 | } 33 | 34 | func (self DefaultKnownHostsCallback) check(hostname string, remote net.Addr, key ssh.PublicKey) (found bool, err error) { 35 | if _, err = os.Stat(self.path); err != nil { 36 | _, _ = os.Create(self.path) 37 | } 38 | 39 | callback, err := knownhosts.New(self.path) 40 | if err != nil { 41 | return false, err 42 | } 43 | err = callback(hostname, remote, key) 44 | if err == nil { 45 | return true, nil 46 | } 47 | // Make sure that the error returned from the callback is host not in file error. 48 | // If keyErr.Want is greater than 0 length, that means host is in file with different key. 49 | if errors.As(err, &keyErr) && len(keyErr.Want) > 0 { 50 | return true, keyErr 51 | } 52 | if err != nil { 53 | return false, err 54 | } 55 | return false, nil 56 | } 57 | 58 | func (self DefaultKnownHostsCallback) add(hostname string, remote net.Addr, key ssh.PublicKey) error { 59 | var err error 60 | f, err := os.OpenFile(self.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) 61 | if err != nil { 62 | return err 63 | } 64 | defer func() { 65 | _ = f.Close() 66 | }() 67 | remoteNormalized := knownhosts.Normalize(remote.String()) 68 | hostNormalized := knownhosts.Normalize(hostname) 69 | addresses := []string{remoteNormalized} 70 | 71 | if hostNormalized != remoteNormalized { 72 | addresses = append(addresses, hostNormalized) 73 | } 74 | _, err = f.WriteString(knownhosts.Line(addresses, key) + "\n") 75 | return err 76 | } 77 | -------------------------------------------------------------------------------- /common/service/ssh/option.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pkg/sftp" 6 | "golang.org/x/crypto/ssh" 7 | "strings" 8 | ) 9 | 10 | func WithAuthBasic(username, password string) Option { 11 | return func(self *Client) error { 12 | self.sshClientConfig.User = username 13 | self.sshClientConfig.Auth = []ssh.AuthMethod{ 14 | ssh.Password(password), 15 | } 16 | return nil 17 | } 18 | } 19 | 20 | func WithAuthPem(username string, privateKeyPem string, password string) Option { 21 | return func(self *Client) error { 22 | var signer ssh.Signer 23 | var err error 24 | self.sshClientConfig.User = username 25 | if password != "" { 26 | signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKeyPem), []byte(password)) 27 | } else { 28 | signer, err = ssh.ParsePrivateKey([]byte(privateKeyPem)) 29 | } 30 | if err != nil { 31 | return err 32 | } 33 | self.sshClientConfig.Auth = []ssh.AuthMethod{ 34 | ssh.PublicKeys(signer), 35 | } 36 | return nil 37 | } 38 | } 39 | 40 | func WithAddress(address string, port int) Option { 41 | return func(self *Client) error { 42 | if strings.Contains(address, ":") { 43 | address = strings.Trim(strings.Trim(address, "["), "]") 44 | self.address = fmt.Sprintf("[%s]:%d", address, port) 45 | self.protocol = "tcp6" 46 | } else { 47 | self.address = fmt.Sprintf("%s:%d", address, port) 48 | self.protocol = "tcp" 49 | } 50 | return nil 51 | } 52 | } 53 | 54 | func WithServerInfo(info *ServerInfo) []Option { 55 | option := make([]Option, 0) 56 | option = append(option, WithAddress(info.Address, info.Port)) 57 | if info.AuthType == SshAuthTypePem { 58 | option = append(option, WithAuthPem(info.Username, info.PrivateKey, info.Password)) 59 | } else if info.AuthType == SshAuthTypeBasic { 60 | option = append(option, WithAuthBasic(info.Username, info.Password)) 61 | } 62 | return option 63 | } 64 | 65 | func WithSftpClient() Option { 66 | return func(self *Client) error { 67 | self.SftpConn = &sftp.Client{} 68 | return nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /common/service/ssh/types.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "context" 5 | "github.com/pkg/sftp" 6 | "golang.org/x/crypto/ssh" 7 | ) 8 | 9 | const ( 10 | SshAuthTypeBasic = "basic" 11 | SshAuthTypePem = "pem" 12 | ) 13 | 14 | type Client struct { 15 | Conn *ssh.Client 16 | SftpConn *sftp.Client 17 | ctx context.Context 18 | ctxCancel context.CancelFunc 19 | sshClientConfig *ssh.ClientConfig 20 | address string 21 | protocol string // 连接协议,tcp or tpc6 22 | } 23 | type Option func(self *Client) error 24 | 25 | type ServerInfo struct { 26 | Username string `json:"username,omitempty"` 27 | Password string `json:"password,omitempty"` 28 | PrivateKey string `json:"privateKey,omitempty"` 29 | Address string `json:"address,omitempty"` 30 | Port int `json:"port,omitempty"` 31 | AuthType string `json:"authType,omitempty"` 32 | } 33 | -------------------------------------------------------------------------------- /common/service/storage/cache.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/patrickmn/go-cache" 5 | "time" 6 | ) 7 | 8 | var ( 9 | CacheKeyImageRemoteList = "image:remoteTag:%s" 10 | CacheKeyExplorerUsername = "explorer:%s:uid:%d" 11 | CacheKeyCommonUserInfo = "user:%d" 12 | ) 13 | 14 | var ( 15 | Cache = cache.New(cache.DefaultExpiration, 5*time.Minute) 16 | ) 17 | -------------------------------------------------------------------------------- /common/service/ws/option.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | func WithMessageRecvHandler(messageType string, call RecvMessageHandlerFn) Option { 4 | return func(self *Client) error { 5 | if self.recvMessageHandler == nil { 6 | self.recvMessageHandler = make(map[string]RecvMessageHandlerFn) 7 | } 8 | if call != nil { 9 | self.recvMessageHandler[messageType] = call 10 | } 11 | return nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/service/ws/types.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | const ( 10 | MessageTypeEvent = "event" 11 | MessageTypeEventFd = "event:fd" 12 | MessageTypeCompose = "compose:%s" 13 | MessageTypeComposeLog = "compose:log:%s" 14 | MessageTypeConsole = "/console/container/%s" 15 | MessageTypeConsoleHost = "/console/host/%s" 16 | MessageTypeContainerLog = "container:log:%s" 17 | MessageTypeContainerAllStat = "container:stat" 18 | MessageTypeContainerStat = "container:stat:%s" 19 | MessageTypeContainerBackup = "container:backup:%d" 20 | MessageTypeImagePull = "image:pull:%s" 21 | MessageTypeImageBuild = "image:build:%d" 22 | MessageTypeImageImport = "image:import" 23 | MessageTypeProgressClose = "progress:close" 24 | MessageTypeDomainApply = "domain:apply" 25 | MessageTypeUserPermission = "user:permission:%s" 26 | ) 27 | 28 | type RespMessage struct { 29 | Fd string `json:"fd"` 30 | Type string `json:"type"` 31 | Data interface{} `json:"data"` 32 | RespAt time.Time `json:"respAt,omitempty"` 33 | } 34 | 35 | func (self RespMessage) ToJson() []byte { 36 | jsonStr, _ := json.Marshal(self) 37 | return jsonStr 38 | } 39 | 40 | type RecvMessage struct { 41 | Fd string `json:"fd"` // 发送消息id 42 | Message []byte `json:"message"` 43 | RecvAt int64 `json:"recv_at"` 44 | Type int `json:"type"` // 消息类型 45 | } 46 | 47 | func (self RecvMessage) IsPing() bool { 48 | if bytes.Equal(self.Message, []byte("ping")) || bytes.Equal(self.Message, []byte("pong")) { 49 | return true 50 | } else { 51 | return false 52 | } 53 | } 54 | 55 | type recvMessageContent struct { 56 | Type string 57 | } 58 | 59 | type RecvMessageHandlerFn func(message *RecvMessage) 60 | 61 | type SendMessageQueue chan *RespMessage // 普通队列,有数据即推送客户端 62 | 63 | type Option func(self *Client) error 64 | -------------------------------------------------------------------------------- /common/types/define/message.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | import "github.com/donknap/dpanel/common/function" 4 | 5 | var ( 6 | ErrorMessageCommonDataNotFoundOrDeleted = function.ErrorMessage(".commonDataNotFoundOrDeleted") 7 | ErrorMessageCommonNotFoundDPanel = function.ErrorMessage(".commonNotFoundDPanel") 8 | ) 9 | -------------------------------------------------------------------------------- /common/types/event/Image_registry.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | "github.com/donknap/dpanel/common/entity" 6 | ) 7 | 8 | var ImageRegistryCreateEvent = "image_registry_create" 9 | var ImageRegistryDeleteEvent = "image_registry_delete" 10 | var ImageRegistryEditEvent = "image_registry_edit" 11 | 12 | type ImageRegistryPayload struct { 13 | Registry *entity.Registry 14 | OldRegistry *entity.Registry 15 | Ctx context.Context 16 | } 17 | -------------------------------------------------------------------------------- /common/types/event/compose.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | "github.com/donknap/dpanel/common/entity" 6 | ) 7 | 8 | var ComposeCreateEvent = "compose_create" 9 | var ComposeDeleteEvent = "compose_delete" 10 | 11 | type ComposePayload struct { 12 | Compose *entity.Compose 13 | Ctx context.Context 14 | } 15 | -------------------------------------------------------------------------------- /common/types/event/container.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | "github.com/docker/docker/api/types/container" 6 | ) 7 | 8 | var ContainerCreateEvent = "container_create" 9 | var ContainerDeleteEvent = "container_delete" 10 | var ContainerEditEvent = "container_edit" 11 | 12 | type ContainerPayload struct { 13 | InspectInfo *container.InspectResponse 14 | OldInspectInfo *container.InspectResponse 15 | Ctx context.Context 16 | } 17 | -------------------------------------------------------------------------------- /common/types/event/env.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "context" 4 | 5 | var EnvDeleteEvent = "env_delete" 6 | 7 | type EnvPayload struct { 8 | Name string 9 | Ctx context.Context 10 | } 11 | -------------------------------------------------------------------------------- /common/types/event/permission.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "github.com/donknap/dpanel/common/accessor" 4 | 5 | var DataPermissionAddEvent = "data_permission_add" 6 | var DataPermissionDeleteEvent = "data_permission_delete" 7 | var DataPermissionEditEvent = "data_permission_edit" 8 | 9 | type DataPermissionAddPayload struct { 10 | Usernames []string 11 | Permission accessor.DataPermissionItem 12 | PermissionType string 13 | Append bool 14 | } 15 | 16 | type DataPermissionDeletePayload struct { 17 | Username string 18 | Permission accessor.DataPermissionItem 19 | } 20 | 21 | type DataPermissionEditPayload struct { 22 | Username string 23 | Permission accessor.DataPermissionItem 24 | OldPermission accessor.DataPermissionItem 25 | } 26 | -------------------------------------------------------------------------------- /common/types/event/store.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | "github.com/donknap/dpanel/common/entity" 6 | ) 7 | 8 | var StoreDeleteEvent = "store_delete" 9 | 10 | type StorePayload struct { 11 | Store *entity.Store 12 | Ctx context.Context 13 | } 14 | -------------------------------------------------------------------------------- /common/types/family.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | FeatureFamilyPe = "canSeeEnablePe" 5 | FeatureFamilyEe = "canSeeEnableEe" 6 | FeatureFamilyCe = "canSeeEnableCe" 7 | FeatureFamilyXk = "canSeeEnableXk" 8 | FeatureVersionEnvLite = "canSeeEnableVersionEnvLite" 9 | FeatureVersionEnvStandard = "canSeeEnableVersionEnvStandard" 10 | FeatureDockerEnvLocal = "canSeeEnableDockerEnvLocal" 11 | FeatureInDPanel = "canSeeEnableInDPanel" 12 | ) 13 | -------------------------------------------------------------------------------- /common/types/fs/file_info.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "time" 7 | ) 8 | 9 | type ChangeType int 10 | 11 | const ( 12 | ChangeDefault = -1 13 | ChangeModified = 0 14 | ChangeAdd = 1 15 | ChangeDeleted = 2 16 | ChangeVolume = 100 17 | ) 18 | 19 | func NewFileInfo(data *FileData) os.FileInfo { 20 | return &FileInfo{ 21 | stat: data, 22 | } 23 | } 24 | 25 | type FileData struct { 26 | Path string `json:"path"` // 完整的目录地址 27 | Name string `json:"name"` // 目录名 28 | Mod os.FileMode `json:"mod"` 29 | ModStr string `json:"modStr"` // 权限字符形式 30 | ModTime time.Time `json:"modTime"` 31 | Change ChangeType `json:"change"` 32 | Size int64 `json:"size"` 33 | User string `json:"user"` 34 | Group string `json:"group"` 35 | LinkName string `json:"linkName"` 36 | IsDir bool `json:"isDir"` 37 | IsSymlink bool `json:"isSymlink"` 38 | } 39 | 40 | func (self *FileData) CheckIsSymlink() bool { 41 | return (self.Mod & os.ModeSymlink) != 0 42 | } 43 | 44 | func (self *FileData) CheckIsBlockDevice() bool { 45 | return self.Mod&os.ModeDevice != 0 && self.Mod&os.ModeCharDevice == 0 46 | } 47 | 48 | type FileInfo struct { 49 | os.FileInfo 50 | stat *FileData 51 | } 52 | 53 | func (self *FileInfo) Name() string { 54 | return self.stat.Name 55 | } 56 | 57 | func (self *FileInfo) Size() int64 { 58 | return self.stat.Size 59 | } 60 | 61 | func (self *FileInfo) Mode() fs.FileMode { 62 | return self.stat.Mod 63 | } 64 | 65 | func (self *FileInfo) ModTime() time.Time { 66 | return self.stat.ModTime 67 | } 68 | 69 | func (self *FileInfo) IsDir() bool { 70 | return self.stat.Mod.IsDir() 71 | } 72 | 73 | func (self *FileInfo) Sys() any { 74 | return self.stat 75 | } 76 | -------------------------------------------------------------------------------- /common/types/permission.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/docker/docker/api/types/image" 5 | "github.com/donknap/dpanel/common/service/registry" 6 | ) 7 | 8 | type PermissionDataValue interface { 9 | GetPermissionDataValue(item any) interface{} 10 | } 11 | 12 | type ImgList []image.Summary 13 | 14 | func (list ImgList) GetPermissionDataValue(item any) interface{} { 15 | return registry.GetImageTagDetail(item.(image.Summary).RepoTags[0]).Registry 16 | } 17 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | name: ${APP_NAME-dpanel} 3 | version: ${APP_VERSION} 4 | env: ${APP_ENV-lite} 5 | family: ${APP_FAMILY-ce} 6 | server: http 7 | cors: 8 | - http://localhost:8000 9 | server: 10 | http: 11 | host: 0.0.0.0 12 | port: ${APP_SERVER_PORT-8086} 13 | log: 14 | default: 15 | driver: stack 16 | channels: 17 | - file 18 | - console 19 | file: 20 | driver: file 21 | path: run.log 22 | level: info 23 | console: 24 | driver: console 25 | level: debug 26 | database: 27 | default: 28 | driver: sqlite 29 | user_name: ${DB_USERNAME-root} 30 | password: ${DB_PASSWORD-123456} 31 | db_name: ${STORAGE_LOCAL_PATH}/dpanel.db 32 | charset: utf8mb4 33 | prefix: ims_ 34 | options: 35 | mode: ${DB_MODE} 36 | storage: 37 | local: 38 | path: ${STORAGE_LOCAL_PATH} 39 | jwt: 40 | secret: ${DP_JWT_SECRET} 41 | common: 42 | public_user_name: ${PUBLIC_USERNAME-__public__} -------------------------------------------------------------------------------- /database/gen-model.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | tag_json_camel: lower 3 | relation: 4 | - table: ims_site 5 | column: 6 | env: 7 | serializer: json 8 | type: SiteEnvOption 9 | container_info: 10 | type: SiteContainerInfoOption 11 | serializer: json 12 | - table: ims_image 13 | column: 14 | image_info: 15 | type: ImageInfoOption 16 | setting: 17 | type: ImageSettingOption 18 | serializer: json 19 | - table: ims_setting 20 | column: 21 | value: 22 | serializer: json 23 | type: SettingValueOption 24 | - table: ims_registry 25 | column: 26 | setting: 27 | serializer: json 28 | type: RegistrySettingOption 29 | - table: ims_site_domain 30 | column: 31 | setting: 32 | type: SiteDomainSettingOption 33 | serializer: json 34 | - table: ims_backup 35 | column: 36 | setting: 37 | type: BackupSettingOption 38 | serializer: json 39 | - table: ims_event 40 | - table: ims_notice 41 | - table: ims_store 42 | column: 43 | setting: 44 | type: StoreSettingOption 45 | serializer: json 46 | - table: ims_compose 47 | column: 48 | setting: 49 | type: ComposeSettingOption 50 | serializer: json 51 | - table: ims_cron 52 | column: 53 | setting: 54 | type: CronSettingOption 55 | serializer: json 56 | - table: ims_cron_log 57 | column: 58 | value: 59 | type: CronLogValueOption 60 | serializer: json 61 | - table: ims_user_permission 62 | column: 63 | value: 64 | type: PermissionValueOption 65 | serializer: json 66 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NGINX_CONFIG_DIR="/dpanel/nginx" 4 | 5 | chmod 755 /app/server/dpanel && mkdir -p /dpanel/nginx/default_host /dpanel/nginx/proxy_host \ 6 | /dpanel/nginx/redirection_host /dpanel/nginx/dead_host /dpanel/nginx/temp \ 7 | /dpanel/cert /dpanel/storage 8 | 9 | reload_nginx() { 10 | echo "Reloading Nginx configuration..." 11 | nginx -s reload 12 | if [ $? -ne 0 ]; then 13 | echo "Failed to reload Nginx configuration." 14 | fi 15 | } 16 | 17 | #while true; do 18 | # inotifywait -r -e modify,create,delete,move "$NGINX_CONFIG_DIR" 19 | # reload_nginx 20 | #done & 21 | 22 | crond 23 | nginx 24 | /app/server/dpanel server:start -f /app/server/config.yaml > /dev/stdout -------------------------------------------------------------------------------- /docker/nginx/dpanel.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen [::]:8080 default_server; 3 | listen 8080 default_server; 4 | server_name _; 5 | 6 | sendfile off; 7 | tcp_nodelay on; 8 | absolute_redirect off; 9 | client_max_body_size 1024M; 10 | 11 | location / { 12 | proxy_http_version 1.1; 13 | proxy_set_header Upgrade $http_upgrade; 14 | proxy_set_header Connection "upgrade"; 15 | proxy_pass http://127.0.0.1:8086; 16 | } 17 | 18 | error_page 500 502 503 504 /50x.html; 19 | location = /50x.html { 20 | root /var/lib/nginx/html; 21 | } 22 | 23 | location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ { 24 | expires 5d; 25 | } 26 | 27 | location ~ /\. { 28 | log_not_found off; 29 | deny all; 30 | } 31 | } -------------------------------------------------------------------------------- /docker/nginx/include/assets.conf: -------------------------------------------------------------------------------- 1 | location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ { 2 | if_modified_since off; 3 | 4 | # use the public cache 5 | proxy_cache public-cache; 6 | proxy_cache_key $host$request_uri; 7 | 8 | # ignore these headers for media 9 | proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires; 10 | 11 | # cache 200s and also 404s (not ideal but there are a few 404 images for some reason) 12 | proxy_cache_valid any 30m; 13 | proxy_cache_valid 404 1m; 14 | 15 | # strip this header to avoid If-Modified-Since requests 16 | proxy_hide_header Last-Modified; 17 | proxy_hide_header Cache-Control; 18 | proxy_hide_header Vary; 19 | 20 | proxy_cache_bypass 0; 21 | proxy_no_cache 0; 22 | 23 | proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404; 24 | proxy_connect_timeout 5s; 25 | proxy_read_timeout 45s; 26 | 27 | expires @30m; 28 | access_log off; 29 | } -------------------------------------------------------------------------------- /docker/nginx/include/challenge.conf: -------------------------------------------------------------------------------- 1 | # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) 2 | # We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel 3 | # other regex checks, because in our other config files have regex rule that denies access to files with dotted names. 4 | location ^~ /.well-known/acme-challenge/ { 5 | # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure 6 | # we need to open up access by turning off auth and IP ACL for this location. 7 | auth_basic off; 8 | auth_request off; 9 | allow all; 10 | 11 | # Set correct content type. According to this: 12 | # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29 13 | # Current specification requires "text/plain" or no content header at all. 14 | # It seems that "text/plain" is a safe option. 15 | default_type "text/plain"; 16 | 17 | # This directory must be the same as in /etc/letsencrypt/cli.ini 18 | # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter 19 | # there to "webroot". 20 | # Do NOT use alias, use root! Target directory is located here: 21 | # /var/www/common/letsencrypt/.well-known/acme-challenge/ 22 | root /dpanel/challenge; 23 | } 24 | 25 | # Hide /acme-challenge subdirectory and return 404 on all requests. 26 | # It is somewhat more secure than letting Nginx return 403. 27 | # Ending slash is important! 28 | location = /.well-known/acme-challenge/ { 29 | return 404; 30 | } 31 | -------------------------------------------------------------------------------- /docker/nginx/include/force-ssl.conf: -------------------------------------------------------------------------------- 1 | set $test ""; 2 | if ($scheme = "http") { 3 | set $test "H"; 4 | } 5 | if ($request_uri = /.well-known/acme-challenge/test-challenge) { 6 | set $test "${test}T"; 7 | } 8 | if ($test = H) { 9 | return 301 https://$host$request_uri; 10 | } 11 | -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | 5 | error_log /dev/stdout warn; 6 | pid /var/run/nginx.pid; 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | sendfile on; 17 | server_tokens off; 18 | tcp_nopush on; 19 | tcp_nodelay on; 20 | client_body_temp_path /tmp/nginx/body 1 2; 21 | keepalive_timeout 90s; 22 | proxy_connect_timeout 90s; 23 | proxy_send_timeout 90s; 24 | proxy_read_timeout 90s; 25 | ssl_prefer_server_ciphers on; 26 | gzip on; 27 | proxy_ignore_client_abort off; 28 | client_max_body_size 2000m; 29 | server_names_hash_bucket_size 1024; 30 | proxy_http_version 1.1; 31 | proxy_set_header X-Forwarded-Scheme $scheme; 32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 33 | proxy_set_header Accept-Encoding ""; 34 | proxy_cache off; 35 | proxy_cache_path /var/lib/nginx/cache/public levels=1:2 keys_zone=public-cache:30m max_size=192m; 36 | proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m; 37 | 38 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 39 | '$status $body_bytes_sent "$http_referer" ' 40 | '"$http_user_agent" "$http_x_forwarded_for"'; 41 | 42 | access_log /var/log/nginx/access.log main; 43 | 44 | map $host $forward_scheme { 45 | default http; 46 | } 47 | # Local subnets: 48 | set_real_ip_from 10.0.0.0/8; 49 | set_real_ip_from 172.16.0.0/12; # Includes Docker subnet 50 | set_real_ip_from 192.168.0.0/16; 51 | real_ip_header X-Real-IP; 52 | real_ip_recursive on; 53 | 54 | include /etc/nginx/http.d/*.conf; 55 | 56 | include /dpanel/nginx/default_host/*.conf; 57 | include /dpanel/nginx/proxy_host/*.conf; 58 | include /dpanel/nginx/redirection_host/*.conf; 59 | include /dpanel/nginx/dead_host/*.conf; 60 | include /dpanel/nginx/temp/*.conf; 61 | } -------------------------------------------------------------------------------- /docker/script/clear-upload/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: clear-upload 3 | descriptionZh: | 4 | 清理上传临时附件 5 | descriptionEn: | 6 | prune upload temp file 7 | script: | 8 | #!/bin/sh 9 | DIR="/dpanel/storage" 10 | EXPIRE_DAYS=3 11 | find "$DIR" -type f -name "dpanel-*" | while read -r file; do 12 | file_mtime=$(stat -c %Y "$file") 13 | current_time=$(date +%s) 14 | time_diff=$((current_time - file_mtime)) 15 | timeout_seconds=$((EXPIRE_DAYS * 24 * 60 * 60)) 16 | if [ "$time_diff" -gt "$timeout_seconds" ]; then 17 | echo "Deleting $file (older than $EXPIRE_DAYS days)" 18 | rm -f "$file" 19 | fi 20 | done 21 | tag: 22 | - dpanel 23 | - run-in-dpanel -------------------------------------------------------------------------------- /docker/script/compose-upgrade/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: compose-upgrade 3 | descriptionZh: | 4 | 更新一个 compose 任务 5 | descriptionEn: | 6 | upgrade compose task 7 | script: | 8 | /app/server/dpanel compose:deploy -f /app/server/config.yaml --name=${COMPOSE_NAME} --pull-image ${PULl_IMAGE} --remove-orphans ${REMOVE_ORPHANS} 9 | tag: 10 | - dpanel 11 | - run-in-dpanel 12 | environment: 13 | - name: COMPOSE_NAME 14 | labelZh: compose 任务名称 15 | labelEn: compose task name 16 | required: true 17 | - name: PULl_IMAGE 18 | type: select 19 | required: true 20 | values: 21 | - label: 使用面板拉取 22 | value: "dpanel" 23 | - label: 使用命令拉取 24 | value: "command" 25 | labelZh: 拉取镜像方式 26 | labelEn: pull image type 27 | - name: REMOVE_ORPHANS 28 | type: select 29 | required: true 30 | values: 31 | - label: 清理 32 | value: "1" 33 | - label: 保留 34 | value: "0" 35 | labelZh: 清理失效的容器 36 | labelEn: pull image type -------------------------------------------------------------------------------- /docker/script/container-backup/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: container-backup 3 | descriptionZh: | 4 | 生成容器快照,支持备份容器配置存储,一键恢复。 5 | descriptionEn: | 6 | Create container snapshot 7 | script: | 8 | /app/server/dpanel container:backup -f /app/server/config.yaml --name=${CONTAINER_NAME} --enable-image ${ENABLE_IMAGE} 9 | tag: 10 | - dpanel 11 | - run-in-dpanel 12 | environment: 13 | - name: CONTAINER_NAME 14 | labelZh: 容器名称 15 | labelEn: container name 16 | required: true 17 | - name: ENABLE_IMAGE 18 | labelZh: 是否备份容器镜像 19 | required: true 20 | labelEn: backup container image 21 | type: select 22 | values: 23 | - label: 是 24 | value: "1" 25 | - label: 否 26 | value: "0" -------------------------------------------------------------------------------- /docker/script/container-clear/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: container-clear 3 | descriptionZh: 清理删除容器内的目录或是文件 4 | descriptionEn: rm container path or file 5 | script: | 6 | docker exec -it ${CONTAINER_NAME} \ 7 | ls ${PATH_OR_FILE} && rm -rf ${PATH_OR_FILE} && \ 8 | echo 上方文件或是目录已被删除 9 | tag: 10 | - dpanel 11 | - run-in-dpanel 12 | environment: 13 | - name: CONTAINER_NAME 14 | labelZh: 容器名称 15 | labelEn: container name 16 | - name: PATH_OR_FILE 17 | labelZh: 容器内的目录或是文件 18 | labelEn: path or file in container 19 | -------------------------------------------------------------------------------- /docker/script/container-restart/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: container-restart 3 | descriptionZh: 重启一个容器 4 | descriptionEn: restart container 5 | script: | 6 | docker restart ${CONTAINER_NAME} && \ 7 | echo 重启 ${CONTAINER_NAME} 容器完成 8 | tag: 9 | - dpanel 10 | - run-in-dpanel 11 | environment: 12 | - name: CONTAINER_NAME 13 | labelZh: 容器名称 14 | labelEn: container name 15 | -------------------------------------------------------------------------------- /docker/script/container-upgrade/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: container-upgrade 3 | descriptionZh: | 4 | 检测容器更新并升级 5 | descriptionEn: | 6 | Check container update and upgrade 7 | script: | 8 | /app/server/dpanel container:upgrade -f /app/server/config.yaml --name=${CONTAINER_NAME} --upgrade ${UPGRADE} 9 | tag: 10 | - dpanel 11 | - run-in-dpanel 12 | environment: 13 | - name: CONTAINER_NAME 14 | labelZh: 容器名称 15 | labelEn: container name 16 | required: true 17 | - name: UPGRADE 18 | type: select 19 | required: true 20 | values: 21 | - label: 执行升级 22 | value: "1" 23 | - label: 只检测更新 24 | value: "0" 25 | labelZh: 是否执行升级 26 | labelEn: upgrade container -------------------------------------------------------------------------------- /docker/script/host-backup/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: host-backup 3 | descriptionZh: | 4 | 备份宿主机的某个目录 5 | descriptionEn: | 6 | Backup a directory in the host 7 | script: | 8 | BACKUP_FILE_NAME=$(echo $BACKUP_SOURCE_PATH | tr "/" "_")_$(date +"%Y%m%d%H%M%S").tar 9 | docker run -it --rm --name dpanel-host-backup \ 10 | -v ${BACKUP_SOURCE_PATH}:/source -v ${BACKUP_TARGET_PATH}:/backup busybox \ 11 | tar czvf /backup/${BACKUP_FILE_NAME} /source 12 | echo "备份完成,备份文件位于: ${BACKUP_PATH}/${BACKUP_FILE_NAME}" 13 | tag: 14 | - dpanel 15 | - run-in-dpanel 16 | environment: 17 | - name: BACKUP_SOURCE_PATH 18 | labelZh: 需要备份的主机目录 19 | labelEn: backup source path of host 20 | - name: BACKUP_TARGET_PATH 21 | labelZh: 备份到主机目录 22 | labelEn: backup target path of host -------------------------------------------------------------------------------- /docker/script/mysql-backup/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: mysql-backup 3 | descriptionZh: | 4 | 备份 mysql 数据库 5 | descriptionEn: | 6 | Backup mysql database 7 | script: | 8 | BACKUP_FILE_NAME=${CONTAINER_NAME}_$(date +"%Y%m%d%H%M%S").sql 9 | docker exec ${CONTAINER_NAME} mysqldump -u root -p${MYSQL_ROOT_PASSWORD} --all-databases > ${BACKUP_TARGET_PATH%/}/${BACKUP_FILE_NAME} 10 | echo "✅ 备份完成,备份文件位于: ${BACKUP_TARGET_PATH%/}/${BACKUP_FILE_NAME}" 11 | tag: 12 | - dpanel 13 | - run-in-dpanel 14 | environment: 15 | - name: CONTAINER_NAME 16 | labelZh: 需要备份的 mysql 数据库容器名称 17 | labelEn: backup container name of mysql 18 | - name: MYSQL_ROOT_PASSWORD 19 | labelZh: mysql 的 root 密码 20 | labelEn: root password 21 | - name: BACKUP_TARGET_PATH 22 | labelZh: 备份到宿主机的目录 23 | labelEn: backup target path of host -------------------------------------------------------------------------------- /docker/script/store-sync/data.yaml: -------------------------------------------------------------------------------- 1 | task: 2 | name: store-sync 3 | descriptionZh: | 4 | 同步某个应用商店应用 5 | descriptionEn: | 6 | sync appstore 7 | script: | 8 | /app/server/dpanel store:sync -f /app/server/config.yaml --name=${STORE_NAME} 9 | tag: 10 | - dpanel 11 | - run-in-dpanel 12 | environment: 13 | - name: STORE_NAME 14 | labelZh: 商店名称 15 | labelEn: name of appstore -------------------------------------------------------------------------------- /reload.sh: -------------------------------------------------------------------------------- 1 | go get github.com/cosmtrek/air 2 | go run github.com/cosmtrek/air -c .air.toml server:start --------------------------------------------------------------------------------