首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >第三方1Panel应用商店不完全指北

第三方1Panel应用商店不完全指北

作者头像
柳神
发布2025-08-30 09:12:15
发布2025-08-30 09:12:15
8200
举报
文章被收录于专栏:清羽飞扬清羽飞扬

介绍

先说说 1Panel。它也算是这两年最火的新一代Linux服务器运维管理面板了吧,主打一个“现代化 + 简洁好用”。不像某Python面板那样一股子历史包袱,1Panel直接走的就是容器化路线,基于Docker来管理应用,UI也比较清爽,该有的功能都安排得明明白白:建站、运维、监控、备份,一套搞定,比较适合既想偷懒又想装专业的用户。

1Panel的应用商店就是它的灵魂之一。简单来说,你可以把它理解成“手机应用商店”在服务器上的翻版:点一点就能把一个完整的服务(比如WordPressHaloRedis之类)拉起来,免去自己维护并更新的麻烦。官方应用商店本身已经挺丰富了,但嘛,跟GitHub这个无底洞比还是差点意思,经常会有一些项目你得自己手动折腾,想加进商店还得自己维护,这就给了像我这种强迫症患者“二次创作”的机会。

第三方应用商店有很多比较著名,比如下面,维护了海量的实用应用,基本上覆盖了全部应用,杂七杂八,你能想到的都有,但是这是他们的优点,也是他们的缺点,如果添加单独一个,会跟不上更新,如果全部添加,会非常冗余,甚至有很多重复应用。

🙄引用站外地址,不保证站点的可用性和安全性

1Panel 应用商店的非官方应用适配库

github.com@okxlin

为了更加直观,且不再冗余,自己维护一个应用商店也就有点用了起来。

仓库介绍

1Panel的资源库默认位置在/opt/1panel,其中我们安装过的所有应用在/opt/1panel/app中,这里涵盖了绝大部分资源,而我们的应用商店,则维护在/opt/1panel/resource/apps中,在其中有一个local文件夹,在其中添加同等格式的应用文件夹,则会被自动解析在应用商店的本地应用中,所以三方仓库的原理就是,将apps中的所有文件夹放在local文件夹中,定时刷新缓存,系统检测到缓存后,就会反馈到仓库中,最终实现推送更新。

我们可以看看okxlin提供的安装第三方应用的命令行:

代码语言:javascript
复制
git clone -b localApps https://ghp.ci/https://github.com/okxlin/appstore /opt/1panel/resource/apps/local/appstore-localApps
cp -rf /opt/1panel/resource/apps/local/appstore-localApps/apps/* /opt/1panel/resource/apps/local/
rm -rf /opt/1panel/resource/apps/local/appstore-localApps

其实就是实现了我们上面说的那些内容。

应用目录

我们再进入应用目录,以AllinSSL为例,目录如下:

代码语言:javascript
复制
. 📂 allinssl
└── 📂 1.0.7/
│  ├── 📄 data.yml
│  ├── 📄 docker-compose.yml
├── 📄 README.md
├── 📄 data.yml
└── 📄 logo.png

其中的readme.md,很明显,是说明文档,展示在安装的首页,还有logo.png,用于展示图标:

除此之外,在一级目录下,有一个data.yml文件内容如下:

代码语言:javascript
复制
name: AllinSSL
tags:
  - SSL
  - 证书管理
  - 自动化运维
  - DevOps
  - 安全
title: SSL证书全流程管理工具,一站式证书生命周期解决方案
description: 一站式SSL证书生命周期管理工具,支持多家CA和多平台自动化部署,提供安全入口保护和证书状态监控。
additionalProperties:
  key: allinssl
  name: AllinSSL
  tags:
    - Tool
    - DevOps
  shortDescZh: 一站式SSL证书生命周期管理解决方案,支持多家CA与多平台自动化运维
  shortDescEn: One-stop SSL certificate lifecycle management tool with multi-CA and platform support
  type: website
  crossVersionUpdate: true
  limit: 0
  website: https://github.com/allinssl/allinssl
  github: https://github.com/allinssl/allinssl
  document: https://github.com/allinssl/allinssl
  description:
    en: One-stop SSL certificate lifecycle management tool supporting multiple CAs and platforms, with automated issuance, renewal, deployment, and monitoring.
    zh: 一站式SSL证书生命周期管理工具,支持多家证书颁发机构和多平台自动化部署,提供证书申请、续期、监控等功能。
    zh-Hant: 一站式SSL憑證生命週期管理工具,支援多家憑證頒發機構及多平台自動化部署,提供憑證申請、續期、監控等功能。
    ja: 複数のCAとプラットフォームに対応したワンストップSSL証明書ライフサイクル管理ツール。自動発行、更新、展開、監視を提供。
    ms: Alat pengurusan kitar hayat sijil SSL sehenti yang menyokong pelbagai CA dan platform, dengan pengeluaran, pembaharuan, penyebaran, dan pemantauan automatik.
    pt-br: Ferramenta de gerenciamento de ciclo de vida de certificado SSL tudo-em-um, suportando múltiplas CAs e plataformas, com emissão, renovação, implantação e monitoramento automatizados.
    ru: Универсальный инструмент управления жизненным циклом SSL-сертификатов с поддержкой множества центров сертификации и платформ, автоматическим выпуском, обновлением, развертыванием и мониторингом.
    ko: 여러 CA 및 플랫폼을 지원하는 원스톱 SSL 인증서 수명 주기 관리 도구로 자동 발급, 갱신, 배포 및 모니터링을 제공합니다.
  architectures:
    - amd64
    - arm64

需要注意其中的key,这个值对应着文件夹名称,不容有错,其他的可以象征性的填写一下,tags标签有几个固定的值,如果写了其他的会不显示,但是不会报错,剩下的,建议gpt生成一下嘻嘻。

在一级目录下,还有一个以版本号命名的文件夹,这个文件夹名称就是我们安装时选择的版本号,一般文件夹内部的docker-compose.yml文件中的版本号需要和文件夹名称对应,非必要不要写latest

不能写latest的原因

这个涉及下一部分,应用商店不单单是维护一个仓库即可,如果应用数量较多,手动更新会非常费神,所以需要自动检测到更新,而latest标签的镜像始终指向最新的哈希值,所以无法检测到更新,导致应用没法推送更新,哪怕应用发布了新的应用。

在版本号文件下还有一个data.yml,这个和上面的根目录不同,根目录的data.yml维护的是应用元信息,而版本号下面的data,yml文件则维护的是安装字段信息,如下:

1Panel会根据这个字段,在目录下创建.env文件,而目录下的docker-compose.yml中的信息也是使用的环境变量,在启动的时候会自动读取.env中维护的信息,从而实现安装,这就是整个安装的过程。

应用更新

这里更新使用的是renovate检测,该组件会定时检测更新,如果有更新则提交PR

这里下一章节讲解,我们先讲解一下1Panel中是怎么实现推送更新的。首先,应用中的文件夹更新,系统会根据版本号大小判断到,当前应用是否有更新,注意这里判断的是文件夹名称,而不是docker-compose.yml中的版本号。

当检测到更新后,系统会提示,更新,首先备份整个目录,由于在1Panel应用商店中,通常会将数据挂载到./data目录下,所以也不用担心。然后将新文件覆盖进来,由于数据文件夹中原始是没有文件的,所以这部分文件不需要担心覆盖。

覆盖完成后,系统会执行docker compose up -d命令,如果一切正常,最终则会正确更新,如果更新出现问题,也会自动回退。

至于在上面设置页面的自定义仓库,其实就是给正常仓库的apps文件夹打包为tar.gz压缩包,个人感觉没必要替换掉所有的应用商店,如果有这部分需求可以看以下视频自行学习,这里不再讲解。

🙄引用站外地址,不保证站点的可用性和安全性

发现了个有手就行的服务器面板工具|使用1Panel自建应用商店!

凌霞实验室

所以难点就集中在怎么自动更新应用啦!下面我们就来讲解一下1Panel工作流中的一些原理!

更新工作流

Renovate

安装应用

Renovate可以说是1Panel自动更新的核心,首先克隆一个仓库,这里推荐克隆窝修改后的appstore应用,支持的功能和完整度会稍微高一些:

🙄引用站外地址,不保证站点的可用性和安全性

🌭清羽飞扬自建非官方第三方1Panel应用仓库

github.com@willow-god

复刻完成后,添加应用,尝试打开Renovate,添加你个人的仓库,如果不出意外,会自动产生一个issue,用于实时观测应用状态:

无需关闭该issue,他会自动打开的QAQ,别问我怎么知道的。

配置文件

应用安装好后,可以自行配置一下根目录中的配置文件,当然也可以保持默认,除非有部分应用超出范围。比如,第三方源。

🙄引用站外地址,不保证站点的可用性和安全性

Revovate.json

github.com@willow-god

打开下面站点,可以看到其中我添加了了一些三方源比如codeberg.org,这里我建议除了docker hub源,其余都按照规则添加进来,比如ghcrk8s

除了第一部分的源配置,下面我限定了更新的范围,比如不更新action,以稳定运行,不更新部分已经停更的应用,指定更新特殊版本号的应用,比如牢Umami,其余的你们自己看咯,完整的配置文件如下:

代码语言:javascript
复制
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:base"],
  "gitIgnoredAuthors": ["githubaction@githubaction.com"],
  "rebaseWhen": "never",
  "prCreation": "immediate",

  "hostRules": [
    {
      "hostType": "docker",
      "matchHost": "codeberg.org",
      "registryUrls": ["https://codeberg.org"]
    },
    {
      "hostType": "docker",
      "matchHost": "code.forgejo.org",
      "registryUrls": ["https://code.forgejo.org"]
    }
  ],

  "packageRules": [
    {
      "matchManagers": ["github-actions"],
      "enabled": false
    },
    {
      "matchDatasources": ["docker"],
      "matchFileNames": ["apps/meting-api/*/docker-compose.yml"],
      "enabled": false
    },
    {
      "matchDatasources": ["docker"],
      "matchFileNames": ["apps/chatnio/*/docker-compose.yml"],
      "enabled": false
    },
    {
      "matchDatasources": ["docker"],
      "matchFileNames": ["apps/*/*/docker-compose.yml"],
      "versioning": "semver"
    },
    {
      "matchDatasources": ["docker"],
      "matchPackageNames": ["ghcr.io/umami-software/umami"],
      "versionCompatibility": "^(?<compatibility>.*)-(?<version>.*)$",
      "versioning": "semver"
    }
  ]
}

按道理默认的够用了,但是万一你们有抽象的要求呢嘻嘻。

作用

Renovate会不定时开始检测,具体看其队列中的检测任务的时间,如果检测到更新,则会自动创建新分支,修改版本号后提交pr,修改docker-compose文件中的镜像版本为最新。

看第二部分配置文件部分,我匹配了apps文件夹下所有的镜像文件,做到不遗漏更新,但是根据第一部分的讲解,仅仅更新docker-compose文件无法推送更新,推送更新主要依赖于文件夹的版本号实现更新,这部分是renovate机器人无法做到的~

那就继续看第二部分!

更新版本

触发机制

在我们仓库的action工作流中,除了Renovate工作流触发器,还有第一个工作流,这个工作流才是整个系统的核心。

工作流内容如下:

代码语言:javascript
复制
name: Update app version in Renovate Branches

on:
  push:
    branches: [ 'renovate/*' ]
  workflow_dispatch:
    inputs:
      manual-trigger:
        description: 'Manually trigger Renovate'
        default: ''

jobs:
  update-app-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Configure git
        run: |
          git config --local user.email "githubaction@githubaction.com"
          git config --local user.name "github-action update-app-version"

      - name: Get list of updated files by the last commit
        id: updated-files
        run: |
          echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT

      - name: Run renovate-app-version.sh on updated files
        id: rename
        run: |
          set -e
          chmod +x .github/workflows/renovate-app-version.sh

          files="${{ steps.updated-files.outputs.files }}"
          declare -a changed_apps=()

          echo "Updated files: $files"

          for file in $files; do
            if [[ $file == *"docker-compose.yml"* ]]; then
              echo "Processing file: $file"

              app_name=$(echo $file | cut -d'/' -f 2)
              old_version=$(echo $file | cut -d'/' -f 3)
              echo "App name: $app_name, old version: $old_version"

              # 获取所有服务名
              services=$(yq '.services | keys | .[]' "$file")
              service=""
              image_line=""

              for s in $services; do
                # 通过awk获取服务下的image行(包含注释)
                image_line=$(awk "/services:/{flag=0} /^\s*$s:/{flag=1} flag && /^\s*image:/{print; exit}" "$file")
                echo "Service $s image line: $image_line"
                if [[ "$image_line" != *"[ignore]"* ]]; then
                  service="$s"
                  break
                else
                  echo "Skipping service $s due to [ignore]"
                fi
              done

              if [[ -z "$service" ]]; then
                echo "No valid service found in $file, skipping..."
                continue
              fi

              # 提取image纯字符串,去除注释和多余空格
              image=$(echo "$image_line" | sed -E 's/^\s*image:\s*([^ #]+).*/\1/')
              echo "Selected service: $service"
              echo "Extracted image: $image"

              if [[ "$image" == *":"* ]]; then
                new_version=$(cut -d ":" -f2- <<< "$image")
                trimmed_version=${new_version/#"v"/}
                echo "Parsed new version: $trimmed_version"
              else
                trimmed_version=""
                echo "No version tag found in image."
              fi

              changed_apps+=("${app_name}:${old_version}:${trimmed_version}")
              echo "Calling renovate-app-version.sh with: $app_name, $old_version, $trimmed_version"
              .github/workflows/renovate-app-version.sh "$app_name" "$old_version" "$trimmed_version"
            fi
          done

          echo "All changed apps: ${changed_apps[*]}"
          echo "apps=$(IFS=, ; echo "${changed_apps[*]}")" >> $GITHUB_OUTPUT

      - name: Commit & Push Changes
        run: |
          set -e
          IFS=',' read -r -a apps <<< "${{ steps.rename.outputs.apps }}"
          for item in "${apps[@]}"; do
            app_name=$(cut -d':' -f1 <<< "$item")
            old_version=$(cut -d':' -f2 <<< "$item")
            new_version=$(cut -d':' -f3 <<< "$item")

            if [[ -n "$app_name" && -n "$new_version" ]]; then
              git add "apps/$app_name/*"
              git commit -m "📈将应用 $app_name 的版本从 $old_version 升级到 $new_version [skip ci]" --no-verify || echo "无内容可提交"
            fi
          done

          git push || echo "无内容可推送"

      - name: Force merge PR after version bump
        if: github.ref_name != 'main'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -e
          branch_name=$(git rev-parse --abbrev-ref HEAD)
          echo "Current branch: $branch_name"

          # 获取 PR 编号
          pr_number=$(gh pr list --state open --head "$branch_name" --json number -q '.[0].number')
          if [ -z "$pr_number" ]; then
            echo "No PR found for branch $branch_name"
            exit 0
          fi

          echo "Found PR #$pr_number, force merging..."

          # 强制合并,不管 mergeable 状态
          gh pr merge "$pr_number" --merge --delete-branch --admin

可以看到触发方式中有,在分支renovate/*触发推送,则会进入到该工作流,恰好,在上一部分renovate中,自动更新创建的分支也是以这个为开头的,所以当renovate更新后,我们可以抓取到更新并触发该工作流。

更新文件夹

renovate机器人更新时,会在提交信息中给出一个规范信息,从xxx版本更新到了yyy版本都有记录,我们可以从该记录中提取到旧版本信息和新版本信息,再执行renovate-app-version.sh脚本,该脚本经过我大量简化,功能仅为输入应用名称,旧版本,新版本,即可实现文件夹的重命名。

具体提取版本号的过程,你们可以自行研究一下,这里不再细讲,能用即可。

自动合并

原版appstore到这里就结束了,而我实现的新版则会自动合并符合要求的更新PR,实现全自动化,由于我们的触发器是Push触发器,我们无法直接获取到PR的编号,所以这里我使用github API,检测PR编号,并自动强制合并。

最终实现的效果如下:

首先,renovate实现创建分支并提交修改,打开PRaction实现修改文件夹,最终检测PR编号,自动合并并删除多余分支。

镜像

由于我们所使用的镜像需要符合docker hubv2 API规范,才能正常通过renovate更新并检测,普通源倒是很多,但是譬如ghcr这种的镜像源,比较稳定的非常有限,ghcr.nju.edu.cn是南京大学官方维护的镜像,稳定,但是很遗憾,经过测试,无法直接作为镜像源添加在列表中,无法支持检测更新的功能。

但是嘛,我总不能每次安装手动改一次镜像地址吧,作为一个彻头彻尾的懒蛋,我是不能接受的,所以我写了一个脚本,用来替换相关的镜像源。

设计

起初我想通过直接维护一个允许api的镜像源,后面发现成本较高,并且暴露在公网,容易被滥用,毕竟反向代理这些被墙的站点,风险是众所周知的,所以我选择了将配置写在服务器本地,提供脚本实现替换并安装。

脚本

首先,更新本地应用的脚本设计如下:

代码语言:javascript
复制
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

GIT_REPO="https://cnb.cool/Liiiu/appstore"
TMP_DIR="/opt/1panel/resource/apps/local/appstore-localApps"
LOCAL_APPS_DIR="/opt/1panel/resource/apps/local"

trap 'rm -rf "$TMP_DIR"' EXIT

echo "📥 Cloning appstore repo..."
[ -d "$TMP_DIR" ] && rm -rf "$TMP_DIR"
git clone "$GIT_REPO" "$TMP_DIR"

echo "🔄 Mirroring apps..."
cd "$TMP_DIR"
if [[ -f ./mirror.sh ]]; then
    chmod +x ./mirror.sh
    ./mirror.sh
else
    echo "⚠️ mirror.sh not found, skipping mirroring"
fi
cd -

mkdir -p "$LOCAL_APPS_DIR"

for app_path in "$TMP_DIR/apps/"*; do
    [ -d "$app_path" ] || continue
    app_name=$(basename "$app_path")
    local_app_path="$LOCAL_APPS_DIR/$app_name"

    echo "🔁 Updating app: $app_name"
    [ -d "$local_app_path" ] && rm -rf "$local_app_path"
    cp -r "$app_path" "$local_app_path"
done

echo "✅ Sync completed."

在其中,会执行一个Mirror.sh脚本,该脚本实现的功能为,首先从本地找到配置文件,地址为/opt/mirror-config.env,内容示例如下:

代码语言:javascript
复制
# ====== GHCR (GitHub Container Registry) ======
# 是否经常被墙:是
GHCR_ENABLE=true
GHCR_MIRROR=ghcr.io.mirror

# ====== Quay.io (RedHat/Community images) ======
# 是否经常被墙:是
QUAY_ENABLE=false
QUAY_MIRROR=quay.io.mirror

# ====== GCR (Google Container Registry) ======
# 是否经常被墙:是
GCR_ENABLE=false
GCR_MIRROR=gcr.io.mirror

# ====== k8s.gcr.io (旧 Kubernetes 镜像仓库) ======
# 是否经常被墙:是
K8S_GCR_ENABLE=false
K8S_GCR_MIRROR=k8s.gcr.io.mirror

# ====== registry.k8s.io (新 Kubernetes 镜像仓库) ======
# 是否经常被墙:是
K8S_REG_ENABLE=false
K8S_REG_MIRROR=registry.k8s.io.mirror

在项目的根目录中,mirror.sh执行后,首先会检测本地的该路径的配置文件,如果存在,则会读取其中的配置,选择是否替换镜像和镜像地址,如果存在文件,且设置为true,则会按照根目录中,维护的.env文件,分辨哪些项目是对应的镜像,并检索目录进行替换。

最终拉取下来后,呈现在应用商店的即为镜像站点,并且由于版本检测并不在本地进行,所以只要可以拉取即可,是否支持api并不重要。

具体的文档也可以看到github

🙄引用站外地址,不保证站点的可用性和安全性

🌭清羽飞扬自建非官方第三方1Panel应用仓库

github.com@willow-god

最终也是基本实现功能,并且保护了私有镜像站不会暴露。

总结

至此,整个流程就算是跑通啦!应用商店的首次维护需要我们手动生成相关的元信息,但后续更新就简单多了:直接交给 action 去跑,再配合一个定时任务,就能实现全自动更新,真正做到“无人参与”。前端点击一下更新,应用就能在商店里展示出来,不仅美观,还方便备份和维护,算是省心又好用。大家有兴趣的话也欢迎试试!

时间过得飞快,转眼暑假就结束了,又要开工了。以前最讨厌的九月一日,如今反倒没什么感觉——毕竟已经没有开学可怕的事了(笑),而是走上了职场的新阶段。希望接下来的日子一切顺利吧!

还有还有,今天是俺的生日!🎂

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
    • 仓库介绍
    • 应用目录
    • 应用更新
  • 更新工作流
    • Renovate
      • 安装应用
      • 配置文件
      • 作用
    • 更新版本
      • 触发机制
      • 更新文件夹
      • 自动合并
  • 镜像
    • 设计
    • 脚本
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档