services: add local services

- portainer
- home
This commit is contained in:
tylen 2025-05-11 23:16:50 +03:00
parent cac19b4716
commit df60f57708
23 changed files with 586 additions and 0 deletions

3
services/homepage/.env Normal file
View File

@ -0,0 +1,3 @@
# Dot Env for service homepage created at Sat May 10 09:14:42 PM EEST 2025
HOMEPAGE_CONFIG='./config'
ALLOWED_HOSTS='192.168.100.16:${SVC_PORT_1},home.davydovcloud.com'

1
services/homepage/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
config/logs

View File

@ -0,0 +1,4 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/bookmarks

View File

View File

View File

@ -0,0 +1,24 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/docker/
vm-personal-100-16:
host: 192.168.100.16
port: 2375
vm-network-100-75:
host: 192.168.100.75
port: 2375
vm-tools-100-65:
host: 192.168.100.65
port: 2375
vm-media-100-55:
host: 192.168.100.55
port: 2375
# my-docker:
# host: 127.0.0.1
# port: 2375
# my-docker:
# socket: /var/run/docker.sock

View File

@ -0,0 +1,2 @@
---
# sample kubernetes config

View File

@ -0,0 +1,136 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/services/
- node-100-99:
- vm-network-100-75:
- QBitTorrent:
href: https://torrent.davydovcloud.com
description: Peer-to-peer freedom 💽
ping: https://torrent.davydovcloud.com
icon: qbittorrent.png
server: vm-network-100-75
container: qbittorrent
siteMonitor: https://torrent.davydovcloud.com
showStats: true
- Wireguard:
href: https://wireguard.davydovcloud.com
description: VPN
ping: https://wireguard.davydovcloud.com
icon: wireguard.png
server: vm-network-100-75
container: wg-easy
siteMonitor: https://wireguard.davydovcloud.com
- vm-tools-100-65:
- Gitea:
href: https://git.davydovcloud.com
description: Personal Git server
ping: https://git.davydovcloud.com
icon: gitea.png
server: vm-tools-100-65
container: gitea
siteMonitor: https://git.davydovcloud.com
widget:
type: gitea
url: https://git.davydovcloud.com
key: 9ba1d59d59826be79c11d0c87fdd11ad9ec11d2c
- Vaultwarden:
href: https://vault.davydovcloud.com
description: Passwords and Secrets
ping: https://vault.davydovcloud.com
icon: vaultwarden.png
server: vm-tools-100-65
container: vaultwarden
siteMonitor: https://vault.davydovcloud.com
- Search:
href: https://search.davydovcloud.com
description: Meta Search Engine
ping: https://search.davydovcloud.com
icon: searxng.png
server: vm-tools-100-65
container: meta_search_engine_gui
siteMonitor: https://search.davydovcloud.com
- Omni-Tools:
href: https://omni-tools.davydovcloud.com
description: Set of different Tools
ping: https://omni-tools.davydovcloud.com
icon: omni-tools.png
server: vm-tools-100-65
container: omni-tools
siteMonitor: https://omni-tools.davydovcloud.com
- Pastebin:
href: https://pastebin.davydovcloud.com
description: Paste and send. Securely
ping: https://pastebin.davydovcloud.com
icon: enclosed.png
server: vm-tools-100-65
container: enclosed_pastebin
siteMonitor: https://pastebin.davydovcloud.com
- YouTube Downloader:
href: https://metube.davydovcloud.com
description: Download any YouTube video
ping: https://metube.davydovcloud.com
icon: metube.png
server: vm-tools-100-65
container: yt_download_stack_metube
siteMonitor: https://metube.davydovcloud.com
- vm-media-100-55:
- Audiobookshelf:
href: https://audiobooks.davydovcloud.com
description: Audiobooks collection 📚
ping: https://audiobooks.davydovcloud.com
icon: audiobookshelf.png
server: vm-media-100-55
container: audiobookshelf
siteMonitor: https://audiobooks.davydovcloud.com
- Navidrome:
href: https://music.davydovcloud.com
description: Music Collection 🎶
ping: https://music.davydovcloud.com
icon: navidrome.png
server: vm-media-100-55
container: navidrome_music
siteMonitor: https://music.davydovcloud.com
- LinkWarden:
href: https://link.davydovcloud.com
description: Links storage 🔗
ping: https://link.davydovcloud.com
icon: linkwarden.png
server: vm-media-100-55
container: linkwarden_main
siteMonitor: https://link.davydovcloud.com
- Jellyfin:
href: https://jellyfin.davydovcloud.com
description: Movies & TV Shows 🎞️
ping: https://jellyfin.davydovcloud.com
icon: jellyfin.png
server: vm-media-100-55
container: jellyfin
showStats: true
siteMonitor: https://jellyfin.davydovcloud.com
- DavydovCloud:
href: https://davydovcloud.com
description: Nextcloud instance ☁️
ping: https://davydovcloud.com
icon: nextcloud.png
server: vm-media-100-55
container: nextcloud-aio-mastercontainer
showStats: true
siteMonitor: https://davydovcloud.com
- vm-personal-100-16:
- Portainer:
href: https://portainer.davydovcloud.com
description: Main Portainer
ping: https://portainer.davydovcloud.com
icon: portainer.png
server: vm-personal-100-16
container: portainer
siteMonitor: https://portainer.davydovcloud.com
showStats: true
- node-100-50:
- True Nas:
href: https://nas.davydovcloud.com
description: Main Portainer
ping: https://nas.davydovcloud.com
icon: truenas.png
siteMonitor: https://nas.davydovcloud.com

View File

@ -0,0 +1,22 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/settings/
title: Davydov's Cloud
headerStyle: clean
language: ru
hideVersion: true
statusStyle: "basic"
background:
image: https://davydovcloud.com/s/J9mMTHjYbKYs8HS/download/background_3.jpg
blur: sm # sm, "", md, xl... see https://tailwindcss.com/docs/backdrop-blur
saturate: 70 # 0, 50, 100... see https://tailwindcss.com/docs/backdrop-saturate
brightness: 70 # 0, 50, 75... see https://tailwindcss.com/docs/backdrop-brightness
opacity: 70 # 0-100
layout:
node-100-99:
icon: https://davydovcloud.com/s/rBYZWxWNmi555DA/download/server.png
style: row
columns: 4
node-100-50:
icon: https://davydovcloud.com/s/rBYZWxWNmi555DA/download/server.png

View File

@ -0,0 +1,25 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/info-widgets/
- logo:
icon: https://davydovcloud.com/s/mWzY8nwSBXMsobG/download/davydovcloud-icon.png
- search:
provider: custom
url: https://search.davydovcloud.com/search?q=
target: _blank
suggestionUrl: https://duckduckgo.com/ac/?type=list&q= # Optional
showSearchSuggestions: true # Optional
# 1/23/22, 1:37 PM
- datetime:
text_size: xl
format:
dateStyle: short
timeStyle: short
hourCycle: h23
timeZone: Europe/Helsinki

View File

@ -0,0 +1,18 @@
# Docker Compose for service homepage created at Sat May 10 09:14:42 PM EEST 2025
services:
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
environment:
HOMEPAGE_ALLOWED_HOSTS: "${ALLOWED_HOSTS}" # required, may need port. See gethomepage.dev/installation/#homepage_allowed_hosts
PID: 1001:1001
GID: 1001:1001
TZ: "Europe/Helsinki" # Set the timezone
ports:
- ${SVC_PORT_1}:3000
volumes:
- ${HOMEPAGE_CONFIG}:/app/config # Make sure your local config directory exists
- /var/run/docker.sock:/var/run/docker.sock:ro
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro # optional, for docker integrations
restart: unless-stopped

View File

@ -0,0 +1,6 @@
scripts
.github
node_modules
data
Dockerfile.tmp

1
services/portainer/.env Normal file
View File

@ -0,0 +1 @@
# Dot Env for service portainer created at Sun May 11 08:03:31 PM EEST 2025

3
services/portainer/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
data
Dockerfile.tmp

View File

@ -0,0 +1,15 @@
FROM portainer/portainer-ce:latest as portainer
FROM node:18-alpine3.17
WORKDIR /
COPY --from=portainer . .
WORKDIR /proxy
RUN npm i express http-proxy-middleware
COPY app.js .
COPY docker-entrypoint.sh /
ENTRYPOINT [ "/docker-entrypoint.sh" ]

View File

@ -0,0 +1,20 @@
Copyright (c) 2012-2022 Scott Chacon and others
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,32 @@
# portainer-ce-without-annoying
This is a drop-in replacement for [portainer/portainer-ce](https://hub.docker.com/r/portainer/portainer-ce), without annoying UI elements.
`portainer-ce-without-annoying` is **NOT** a fork of `portainer-ce`. It is just an overlay script / proxy to inject styles / scripts, allow removing DOM elements.
| Before || After |
|---|---|---|
| ![image](https://user-images.githubusercontent.com/7702203/236629225-5703d704-0d3e-4eb4-b460-e91bb7dbe19d.png) | ==> | ![image](https://user-images.githubusercontent.com/7702203/236629236-53df8ff4-7fb3-4144-876f-a04ce8ab9ea4.png) |
| ![image](https://user-images.githubusercontent.com/7702203/236629290-e616ff6a-b69b-4848-80ab-b4d93ca9d25e.png) | ==> | ![image](https://user-images.githubusercontent.com/7702203/236629305-9130c816-2fd6-4bec-b1c8-c117b6381d4b.png) |
| ![image](https://user-images.githubusercontent.com/7702203/236629353-5fd003d4-1725-46ab-bed9-15df02705263.png) | ==> | ![image](https://user-images.githubusercontent.com/7702203/236629375-a248f359-2730-4dc0-9206-5de84d5ed831.png) |
**Bonus**: tracking script is also removed. See [this issue](https://github.com/ngxson/portainer-ce-without-annoying/issues/5)
## How to use
If you already have `portainer-ce` installation, just replace `portainer/portainer-ce:latest` with `ngxson/portainer-ce-without-annoying:latest`
For example, if you use the command from the [official installation guide](https://docs.portainer.io/start/install-ce/server/docker/linux), the command will be:
```
docker volume create portainer_data
docker run -d \
-p 8000:8000 -p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
ngxson/portainer-ce-without-annoying:latest
```
Alternatively, you can use [this docker-compose.yml](https://github.com/ngxson/portainer-ce-without-annoying/blob/master/docker-compose.yml)

115
services/portainer/app.js Normal file
View File

@ -0,0 +1,115 @@
const https = require('https');
const http = require('http');
const fs = require('fs');
const spawn = require('child_process').spawn;
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const INJECTED_HTML = `
<style>
/* hide Upgrade to Business on sidebar */
div.sidebar > button {display: none !important;}
/* hide Authentication logs */
[aria-label="Authentication logs"] {display: none !important;}
/* hide everything having the BE Feature banner */
.be-indicator-container, .limited-be {display: none !important;}
/* FIXME: hot fix to show OAuth save button #10 */
.oauth-save-settings-button {display: inline-block !important;}
/* this should not be hidden, but let's make it more subtle */
.be-indicator {
filter: saturate(0) !important;
opacity: 0.2 !important;
pointer-events: none !important;
}
</style>
<script>
// Block tracking script matomo.cloud
// https://github.com/ngxson/portainer-ce-without-annoying/issues/5
(function () {
var headNode = document.getElementsByTagName('script')[0].parentNode;
// save the original function
headNode.originalInsertBefore = headNode.insertBefore;
// intercept the function call
headNode.insertBefore = function(newNode, referenceNode) {
if (newNode && newNode.src && newNode.src.indexOf('matomo') !== -1) {
console.log('Blocked insertion of matomo script node');
} else {
headNode.originalInsertBefore(newNode, referenceNode);
}
}
})();
</script>
`;
const TARGET_URL = 'http://localhost:19000';
const SSL_CERT_PATH = '/data/certs/cert.pem';
const SSL_KEY_PATH = '/data/certs/key.pem';
const FORWARDED_ARGS = process.argv.slice(2);
// proxy logic
const app = express();
app.get('/', async (req, res) => {
try {
const response = await fetch(TARGET_URL);
const body = await response.text();
const newBody = body.replace('<head>', `<head>${INJECTED_HTML}`);
res.send(newBody);
} catch (e) {
console.error(e);
res.status(500).json(e);
}
});
app.get('/api/motd', (req, res) => {
// hide the "Latest News From Portainer"
// https://github.com/portainer/portainer/blob/master/app/portainer/views/home/home.html
res.json({});
});
app.use(createProxyMiddleware({
target: TARGET_URL,
ws: true,
}));
// http + https server
async function waitUntilCertAvailable() {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
while (!fs.existsSync(SSL_CERT_PATH)) {
await sleep(1000);
}
};
async function runServer() {
http.createServer(app).listen(9000);
await waitUntilCertAvailable();
https.createServer({
key: fs.readFileSync(SSL_KEY_PATH),
cert: fs.readFileSync(SSL_CERT_PATH),
}, app).listen(9443);
}
// child process for portainer
function runPortainer() {
const fwdArgs = FORWARDED_ARGS.join(' ');
console.log(`Launching portainer with args ${fwdArgs}`)
const child = spawn('/bin/sh', [
'-c',
`/portainer --bind=":19000" --bind-https=":19443" ${fwdArgs}`
]);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
child.on('exit', function (code) {
console.log(`portainer exited with status code ${code}`);
process.exit(code);
});
process.on('SIGTERM', function () {
child.kill('SIGTERM');
});
}
// run it
runPortainer();
runServer();

View File

@ -0,0 +1,16 @@
# Docker Compose for service portainer created at Sun May 11 08:03:31 PM EEST 2025
services:
portainer:
build: .
privileged: true
container_name: portainer
hostname: portainer
ports:
- ${SVC_PORT_1}:9443
volumes:
- portainer_data:/data:Z
- /var/run/docker.sock:/var/run/docker.sock
volumes:
portainer_data:

View File

@ -0,0 +1,3 @@
#!/bin/sh
node app.js "$@"

View File

@ -0,0 +1,18 @@
#!/bin/bash
# Get newest tag from https://hub.docker.com/r/portainer/portainer-ce/tags
# Also build one for :latest
IMAGE="ngxson/portainer-ce-without-annoying"
ARCHS="linux/amd64,linux/arm64,linux/arm/v7"
if [ -z "$TAG" ]; then
echo "Please set TAG environment variable"
exit 1
fi
cp Dockerfile Dockerfile.tmp
sed -i "s/portainer-ce:latest/portainer-ce:$TAG/g" Dockerfile.tmp
echo "Multi-arch build..."
docker buildx build --platform=$ARCHS --push -t "$IMAGE:$TAG" -f Dockerfile.tmp .

View File

@ -0,0 +1,101 @@
const path = require('path');
const { spawn } = require('child_process');
const ACCEPTED_TAGS_FROM = '2.17.0';
const UPSTREAM_REPO = 'portainer/portainer-ce';
const OUTPUT_REPO = 'ngxson/portainer-ce-without-annoying';
const NUMBER_OF_TAGS_REBUILD = 5;
const shouldRebuild = !!process.argv.join(' ').match(/rebuild=true/);
function build_and_push(tag) {
const cwd = path.join(__dirname, '..');
const command = `TAG=${tag} ./scripts/build_and_push.sh`;
return new Promise(resolve => {
const subproc = spawn('/bin/sh', ['-c', command], { cwd });
subproc.stdout.pipe(process.stdout);
subproc.stderr.pipe(process.stderr);
subproc.on('close', () => resolve());
});
}
/////////////////////////////////////
/**
* Prompt to ChatGPT: write js function that converts semver to int, for example 12.34.567 to 012034567. take into account case that input maybe 12.34 (output should be 012034000) or just 12 (output is 012000000)
*/
function semverToInt(semver) {
const versionParts = semver.split('.');
const paddedVersionParts = versionParts.map((part, index) => {
const paddedPart = part.padStart(3, '0');
return index < 2 ? paddedPart : paddedPart + '0'.repeat(6 - (versionParts.length - 1) * 3);
});
return parseInt(paddedVersionParts.join(''));
}
/////////////////////////////////////
/**
* Prompt to ChatGPT: now, I have docker hub api to return a list of tags of a repo, it's in results[i].name (a semver string)
* the api url is `https://hub.docker.com/v2/repositories/${repoName}/tags/`
* write nodejs code using fetch (async - await) to:
* 1. fetch name of all tags of repo_a and repo_b
* 2. get a list of difference of tags between the 2 (for example, repo_a has tag 1, 2, 3 and repo_b has 1, 2 ==> difference of tags is the "3")
* 3. for each difference of tags, call build_and_push(tag)
*/
// Utility function to fetch tags for a given repo
async function fetchTags(repoName) {
const url = `https://hub.docker.com/v2/repositories/${repoName}/tags/?page_size=50&page=1`;
const response = await fetch(url);
return (await response.json())
.results.map(result => result.name)
.filter(t => t.match(/^(latest|[\d.]+)$/));
}
// Utility function to find the difference between two tag arrays
function findTagDifference(tagsA, tagsB) {
return tagsA.filter(tag => !tagsB.includes(tag));
}
// Main function to fetch tags, find the difference, and call build_and_push
async function processRepos(repoA, repoB) {
try {
const tagsA = await fetchTags(repoA);
const tagsB = await fetchTags(repoB);
console.log({ tagsA, tagsB });
const tagDifference = shouldRebuild
? [...tagsB]
.sort((a, b) => semverToInt(b) - semverToInt(a)) // sort desc
.filter(t => t !== 'latest')
.slice(0, NUMBER_OF_TAGS_REBUILD)
: findTagDifference(tagsA, tagsB);
// added by me
const acceptTagsFrom = semverToInt(ACCEPTED_TAGS_FROM);
const acceptedTags = tagDifference.filter(tag => semverToInt(tag) >= acceptTagsFrom);
acceptedTags.sort((a, b) => semverToInt(a) - semverToInt(b));
if (acceptedTags.length > 0) acceptedTags.push('latest');
if (acceptedTags.length === 0) {
console.log('No new tags to build, exit now');
process.exit(0);
}
console.log('Tags to build:', acceptedTags);
for (const tag of acceptedTags) {
console.log(`============= Building ${tag} =============`);
await build_and_push(tag);
console.log(`============= Done ${tag} =============`);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
processRepos(UPSTREAM_REPO, OUTPUT_REPO);

View File

@ -2,6 +2,10 @@ defaultServiceValues: &defaultServiceValues
composeFile: "docker-compose.yml" composeFile: "docker-compose.yml"
envFile: ".env" envFile: ".env"
vm-personal-100-16: &vm-personal-100-16
ip: "localhost"
user: tylen
vm-network-100-75: &vm-network-100-75 vm-network-100-75: &vm-network-100-75
ip: "192.168.100.75" ip: "192.168.100.75"
user: vm-user user: vm-user
@ -15,6 +19,23 @@ vm-media-100-55: &vm-media-100-55
user: vm-user user: vm-user
services: services:
# ================================
# vm-personal-100-16
# ================================
- name: "homepage"
ports:
- 3018
host:
<<: *vm-personal-100-16
<<: *defaultServiceValues
- name: "portainer"
ports:
- 9443
host:
<<: *vm-personal-100-16
<<: *defaultServiceValues
# ================================ # ================================
# vm-media-100-55 # vm-media-100-55
# ================================ # ================================