update
This commit is contained in:
parent
c84fda10ff
commit
d206f075cf
37 changed files with 563 additions and 137 deletions
3
apphub/src/apphub.egg-info/PKG-INFO
Normal file
3
apphub/src/apphub.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,3 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: apphub
|
||||
Version: 0.2
|
10
apphub/src/apphub.egg-info/SOURCES.txt
Normal file
10
apphub/src/apphub.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
README.md
|
||||
setup.py
|
||||
src/apphub.egg-info/PKG-INFO
|
||||
src/apphub.egg-info/SOURCES.txt
|
||||
src/apphub.egg-info/dependency_links.txt
|
||||
src/apphub.egg-info/entry_points.txt
|
||||
src/apphub.egg-info/requires.txt
|
||||
src/apphub.egg-info/top_level.txt
|
||||
src/cli/__init__.py
|
||||
src/cli/apphub_cli.py
|
1
apphub/src/apphub.egg-info/dependency_links.txt
Normal file
1
apphub/src/apphub.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
2
apphub/src/apphub.egg-info/entry_points.txt
Normal file
2
apphub/src/apphub.egg-info/entry_points.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
[console_scripts]
|
||||
apphub = cli.apphub_cli:cli
|
1
apphub/src/apphub.egg-info/requires.txt
Normal file
1
apphub/src/apphub.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
click
|
1
apphub/src/apphub.egg-info/top_level.txt
Normal file
1
apphub/src/apphub.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
cli
|
|
@ -22,7 +22,7 @@ path = /websoft9/library/apps
|
|||
path = /websoft9/media/json/
|
||||
|
||||
[api_key]
|
||||
key = 9deeaf0d9f6f78c289f199a2631dc793b823d24024258385faaf833ea31e4ff6
|
||||
key = d0a4996ad7819ae91a80c05c0d21800b610b5bf9fd53745db16e2f2ad9ae193c
|
||||
|
||||
[domain]
|
||||
wildcard_domain = test.websoft9.cn
|
||||
|
|
|
@ -14,11 +14,6 @@ from fastapi.responses import HTMLResponse
|
|||
from fastapi.security.api_key import APIKeyHeader
|
||||
|
||||
uvicorn_logger = logging.getLogger("uvicorn")
|
||||
|
||||
# for handler in uvicorn_logger.handlers:
|
||||
# uvicorn_logger.removeHandler(handler)
|
||||
# for handler in logger._error_logger.handlers:
|
||||
# uvicorn_logger.addHandler(handler)
|
||||
|
||||
# 创建一个日志处理器,将日志发送到 stdout
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
|
@ -28,11 +23,13 @@ uvicorn_logger.addHandler(stdout_handler)
|
|||
uvicorn_logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
API_KEY_NAME = "api_key"
|
||||
API_KEY_NAME = "x-api-key"
|
||||
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
||||
|
||||
async def verify_key(request: Request, api_key_header: str = Security(api_key_header)):
|
||||
if request.url.path == "/docs":
|
||||
logger.access(request.headers)
|
||||
logger.access(request.url.path)
|
||||
if request.url.path == "/api/docs":
|
||||
return None
|
||||
|
||||
if api_key_header is None:
|
||||
|
@ -59,7 +56,9 @@ app = FastAPI(
|
|||
description="This documentation describes the AppHub API.",
|
||||
version="0.0.1",
|
||||
docs_url=None,
|
||||
dependencies=[Depends(verify_key)]
|
||||
root_path="/api",
|
||||
servers=[{"url": "/api"}],
|
||||
dependencies=[Depends(verify_key)],
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="swagger-ui"), name="static")
|
||||
|
@ -71,14 +70,14 @@ async def custom_swagger_ui_html():
|
|||
<html>
|
||||
<head>
|
||||
<title>Websoft9 API</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/swagger-ui.css">
|
||||
<script src="/static/swagger-ui-bundle.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/api/static/swagger-ui.css">
|
||||
<script src="/api/static/swagger-ui-bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script>
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/openapi.json",
|
||||
url: "/api/openapi.json",
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
||||
layout: "BaseLayout"
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
## 0.8.22 release on 2023-09-27
|
||||
## 0.8.29-rc1 release on 2023-11-04
|
||||
|
||||
1. add systemd to websoft9 artifacts
|
||||
1. gitea,myapps,appstore update
|
||||
2. apphub domains
|
||||
|
||||
## 0.8.28 release on 2023-11-01
|
||||
|
||||
1. improve dockerfile to reduce image size
|
||||
2. fixed update_zip.sh
|
||||
3. hide websoft9 containers
|
||||
|
||||
## 0.8.27 release on 2023-10-31
|
||||
|
||||
1. new websoft9 init
|
||||
|
||||
## 0.8.26 release on 2023-09-27
|
||||
|
||||
1. appmanage change to apphub
|
||||
|
||||
## 0.8.20 release on 2023-08-23
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
1. Add install: developer mode at install.sh
|
||||
1. gitea,myapps,appstore update
|
||||
2. apphub domains
|
||||
|
|
|
@ -1,55 +1,59 @@
|
|||
# modify time: 202310231530, you can modify here to trigger Docker Build action
|
||||
# modify time: 202311040940, you can modify here to trigger Docker Build action
|
||||
|
||||
FROM python:3.10-bullseye AS buildstage
|
||||
FROM python:3.10-slim-bullseye
|
||||
LABEL maintainer="Websoft9<help@websoft9.com>"
|
||||
LABEL version="0.0.6"
|
||||
|
||||
ENV LIBRARY_VERSION=v0.5.8
|
||||
|
||||
# Prepare library
|
||||
RUN wget https://github.com/Websoft9/docker-library/archive/refs/tags/$LIBRARY_VERSION.zip -O ./library.zip && \
|
||||
unzip library.zip && \
|
||||
mv docker-library-* library && \
|
||||
mkdir credentials && \
|
||||
echo "This folder stored the credentials of other services that apphub will connect" > credentials/readme && \
|
||||
# Prepare Media and master data from Contentful
|
||||
git clone --depth=1 https://github.com/swagger-api/swagger-ui.git && \
|
||||
wget https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js && \
|
||||
cp redoc.standalone.js swagger-ui/dist && \
|
||||
git clone --depth=1 https://github.com/Websoft9/plugin-appstore && \
|
||||
mv -f plugin-appstore/data ./media && \
|
||||
git clone --depth=1 https://github.com/Websoft9/websoft9
|
||||
|
||||
FROM python:3.10-slim-bullseye
|
||||
WORKDIR /websoft9
|
||||
|
||||
COPY --from=buildstage /media/data ./media
|
||||
COPY --from=buildstage /library ./library
|
||||
COPY --from=buildstage /websoft9/apphub ./apphub
|
||||
COPY --from=buildstage /swagger-ui/dist ./apphub/swagger-ui
|
||||
ENV LIBRARY_VERSION=0.5.8
|
||||
ENV MEDIA_VERSION=0.0.3
|
||||
ENV websoft9_repo="https://github.com/Websoft9/websoft9"
|
||||
ENV docker_library_repo="https://github.com/Websoft9/docker-library"
|
||||
ENV media_repo="https://github.com/Websoft9/media"
|
||||
ENV source_github_pages="https://websoft9.github.io/websoft9"
|
||||
|
||||
RUN apt update && apt install git jq iproute2 supervisor -y && \
|
||||
mkdir credentials && \
|
||||
echo "This folder stored the credentials of other services that integrated with apphub" > credentials/readme
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r apphub/requirements.txt
|
||||
RUN pip install -e ./apphub
|
||||
RUN apt update && apt install -y --no-install-recommends curl git jq cron iproute2 supervisor rsync wget unzip zip && \
|
||||
# Prepare source files
|
||||
wget $docker_library_repo/archive/refs/tags/$LIBRARY_VERSION.zip -O ./library.zip && \
|
||||
unzip library.zip && \
|
||||
mv docker-library-* w9library && \
|
||||
rm -rf w9library/.github && \
|
||||
wget $media_repo/archive/refs/tags/$MEDIA_VERSION.zip -O ./media.zip && \
|
||||
unzip media.zip && \
|
||||
mv media-* w9media && \
|
||||
rm -rf w9media/.github && \
|
||||
git clone --depth=1 https://github.com/swagger-api/swagger-ui.git && \
|
||||
wget https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js && \
|
||||
cp redoc.standalone.js swagger-ui/dist && \
|
||||
git clone --depth=1 $websoft9_repo ./w9source && \
|
||||
cp -r ./w9media ./media && \
|
||||
cp -r ./w9library ./library && \
|
||||
cp -r ./w9source/apphub ./apphub && \
|
||||
cp -r ./swagger-ui/dist ./apphub/swagger-ui && \
|
||||
cp -r ./w9source/apphub/src/config ./config && \
|
||||
cp -r ./w9source/docker/apphub/script ./script && \
|
||||
curl -o ./script/update_zip.sh $source_github_pages/scripts/update_zip.sh && \
|
||||
pip install --no-cache-dir --upgrade -r apphub/requirements.txt && \
|
||||
pip install -e ./apphub && \
|
||||
# Clean cache and install files
|
||||
rm -rf apphub/docs apphub/tests library.zip media.zip redoc.standalone.js swagger-ui w9library w9media w9source && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/man /usr/share/doc /usr/share/doc-base
|
||||
|
||||
# supervisor
|
||||
COPY config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
RUN chmod +r /etc/supervisor/conf.d/supervisord.conf
|
||||
COPY config/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# cron
|
||||
COPY config/cron /etc/cron.d/cron
|
||||
RUN echo "" >> /etc/cron.d/cron && crontab /etc/cron.d/cron
|
||||
|
||||
# chmod for all .sh script
|
||||
RUN find /websoft9/script -name "*.sh" -exec chmod +x {} \;
|
||||
|
||||
VOLUME /websoft9/apphub/logs
|
||||
VOLUME /websoft9/apphub/src/config
|
||||
VOLUME /websoft9/media
|
||||
|
||||
# Clean cache and install files
|
||||
RUN rm -rf apphub/docs apphub/tests library.zip plugin-appstore && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/man /usr/share/doc /usr/share/doc-base
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
ENTRYPOINT ["/websoft9/script/entrypoint.sh"]
|
|
@ -1,6 +1,4 @@
|
|||
ARG APPHUB_VERSION
|
||||
FROM websoft9dev/apphub:${APPHUB_VERSION} as buildstage
|
||||
RUN mkdir -p /websoft9/apphub-dev
|
||||
COPY apphub/config/developer.sh /developer.sh
|
||||
RUN chmod +x /developer.sh
|
||||
RUN sed -i '/supervisorctl start apphub/c\supervisorctl start apphubdev' /entrypoint.sh
|
||||
RUN sed -i '/supervisorctl start apphub/c\supervisorctl start apphubdev' /websoft9/script/entrypoint.sh
|
1
source/docker/apphub/config/cron
Normal file
1
source/docker/apphub/config/cron
Normal file
|
@ -0,0 +1 @@
|
|||
0 2 * * * /websoft9/script/update.sh
|
|
@ -17,7 +17,7 @@ stderr_logfile=/var/log/supervisord.log
|
|||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:apphubdev]
|
||||
command=/developer.sh
|
||||
command=/websoft9/script/developer.sh
|
||||
autostart=false
|
||||
user=root
|
||||
stdout_logfile=/var/log/supervisord.log
|
||||
|
@ -25,6 +25,15 @@ stdout_logfile_maxbytes=0
|
|||
stderr_logfile=/var/log/supervisord.log
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:cron]
|
||||
command=cron -f
|
||||
autostart=true
|
||||
user=root
|
||||
stdout_logfile=/var/log/supervisord.log
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/var/log/supervisord.log
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:media]
|
||||
command=uvicorn src.media:app --host 0.0.0.0 --port 8081
|
||||
autostart=true
|
||||
|
|
17
source/docker/apphub/script/developer.sh
Normal file
17
source/docker/apphub/script/developer.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
source_path="/websoft9/apphub-dev"
|
||||
|
||||
echo "Start to cp source code"
|
||||
if [ ! "$(ls -A $source_path)" ]; then
|
||||
cp -r /websoft9/apphub/* $source_path
|
||||
fi
|
||||
cp -r /websoft9/apphub/swagger-ui $source_path
|
||||
|
||||
echo "Install apphub cli"
|
||||
pip uninstall apphub -y
|
||||
pip install -e $source_path
|
||||
|
||||
echo "Running the apphub"
|
||||
cd $source_path
|
||||
exec uvicorn src.main:app --reload --host 0.0.0.0 --port 8080
|
56
source/docker/apphub/script/entrypoint.sh
Normal file
56
source/docker/apphub/script/entrypoint.sh
Normal file
|
@ -0,0 +1,56 @@
|
|||
#!/bin/bash
|
||||
# Define PATH
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
# Export PATH
|
||||
export PATH
|
||||
|
||||
set -e
|
||||
|
||||
bash /websoft9/script/migration.sh
|
||||
|
||||
try_times=5
|
||||
supervisord
|
||||
supervisorctl start apphub
|
||||
|
||||
# set git user and email
|
||||
for ((i=0; i<$try_times; i++)); do
|
||||
set +e
|
||||
username=$(apphub getconfig --section gitea --key user_name 2>/dev/null)
|
||||
email=$(apphub getconfig --section gitea --key user_email 2>/dev/null)
|
||||
set -e
|
||||
if [ -n "$username" ] && [ -n "$email" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Wait for service running, retrying..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [[ -n "$username" ]]; then
|
||||
echo "git config --global user.name $username"
|
||||
git config --global user.name "$username"
|
||||
else
|
||||
echo "username is null, git config username failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||||
if [[ $email =~ $regex ]]; then
|
||||
echo "git config --global user.email $email"
|
||||
git config --global user.email "$email"
|
||||
else
|
||||
echo "Not have correct email, git config email failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
create_apikey() {
|
||||
|
||||
if [ ! -f /websoft9/apphub/src/config/initialized ] || [ -z "$(apphub getkey)" ]; then
|
||||
echo "Create new apikey"
|
||||
apphub genkey
|
||||
touch /websoft9/apphub/src/config/initialized 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
create_apikey
|
||||
|
||||
tail -n 1000 -f /var/log/supervisord.log
|
51
source/docker/apphub/script/migration.sh
Normal file
51
source/docker/apphub/script/migration.sh
Normal file
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "start to migrate config.ini"
|
||||
|
||||
migrate_ini() {
|
||||
|
||||
# Define file paths, use template ini and syn exsit items from target ini
|
||||
export target_ini="$1"
|
||||
export template_ini="$2"
|
||||
|
||||
python3 - <<EOF
|
||||
import configparser
|
||||
import os
|
||||
import sys
|
||||
|
||||
target_ini = os.environ['target_ini']
|
||||
template_ini = os.environ['template_ini']
|
||||
|
||||
# Create two config parsers
|
||||
target_parser = configparser.ConfigParser()
|
||||
template_parser = configparser.ConfigParser()
|
||||
|
||||
try:
|
||||
|
||||
target_parser.read(target_ini)
|
||||
template_parser.read(template_ini)
|
||||
except configparser.MissingSectionHeaderError:
|
||||
print("Error: The provided files are not valid INI files.")
|
||||
sys.exit(1)
|
||||
|
||||
# use target_parser to override template_parser
|
||||
for section in target_parser.sections():
|
||||
if template_parser.has_section(section):
|
||||
for key, value in target_parser.items(section):
|
||||
if template_parser.has_option(section, key):
|
||||
template_parser.set(section, key, value)
|
||||
|
||||
|
||||
with open(target_ini, 'w') as f:
|
||||
template_parser.write(f)
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
migrate_ini "/websoft9/apphub/src/config/config.ini" "/websoft9/config/config.ini"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Success to update config.ini"
|
||||
else
|
||||
echo "Fail to update config.ini, skip it"
|
||||
fi
|
9
source/docker/apphub/script/update.sh
Normal file
9
source/docker/apphub/script/update.sh
Normal file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "$(date) - Compare remote version and local version." | tee -a /var/log/supervisord.log
|
||||
|
||||
echo "$(date) - Download remote packages and replace local data." | tee -a /var/log/supervisord.log
|
||||
bash /websoft9/script/update_zip.sh --package_name "media-latest.zip" --sync_to "/websoft9/media"
|
||||
bash /websoft9/script/update_zip.sh --package_name "library-latest.zip" --sync_to "/websoft9/library"
|
||||
|
||||
echo "$(date) - Success to update library and media."
|
|
@ -1,4 +1,4 @@
|
|||
# modify time: 202310191733, you can modify here to trigger Docker Build action
|
||||
# modify time: 202311031633, you can modify here to trigger Docker Build action
|
||||
# step1: Build entrypoint execute program init_portainer by golang
|
||||
|
||||
FROM golang:latest AS builder
|
||||
|
|
|
@ -12,19 +12,27 @@ import (
|
|||
func main() {
|
||||
|
||||
filePath := "/data/credential"
|
||||
initPath := "/data/init"
|
||||
|
||||
_, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("credential is not exist, create it.")
|
||||
password := generatePassword(16)
|
||||
|
||||
err := writeToFile(filePath, password)
|
||||
if err != nil {
|
||||
fmt.Println("write file error:", err)
|
||||
return
|
||||
_, err := os.Stat(initPath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("credential is not exist, create it.")
|
||||
password := generatePassword(16)
|
||||
err := writeToFile(filePath, password)
|
||||
if err != nil {
|
||||
fmt.Println("write file error:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Println("credential is exist, skip it.")
|
||||
}
|
||||
|
||||
// call portainer
|
||||
cmd := exec.Command("./portainer", "--admin-password-file", filePath)
|
||||
cmd := exec.Command("./portainer", "--admin-password-file", filePath, "--hide-label", "owner=websoft9")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
|
@ -32,10 +40,12 @@ func main() {
|
|||
if err != nil {
|
||||
fmt.Println("error running compiled_program:", err)
|
||||
return
|
||||
}else{
|
||||
os.Create(initPath)
|
||||
}
|
||||
}else{
|
||||
fmt.Println("credential is exist, skip it.")
|
||||
cmd := exec.Command("./portainer")
|
||||
cmd := exec.Command("./portainer", "--hide-label", "owner=websoft9")
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ services:
|
|||
- deployment
|
||||
- git
|
||||
- proxy
|
||||
labels:
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:8080"
|
||||
|
||||
deployment:
|
||||
image: websoft9dev/deployment:$DEPLOYMENT_VERSION
|
||||
|
@ -40,7 +43,8 @@ services:
|
|||
timeout: 30s
|
||||
retries: 4
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 9000
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:9000"
|
||||
|
||||
git:
|
||||
image: websoft9dev/git:$GIT_VERSION
|
||||
|
@ -61,7 +65,8 @@ services:
|
|||
- REQUIRE_SIGNIN_VIEW=false
|
||||
- ROOT_URL=http://localhost/w9git/
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 3000
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:3000"
|
||||
|
||||
proxy:
|
||||
image: websoft9dev/proxy:$PROXY_VERSION
|
||||
|
@ -75,9 +80,10 @@ services:
|
|||
- nginx_data:/data
|
||||
- nginx_letsencrypt:/etc/letsencrypt
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 80
|
||||
com.docker.compose.w9_https.port: 443
|
||||
com.docker.compose.w9_console.port: 81
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port: 80"
|
||||
- "com.docker.compose.w9_https.port: 443"
|
||||
- "com.docker.compose.w9_console.port: 81"
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
|
|
@ -7,14 +7,14 @@ services:
|
|||
restart: always
|
||||
volumes:
|
||||
- apphub_logs:/websoft9/apphub/logs
|
||||
- apphub_media:/websoft9/media
|
||||
- apphub_config:/websoft9/apphub/src/config
|
||||
depends_on:
|
||||
- deployment
|
||||
- git
|
||||
- proxy
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 8080
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:8080"
|
||||
|
||||
deployment:
|
||||
image: websoft9dev/deployment:$DEPLOYMENT_VERSION
|
||||
|
@ -31,7 +31,8 @@ services:
|
|||
timeout: 30s
|
||||
retries: 4
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 9000
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:9000"
|
||||
|
||||
git:
|
||||
image: websoft9dev/git:$GIT_VERSION
|
||||
|
@ -50,7 +51,8 @@ services:
|
|||
- REQUIRE_SIGNIN_VIEW=false
|
||||
- ROOT_URL=http://localhost/w9git/
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 3000
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port:3000"
|
||||
|
||||
proxy:
|
||||
image: websoft9dev/proxy:$PROXY_VERSION
|
||||
|
@ -63,9 +65,10 @@ services:
|
|||
- nginx_data:/data
|
||||
- nginx_letsencrypt:/etc/letsencrypt
|
||||
labels:
|
||||
com.docker.compose.w9_http.port: 80
|
||||
com.docker.compose.w9_https.port: 443
|
||||
com.docker.compose.w9_console.port: 81
|
||||
- "owner=websoft9"
|
||||
- "com.docker.compose.w9_http.port: 80"
|
||||
- "com.docker.compose.w9_https.port: 443"
|
||||
- "com.docker.compose.w9_console.port: 81"
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
@ -74,7 +77,6 @@ networks:
|
|||
|
||||
volumes:
|
||||
apphub_logs:
|
||||
apphub_media:
|
||||
apphub_config:
|
||||
portainer:
|
||||
gitea:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# modify time: 202310181524, you can modify here to trigger Docker Build action
|
||||
# modify time: 202310250925, you can modify here to trigger Docker Build action
|
||||
# Dockerfile refer to: https://github.com/go-gitea/gitea/blob/main/Dockerfile
|
||||
FROM gitea/gitea:1.20.4
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ response=""
|
|||
cred_path="/data/gitea/credential"
|
||||
admin_username="websoft9"
|
||||
admin_email="help@websoft9.com"
|
||||
user_exist=0
|
||||
|
||||
while [ "$response" != "200" ]; do
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" localhost:3000)
|
||||
|
@ -35,11 +34,11 @@ su -c "
|
|||
exit 0
|
||||
else
|
||||
gitea admin user create --admin --username '$admin_username' --random-password --email '$admin_email' > /tmp/credential
|
||||
user_exist=1
|
||||
touch /data/gitea/create_user 2>/dev/null
|
||||
fi
|
||||
" git
|
||||
|
||||
if [ "$user_exist" -eq 1 ]; then
|
||||
if [ -f /data/gitea/create_user ]; then
|
||||
echo "Read credential from tmp"
|
||||
username=$(grep -o "New user '[^']*" /tmp/credential | sed "s/New user '//")
|
||||
if [ -z "$username" ] || [ "$username" != "websoft9" ]; then
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
# modify time: 202310181524, you can modify here to trigger Docker Build action
|
||||
# Dockerfile refer to:https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/Dockerfile
|
||||
# modify time: 202310261640, you can modify here to trigger Docker Build action
|
||||
# from Dockerfile: https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/Dockerfile
|
||||
# from image: https://hub.docker.com/r/jc21/nginx-proxy-manager
|
||||
|
||||
FROM jc21/nginx-proxy-manager:2.10.4
|
||||
|
||||
LABEL maintainer="Websoft9<help@websoft9.com>"
|
||||
LABEL version="2.10.4"
|
||||
|
||||
RUN apt-get update && apt-get install -y curl jq
|
||||
COPY ./config/initproxy.conf /data/nginx/proxy_host/
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y curl jq && rm -rf /var/lib/apt/lists/*
|
||||
COPY ./config/initproxy.conf /etc/
|
||||
COPY ./s6/w9init/setuser.sh /app/setuser.sh
|
||||
COPY ./s6/w9init/migration.sh /app/migration.sh
|
||||
RUN chmod +x /app/setuser.sh /app/migration.sh
|
||||
|
||||
COPY ./s6/init_user/init_user.sh /app/init_user.sh
|
||||
RUN chmod +x /app/init_user.sh
|
||||
CMD ["/bin/sh", "-c", "/app/init_user.sh && tail -f /dev/null"]
|
||||
RUN export add_ip_data="const ipDataFile={[CLOUDFRONT_URL]:'ip-ranges.json',[CLOUDFARE_V4_URL]:'ips-v4',[CLOUDFARE_V6_URL]:'ips-v6'}[url];logger.info(ipDataFile);if(ipDataFile){return fs.readFile(__dirname+'/../lib/ipData/'+ipDataFile,'utf8',(error,data)=>{if(error){logger.error('fetch '+ipDataFile+' error');reject(error);return}logger.info('fetch '+ipDataFile+' success');resolve(data)})}" && \
|
||||
sed -i "s#url);#&${add_ip_data}#g" /app/internal/ip_ranges.js && \
|
||||
mkdir -p /app/lib/ipData && cd /app/lib/ipData && \
|
||||
curl -O https://ip-ranges.amazonaws.com/ip-ranges.json && \
|
||||
curl -O https://www.cloudflare.com/ips-v4 && \
|
||||
curl -O https://www.cloudflare.com/ips-v6
|
||||
|
||||
CMD ["/bin/sh", "-c", "/app/migration.sh && /app/setuser.sh && tail -f /dev/null"]
|
|
@ -102,20 +102,35 @@ server {
|
|||
}
|
||||
}
|
||||
|
||||
location /apidocs/static {
|
||||
proxy_pass http://websoft9-apphub:8080/static;
|
||||
location /apidocs/ {
|
||||
proxy_pass http://websoft9-apphub:8080/docs;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
sub_filter 'spec-url="/apidocs/openapi.json' 'spec-url="/openapi.json';
|
||||
}
|
||||
|
||||
location /apidocs/openapi.json {
|
||||
proxy_pass http://websoft9-apphub:8080/;
|
||||
location /static/ {
|
||||
proxy_pass http://websoft9-apphub:8080/static/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /apidocs/ {
|
||||
proxy_pass http://websoft9-apphub:8080/redoc;
|
||||
sub_filter 'src="/static' 'src="/apidocs/static';
|
||||
sub_filter 'spec-url="/' 'spec-url="/apidocs/openapi.json';
|
||||
location = /openapi.json {
|
||||
proxy_pass http://websoft9-apphub:8080/openapi.json;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
proxy_pass http://websoft9-apphub:8081/images/;
|
||||
}
|
||||
|
||||
# Custom
|
||||
include /data/nginx/custom/server_proxy[.]conf;
|
||||
}
|
||||
|
|
5
source/docker/proxy/s6/README.md
Normal file
5
source/docker/proxy/s6/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# S6
|
||||
|
||||
S6 is a mulitply process management tools at Nginx Proxy Manager.
|
||||
|
||||
- nginx_proxy() at migration.sh: Migration initproxy.conf to Nginx, condition is compare Container created time and Named Volumes created time
|
23
source/docker/proxy/s6/w9init/migration.sh
Normal file
23
source/docker/proxy/s6/w9init/migration.sh
Normal file
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
set +e
|
||||
|
||||
nginx_proxy(){
|
||||
|
||||
current_time=$(date +%s)
|
||||
shadow_modified_time=$(stat -c %Y /etc/shadow)
|
||||
time_difference=$((current_time - shadow_modified_time))
|
||||
|
||||
if [ ! -f /data/nginx/proxy_host/initproxy.conf ] || [ $time_difference -le 60 ]
|
||||
then
|
||||
cp /etc/initproxy.conf /data/nginx/proxy_host/
|
||||
echo "Update initproxy.conf to Nginx"
|
||||
else
|
||||
echo "Don't need to update initproxy.conf to Nginx"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
nginx_proxy
|
||||
|
||||
set -e
|
61
source/docker/proxy/s6/w9init/setuser.sh
Normal file
61
source/docker/proxy/s6/w9init/setuser.sh
Normal file
|
@ -0,0 +1,61 @@
|
|||
#!/bin/bash
|
||||
|
||||
set +e
|
||||
username="help@websoft9.com"
|
||||
password=$(openssl rand -base64 16 | tr -d '/+' | cut -c1-16)
|
||||
token=""
|
||||
cred_path="/data/credential"
|
||||
max_attempts=10
|
||||
|
||||
echo "Start to change nginxproxymanage users"
|
||||
if [ -e "$cred_path" ]; then
|
||||
echo "File $cred_path exists. Exiting script."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "create diretory"
|
||||
mkdir -p "$(dirname "$cred_path")"
|
||||
|
||||
sleep 10
|
||||
while [ -z "$token" ]; do
|
||||
sleep 5
|
||||
login_data=$(curl -X POST -H "Content-Type: application/json" -d '{"identity":"admin@example.com","scope":"user", "secret":"changeme"}' http://localhost:81/api/tokens)
|
||||
token=$(echo $login_data | jq -r '.token')
|
||||
done
|
||||
|
||||
echo "Change username(email)"
|
||||
for attempt in $(seq 1 $max_attempts); do
|
||||
response=$(curl -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"email": "'$username'", "nickname": "admin", "is_disabled": false, "roles": ["admin"]}' http://localhost:81/api/users/1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Set username successful"
|
||||
break
|
||||
else
|
||||
echo "Set username failed, retrying..."
|
||||
sleep 5
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
echo "Failed to set username after $max_attempts attempts. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Update password"
|
||||
for attempt in $(seq 1 $max_attempts); do
|
||||
response=$(curl -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"type":"password","current":"changeme","secret":"'$password'"}' http://localhost:81/api/users/1/auth)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Set password successful"
|
||||
echo "Save to credential"
|
||||
json="{\"username\":\"$username\",\"password\":\"$password\"}"
|
||||
echo "$json" > "$cred_path"
|
||||
break
|
||||
else
|
||||
echo "Set password failed, retrying..."
|
||||
sleep 5
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
echo "Failed to set password after $max_attempts attempts. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
set -e
|
|
@ -198,18 +198,14 @@ check_ports() {
|
|||
|
||||
echo "Stop Websoft9 Proxy and Cockpit service for reserve ports..."
|
||||
sudo docker stop websoft9-proxy 2>/dev/null || echo "docker stop websoft9-proxy not need "
|
||||
sudo systemctl stop cockpit 2>/dev/null || echo "systemctl stop cockpit not need"
|
||||
sudo systemctl stop cockpit.socket 2>/dev/null || echo "systemctl stop cockpit.socket not need"
|
||||
|
||||
|
||||
for port in "${ports[@]}"; do
|
||||
if ss -tuln | grep ":$port " >/dev/null; then
|
||||
echo "Port $port is in use, install failed"
|
||||
if ss -tuln | grep ":$port " >/dev/null && ! systemctl status cockpit.socket | grep "$port" >/dev/null; then
|
||||
echo "Port $port is in use or not in cockpit.socket, install failed"
|
||||
exit
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo "All ports are available"
|
||||
}
|
||||
|
||||
|
@ -334,8 +330,9 @@ install_backends() {
|
|||
|
||||
|
||||
install_systemd() {
|
||||
echo -e "\n\n-------- Systemd --------"
|
||||
echo_prefix_systemd=$'\n[Systemd] - '
|
||||
echo "$echo_prefix_systemdInstall Systemd service"
|
||||
echo "$echo_prefix_systemd Install Systemd service"
|
||||
|
||||
if [ ! -d "$systemd_path" ]; then
|
||||
sudo mkdir -p "$systemd_path"
|
||||
|
@ -372,34 +369,17 @@ install_systemd() {
|
|||
#--------------- main-----------------------------------------
|
||||
check_ports $http_port $https_port $port
|
||||
install_tools
|
||||
download_source
|
||||
|
||||
bash $install_path/install/install_docker.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "install_docker failed with error $?. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_backends
|
||||
|
||||
bash $install_path/install/install_cockpit.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "install_cockpit failed with error $?. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
install_systemd
|
||||
|
||||
|
||||
bash $install_path/install/install_plugins.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "install_plugins failed with error $?. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restart Docker for Firewalld..."
|
||||
sudo systemctl restart docker
|
||||
|
||||
install_systemd
|
||||
|
||||
|
||||
endtime=$(date +%s)
|
||||
runtime=$((endtime-starttime))
|
||||
echo "Script execution time: $runtime seconds"
|
||||
|
|
|
@ -82,7 +82,7 @@ echo "install_path:$install_path"
|
|||
related_containers=("websoft9-apphub")
|
||||
echo_prefix_cockpit=$'\n[Cockpit] - '
|
||||
# package cockpit depends_on [cockpit-bridge,cockpit-ws,cockpit-system], but update cockpit the depends don't update
|
||||
cockpit_packages="cockpit cockpit-ws cockpit-bridge cockpit-system cockpit-pcp cockpit-storaged cockpit-networkmanager cockpit-session-recording cockpit-doc cockpit-packagekit cockpit-sosreport"
|
||||
cockpit_packages="cockpit cockpit-ws cockpit-bridge cockpit-system cockpit-pcp cockpit-networkmanager cockpit-session-recording cockpit-doc cockpit-packagekit cockpit-sosreport"
|
||||
menu_overrides_github_page_url="https://websoft9.github.io/websoft9/cockpit/menu_override"
|
||||
cockpit_config_github_page_url="https://websoft9.github.io/websoft9/cockpit/cockpit.conf"
|
||||
cockpit_menu_overrides=()
|
||||
|
|
|
@ -51,6 +51,7 @@ docker_exist() {
|
|||
}
|
||||
|
||||
Install_Docker(){
|
||||
|
||||
echo "$echo_prefix_docker Installing Docker for your system"
|
||||
|
||||
# For redhat family
|
||||
|
@ -73,7 +74,6 @@ Install_Docker(){
|
|||
fi
|
||||
}
|
||||
|
||||
|
||||
Upgrade_Docker(){
|
||||
if docker_exist; then
|
||||
echo "$echo_prefix_docker Upgrading Docker for your system..."
|
||||
|
@ -101,7 +101,7 @@ else
|
|||
|
||||
while ((retry_count < max_retries)); do
|
||||
Install_Docker
|
||||
if [ $? -ne 0 ]; then
|
||||
if ! docker_exist; then
|
||||
echo "Installation timeout or failed, retrying..."
|
||||
((retry_count++))
|
||||
sleep 3
|
||||
|
|
|
@ -106,4 +106,6 @@ while not q.empty():
|
|||
sys.exit(1) # Exit the program if an error occurred
|
||||
END
|
||||
|
||||
find /usr/share/cockpit -type f -name "*.py3" -exec chmod +x {} \;
|
||||
|
||||
echo "Plugins install successfully..."
|
||||
|
|
118
source/scripts/update_zip.sh
Normal file
118
source/scripts/update_zip.sh
Normal file
|
@ -0,0 +1,118 @@
|
|||
#!/bin/bash
|
||||
|
||||
## This script is used for download zip files to a target directory
|
||||
## apphub dockerfile is depends on this script
|
||||
|
||||
# Command-line options
|
||||
# ==========================================================
|
||||
#
|
||||
# --channel <release|dev>
|
||||
# Use the --channel option to install a release(production) or dev distribution. default is release, for example:
|
||||
#
|
||||
# $ sudo bash install.sh --channel release
|
||||
#
|
||||
# --package_name
|
||||
# Use the --package_name option to define a zip file, for example:
|
||||
#
|
||||
# $ sudo bash install.sh --package_name media.zip
|
||||
#
|
||||
# --sync_to
|
||||
# Use the sync_to option to define the target directory which zip file will unzip, for example:
|
||||
#
|
||||
# $ sudo bash install.sh --sync_to "/websoft9/media"
|
||||
|
||||
channel="release"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--channel)
|
||||
channel="$2"
|
||||
shift 2
|
||||
;;
|
||||
--package_name)
|
||||
package_name="$2"
|
||||
shift 2
|
||||
;;
|
||||
--sync_to)
|
||||
sync_to="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$package_name" ] || [ -z "$sync_to" ]; then
|
||||
echo "Parameter package_name and sync_to is necessary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define the artifact URL as a global variable
|
||||
artifact_url="https://w9artifact.blob.core.windows.net/$channel/websoft9/plugin"
|
||||
|
||||
upgrade_zip() {
|
||||
|
||||
# Create the full URL by appending the package name to the artifact URL
|
||||
local plugin_name=${package_name%%-*}
|
||||
local url="$artifact_url/$plugin_name/$package_name"
|
||||
|
||||
# Initialize download attempts
|
||||
local attempts=0
|
||||
local max_attempts=2
|
||||
|
||||
# Download the package using wget
|
||||
while [ $attempts -lt $max_attempts ]; do
|
||||
wget --timeout=120 --no-clobber "$url" -O "/tmp/$package_name"
|
||||
# Check if the download was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
attempts=$((attempts+1))
|
||||
echo "Download attempt $attempts failed. Retrying in 5 seconds..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# If the download still failed after max attempts, report an error and exit
|
||||
if [ $attempts -eq $max_attempts ]; then
|
||||
echo "Download failed for package: $package_name after $max_attempts attempts."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Unzip the downloaded package
|
||||
unzip "/tmp/$package_name" -d "/tmp"
|
||||
|
||||
# Check if the unzip was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unzip failed for package: $package_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get the name of the package without the .zip extension for syncing
|
||||
local package_directory="${package_name%.zip}"
|
||||
package_directory="${package_directory%%-*}"
|
||||
|
||||
if [ "$unzipped_folder" != "/tmp/$package_directory/" ]; then
|
||||
mv "$unzipped_folder" "/tmp/$package_directory"
|
||||
else
|
||||
echo "The unzipped folder has the same name as the target folder."
|
||||
fi
|
||||
|
||||
# Sync the unzipped package to the desired location
|
||||
rsync -av "/tmp/$package_directory/" "$sync_to"
|
||||
|
||||
# Check if the sync was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Sync failed for package: $package_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Successfully downloaded, unzipped, and synced package: $package_name"
|
||||
|
||||
# Remove the downloaded .zip file and the unzipped directory
|
||||
rm -f "/tmp/$package_name"
|
||||
rm -rf "/tmp/$package_directory"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
upgrade_zip
|
|
@ -1,9 +1,22 @@
|
|||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
|
||||
# 监控的文件
|
||||
FILES="/lib/systemd/system/cockpit.socket /var/lib/docker/volumes/websoft9_apphub_config/_data/config.ini"
|
||||
cockpit_port="9000"
|
||||
container_name="websoft9-apphub"
|
||||
volume_name="websoft9_apphub_config"
|
||||
|
||||
# get volume from container
|
||||
function get_volume_path() {
|
||||
local container_name="$1"
|
||||
local volume_name="$2"
|
||||
local mounts=$(docker inspect -f '{{ json .Mounts }}' "$container_name" | jq -r '.[] | select(.Name == "'$volume_name'") | .Source')
|
||||
echo "$mounts"
|
||||
}
|
||||
|
||||
volume_path=$(get_volume_path "$container_name" "$volume_name")
|
||||
config_path="$volume_path/config.ini"
|
||||
cockpit_service_path="/lib/systemd/system/cockpit.socket"
|
||||
FILES="$cockpit_service_path $config_path"
|
||||
|
||||
# 监控文件发生变动时需要做的事情
|
||||
on_change() {
|
||||
|
@ -30,5 +43,4 @@ inotifywait -e modify -m $FILES | while read PATH EVENT FILE; do
|
|||
echo "Set cockpit port by config.ini..."
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH
|
||||
on_change
|
||||
done
|
||||
|
||||
done
|
|
@ -60,8 +60,13 @@ set -e # Stop ignoring errors
|
|||
|
||||
length=${#containers[@]}
|
||||
for ((i=0; i<$length; i++)); do
|
||||
|
||||
container=${containers[$i]}
|
||||
section=${sections[$i]}
|
||||
echo "$container:"
|
||||
docker exec -i websoft9-apphub apphub setconfig --section $section --key user_pwd --value ${passwords[$container]}
|
||||
if [[ -n ${passwords[$container]} ]]; then
|
||||
echo "$container start to set password"
|
||||
docker exec -i websoft9-apphub apphub setconfig --section $section --key user_pwd --value ${passwords[$container]}
|
||||
else
|
||||
echo "Password for $container is not set or empty. Skipping..."
|
||||
fi
|
||||
done
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"version": "0.8.26-rc81",
|
||||
"version": "0.8.29-rc1",
|
||||
"plugins": {
|
||||
"portainer": "0.0.7",
|
||||
"nginx": "0.0.5",
|
||||
"gitea": "0.0.1",
|
||||
"myapps": "0.0.8",
|
||||
"appstore": "0.0.7",
|
||||
"gitea": "0.0.2",
|
||||
"myapps": "0.0.9",
|
||||
"appstore": "0.0.8",
|
||||
"settings": "0.0.5",
|
||||
"navigator": "0.5.10"
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue