Merge branch 'master' into develop
This commit is contained in:
commit
fda8a4d824
28 changed files with 354 additions and 126 deletions
|
@ -128,6 +128,24 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "iBicha",
|
||||
"name": "Brahim Hadriche",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/17722782?v=4",
|
||||
"profile": "https://github.com/iBicha",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andrewbrereton",
|
||||
"name": "Andrew Brereton",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/682893?v=4",
|
||||
"profile": "https://andrewbrereton.com",
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -135,5 +153,6 @@
|
|||
"projectOwner": "meienberger",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"skipCi": true
|
||||
"skipCi": true,
|
||||
"commitConvention": "angular"
|
||||
}
|
||||
|
|
|
@ -5,5 +5,8 @@
|
|||
node_modules
|
||||
.next
|
||||
dist/
|
||||
**/dist/
|
||||
**/next/
|
||||
|
||||
# all docker-compose files
|
||||
docker-compose*.yml
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
|
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -6,7 +6,7 @@ open_collective: # Replace with a single Open Collective username
|
|||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
liberapay: meienberger # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
image: postgres:14
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
*.swp
|
||||
|
||||
.DS_Store
|
||||
.vscode
|
||||
|
||||
logs
|
||||
.pnpm-debug.log
|
||||
|
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"yaml.schemas": {
|
||||
"https://raw.githubusercontent.com/ansible-community/schemas/main/f/ansible-playbook.json": "file:///Users/nicolas/Projects/runtipi/ansible/playbooks/install-dependencies.yml"
|
||||
}
|
||||
}
|
16
Dockerfile
16
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM node:18 AS build
|
||||
FROM node:18 AS builder
|
||||
|
||||
RUN npm install node-gyp -g
|
||||
|
||||
|
@ -35,13 +35,13 @@ WORKDIR /api
|
|||
COPY ./packages/system-api/package*.json /api/
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY --from=builder /api/dist /api/dist
|
||||
|
||||
WORKDIR /dashboard
|
||||
COPY ./packages/dashboard/package*.json /dashboard/
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY --from=build /api/dist /api/dist
|
||||
|
||||
COPY --from=build /dashboard/.next /dashboard/.next
|
||||
COPY ./packages/dashboard /dashboard
|
||||
COPY --from=builder /dashboard/next.config.js ./
|
||||
COPY --from=builder /dashboard/public ./public
|
||||
COPY --from=builder /dashboard/package.json ./package.json
|
||||
COPY --from=builder --chown=node:node /dashboard/.next/standalone ./
|
||||
COPY --from=builder --chown=node:node /dashboard/.next/static ./.next/static
|
||||
|
||||
WORKDIR /
|
61
README.md
61
README.md
|
@ -1,6 +1,6 @@
|
|||
# ⛺️ Tipi — A personal homeserver for everyone
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-)
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
[![License](https://img.shields.io/github/license/meienberger/runtipi)](https://github.com/meienberger/runtipi/blob/master/LICENSE)
|
||||
[![Version](https://img.shields.io/github/v/release/meienberger/runtipi?color=%235351FB&label=version)](https://github.com/meienberger/runtipi/releases)
|
||||
|
@ -23,23 +23,32 @@ Check our demo instance : **[demo.runtipi.com](https://demo.runtipi.com)** / use
|
|||
## Apps available
|
||||
- [Adguard Home](https://github.com/AdguardTeam/AdGuardHome) - Adguard Home DNS adblocker
|
||||
- [Booksonic](https://github.com/popeen) - A server for streaming your audiobooks
|
||||
- [BookStack](https://www.bookstackapp.com/) - BookStack is a self-hosted platform for organising and storing information.
|
||||
- [Calibre-Web](https://github.com/janeczku/calibre-web) - Web Ebook Reader
|
||||
- [Code-Server](https://github.com/coder/code-server) - Web VS Code
|
||||
- [Filebrowser](https://github.com/filebrowser/filebrowser) - Web File Browser
|
||||
- [Firefly III](https://github.com/firefly-iii/firefly-iii) - A personal finances manager
|
||||
- [Freshrss](https://github.com/FreshRSS/FreshRSS) - A free, self-hostable RSS aggregator
|
||||
- [Gitea](https://github.com/go-gitea/gitea) - Gitea - A painless self-hosted Git service
|
||||
- [FreshRSS](https://github.com/FreshRSS/FreshRSS) - A free, self-hostable RSS aggregator
|
||||
- [Ghost](https://github.com/TryGhost/Ghost) - Ghost - Turn your audience into a business
|
||||
- [Gitea](https://github.com/go-gitea/gitea) - Gitea - A painless self-hosted Git service
|
||||
- [Gotify](https://github.com/gotify/server) - Simple server for sending and receiving notification messages.
|
||||
- [Haven](https://github.com/havenweb/haven) - Haven is a self-hosted private blog and feedreader you can use instead of Facebook
|
||||
- [Homarr](https://github.com/ajnart/homarr) - A homepage for your server
|
||||
- [Home Assistant](https://github.com/home-assistant/core) - Open source home automation that puts local control and privacy first
|
||||
- [Immich](https://www.immich.app/) - Photo and video backup solution directly from your mobile phone
|
||||
- [Invidious](https://github.com/iv-org/invidious) - An alternative front-end to YouTube
|
||||
- [Jackett](https://github.com/Jackett/Jackett) - API Support for your favorite torrent trackers
|
||||
- [Jellyfin](https://github.com/jellyfin/jellyfin) - A media server for your home collection
|
||||
- [Joplin](https://github.com/laurent22/joplin) - Privacy focused note-taking app
|
||||
- [Libreddit](https://github.com/spikecodes/libreddit) - Private front-end for Reddit
|
||||
- [Mealie](https://github.com/hay-kot/mealie) - Self-hosted recipe manager and meal planner.
|
||||
- [LibrePhotos](https://github.com/LibrePhotos/librephotos) - A self-hosted open source photo management service
|
||||
- [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) - Free and open source machine translation API
|
||||
- [Lidarr](https://github.com/Lidarr/Lidarr) - Looks and smells like Sonarr but made for music
|
||||
- [Mealie](https://github.com/hay-kot/mealie) - Self-hosted recipe manager and meal planner
|
||||
- [MoneroBlock](https://github.com/duggavo/MoneroBlock) - Decentralized and trustless Monero block explorer
|
||||
- [Monero Daemon](https://github.com/sethforprivacy/simple-monerod-docker) - Monero is a private, decentralized cryptocurrency that keeps your finances confidential and secure
|
||||
- [n8n](https://github.com/n8n-io/n8n) - Workflow Automation Tool
|
||||
- [Navidrome](https://github.com/navidrome/navidrome/) - Modern Music Server and Streamer compatible with Subsonic/Airsonic
|
||||
- [Nextcloud](https://github.com/nextcloud/server) - A safe home for all your data
|
||||
- [Nitter](https://github.com/zedeus/nitter) - Alternative Twitter front-end
|
||||
- [Node-RED](https://github.com/node-red/node-red) - Low-code programming for event-driven applications
|
||||
|
@ -47,18 +56,28 @@ Check our demo instance : **[demo.runtipi.com](https://demo.runtipi.com)** / use
|
|||
- [Photoprism](https://github.com/photoprism/photoprism) - AI-Powered Photos App for the Decentralized Web. We are on a mission to protect your freedom and privacy.
|
||||
- [Pihole](https://github.com/pi-hole/pi-hole) - A black hole for Internet advertisements
|
||||
- [Plex](https://github.com/plexinc/pms-docker) - Stream Movies & TV Shows
|
||||
- [Portainer](https://github.com/portainer/portainer) - Making Docker and Kubernetes management easy.
|
||||
- [Portainer](https://github.com/portainer/portainer) - Making Docker and Kubernetes management easy
|
||||
- [PrivateBin](https://github.com/PrivateBin/PrivateBin) - A minimalist, open source online pastebin where the server has zero knowledge of pasted data
|
||||
- [Prowlarr](https://github.com/Prowlarr/Prowlarr/) - A torrent/usenet indexer manager/proxy
|
||||
- [ProxiTok](https://github.com/pablouser1/ProxiTok) - Open source alternative frontend for TikTok made using PHP
|
||||
- [qBittorrent](https://github.com/qbittorrent/qBittorrent) - Fast, easy, and free BitTorrent client
|
||||
- [Radarr](https://github.com/Radarr/Radarr) - Movie collection manager for Usenet and BitTorrent users
|
||||
- [Readarr](https://github.com/Readarr/Readarr) - Book Manager and Automation (Sonarr for Ebooks)
|
||||
- [Resilio Sync](https://github.com/bt-sync) - Fast, reliable, and simple file sync and share solution
|
||||
- [SearXNG](https://github.com/searxng/searxng) - Privacy-respecting, hackable metasearch engine
|
||||
- [Send](https://gitlab.com/timvisee/send) - Simple, private file sharing
|
||||
- [Sonarr](https://github.com/Sonarr/Sonarr) - TV show manager for Usenet and BitTorrent
|
||||
- [Syncthing](https://github.com/syncthing/syncthing) - Continuous File Synchronization
|
||||
- [Tailscale](https://github.com/tailscale/tailscale) - The easiest, most secure way to use WireGuard and 2FA
|
||||
- [Tautulli](https://github.com/Tautulli/Tautulli) - A Python based monitoring and tracking tool for Plex Media Server
|
||||
- [teddit](https://codeberg.org/teddit/teddit) - Alternative Reddit front-end focused on privacy
|
||||
- [Transmission](https://github.com/transmission/transmission) - Fast, easy, and free BitTorrent client
|
||||
- [Wireguard Easy](https://github.com/WeeJeWel/wg-easy) - WireGuard VPN + Web-based Admin UI
|
||||
- [Tube Archivist](https://github.com/tubearchivist/tubearchivist) - Your self-hosted YouTube media server
|
||||
- [Uptime Kuma](https://github.com/louislam/uptime-kuma) - A fancy self-hosted monitoring tool
|
||||
- [Vaultwarden](https://github.com/dani-garcia/vaultwarden) - Unofficial Bitwarden compatible server
|
||||
- [Wireguard Easy](https://github.com/WeeJeWel/wg-easy) - WireGuard VPN + Web-based Admin UI
|
||||
- [Your Spotify](https://github.com/Yooooomi/your_spotify) - Self hosted Spotify tracking dashboard
|
||||
- [Zerotier](https://www.zerotier.com) - Easy to use zero configuration VPN
|
||||
|
||||
You can find and submit new apps inside of the [RunTipi Appstore](https://github.com/meienberger/runtipi-appstore).
|
||||
|
||||
|
@ -150,21 +169,25 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="https://meienberger.dev/"><img src="https://avatars.githubusercontent.com/u/47644445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nicolas Meienberger</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Code">💻</a> <a href="#infra-meienberger" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Tests">⚠️</a> <a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ArneNaessens"><img src="https://avatars.githubusercontent.com/u/16622722?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ArneNaessens</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=ArneNaessens" title="Code">💻</a> <a href="#ideas-ArneNaessens" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/meienberger/runtipi/commits?author=ArneNaessens" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/u/58747968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DrMxrcy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=DrMxrcy" title="Code">💻</a> <a href="#ideas-DrMxrcy" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/meienberger/runtipi/commits?author=DrMxrcy" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://cobre.dev"><img src="https://avatars.githubusercontent.com/u/36574329?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cooper</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=CobreDev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/JTruj1ll0923"><img src="https://avatars.githubusercontent.com/u/6656643?v=4?s=100" width="100px;" alt=""/><br /><sub><b>JTruj1ll0923</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=JTruj1ll0923" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Stetsed"><img src="https://avatars.githubusercontent.com/u/33891782?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stetsed</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=Stetsed" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/blushell"><img src="https://avatars.githubusercontent.com/u/3621606?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jones_Town</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=blushell" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://meienberger.dev/"><img src="https://avatars.githubusercontent.com/u/47644445?v=4?s=100" width="100px;" alt="Nicolas Meienberger"/><br /><sub><b>Nicolas Meienberger</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Code">💻</a> <a href="#infra-meienberger" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Tests">⚠️</a> <a href="https://github.com/meienberger/runtipi/commits?author=meienberger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ArneNaessens"><img src="https://avatars.githubusercontent.com/u/16622722?v=4?s=100" width="100px;" alt="ArneNaessens"/><br /><sub><b>ArneNaessens</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=ArneNaessens" title="Code">💻</a> <a href="#ideas-ArneNaessens" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/meienberger/runtipi/commits?author=ArneNaessens" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/u/58747968?v=4?s=100" width="100px;" alt="DrMxrcy"/><br /><sub><b>DrMxrcy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=DrMxrcy" title="Code">💻</a> <a href="#ideas-DrMxrcy" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/meienberger/runtipi/commits?author=DrMxrcy" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://cobre.dev"><img src="https://avatars.githubusercontent.com/u/36574329?v=4?s=100" width="100px;" alt="Cooper"/><br /><sub><b>Cooper</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=CobreDev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/JTruj1ll0923"><img src="https://avatars.githubusercontent.com/u/6656643?v=4?s=100" width="100px;" alt="JTruj1ll0923"/><br /><sub><b>JTruj1ll0923</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=JTruj1ll0923" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Stetsed"><img src="https://avatars.githubusercontent.com/u/33891782?v=4?s=100" width="100px;" alt="Stetsed"/><br /><sub><b>Stetsed</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=Stetsed" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/blushell"><img src="https://avatars.githubusercontent.com/u/3621606?v=4?s=100" width="100px;" alt="Jones_Town"/><br /><sub><b>Jones_Town</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=blushell" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://rushichaudhari.github.io/"><img src="https://avatars.githubusercontent.com/u/6279035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rushi Chaudhari</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=rushic24" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rblaine95"><img src="https://avatars.githubusercontent.com/u/4052340?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Blaine</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=rblaine95" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://sethforprivacy.com"><img src="https://avatars.githubusercontent.com/u/40500387?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Seth For Privacy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=sethforprivacy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/hqwuzhaoyi"><img src="https://avatars.githubusercontent.com/u/44605072?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Prajna</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=hqwuzhaoyi" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/justincmoy"><img src="https://avatars.githubusercontent.com/u/14875982?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin Moy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=justincmoy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/dextreem"><img src="https://avatars.githubusercontent.com/u/11060652?v=4?s=100" width="100px;" alt=""/><br /><sub><b>dextreem</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=dextreem" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rushichaudhari.github.io/"><img src="https://avatars.githubusercontent.com/u/6279035?v=4?s=100" width="100px;" alt="Rushi Chaudhari"/><br /><sub><b>Rushi Chaudhari</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=rushic24" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rblaine95"><img src="https://avatars.githubusercontent.com/u/4052340?v=4?s=100" width="100px;" alt="Robert Blaine"/><br /><sub><b>Robert Blaine</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=rblaine95" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://sethforprivacy.com"><img src="https://avatars.githubusercontent.com/u/40500387?v=4?s=100" width="100px;" alt="Seth For Privacy"/><br /><sub><b>Seth For Privacy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=sethforprivacy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/hqwuzhaoyi"><img src="https://avatars.githubusercontent.com/u/44605072?v=4?s=100" width="100px;" alt="Prajna"/><br /><sub><b>Prajna</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=hqwuzhaoyi" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/justincmoy"><img src="https://avatars.githubusercontent.com/u/14875982?v=4?s=100" width="100px;" alt="Justin Moy"/><br /><sub><b>Justin Moy</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=justincmoy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/dextreem"><img src="https://avatars.githubusercontent.com/u/11060652?v=4?s=100" width="100px;" alt="dextreem"/><br /><sub><b>dextreem</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=dextreem" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iBicha"><img src="https://avatars.githubusercontent.com/u/17722782?v=4?s=100" width="100px;" alt="Brahim Hadriche"/><br /><sub><b>Brahim Hadriche</b></sub></a><br /><a href="https://github.com/meienberger/runtipi/commits?author=iBicha" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://andrewbrereton.com"><img src="https://avatars.githubusercontent.com/u/682893?v=4?s=100" width="100px;" alt="Andrew Brereton"/><br /><sub><b>Andrew Brereton</b></sub></a><br /><a href="#content-andrewbrereton" title="Content">🖋</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -20,7 +20,7 @@ services:
|
|||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
image: postgres:latest
|
||||
image: postgres:14
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
volumes:
|
||||
|
@ -80,6 +80,7 @@ services:
|
|||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
ARCHITECTURE: ${ARCHITECTURE}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
|
|
|
@ -19,7 +19,7 @@ services:
|
|||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
image: postgres:latest
|
||||
image: postgres:14
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
volumes:
|
||||
|
@ -72,6 +72,7 @@ services:
|
|||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
ARCHITECTURE: ${ARCHITECTURE}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
|
@ -94,7 +95,7 @@ services:
|
|||
|
||||
dashboard:
|
||||
image: meienberger/runtipi:rc-${TIPI_VERSION}
|
||||
command: /bin/sh -c "cd /dashboard && npm run start"
|
||||
command: /bin/sh -c "cd /dashboard && node server.js"
|
||||
container_name: dashboard
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
|
|
@ -18,7 +18,7 @@ services:
|
|||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
image: postgres:latest
|
||||
image: postgres:14
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
volumes:
|
||||
|
@ -72,6 +72,7 @@ services:
|
|||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
ARCHITECTURE: ${ARCHITECTURE}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
|
@ -94,7 +95,7 @@ services:
|
|||
|
||||
dashboard:
|
||||
image: meienberger/runtipi:${TIPI_VERSION}
|
||||
command: /bin/sh -c "cd /dashboard && npm run start"
|
||||
command: /bin/sh -c "cd /dashboard && node server.js"
|
||||
restart: unless-stopped
|
||||
container_name: dashboard
|
||||
networks:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "runtipi",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"description": "A homeserver for everyone",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const { INTERNAL_IP, DOMAIN, NGINX_PORT } = process.env;
|
||||
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
webpackDevMiddleware: (config) => {
|
||||
config.watchOptions = {
|
||||
poll: 1000,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dashboard",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "jest --colors",
|
||||
|
@ -22,7 +22,7 @@
|
|||
"framer-motion": "^6",
|
||||
"graphql": "^15.8.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"next": "12.1.6",
|
||||
"next": "12.3.1",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-final-form": "^6.5.9",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "system-api",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"description": "",
|
||||
"exports": "./dist/server.js",
|
||||
"type": "module",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
import * as dotenv from 'dotenv';
|
||||
import fs from 'fs-extra';
|
||||
import { readJsonFile } from '../../modules/fs/fs.helpers';
|
||||
import { AppSupportedArchitecturesEnum } from '../../modules/apps/apps.types';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
dotenv.config({ path: '.env.dev' });
|
||||
|
@ -22,12 +23,14 @@ const {
|
|||
DOMAIN = '',
|
||||
STORAGE_PATH = '/runtipi',
|
||||
REDIS_HOST = 'tipi-redis',
|
||||
ARCHITECTURE = 'amd64',
|
||||
} = process.env;
|
||||
|
||||
const configSchema = z.object({
|
||||
NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]),
|
||||
REDIS_HOST: z.string(),
|
||||
status: z.union([z.literal('RUNNING'), z.literal('UPDATING'), z.literal('RESTARTING')]),
|
||||
architecture: z.nativeEnum(AppSupportedArchitecturesEnum),
|
||||
logs: z.object({
|
||||
LOGS_FOLDER: z.string(),
|
||||
LOGS_APP: z.string(),
|
||||
|
@ -59,6 +62,7 @@ class Config {
|
|||
},
|
||||
REDIS_HOST,
|
||||
NODE_ENV: NODE_ENV as z.infer<typeof configSchema>['NODE_ENV'],
|
||||
architecture: ARCHITECTURE as z.infer<typeof configSchema>['architecture'],
|
||||
rootFolder: '/runtipi',
|
||||
internalIp: INTERNAL_IP,
|
||||
version: TIPI_VERSION,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import { AppCategoriesEnum, AppInfo, AppStatusEnum, FieldTypes } from '../apps.types';
|
||||
import { AppCategoriesEnum, AppInfo, AppStatusEnum, AppSupportedArchitecturesEnum, FieldTypes } from '../apps.types';
|
||||
import App from '../app.entity';
|
||||
|
||||
interface IProps {
|
||||
|
@ -10,10 +10,11 @@ interface IProps {
|
|||
exposed?: boolean;
|
||||
domain?: string;
|
||||
exposable?: boolean;
|
||||
supportedArchitectures?: AppSupportedArchitecturesEnum[];
|
||||
}
|
||||
|
||||
const createApp = async (props: IProps) => {
|
||||
const { installed = false, status = AppStatusEnum.RUNNING, requiredPort, randomField = false, exposed = false, domain = '', exposable = false } = props;
|
||||
const { installed = false, status = AppStatusEnum.RUNNING, requiredPort, randomField = false, exposed = false, domain = '', exposable = false, supportedArchitectures } = props;
|
||||
|
||||
const categories = Object.values(AppCategoriesEnum);
|
||||
|
||||
|
@ -29,6 +30,7 @@ const createApp = async (props: IProps) => {
|
|||
env_variable: 'TEST_FIELD',
|
||||
},
|
||||
],
|
||||
|
||||
name: faker.random.word(),
|
||||
description: faker.random.words(),
|
||||
tipi_version: faker.datatype.number({ min: 1, max: 10 }),
|
||||
|
@ -37,6 +39,7 @@ const createApp = async (props: IProps) => {
|
|||
source: faker.internet.url(),
|
||||
categories: [categories[faker.datatype.number({ min: 0, max: categories.length - 1 })]],
|
||||
exposable,
|
||||
supported_architectures: supportedArchitectures,
|
||||
};
|
||||
|
||||
if (randomField) {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import AppsService from '../apps.service';
|
||||
import fs from 'fs-extra';
|
||||
import { AppInfo, AppStatusEnum } from '../apps.types';
|
||||
import { AppInfo, AppStatusEnum, AppSupportedArchitecturesEnum } from '../apps.types';
|
||||
import App from '../app.entity';
|
||||
import { createApp } from './apps.factory';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { getEnvMap } from '../apps.helpers';
|
||||
import EventDispatcher, { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('child_process');
|
||||
|
@ -152,6 +153,38 @@ describe('Install app', () => {
|
|||
|
||||
await expect(AppsService.installApp(app3.appInfo.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`Domain test.com already in use by app ${app2.appInfo.id}`);
|
||||
});
|
||||
|
||||
it('Should throw if architecure is not supported', async () => {
|
||||
const { MockFiles, appInfo } = await createApp({ supportedArchitectures: [AppSupportedArchitecturesEnum.ARM] });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
await expect(AppsService.installApp(appInfo.id, { TEST_FIELD: 'test' })).rejects.toThrowError(`App ${appInfo.id} is not supported on this architecture`);
|
||||
});
|
||||
|
||||
it('Can install if architecture is supported', async () => {
|
||||
setConfig('architecture', AppSupportedArchitecturesEnum.ARM);
|
||||
const { MockFiles, appInfo } = await createApp({ supportedArchitectures: [AppSupportedArchitecturesEnum.ARM, AppSupportedArchitecturesEnum.ARM64] });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
await AppsService.installApp(appInfo.id, { TEST_FIELD: 'test' });
|
||||
const app = await App.findOne({ where: { id: appInfo.id } });
|
||||
|
||||
expect(app).toBeDefined();
|
||||
});
|
||||
|
||||
it('Can install if no architecture is specified', async () => {
|
||||
setConfig('architecture', AppSupportedArchitecturesEnum.ARM);
|
||||
const { MockFiles, appInfo } = await createApp({ supportedArchitectures: undefined });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
await AppsService.installApp(appInfo.id, { TEST_FIELD: 'test' });
|
||||
const app = await App.findOne({ where: { id: appInfo.id } });
|
||||
|
||||
expect(app).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Uninstall app', () => {
|
||||
|
@ -431,6 +464,50 @@ describe('List apps', () => {
|
|||
expect(apps[1].id).toBe(sortedApps[1].id);
|
||||
expect(apps[0].description).toBe('md desc');
|
||||
});
|
||||
|
||||
it('Should not list apps that have supportedArchitectures and are not supported', async () => {
|
||||
// Arrange
|
||||
setConfig('architecture', AppSupportedArchitecturesEnum.ARM64);
|
||||
const app3 = await createApp({ supportedArchitectures: [AppSupportedArchitecturesEnum.ARM] });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(Object.assign(app3.MockFiles));
|
||||
|
||||
// Act
|
||||
const { apps } = await AppsService.listApps();
|
||||
|
||||
// Assert
|
||||
expect(apps).toBeDefined();
|
||||
expect(apps.length).toBe(0);
|
||||
});
|
||||
|
||||
it('Should list apps that have supportedArchitectures and are supported', async () => {
|
||||
// Arrange
|
||||
setConfig('architecture', AppSupportedArchitecturesEnum.ARM);
|
||||
const app3 = await createApp({ supportedArchitectures: [AppSupportedArchitecturesEnum.ARM] });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(Object.assign(app3.MockFiles));
|
||||
// Act
|
||||
const { apps } = await AppsService.listApps();
|
||||
|
||||
// Assert
|
||||
expect(apps).toBeDefined();
|
||||
expect(apps.length).toBe(1);
|
||||
});
|
||||
|
||||
it('Should list apps that have no supportedArchitectures specified', async () => {
|
||||
// Arrange
|
||||
setConfig('architecture', AppSupportedArchitecturesEnum.ARM);
|
||||
const app3 = await createApp({});
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(Object.assign(app3.MockFiles));
|
||||
|
||||
// Act
|
||||
const { apps } = await AppsService.listApps();
|
||||
|
||||
// Assert
|
||||
expect(apps).toBeDefined();
|
||||
expect(apps.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Start all apps', () => {
|
||||
|
|
|
@ -26,6 +26,10 @@ export const checkAppRequirements = async (appName: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (configFile?.supported_architectures && !configFile.supported_architectures.includes(getConfig().architecture)) {
|
||||
throw new Error(`App ${appName} is not supported on this architecture`);
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,18 @@ import { getConfig } from '../../core/config/TipiConfig';
|
|||
import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher';
|
||||
|
||||
const sortApps = (a: AppInfo, b: AppInfo) => a.name.localeCompare(b.name);
|
||||
const filterApp = (app: AppInfo): boolean => {
|
||||
if (!app.supported_architectures) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const arch = getConfig().architecture;
|
||||
return app.supported_architectures.includes(arch);
|
||||
};
|
||||
|
||||
const filterApps = (apps: AppInfo[]): AppInfo[] => {
|
||||
return apps.sort(sortApps).filter(filterApp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start all apps which had the status RUNNING in the database
|
||||
|
@ -159,7 +171,7 @@ const listApps = async (): Promise<ListAppsResonse> => {
|
|||
app.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app.id}/metadata/description.md`);
|
||||
});
|
||||
|
||||
return { apps: apps.sort(sortApps), total: apps.length };
|
||||
return { apps: filterApps(apps), total: apps.length };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -41,6 +41,12 @@ export enum AppStatusEnum {
|
|||
UPDATING = 'updating',
|
||||
}
|
||||
|
||||
export enum AppSupportedArchitecturesEnum {
|
||||
ARM = 'arm',
|
||||
ARM64 = 'arm64',
|
||||
AMD64 = 'amd64',
|
||||
}
|
||||
|
||||
registerEnumType(AppCategoriesEnum, {
|
||||
name: 'AppCategoriesEnum',
|
||||
});
|
||||
|
@ -49,6 +55,10 @@ registerEnumType(FieldTypes, {
|
|||
name: 'FieldTypesEnum',
|
||||
});
|
||||
|
||||
registerEnumType(AppSupportedArchitecturesEnum, {
|
||||
name: 'AppSupportedArchitecturesEnum',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
class FormField {
|
||||
@Field(() => FieldTypes)
|
||||
|
@ -128,6 +138,9 @@ class AppInfo {
|
|||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
exposable?: boolean;
|
||||
|
||||
@Field(() => [AppSupportedArchitecturesEnum], { nullable: true })
|
||||
supported_architectures?: AppSupportedArchitecturesEnum[];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
|
|
@ -85,6 +85,7 @@ const main = async () => {
|
|||
// Start apps
|
||||
appsService.startAllApps();
|
||||
logger.info(`Server running on port ${port} 🚀 Production => ${__prod__}`);
|
||||
logger.info(`Config: ${JSON.stringify(getConfig(), null, 2)}`);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
|
|
137
pnpm-lock.yaml
137
pnpm-lock.yaml
|
@ -49,7 +49,7 @@ importers:
|
|||
graphql: ^15.8.0
|
||||
graphql-tag: ^2.12.6
|
||||
jest: ^28.1.0
|
||||
next: 12.1.6
|
||||
next: 12.3.1
|
||||
postcss: ^8.4.12
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0
|
||||
|
@ -78,7 +78,7 @@ importers:
|
|||
framer-motion: 6.3.3_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
graphql: 15.8.0
|
||||
graphql-tag: 2.12.6_graphql@15.8.0
|
||||
next: 12.1.6_talmm3uuvp6ssixt2qevhfgvue
|
||||
next: 12.3.1_talmm3uuvp6ssixt2qevhfgvue
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
react-final-form: 6.5.9_bnxchjdfy45cdln7bu7hnhf37u
|
||||
|
@ -109,7 +109,7 @@ importers:
|
|||
autoprefixer: 10.4.7_postcss@8.4.13
|
||||
eslint: 8.12.0
|
||||
eslint-config-airbnb-typescript: 17.0.0_r46exuh3jlhq2wmrnqx2ufqspa
|
||||
eslint-config-next: 12.1.4_e6a2zi6fqdwfehht5cxvkmo3zu
|
||||
eslint-config-next: 12.1.4_c2ous3fcmy6f3xlzfnkf2jzbvm
|
||||
eslint-plugin-import: 2.26.0_hhyjdrupy4c2vgtpytri6cjwoy
|
||||
jest: 28.1.0_@types+node@17.0.31
|
||||
postcss: 8.4.13
|
||||
|
@ -3171,8 +3171,8 @@ packages:
|
|||
graphql: 15.8.0
|
||||
dev: true
|
||||
|
||||
/@next/env/12.1.6:
|
||||
resolution: {integrity: sha512-Te/OBDXFSodPU6jlXYPAXpmZr/AkG6DCATAxttQxqOWaq6eDFX25Db3dK0120GZrSZmv4QCe9KsZmJKDbWs4OA==}
|
||||
/@next/env/12.3.1:
|
||||
resolution: {integrity: sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next/12.1.4:
|
||||
|
@ -3181,8 +3181,8 @@ packages:
|
|||
glob: 7.1.7
|
||||
dev: true
|
||||
|
||||
/@next/swc-android-arm-eabi/12.1.6:
|
||||
resolution: {integrity: sha512-BxBr3QAAAXWgk/K7EedvzxJr2dE014mghBSA9iOEAv0bMgF+MRq4PoASjuHi15M2zfowpcRG8XQhMFtxftCleQ==}
|
||||
/@next/swc-android-arm-eabi/12.3.1:
|
||||
resolution: {integrity: sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
@ -3190,8 +3190,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/12.1.6:
|
||||
resolution: {integrity: sha512-EboEk3ROYY7U6WA2RrMt/cXXMokUTXXfnxe2+CU+DOahvbrO8QSWhlBl9I9ZbFzJx28AGB9Yo3oQHCvph/4Lew==}
|
||||
/@next/swc-android-arm64/12.3.1:
|
||||
resolution: {integrity: sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
@ -3199,8 +3199,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/12.1.6:
|
||||
resolution: {integrity: sha512-P0EXU12BMSdNj1F7vdkP/VrYDuCNwBExtRPDYawgSUakzi6qP0iKJpya2BuLvNzXx+XPU49GFuDC5X+SvY0mOw==}
|
||||
/@next/swc-darwin-arm64/12.3.1:
|
||||
resolution: {integrity: sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
@ -3208,8 +3208,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/12.1.6:
|
||||
resolution: {integrity: sha512-9FptMnbgHJK3dRDzfTpexs9S2hGpzOQxSQbe8omz6Pcl7rnEp9x4uSEKY51ho85JCjL4d0tDLBcXEJZKKLzxNg==}
|
||||
/@next/swc-darwin-x64/12.3.1:
|
||||
resolution: {integrity: sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
@ -3217,8 +3217,17 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/12.1.6:
|
||||
resolution: {integrity: sha512-PvfEa1RR55dsik/IDkCKSFkk6ODNGJqPY3ysVUZqmnWMDSuqFtf7BPWHFa/53znpvVB5XaJ5Z1/6aR5CTIqxPw==}
|
||||
/@next/swc-freebsd-x64/12.3.1:
|
||||
resolution: {integrity: sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/12.3.1:
|
||||
resolution: {integrity: sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
@ -3226,8 +3235,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/12.1.6:
|
||||
resolution: {integrity: sha512-53QOvX1jBbC2ctnmWHyRhMajGq7QZfl974WYlwclXarVV418X7ed7o/EzGY+YVAEKzIVaAB9JFFWGXn8WWo0gQ==}
|
||||
/@next/swc-linux-arm64-gnu/12.3.1:
|
||||
resolution: {integrity: sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -3235,8 +3244,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/12.1.6:
|
||||
resolution: {integrity: sha512-CMWAkYqfGdQCS+uuMA1A2UhOfcUYeoqnTW7msLr2RyYAys15pD960hlDfq7QAi8BCAKk0sQ2rjsl0iqMyziohQ==}
|
||||
/@next/swc-linux-arm64-musl/12.3.1:
|
||||
resolution: {integrity: sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -3244,8 +3253,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/12.1.6:
|
||||
resolution: {integrity: sha512-AC7jE4Fxpn0s3ujngClIDTiEM/CQiB2N2vkcyWWn6734AmGT03Duq6RYtPMymFobDdAtZGFZd5nR95WjPzbZAQ==}
|
||||
/@next/swc-linux-x64-gnu/12.3.1:
|
||||
resolution: {integrity: sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -3253,8 +3262,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/12.1.6:
|
||||
resolution: {integrity: sha512-c9Vjmi0EVk0Kou2qbrynskVarnFwfYIi+wKufR9Ad7/IKKuP6aEhOdZiIIdKsYWRtK2IWRF3h3YmdnEa2WLUag==}
|
||||
/@next/swc-linux-x64-musl/12.3.1:
|
||||
resolution: {integrity: sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -3262,8 +3271,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/12.1.6:
|
||||
resolution: {integrity: sha512-3UTOL/5XZSKFelM7qN0it35o3Cegm6LsyuERR3/OoqEExyj3aCk7F025b54/707HTMAnjlvQK3DzLhPu/xxO4g==}
|
||||
/@next/swc-win32-arm64-msvc/12.3.1:
|
||||
resolution: {integrity: sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
@ -3271,8 +3280,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/12.1.6:
|
||||
resolution: {integrity: sha512-8ZWoj6nCq6fI1yCzKq6oK0jE6Mxlz4MrEsRyu0TwDztWQWe7rh4XXGLAa2YVPatYcHhMcUL+fQQbqd1MsgaSDA==}
|
||||
/@next/swc-win32-ia32-msvc/12.3.1:
|
||||
resolution: {integrity: sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
@ -3280,8 +3289,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/12.1.6:
|
||||
resolution: {integrity: sha512-4ZEwiRuZEicXhXqmhw3+de8Z4EpOLQj/gp+D9fFWo6ii6W1kBkNNvvEx4A90ugppu+74pT1lIJnOuz3A9oQeJA==}
|
||||
/@next/swc-win32-x64-msvc/12.3.1:
|
||||
resolution: {integrity: sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
@ -3613,6 +3622,12 @@ packages:
|
|||
'@swc/core-win32-x64-msvc': 1.2.210
|
||||
dev: true
|
||||
|
||||
/@swc/helpers/0.4.11:
|
||||
resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==}
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@szmarczak/http-timer/1.1.2:
|
||||
resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -5077,6 +5092,11 @@ packages:
|
|||
|
||||
/caniuse-lite/1.0.30001338:
|
||||
resolution: {integrity: sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite/1.0.30001419:
|
||||
resolution: {integrity: sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==}
|
||||
dev: false
|
||||
|
||||
/capital-case/1.0.4:
|
||||
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
||||
|
@ -6148,7 +6168,7 @@ packages:
|
|||
eslint-plugin-import: 2.26.0_hhyjdrupy4c2vgtpytri6cjwoy
|
||||
dev: true
|
||||
|
||||
/eslint-config-next/12.1.4_e6a2zi6fqdwfehht5cxvkmo3zu:
|
||||
/eslint-config-next/12.1.4_c2ous3fcmy6f3xlzfnkf2jzbvm:
|
||||
resolution: {integrity: sha512-Uj0jrVjoQbg9qerxRjSHoOOv3PEzoZxpb8G9LYct25fsflP8xIiUq0l4WEu2KSB5owuLv5hie7wSMqPEsHj+bQ==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0
|
||||
|
@ -6168,7 +6188,7 @@ packages:
|
|||
eslint-plugin-jsx-a11y: 6.5.1_eslint@8.12.0
|
||||
eslint-plugin-react: 7.29.1_eslint@8.12.0
|
||||
eslint-plugin-react-hooks: 4.3.0_eslint@8.12.0
|
||||
next: 12.1.6_talmm3uuvp6ssixt2qevhfgvue
|
||||
next: 12.3.1_talmm3uuvp6ssixt2qevhfgvue
|
||||
typescript: 4.6.4
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
|
@ -9670,8 +9690,8 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/next/12.1.6_talmm3uuvp6ssixt2qevhfgvue:
|
||||
resolution: {integrity: sha512-cebwKxL3/DhNKfg9tPZDQmbRKjueqykHHbgaoG4VBRH3AHQJ2HO0dbKFiS1hPhe1/qgc2d/hFeadsbPicmLD+A==}
|
||||
/next/12.3.1_talmm3uuvp6ssixt2qevhfgvue:
|
||||
resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -9688,25 +9708,28 @@ packages:
|
|||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 12.1.6
|
||||
caniuse-lite: 1.0.30001338
|
||||
postcss: 8.4.5
|
||||
'@next/env': 12.3.1
|
||||
'@swc/helpers': 0.4.11
|
||||
caniuse-lite: 1.0.30001419
|
||||
postcss: 8.4.14
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
styled-jsx: 5.0.2_vm2wkhzl5f5eyl7hfuywll6uzq
|
||||
styled-jsx: 5.0.7_vm2wkhzl5f5eyl7hfuywll6uzq
|
||||
use-sync-external-store: 1.2.0_react@18.1.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 12.1.6
|
||||
'@next/swc-android-arm64': 12.1.6
|
||||
'@next/swc-darwin-arm64': 12.1.6
|
||||
'@next/swc-darwin-x64': 12.1.6
|
||||
'@next/swc-linux-arm-gnueabihf': 12.1.6
|
||||
'@next/swc-linux-arm64-gnu': 12.1.6
|
||||
'@next/swc-linux-arm64-musl': 12.1.6
|
||||
'@next/swc-linux-x64-gnu': 12.1.6
|
||||
'@next/swc-linux-x64-musl': 12.1.6
|
||||
'@next/swc-win32-arm64-msvc': 12.1.6
|
||||
'@next/swc-win32-ia32-msvc': 12.1.6
|
||||
'@next/swc-win32-x64-msvc': 12.1.6
|
||||
'@next/swc-android-arm-eabi': 12.3.1
|
||||
'@next/swc-android-arm64': 12.3.1
|
||||
'@next/swc-darwin-arm64': 12.3.1
|
||||
'@next/swc-darwin-x64': 12.3.1
|
||||
'@next/swc-freebsd-x64': 12.3.1
|
||||
'@next/swc-linux-arm-gnueabihf': 12.3.1
|
||||
'@next/swc-linux-arm64-gnu': 12.3.1
|
||||
'@next/swc-linux-arm64-musl': 12.3.1
|
||||
'@next/swc-linux-x64-gnu': 12.3.1
|
||||
'@next/swc-linux-x64-musl': 12.3.1
|
||||
'@next/swc-win32-arm64-msvc': 12.3.1
|
||||
'@next/swc-win32-ia32-msvc': 12.3.1
|
||||
'@next/swc-win32-x64-msvc': 12.3.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
@ -10350,8 +10373,8 @@ packages:
|
|||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/postcss/8.4.5:
|
||||
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
|
||||
/postcss/8.4.14:
|
||||
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.4
|
||||
|
@ -11420,8 +11443,8 @@ packages:
|
|||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/styled-jsx/5.0.2_vm2wkhzl5f5eyl7hfuywll6uzq:
|
||||
resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==}
|
||||
/styled-jsx/5.0.7_vm2wkhzl5f5eyl7hfuywll6uzq:
|
||||
resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '*'
|
||||
|
@ -12246,6 +12269,14 @@ packages:
|
|||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/use-sync-external-store/1.2.0_react@18.1.0:
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.1.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
|
|
|
@ -141,10 +141,14 @@ if [[ "$command" = "uninstall" ]]; then
|
|||
write_log "Failed to uninstall app ${app}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! compose "${app}" down --rmi all --remove-orphans; then
|
||||
# just stop it if we can't remove the images
|
||||
if ! compose "${app}" rm --force --stop; then
|
||||
write_log "Failed to uninstall app ${app}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
write_log "Deleting app data for app ${app}..."
|
||||
if [[ -d "${app_data_dir}" ]]; then
|
||||
|
@ -167,9 +171,12 @@ if [[ "$command" = "update" ]]; then
|
|||
fi
|
||||
|
||||
if ! compose "${app}" down --rmi all --remove-orphans; then
|
||||
write_log "Failed to update app ${app}"
|
||||
# just stop it if we can't remove the images
|
||||
if ! compose "${app}" rm --force --stop; then
|
||||
write_log "Failed to uninstall app ${app}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove app
|
||||
if [[ -d "${app_dir}" ]]; then
|
||||
|
@ -214,4 +221,12 @@ if [[ "$command" = "compose" ]]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$command" = "clean" ]]; then
|
||||
# Remove all stopped containers and unused images
|
||||
write_log "Cleaning up..."
|
||||
docker system prune --all --force
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
|
|
|
@ -55,7 +55,7 @@ if [[ "$command" = "update" ]]; then
|
|||
write_log "Updating ${repo} in ${repo_hash}"
|
||||
cd "${repo_dir}" || exit
|
||||
|
||||
if ! git pull origin master; then
|
||||
if ! git pull origin "$(git rev-parse --abbrev-ref HEAD)"; then
|
||||
cd "${ROOT_FOLDER}" || exit
|
||||
write_log "Failed to update repo"
|
||||
exit 1
|
||||
|
|
|
@ -12,7 +12,7 @@ SED_ROOT_FOLDER="$(echo "$ROOT_FOLDER" | sed 's/\//\\\//g')"
|
|||
NGINX_PORT=80
|
||||
NGINX_PORT_SSL=443
|
||||
DOMAIN=tipi.localhost
|
||||
DNS_IP=9.9.9.9 # Default to Quad9 DNS
|
||||
DNS_IP="9.9.9.9" # Default to Quad9 DNS
|
||||
ARCHITECTURE="$(uname -m)"
|
||||
TZ="UTC"
|
||||
JWT_SECRET=secret
|
||||
|
@ -21,6 +21,18 @@ TIPI_VERSION=$(get_json_field "${ROOT_FOLDER}/package.json" version)
|
|||
INTERNAL_IP=localhost
|
||||
storage_path="${ROOT_FOLDER}"
|
||||
STORAGE_PATH_ESCAPED="$(echo "${storage_path}" | sed 's/\//\\\//g')"
|
||||
if [[ "$ARCHITECTURE" == "aarch64" ]]; then
|
||||
ARCHITECTURE="arm64"
|
||||
elif [[ "$ARCHITECTURE" == "armv7l" ]]; then
|
||||
ARCHITECTURE="arm"
|
||||
elif [[ "$ARCHITECTURE" == "x86_64" ]]; then
|
||||
ARCHITECTURE="amd64"
|
||||
fi
|
||||
# If none of the above conditions are met, the architecture is not supported
|
||||
if [[ "$ARCHITECTURE" != "arm64" ]] && [[ "$ARCHITECTURE" != "arm" ]] && [[ "$ARCHITECTURE" != "amd64" ]]; then
|
||||
echo "Architecture not supported!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
### --------------------------------
|
||||
### Apps repository configuration
|
||||
|
|
|
@ -12,7 +12,9 @@ ensure_pwd
|
|||
ensure_root
|
||||
clean_logs
|
||||
|
||||
# Configure Tipi
|
||||
### --------------------------------
|
||||
### Pre-configuration
|
||||
### --------------------------------
|
||||
"${ROOT_FOLDER}/scripts/configure.sh"
|
||||
|
||||
STATE_FOLDER="${ROOT_FOLDER}/state"
|
||||
|
@ -22,12 +24,14 @@ if [[ ! -f "${STATE_FOLDER}/seed" ]]; then
|
|||
tr </dev/urandom -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 >"${STATE_FOLDER}/seed"
|
||||
fi
|
||||
|
||||
# Default variables
|
||||
### --------------------------------
|
||||
### General variables
|
||||
### --------------------------------
|
||||
NGINX_PORT=80
|
||||
NGINX_PORT_SSL=443
|
||||
DOMAIN=tipi.localhost
|
||||
SED_ROOT_FOLDER="$(echo "$ROOT_FOLDER" | sed 's/\//\\\//g')"
|
||||
DNS_IP=9.9.9.9 # Default to Quad9 DNS
|
||||
DNS_IP="9.9.9.9" # Default to Quad9 DNS
|
||||
ARCHITECTURE="$(uname -m)"
|
||||
TZ="$(timedatectl | grep "Time zone" | awk '{print $3}' | sed 's/\//\\\//g' || Europe\/Berlin)"
|
||||
apps_repository="https://github.com/meienberger/runtipi-appstore"
|
||||
|
@ -61,6 +65,16 @@ INTERNAL_IP="$(ip addr show "${NETWORK_INTERFACE}" | grep "inet " | awk '{print
|
|||
|
||||
if [[ "$ARCHITECTURE" == "aarch64" ]]; then
|
||||
ARCHITECTURE="arm64"
|
||||
elif [[ "$ARCHITECTURE" == "armv7"* || "$ARCHITECTURE" == "armv8"* ]]; then
|
||||
ARCHITECTURE="arm"
|
||||
elif [[ "$ARCHITECTURE" == "x86_64" ]]; then
|
||||
ARCHITECTURE="amd64"
|
||||
fi
|
||||
|
||||
# If none of the above conditions are met, the architecture is not supported
|
||||
if [[ "$ARCHITECTURE" != "arm64" ]] && [[ "$ARCHITECTURE" != "arm" ]] && [[ "$ARCHITECTURE" != "amd64" ]]; then
|
||||
echo "Architecture ${ARCHITECTURE} not supported!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
### --------------------------------
|
||||
|
@ -129,21 +143,18 @@ if [[ "${NGINX_PORT}" != "80" ]] && [[ "${DOMAIN}" != "tipi.localhost" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Run system-info.sh
|
||||
### --------------------------------
|
||||
### Watcher and system-info
|
||||
### --------------------------------
|
||||
echo "Running system-info.sh..."
|
||||
"${ROOT_FOLDER}/scripts/system-info.sh"
|
||||
|
||||
kill_watcher
|
||||
"${ROOT_FOLDER}/scripts/watcher.sh" &
|
||||
|
||||
# Copy the config sample if it isn't here
|
||||
if [[ ! -f "${STATE_FOLDER}/apps.json" ]]; then
|
||||
cp "${ROOT_FOLDER}/templates/config-sample.json" "${STATE_FOLDER}/config.json"
|
||||
fi
|
||||
|
||||
export DOCKER_CLIENT_TIMEOUT=240
|
||||
export COMPOSE_HTTP_TIMEOUT=240
|
||||
|
||||
### --------------------------------
|
||||
### settings.json overrides
|
||||
### --------------------------------
|
||||
echo "Generating config files..."
|
||||
# Override vars with values from settings.json
|
||||
if [[ -f "${STATE_FOLDER}/settings.json" ]]; then
|
||||
|
@ -216,7 +227,9 @@ done
|
|||
|
||||
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
|
||||
|
||||
## Don't run if config-only
|
||||
### --------------------------------
|
||||
### Start the project
|
||||
### --------------------------------
|
||||
if [[ ! $ci == "true" ]]; then
|
||||
|
||||
if [[ $rc == "true" ]]; then
|
||||
|
|
|
@ -27,7 +27,7 @@ if [[ "$command" = "update" ]]; then
|
|||
|
||||
scripts/stop.sh
|
||||
git config --global --add safe.directory "${ROOT_FOLDER}"
|
||||
git pull origin master
|
||||
git pull origin "$(git rev-parse --abbrev-ref HEAD)"
|
||||
scripts/start.sh
|
||||
exit
|
||||
fi
|
||||
|
|
Loading…
Reference in a new issue