From 631ebaacb028ddc995bc5b8c693a4720292773ce Mon Sep 17 00:00:00 2001 From: Alexandre Date: Sat, 20 May 2023 20:52:38 +0200 Subject: [PATCH] First build https://github.com/alexbelgium/hassio-addons/issues/840 https://github.com/alexbelgium/hassio-addons/issues/565 --- portainer_agent/CHANGELOG.md | 1 + portainer_agent/Dockerfile | 113 +++ portainer_agent/README.md | 84 +++ portainer_agent/apparmor.txt | 59 ++ portainer_agent/build.json | 10 + portainer_agent/config.json | 44 ++ portainer_agent/icon.png | Bin 0 -> 1894 bytes portainer_agent/logo.png | Bin 0 -> 1894 bytes portainer_agent/rootfs/entrypoint.sh | 16 + .../rootfs/etc/cont-init.d/99-run.sh | 12 + portainer_agent/stats.png | Bin 0 -> 1736 bytes portainer_agent/translations/en.yaml | 662 ++++++++++++++++++ portainer_agent/updater.json | 9 + 13 files changed, 1010 insertions(+) create mode 100644 portainer_agent/CHANGELOG.md create mode 100644 portainer_agent/Dockerfile create mode 100644 portainer_agent/README.md create mode 100644 portainer_agent/apparmor.txt create mode 100644 portainer_agent/build.json create mode 100644 portainer_agent/config.json create mode 100644 portainer_agent/icon.png create mode 100644 portainer_agent/logo.png create mode 100644 portainer_agent/rootfs/entrypoint.sh create mode 100644 portainer_agent/rootfs/etc/cont-init.d/99-run.sh create mode 100644 portainer_agent/stats.png create mode 100644 portainer_agent/translations/en.yaml create mode 100644 portainer_agent/updater.json diff --git a/portainer_agent/CHANGELOG.md b/portainer_agent/CHANGELOG.md new file mode 100644 index 000000000..108d8df64 --- /dev/null +++ b/portainer_agent/CHANGELOG.md @@ -0,0 +1 @@ +- First build diff --git a/portainer_agent/Dockerfile b/portainer_agent/Dockerfile new file mode 100644 index 000000000..a81d12e8d --- /dev/null +++ b/portainer_agent/Dockerfile @@ -0,0 +1,113 @@ +#============================# +# ALEXBELGIUM'S DOCKERFILE # +#============================# +# _.------. +# _.-` ('>.-`"""-. +# '.--'` _'` _ .--.) +# -' '-.-';` ` +# ' - _.' ``'--. +# '---` .-'""` +# /` +#=== Home Assistant Addon ===# + +################# +# 1 Build Image # +################# + +ARG BUILD_FROM=ghcr.io/hassio-addons/base/amd64:11.0.0 +FROM ${BUILD_FROM} + +################## +# 2 Modify Image # +################## + +# Set S6 wait time +ENV S6_CMD_WAIT_FOR_SERVICES=1 \ + S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ + S6_SERVICES_GRACETIME=0 + +################## +# 3 Install apps # +################## + +# Add rootfs +COPY rootfs/ / + +# Corrects permissions for s6 v3 +RUN if [ -d /etc/cont-init.d ]; then chmod -R 755 /etc/cont-init.d; fi && \ + if [ -d /etc/services.d ]; then chmod -R 755 /etc/services.d; fi && \ + if [ -f /entrypoint.sh ]; then chmod 755 /entrypoint.sh; fi + +# Modules +ARG MODULES="00-banner.sh 01-custom_script.sh 00-global_var.sh" + +# Automatic modules download +RUN if ! command -v bash >/dev/null 2>/dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends bash || apk add --no-cache bash) >/dev/null; fi \ + && if ! command -v curl >/dev/null 2>/dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends curl || apk add --no-cache curl) >/dev/null; fi \ + && apt-get update && apt-get install -yqq --no-install-recommends ca-certificates || apk add --no-cache ca-certificates >/dev/null || true \ + && mkdir -p /etc/cont-init.d \ + && for scripts in $MODULES; do echo "$scripts" && curl -f -L -s -S "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/$scripts" -o /etc/cont-init.d/"$scripts" && [ "$(sed -n '/\/bin/p;q' /etc/cont-init.d/"$scripts")" != "" ] || (echo "script failed to install $scripts" && exit 1); done \ + && chmod -R 755 /etc/cont-init.d + +# Manual apps +ENV PACKAGES="nginx" + +# Automatic apps & bashio +# hadolint ignore=SC2015 +RUN if ! command -v bash >/dev/null 2>/dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends bash || apk add --no-cache bash) >/dev/null; fi \ + && if ! command -v curl >/dev/null 2>/dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends curl || apk add --no-cache curl) >/dev/null; fi \ + && curl -f -L -s -S "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/automatic_packages.sh" --output /automatic_packages.sh \ + && chmod 777 /automatic_packages.sh \ + && eval /./automatic_packages.sh "${PACKAGES:-}" \ + && rm /automatic_packages.sh + +################ +# 4 Entrypoint # +################ + +RUN chmod 777 /entrypoint.sh +ENTRYPOINT [ "/usr/bin/env" ] +CMD [ "/entrypoint.sh" ] + +############ +# 5 Labels # +############ + +ARG BUILD_ARCH +ARG BUILD_DATE +ARG BUILD_DESCRIPTION +ARG BUILD_NAME +ARG BUILD_REF +ARG BUILD_REPOSITORY +ARG BUILD_VERSION +LABEL \ + io.hass.name="${BUILD_NAME}" \ + io.hass.description="${BUILD_DESCRIPTION}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.type="addon" \ + io.hass.version=${BUILD_VERSION} \ + maintainer="alexbelgium (https://github.com/alexbelgium)" \ + org.opencontainers.image.title="${BUILD_NAME}" \ + org.opencontainers.image.description="${BUILD_DESCRIPTION}" \ + org.opencontainers.image.vendor="Home Assistant Add-ons" \ + org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.url="https://github.com/alexbelgium" \ + org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \ + org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \ + org.opencontainers.image.created=${BUILD_DATE} \ + org.opencontainers.image.revision=${BUILD_REF} \ + org.opencontainers.image.version=${BUILD_VERSION} + +################# +# 6 Healthcheck # +################# + +ENV HEALTH_PORT="9000" \ + HEALTH_URL="/api/system/status" +HEALTHCHECK \ + --interval=5s \ + --retries=5 \ + --start-period=30s \ + --timeout=25s \ + CMD curl --fail "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1 diff --git a/portainer_agent/README.md b/portainer_agent/README.md new file mode 100644 index 000000000..efcc8b119 --- /dev/null +++ b/portainer_agent/README.md @@ -0,0 +1,84 @@ +# Home assistant add-on: Portainer_agent + +[![Donate][donation-badge]](https://www.buymeacoffee.com/alexbelgium) + +![Version](https://img.shields.io/badge/dynamic/json?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fportainer_agent%2Fconfig.json) +![Ingress](https://img.shields.io/badge/dynamic/json?label=Ingress&query=%24.ingress&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fportainer_agent%2Fconfig.json) +![Arch](https://img.shields.io/badge/dynamic/json?color=success&label=Arch&query=%24.arch&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fportainer_agent%2Fconfig.json) + +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9c6cf10bdbba45ecb202d7f579b5be0e)](https://www.codacy.com/gh/alexbelgium/hassio-addons/dashboard?utm_source=github.com&utm_medium=referral&utm_content=alexbelgium/hassio-addons&utm_campaign=Badge_Grade) +[![GitHub Super-Linter](https://github.com/alexbelgium/hassio-addons/workflows/Lint%20Code%20Base/badge.svg)](https://github.com/marketplace/actions/super-linter) +[![Builder](https://github.com/alexbelgium/hassio-addons/workflows/Builder/badge.svg)](https://github.com/alexbelgium/hassio-addons/actions/workflows/builder.yaml) + +[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white + +Forked from : https://github.com/hassio-addons/addon-portainer_agent +Implemented changes : update to latest versions ; ingress ; ssl ; password setting through addon option ; allow manual override + +_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_ + +[![Stargazers repo roster for @alexbelgium/hassio-addons](https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.github/stars2.svg)](https://github.com/alexbelgium/hassio-addons/stargazers) + +![downloads evolution](https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/portainer_agent/stats.png) + +## About + +--- + +portainer_agent is an open-source lightweight management UI which allows you to +easily manage your a Docker host(s) or Docker swarm clusters. + +It has never been so easy to manage Docker. portainer_agent provides a detailed +overview of Docker and allows you to manage containers, images, networks and +volumes. + +## RESTORE BACKUP + +Open the addon options and set the password to "". Restart the addon, it will allow to restore portainer_agent from a backup. You need to put your backup in an accessible folder such as /share to have it mounted in the addon + +## WARNING + +The portainer_agent add-on is really powerful and gives you virtually access to +your whole system. While this add-on is created and maintained with care and +with security in mind, in the wrong or inexperienced hands, +it could damage your system. + +## Installation + +--- + +The installation of this add-on is pretty straightforward and not different in comparison to installing any other add-on. + +1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA) + [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) +1. Install this add-on. +1. Click the `Save` button to store your configuration. +1. Set the add-on options to your preferences +1. Start the add-on. +1. Check the logs of the add-on to see if everything went well. +1. Open the webUI and adapt the software options + +## Configuration + +--- + +Webui can be found at , or in your sidebar using Ingress. +The default username/password : described in the startup log. +Configurations can be done through the app webUI, except for the following options + +```yaml +ssl: true/false +certfile: fullchain.pem #ssl certificate, must be located in /ssl +keyfile: privkey.pem #sslkeyfile, must be located in /ssl +password: define admin password. If kept blank, will allow manual restore of previous backup. At least 12 characters. +``` + +## Support + +Create an issue on github + +## Illustration + +--- + +![illustration](https://github.com/hassio-addons/addon-portainer_agent/raw/main/images/screenshot.png) diff --git a/portainer_agent/apparmor.txt b/portainer_agent/apparmor.txt new file mode 100644 index 000000000..1374c50a7 --- /dev/null +++ b/portainer_agent/apparmor.txt @@ -0,0 +1,59 @@ +#include + +profile portainer_agent_addon flags=(attach_disconnected,mediate_deleted) { + #include + + capability, + file, + signal, + mount, + umount, + remount, + network udp, + network tcp, + network dgram, + network stream, + network inet, + network inet6, + network netlink raw, + network unix dgram, + capability setgid, + capability setuid, + + +# S6-Overlay + /init ix, + /run/{s6,s6-rc*,service}/** ix, + /package/** ix, + /command/** ix, + /run/{,**} rwk, + /dev/tty rw, + /bin/** ix, + /usr/bin/** ix, + /usr/lib/bashio/** ix, + /etc/s6/** rix, + /run/s6/** rix, + /etc/services.d/** rwix, + /etc/cont-init.d/** rwix, + /etc/cont-finish.d/** rwix, + /init rix, + /var/run/** mrwkl, + /var/run/ mrwkl, + /dev/i2c-1 mrwkl, + /dev/fuse mrwkl, + /dev/sda1 mrwkl, + /dev/sdb1 mrwkl, + /dev/nvme0 mrwkl, + /dev/nvme1 mrwkl, + /dev/mmcblk0p1 mrwkl, + + # Data access + /data/** rw, + + # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container + ptrace (trace,read) peer=docker-default, + + # docker daemon confinement requires explict allow rule for signal + signal (receive) set=(kill,term) peer=/usr/bin/docker, + +} diff --git a/portainer_agent/build.json b/portainer_agent/build.json new file mode 100644 index 000000000..c92d39a3f --- /dev/null +++ b/portainer_agent/build.json @@ -0,0 +1,10 @@ +{ + "build_from": { + "aarch64": "portainer/agent:latest", + "amd64": "portainer/agent:latest", + "armv7": "portainer/agent:latest" + }, + "codenotary": { + "signer": "alexandrep.github@gmail.com" + } +} \ No newline at end of file diff --git a/portainer_agent/config.json b/portainer_agent/config.json new file mode 100644 index 000000000..2878135b9 --- /dev/null +++ b/portainer_agent/config.json @@ -0,0 +1,44 @@ +{ + "arch": [ + "aarch64", + "amd64", + "armv7" + ], + "backup_exclude": [ + "backups" + ], + "codenotary": "alexandrep.github@gmail.com", + "description": "An agent used to manage all the resources in a Swarm cluster", + "image": "ghcr.io/alexbelgium/portainer_agent-{arch}", + "init": false, + "map": [ + "share:rw", + "ssl" + ], + "name": "portainer_agent", + "options": {}, + "ports": { + "9001/tcp": 9001, + "80/tcp": null + }, + "ports_description": { + "9001/tcp": "Portainer agent", + "80/tcp": "Portainer edge agent" + }, + "schema": { + "AGENT_CLUSTER_ADDR": "str", + "AGENT_SECRET": "str?", + "AGENT_SECRET_TIMEOUT": "str?", + "AGENT_CLUSTER_PROBE_TIMEOUT": "str?", + "AGENT_CLUSTER_PROBE_INTERVAL": "str?", + "LOG_LEVEL": "str?", + "EDGE": "list(0|1)?", + "EDGE_KEY": "str?", + "EDGE_ID": "str?", + "EDGE_INACTIVITY_TIMEOUT": "str?", + "EDGE_INSECURE_POLL": "list(0|1)?" + }, + "slug": "portainer_agent", + "url": "https://github.com/alexbelgium/hassio-addons", + "version": "2.18.2-test" +} \ No newline at end of file diff --git a/portainer_agent/icon.png b/portainer_agent/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d114c26152aaecbb8fe026a43243710866e37d2 GIT binary patch literal 1894 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 e^7&wRpNjv93wNsS36