From b60d5dbf4de49ec1ebcc0e649d37edebc072bf49 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:56:02 +0800 Subject: [PATCH] feat: add feishin and pingap Add Feishin and Pingap app definitions with source evidence, README guidance, strict adapter validation, env closure checks, and moelin/1panel:v2 smoke coverage. --- apps/feishin/README.md | 57 ++++++++ apps/feishin/data.yml | 30 ++++ apps/feishin/latest/.env.sample | 10 ++ apps/feishin/latest/data.yml | 171 +++++++++++++++++++++++ apps/feishin/latest/data/.gitkeep | 0 apps/feishin/latest/docker-compose.yml | 24 ++++ apps/feishin/latest/scripts/.gitkeep | 0 apps/feishin/latest/scripts/init.sh | 3 + apps/feishin/latest/scripts/uninstall.sh | 2 + apps/feishin/latest/scripts/upgrade.sh | 3 + apps/feishin/logo.png | Bin 0 -> 7255 bytes apps/feishin/source-evidence.json | 5 + apps/pingap/README.md | 58 ++++++++ apps/pingap/data.yml | 30 ++++ apps/pingap/latest/.env.sample | 7 + apps/pingap/latest/data.yml | 101 +++++++++++++ apps/pingap/latest/data/.gitkeep | 0 apps/pingap/latest/docker-compose.yml | 27 ++++ apps/pingap/latest/scripts/.gitkeep | 0 apps/pingap/latest/scripts/init.sh | 4 + apps/pingap/latest/scripts/uninstall.sh | 2 + apps/pingap/latest/scripts/upgrade.sh | 3 + apps/pingap/logo.png | Bin 0 -> 7853 bytes apps/pingap/source-evidence.json | 5 + 24 files changed, 542 insertions(+) create mode 100644 apps/feishin/README.md create mode 100644 apps/feishin/data.yml create mode 100644 apps/feishin/latest/.env.sample create mode 100644 apps/feishin/latest/data.yml create mode 100644 apps/feishin/latest/data/.gitkeep create mode 100644 apps/feishin/latest/docker-compose.yml create mode 100644 apps/feishin/latest/scripts/.gitkeep create mode 100755 apps/feishin/latest/scripts/init.sh create mode 100755 apps/feishin/latest/scripts/uninstall.sh create mode 100755 apps/feishin/latest/scripts/upgrade.sh create mode 100644 apps/feishin/logo.png create mode 100644 apps/feishin/source-evidence.json create mode 100644 apps/pingap/README.md create mode 100644 apps/pingap/data.yml create mode 100644 apps/pingap/latest/.env.sample create mode 100644 apps/pingap/latest/data.yml create mode 100644 apps/pingap/latest/data/.gitkeep create mode 100644 apps/pingap/latest/docker-compose.yml create mode 100644 apps/pingap/latest/scripts/.gitkeep create mode 100755 apps/pingap/latest/scripts/init.sh create mode 100755 apps/pingap/latest/scripts/uninstall.sh create mode 100755 apps/pingap/latest/scripts/upgrade.sh create mode 100644 apps/pingap/logo.png create mode 100644 apps/pingap/source-evidence.json diff --git a/apps/feishin/README.md b/apps/feishin/README.md new file mode 100644 index 000000000..ea37378c3 --- /dev/null +++ b/apps/feishin/README.md @@ -0,0 +1,57 @@ +# Feishin + +## 产品介绍 +Feishin 是现代化自托管音乐服务器 Web 播放器,可连接 Navidrome、Jellyfin 或 OpenSubsonic 兼容服务进行音乐浏览与播放。 + +## 主要功能 +- 提供现代化 Web 音乐播放界面 +- 支持 Navidrome、Jellyfin 和 OpenSubsonic 兼容服务器 +- 支持通过环境变量预置服务器地址,并默认关闭 Umami analytics + +## 访问说明 +安装后通过 `http://<服务器 IP>:15078` 访问,实际端口以安装表单中的 `PANEL_APP_PORT_HTTP` 为准。 + +## Introduction +Feishin is a modern web music player for self-hosted Navidrome, Jellyfin and OpenSubsonic-compatible music servers. + +## Features +- Modern web music player interface +- Supports Navidrome, Jellyfin and OpenSubsonic-compatible servers +- Optional server preconfiguration through environment variables, with Umami analytics disabled by default + +## 部署说明 +- 本应用使用官方 GitHub Container Registry 镜像 `ghcr.io/jeffvli/feishin` 部署。 +- Feishin 是 Web 播放器,不自带音乐服务器;请先准备 Navidrome、Jellyfin 或 OpenSubsonic 兼容服务。 +- 应用分类:多媒体。 +- 支持架构:amd64、arm64。 +- 可选版本:`latest`。 + +## 端口 +| 变量 | 说明 | 默认值 | 必填 | +| --- | --- | --- | --- | +| PANEL_APP_PORT_HTTP | Feishin Web 访问端口 | 15078 | 是 | + +## 数据持久化 +Feishin Docker Web 版本默认不需要挂载服务端数据目录;播放服务器和用户数据由你连接的 Navidrome、Jellyfin 或 OpenSubsonic 服务保存。 + +## 参数说明 +| 变量 | 说明 | 默认值 | 必填 | +| --- | --- | --- | --- | +| ANALYTICS_DISABLED | 是否禁用 Umami analytics | true | 是 | +| SERVER_NAME | 预置音乐服务器名称 | 空 | 否 | +| SERVER_TYPE | 预置音乐服务器类型:jellyfin、navidrome、subsonic | 空 | 否 | +| SERVER_URL | 预置音乐服务器地址 | 空 | 否 | +| SERVER_LOCK | 是否锁定服务器配置 | false | 是 | +| REMOTE_URL | 单独的公网音乐服务器地址 | 空 | 否 | +| LEGACY_AUTHENTICATION | Subsonic/OpenSubsonic 传统认证开关 | false | 是 | +| TZ | 容器时区 | Asia/Shanghai | 是 | + +## 使用说明 +- 如果设置 `SERVER_LOCK=true`,请同时填写 `SERVER_NAME`、`SERVER_TYPE` 和 `SERVER_URL`。 +- 若音乐服务器在内网,请确认 Feishin 容器可以访问该地址。 +- 如对公网开放访问,请同步检查防火墙、安全组、反向代理和 HTTPS 配置。 + +## 参考资料 +- 项目仓库: +- Docker 使用说明: +- Docker Compose 示例: diff --git a/apps/feishin/data.yml b/apps/feishin/data.yml new file mode 100644 index 000000000..a24827001 --- /dev/null +++ b/apps/feishin/data.yml @@ -0,0 +1,30 @@ +name: Feishin +tags: + - Media +title: 现代化自托管音乐服务器 Web 播放器 +description: 现代化自托管音乐服务器 Web 播放器 +additionalProperties: + key: feishin + name: Feishin + tags: + - Media + shortDescZh: 现代化自托管音乐服务器 Web 播放器 + shortDescEn: A modern web player for self-hosted music servers + description: + en: "A modern web music player for Navidrome, Jellyfin and OpenSubsonic-compatible servers." + zh: "面向 Navidrome、Jellyfin 和 OpenSubsonic 兼容服务器的现代化 Web 音乐播放器。" + zh-Hant: "面向 Navidrome、Jellyfin 和 OpenSubsonic 相容伺服器的現代化 Web 音樂播放器。" + ja: "Navidrome、Jellyfin、OpenSubsonic 互換サーバー向けのモダンな Web 音楽プレーヤーです。" + ko: "Navidrome, Jellyfin 및 OpenSubsonic 호환 서버를 위한 최신 웹 음악 플레이어입니다." + ru: "Современный веб-плеер для музыкальных серверов Navidrome, Jellyfin и совместимых с OpenSubsonic." + ms: "Pemain muzik web moden untuk pelayan Navidrome, Jellyfin dan serasi OpenSubsonic." + pt-br: "Player web moderno para servidores de musica Navidrome, Jellyfin e compativeis com OpenSubsonic." + type: tool + crossVersionUpdate: true + limit: 0 + website: https://feishin.vercel.app + github: https://github.com/jeffvli/feishin + document: https://github.com/jeffvli/feishin#web-and-docker + architectures: + - amd64 + - arm64 diff --git a/apps/feishin/latest/.env.sample b/apps/feishin/latest/.env.sample new file mode 100644 index 000000000..868370085 --- /dev/null +++ b/apps/feishin/latest/.env.sample @@ -0,0 +1,10 @@ +CONTAINER_NAME=feishin +PANEL_APP_PORT_HTTP=15078 +TZ=Asia/Shanghai +ANALYTICS_DISABLED=true +SERVER_NAME= +SERVER_TYPE= +SERVER_URL= +SERVER_LOCK=false +REMOTE_URL= +LEGACY_AUTHENTICATION=false diff --git a/apps/feishin/latest/data.yml b/apps/feishin/latest/data.yml new file mode 100644 index 000000000..9f73594ad --- /dev/null +++ b/apps/feishin/latest/data.yml @@ -0,0 +1,171 @@ +additionalProperties: + formFields: + - default: 15078 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text + - default: "true" + edit: true + envKey: ANALYTICS_DISABLED + labelEn: Disable Analytics + labelZh: 禁用统计分析 + label: + en: Disable Analytics + zh: 禁用统计分析 + zh-Hant: 停用統計分析 + ja: 分析を無効化 + ko: 분석 비활성화 + ru: Отключить аналитику + ms: Lumpuhkan analitik + pt-br: Desativar analiticos + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - default: "" + edit: true + envKey: SERVER_NAME + labelEn: Server Name + labelZh: 服务器名称 + label: + en: Server Name + zh: 服务器名称 + zh-Hant: 伺服器名稱 + ja: サーバー名 + ko: 서버 이름 + ru: Имя сервера + ms: Nama pelayan + pt-br: Nome do servidor + required: false + type: text + - default: "" + edit: true + envKey: SERVER_TYPE + labelEn: Server Type + labelZh: 服务器类型 + label: + en: Server Type + zh: 服务器类型 + zh-Hant: 伺服器類型 + ja: サーバー種別 + ko: 서버 유형 + ru: Тип сервера + ms: Jenis pelayan + pt-br: Tipo de servidor + required: false + type: select + values: + - label: "" + value: "" + - label: jellyfin + value: jellyfin + - label: navidrome + value: navidrome + - label: subsonic + value: subsonic + - default: "" + edit: true + envKey: SERVER_URL + labelEn: Server URL + labelZh: 服务器 URL + label: + en: Server URL + zh: 服务器 URL + zh-Hant: 伺服器 URL + ja: サーバー URL + ko: 서버 URL + ru: URL сервера + ms: URL pelayan + pt-br: URL do servidor + required: false + type: text + - default: "false" + edit: true + envKey: SERVER_LOCK + labelEn: Lock Server Settings + labelZh: 锁定服务器设置 + label: + en: Lock Server Settings + zh: 锁定服务器设置 + zh-Hant: 鎖定伺服器設定 + ja: サーバー設定をロック + ko: 서버 설정 잠금 + ru: Заблокировать настройки сервера + ms: Kunci tetapan pelayan + pt-br: Bloquear configuracoes do servidor + required: true + type: select + values: + - label: "false" + value: "false" + - label: "true" + value: "true" + - default: "" + edit: true + envKey: REMOTE_URL + labelEn: Public Server URL + labelZh: 公网服务器 URL + label: + en: Public Server URL + zh: 公网服务器 URL + zh-Hant: 公網伺服器 URL + ja: 公開サーバー URL + ko: 공개 서버 URL + ru: Публичный URL сервера + ms: URL pelayan awam + pt-br: URL publico do servidor + required: false + type: text + - default: "false" + edit: true + envKey: LEGACY_AUTHENTICATION + labelEn: Legacy Authentication + labelZh: 传统认证 + label: + en: Legacy Authentication + zh: 传统认证 + zh-Hant: 傳統認證 + ja: レガシー認証 + ko: 레거시 인증 + ru: Устаревшая аутентификация + ms: Pengesahan legasi + pt-br: Autenticacao legada + required: true + type: select + values: + - label: "false" + value: "false" + - label: "true" + value: "true" diff --git a/apps/feishin/latest/data/.gitkeep b/apps/feishin/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/feishin/latest/docker-compose.yml b/apps/feishin/latest/docker-compose.yml new file mode 100644 index 000000000..3b6e78b66 --- /dev/null +++ b/apps/feishin/latest/docker-compose.yml @@ -0,0 +1,24 @@ +services: + feishin: + image: "ghcr.io/jeffvli/feishin:latest" + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:9180" + environment: + - TZ=${TZ} + - ANALYTICS_DISABLED=${ANALYTICS_DISABLED} + - SERVER_NAME=${SERVER_NAME} + - SERVER_TYPE=${SERVER_TYPE} + - SERVER_URL=${SERVER_URL} + - SERVER_LOCK=${SERVER_LOCK} + - REMOTE_URL=${REMOTE_URL} + - LEGACY_AUTHENTICATION=${LEGACY_AUTHENTICATION} + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/feishin/latest/scripts/.gitkeep b/apps/feishin/latest/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/feishin/latest/scripts/init.sh b/apps/feishin/latest/scripts/init.sh new file mode 100755 index 000000000..9879df4c3 --- /dev/null +++ b/apps/feishin/latest/scripts/init.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exit 0 diff --git a/apps/feishin/latest/scripts/uninstall.sh b/apps/feishin/latest/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/feishin/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/feishin/latest/scripts/upgrade.sh b/apps/feishin/latest/scripts/upgrade.sh new file mode 100755 index 000000000..9879df4c3 --- /dev/null +++ b/apps/feishin/latest/scripts/upgrade.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exit 0 diff --git a/apps/feishin/logo.png b/apps/feishin/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1543b47557f6fb3e92eb9f53210663a3c4e7af6d GIT binary patch literal 7255 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:15079/pingap` 访问管理界面,实际 HTTP 端口以安装表单中的 `PANEL_APP_PORT_HTTP` 为准。 + +## Introduction +Pingap is a high-performance reverse proxy and application gateway powered by Cloudflare Pingora, with hot reload, TOML configuration and a web admin interface. + +## Features +- HTTP/HTTPS reverse proxy and application gateway +- Hot reload for containerized configuration changes +- Web admin interface for routes, upstreams and plugins + +## 部署说明 +- 本应用使用官方 Docker 镜像 `vicanso/pingap` 部署。 +- 默认按官方 Compose 示例启用 `pingap --autoreload`。 +- 应用分类:工具。 +- 支持架构:amd64、arm64。 +- 可选版本:`latest`。 + +## 端口 +| 变量 | 说明 | 默认值 | 必填 | +| --- | --- | --- | --- | +| PANEL_APP_PORT_HTTP | HTTP 与管理界面端口,容器内映射到 `80` | 15079 | 是 | +| PANEL_APP_PORT_HTTPS | HTTPS 代理端口,容器内映射到 `443` | 15443 | 是 | + +## 数据持久化 +| 变量 | 说明 | 默认值 | 必填 | +| --- | --- | --- | --- | +| APP_DATA_DIR | Pingap 配置和运行数据目录,挂载到容器 `/opt/pingap` | ./data | 是 | + +升级、迁移或批量调整代理配置前,请先在 1Panel 中备份 `APP_DATA_DIR`。 + +## 参数说明 +| 变量 | 说明 | 默认值 | 必填 | +| --- | --- | --- | --- | +| PINGAP_ADMIN_USER | Web 管理界面用户名 | pingap | 是 | +| PINGAP_ADMIN_PASSWORD | Web 管理界面密码,安装时随机生成 | 随机生成 | 是 | +| TZ | 容器时区 | Asia/Shanghai | 是 | + +## 使用说明 +- 管理界面路径固定为 `/pingap`,请使用安装时生成的管理员用户名和密码登录。 +- 这是反向代理/应用网关类应用,对公网开放前请先配置强密码、访问控制、防火墙、安全组、TLS 证书和路由规则。 +- HTTPS 端口默认仅做端口映射;请在 Pingap 内正确配置证书、SNI 和路由后再对外开放。 +- 若代理内网服务,请确认 Pingap 容器与目标服务网络可达。 + +## 参考资料 +- 项目仓库: +- Docker Compose 快速开始: +- 官方文档: diff --git a/apps/pingap/data.yml b/apps/pingap/data.yml new file mode 100644 index 000000000..26b0d3d10 --- /dev/null +++ b/apps/pingap/data.yml @@ -0,0 +1,30 @@ +name: Pingap +tags: + - Tool +title: 基于 Pingora 的高性能反向代理和应用网关 +description: 基于 Pingora 的高性能反向代理和应用网关 +additionalProperties: + key: pingap + name: Pingap + tags: + - Tool + shortDescZh: 基于 Pingora 的高性能反向代理和应用网关 + shortDescEn: A high-performance reverse proxy and application gateway powered by Pingora + description: + en: "A high-performance reverse proxy and application gateway powered by Cloudflare Pingora, with hot reload and a web admin interface." + zh: "基于 Cloudflare Pingora 的高性能反向代理和应用网关,支持热更新和 Web 管理界面。" + zh-Hant: "基於 Cloudflare Pingora 的高效能反向代理和應用閘道,支援熱更新和 Web 管理介面。" + ja: "Cloudflare Pingora を利用した高性能リバースプロキシ兼アプリケーションゲートウェイです。ホットリロードと Web 管理画面に対応します。" + ko: "Cloudflare Pingora 기반의 고성능 리버스 프록시 및 애플리케이션 게이트웨이로, 핫 리로드와 웹 관리 UI를 지원합니다." + ru: "Высокопроизводительный обратный прокси и шлюз приложений на Cloudflare Pingora с горячей перезагрузкой и веб-панелью управления." + ms: "Proksi songsang dan gerbang aplikasi berprestasi tinggi berasaskan Cloudflare Pingora dengan muat semula panas dan UI pentadbiran web." + pt-br: "Proxy reverso e gateway de aplicacao de alto desempenho baseado no Cloudflare Pingora, com hot reload e interface web de administracao." + type: tool + crossVersionUpdate: true + limit: 0 + website: https://pingap.io + github: https://github.com/vicanso/pingap + document: https://pingap.io/pingap-en/docs/getting_started + architectures: + - amd64 + - arm64 diff --git a/apps/pingap/latest/.env.sample b/apps/pingap/latest/.env.sample new file mode 100644 index 000000000..75b9f857b --- /dev/null +++ b/apps/pingap/latest/.env.sample @@ -0,0 +1,7 @@ +CONTAINER_NAME=pingap +PANEL_APP_PORT_HTTP=15079 +PANEL_APP_PORT_HTTPS=15443 +TZ=Asia/Shanghai +APP_DATA_DIR=./data +PINGAP_ADMIN_USER=pingap +PINGAP_ADMIN_PASSWORD=change-me-before-install diff --git a/apps/pingap/latest/data.yml b/apps/pingap/latest/data.yml new file mode 100644 index 000000000..d5b7b8a7a --- /dev/null +++ b/apps/pingap/latest/data.yml @@ -0,0 +1,101 @@ +additionalProperties: + formFields: + - default: 15079 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: HTTP-порт + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: 15443 + edit: true + envKey: PANEL_APP_PORT_HTTPS + labelEn: HTTPS Port + labelZh: HTTPS 端口 + label: + en: HTTPS Port + zh: HTTPS 端口 + zh-Hant: HTTPS 埠 + ja: HTTPS ポート + ko: HTTPS 포트 + ru: HTTPS-порт + ms: Port HTTPS + pt-br: Porta HTTPS + required: true + rule: paramPort + type: number + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori data + pt-br: Diretório de dados + required: true + type: text + - default: pingap + edit: true + envKey: PINGAP_ADMIN_USER + labelEn: Admin Username + labelZh: 管理员用户名 + label: + en: Admin Username + zh: 管理员用户名 + zh-Hant: 管理員使用者名稱 + ja: 管理者ユーザー名 + ko: 관리자 사용자 이름 + ru: Имя администратора + ms: Nama pengguna pentadbir + pt-br: Usuario administrador + required: true + type: text + - edit: true + envKey: PINGAP_ADMIN_PASSWORD + labelEn: Admin Password + labelZh: 管理员密码 + label: + en: Admin Password + zh: 管理员密码 + zh-Hant: 管理員密碼 + ja: 管理者パスワード + ko: 관리자 비밀번호 + ru: Пароль администратора + ms: Kata laluan pentadbir + pt-br: Senha do administrador + random: true + required: true + rule: paramComplexity + type: password diff --git a/apps/pingap/latest/data/.gitkeep b/apps/pingap/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/pingap/latest/docker-compose.yml b/apps/pingap/latest/docker-compose.yml new file mode 100644 index 000000000..db1bdcf3e --- /dev/null +++ b/apps/pingap/latest/docker-compose.yml @@ -0,0 +1,27 @@ +services: + pingap: + image: "vicanso/pingap:latest" + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:80" + - "${PANEL_APP_PORT_HTTPS}:443" + environment: + - TZ=${TZ} + - PINGAP_CONF=/opt/pingap/conf + - PINGAP_ADMIN_ADDR=0.0.0.0:80/pingap + - PINGAP_ADMIN_USER=${PINGAP_ADMIN_USER} + - PINGAP_ADMIN_PASSWORD=${PINGAP_ADMIN_PASSWORD} + command: + - pingap + - --autoreload + volumes: + - "${APP_DATA_DIR}:/opt/pingap" + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/pingap/latest/scripts/.gitkeep b/apps/pingap/latest/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/pingap/latest/scripts/init.sh b/apps/pingap/latest/scripts/init.sh new file mode 100755 index 000000000..22f0c8ad8 --- /dev/null +++ b/apps/pingap/latest/scripts/init.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +DATA_DIR="${APP_DATA_DIR:-./data}" +mkdir -p "$DATA_DIR" diff --git a/apps/pingap/latest/scripts/uninstall.sh b/apps/pingap/latest/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/pingap/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/pingap/latest/scripts/upgrade.sh b/apps/pingap/latest/scripts/upgrade.sh new file mode 100755 index 000000000..9879df4c3 --- /dev/null +++ b/apps/pingap/latest/scripts/upgrade.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exit 0 diff --git a/apps/pingap/logo.png b/apps/pingap/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4f756bb4f7773ac1c1fa00e4b845171e2dafe8bc GIT binary patch literal 7853 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