From b0947474ad2775157f82ac83c2e22b3a4d82c71a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 4 Dec 2018 16:49:56 +0100 Subject: [PATCH] :tada: Initial add-on code --- .editorconfig | 19 + .github/CODEOWNERS | 4 + .github/ISSUE_TEMPLATE.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 9 + .github/autolabeler.yml | 2 + .github/config.yml | 50 +++ .github/invite-contributors.yml | 10 + .github/lock.yml | 20 + .github/move.yml | 20 + .github/no-response.yml | 13 + .github/potential-duplicates.yml | 14 + .github/settings.yml | 150 +++++++ .github/stale.yml | 61 +++ .github/support.yml | 22 ++ .gitignore | 0 .gitlab-ci.yml | 365 ++++++++++++++++++ .mdlrc | 1 + .yamllint | 66 ++++ CHANGELOG.md | 13 + CODE_OF_CONDUCT.md | 74 ++++ CONTRIBUTING.md | 29 ++ LICENSE.md | 21 + README.md | 324 ++++++++++++++++ jupyterlab/.README.j2 | 74 ++++ jupyterlab/Dockerfile | 125 ++++++ jupyterlab/build.json | 9 + jupyterlab/config.json | 65 ++++ jupyterlab/dive.log | 0 jupyterlab/icon.png | Bin 0 -> 4678 bytes jupyterlab/logo.png | Bin 0 -> 17809 bytes jupyterlab/requirements.txt | 19 + .../rootfs/etc/cont-init.d/10-requirements.sh | 26 ++ .../etc/cont-init.d/20-notebooks-dir.sh | 27 ++ .../etc/cont-init.d/21-persistent-storage.sh | 23 ++ .../rootfs/etc/cont-init.d/50-password.sh | 17 + .../rootfs/etc/cont-init.d/51-github.sh | 16 + .../etc/cont-init.d/80-system-packages.sh | 17 + .../etc/cont-init.d/81-python-packages.sh | 14 + .../etc/jupyter/jupyter_notebook_config.py | 29 ++ .../rootfs/etc/services.d/jupyter/finish | 9 + jupyterlab/rootfs/etc/services.d/jupyter/run | 43 +++ 41 files changed, 1820 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/autolabeler.yml create mode 100644 .github/config.yml create mode 100644 .github/invite-contributors.yml create mode 100644 .github/lock.yml create mode 100644 .github/move.yml create mode 100644 .github/no-response.yml create mode 100644 .github/potential-duplicates.yml create mode 100644 .github/settings.yml create mode 100644 .github/stale.yml create mode 100644 .github/support.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .mdlrc create mode 100644 .yamllint create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 jupyterlab/.README.j2 create mode 100644 jupyterlab/Dockerfile create mode 100644 jupyterlab/build.json create mode 100644 jupyterlab/config.json create mode 100644 jupyterlab/dive.log create mode 100644 jupyterlab/icon.png create mode 100644 jupyterlab/logo.png create mode 100644 jupyterlab/requirements.txt create mode 100644 jupyterlab/rootfs/etc/cont-init.d/10-requirements.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/20-notebooks-dir.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/21-persistent-storage.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/50-password.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/51-github.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/80-system-packages.sh create mode 100644 jupyterlab/rootfs/etc/cont-init.d/81-python-packages.sh create mode 100644 jupyterlab/rootfs/etc/jupyter/jupyter_notebook_config.py create mode 100644 jupyterlab/rootfs/etc/services.d/jupyter/finish create mode 100644 jupyterlab/rootfs/etc/services.d/jupyter/run diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7a12570 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +ident_size = 4 + +[*.md] +ident_size = 2 +trim_trailing_whitespace = false + +[*.json] +ident_size = 2 + +[{.gitignore,.gitkeep,.editorconfig}] +ident_size = 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3fbad49 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# Require maintainer's :+1: for changes to the .github/ repo-config files +# mainly due to https://github.com/probot/settings privilege escalation +.github/* @frenck +.gitlab-ci.yml @frenck diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..544da8f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +# Problem/Motivation + +> (Why the issue was filed) + +## Expected behavior + +> (What you expected to happen) + +## Actual behavior + +> (What actually happened) + +## Steps to reproduce + +> (How can someone else make/see it happen) + +## Proposed changes + +> (If you have a proposed change, workaround or fix, +> describe the rationale behind it) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..cbd529a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +# Proposed Changes + +> (Describe the changes and rationale behind them) + +## Related Issues + +> ([Github link][autolink-references] to related issues or pull requests) + +[autolink-references]: https://help.github.com/articles/autolinked-references-and-urls/ \ No newline at end of file diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml new file mode 100644 index 0000000..3ce5703 --- /dev/null +++ b/.github/autolabeler.yml @@ -0,0 +1,2 @@ +--- +"Type: Documentation": ["*.md", "*.j2"] diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 0000000..71d2f6d --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,50 @@ +--- +# Configuration for request-info - https://github.com/behaviorbot/request-info + +# *OPTIONAL* Comment to reply with +# Can be either a string : +requestInfoReplyComment: + - "We would appreciate it if you could provide us with more info about this issue/pr!" + - "Hmmm... That issue/PR is kinda low on text. Could you please provide some more content?" + +# *OPTIONAL* default titles to check against for lack of descriptiveness +# MUST BE ALL LOWERCASE +requestInfoDefaultTitles: [] + +# *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given +requestInfoLabelToAdd: "Incomplete" + +# *OPTIONAL* Require Pull Requests to contain more information than what is provided in the PR template +# Will fail if the pull request's body is equal to the provided template +checkPullRequestTemplate: true + +# *OPTIONAL* Only warn about insufficient information on these events type +# Keys must be lowercase. Valid values are 'issue' and 'pullRequest' +requestInfoOn: + pullRequest: true + issue: true + +# *OPTIONAL* Add a list of people whose Issues/PRs will not be commented on +# keys must be GitHub usernames +requestInfoUserstoExclude: [] + +# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome + +# Comment to be posted to on first time issues +newIssueWelcomeComment: > + :wave: Thanks for opening your first issue here! + If you're reporting a :bug: bug, please make sure you include steps to reproduce it. + Also, logs, error messages and information about your hardware might be usefull. + +# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome + +# Comment to be posted to on PRs from first time contributors in your repository +newPRWelcomeComment: > + :sparkling_heart: Thanks for opening this pull request! :sparkling_heart: + If your PR gets accepted and merged in, we will invite you to the project :tada: + +# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge + +# Comment to be posted to on pull requests merged by a first time user +firstPRMergeComment: > + Congrats on merging your first pull request! :tada::tada::tada: diff --git a/.github/invite-contributors.yml b/.github/invite-contributors.yml new file mode 100644 index 0000000..25eb31a --- /dev/null +++ b/.github/invite-contributors.yml @@ -0,0 +1,10 @@ +--- +# If true, this will add new contributors as outside collaborators +# to the repo their PR was merged in. Team name is ignored if this +# flag is set to true. +isOutside: false + +# Specify team name to add new contributors to a specific team +# within your organization. +# Use team name or team-name-slug +team: Contributors diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 0000000..4b1e816 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,20 @@ +--- +# Configuration for lock-threads - https://github.com/dessant/lock-threads +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 30 + +# Comment to post before locking. Set to `false` to disable +lockComment: > + This thread has been automatically locked because it has not had recent + activity. Please open a new issue for related bugs and link to relevant + comments in this thread. + +# Issues or pull requests with these labels will not be locked +# exemptLabels: +# - no-locking + +# Limit to only `issues` or `pulls` +# only: issues + +# Add a label when locking. Set to `false` to disable +lockLabel: false diff --git a/.github/move.yml b/.github/move.yml new file mode 100644 index 0000000..0c2276a --- /dev/null +++ b/.github/move.yml @@ -0,0 +1,20 @@ +--- +# Delete the command comment when it contains no other content +deleteCommand: true + +# Close the source issue after moving +closeSourceIssue: true + +# Lock the source issue after moving +lockSourceIssue: true + +# Mention issue and comment authors +mentionAuthors: true + +# Preserve mentions in the issue content +keepContentMentions: false + +# Set custom aliases for targets +# aliases: +# r: repo +# or: owner/repo diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 0000000..bb9f0f2 --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,13 @@ +--- +# Configuration for probot-no-response - https://github.com/probot/no-response +# Number of days of inactivity before an Issue is closed for lack of response +daysUntilClose: 14 +# Label requiring a response +responseRequiredLabel: "Status: Awaiting response" +# Comment to post when closing an Issue for lack of response. Set to `false` to disable +closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. Please reach out if you have or find the answers we need so + that we can investigate further. diff --git a/.github/potential-duplicates.yml b/.github/potential-duplicates.yml new file mode 100644 index 0000000..00c7c0f --- /dev/null +++ b/.github/potential-duplicates.yml @@ -0,0 +1,14 @@ +--- +# Label name and color to set, when potential duplicates are detected +issueLabel: "Potential duplicate" +labelColor: e6e6e6 + +# If similarity is higher than this threshold, issue will be marked as duplicate +threshold: 0.70 + +# Comment to post when potential duplicates are detected +referenceComment: > + Potential duplicates found: + {{#issues}} + - [#{{ number }}] {{ title }} ({{ accuracy }}%) + {{/issues}} diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..91eb234 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,150 @@ +--- +repository: + description: "JupyterLab Lite - Community Hass.io Add-on for Home Assistant" + homepage: https://addons.community + topics: jupyter, jupyterlab, hassio-addons, hassio, hass, home-assistant, homeassistant + private: false + has_issues: true + has_projects: false + has_wiki: false + has_downloads: false + default_branch: master + allow_squash_merge: true + allow_merge_commit: false + allow_rebase_merge: true +labels: + # Priority labels + - name: "Priority: Critical" + color: ee0701 + description: "This should be dealt with ASAP. Not fixing this issue would be a serious error." + - name: "Priority: High" + color: b60205 + description: "After critical issues are fixed, these should be dealt with before any further issues." + - name: "Priority: Medium" + color: 0e8a16 + description: "This issue may be useful, and needs some attention." + - name: "Priority: Low" + color: e4ea8a + description: "Nice addition, maybe... someday..." + + # Type labels + - name: "Type: Bug" + color: ee0701 + description: "Inconsistencies or issues which will cause a problem for users or implementors." + - name: "Type: Documentation" + color: 0052cc + description: "Solely about the documentation of the project." + - name: "Type: Enhancement" + color: 1d76db + description: "Enhancement of the code, not introducing new features." + - name: "Type: Feature" + color: 0e8a16 + description: "New features or options." + - name: "Type: Support" + color: 5319e7 + description: "Marks an issue as an support ticket." + - name: "Type: Discussion" + color: d4c5f9 + description: "Marks an issue as an generic discussion ticket." + - name: "Type: Maintaince" + color: 2af79e + description: "Generic maintaince tasks, e.g., package updates." + + # Additional markers + - name: "Security" + color: ee0701 + description: "Marks an security issues that needs to be resolved asap." + - name: "Idea" + color: fef2c0 + description: "Marks an idea, which might be excepted and implemented." + - name: "Incomplete" + color: fef2c0 + description: "Marks an PR or issue that is missing information." + - name: "Pull request" + color: fbca04 + description: "There is an PR opened for this issue." + - name: "Accepted" + color: c2e0c6 + description: "This issue or PR has been accepted." + - name: "Declined" + color: f9d0c4 + description: "This issue or PR has been declined." + - name: "Potential duplicate" + color: e6e6e6 + description: "This issue has been automatically marked as a potential duplicate." + + # Ongoing Status labels + - name: "Status: Triage" + color: fbca04 + description: "This issue needs to be triaged." + - name: "Status: On hold" + color: cccccc + description: "Issue or PR that has been placed on hold for now." + - name: "Status: In progress" + color: fbca04 + description: "Issue is currently being resolved by a developer." + - name: "Status: Stale" + color: fef2c0 + description: "There has not been activity on this issue or PR for quite some time." + - name: "Status: Awaiting response" + color: fef2c0 + description: "Issue or PR awaits response from the creator." + - name: "Status: Blocked" + color: fef2c0 + description: "Progress on this issue is currently not possible." + + # Closing status labels + - name: "Closed: Known limitation" + color: e6e6e6 + description: "Issue is closed, it is a known limitation." + - name: "Closed: Expected behavior" + color: e6e6e6 + description: "Issues is closed, it is expected behavior." + - name: "Closed: Duplicate" + color: e6e6e6 + description: "Issue is closed, duplicate of an existing issue." + - name: "Closed: Invalid" + color: e6e6e6 + description: "Issue is closed, marked as not a valid issue (e.g., an user error)." + - name: "Closed: Wrong repository" + color: e6e6e6 + description: "Issue is closed, was created in the wrong repository." + - name: "Closed: Won't Fix" + color: e6e6e6 + description: "Issue is closed, it won't be fixed." + - name: "Closed: Done" + color: c2e0c6 + description: "Issue closed, work on this issue has been marked complete." + + # Others + - name: "Beginner Friendly" + color: 0e8a16 + description: "Good first issue for people wanting to contribute to the project." + - name: "Help wanted" + color: 0e8a16 + description: "We need some extra helping hands or expertise in order to resolve this." + - name: "Hacktoberfest" + description: "Issues/PRs are participating in the Hacktoberfest" + color: fbca04 + +branches: + - name: master + protection: + required_pull_request_reviews: + # required_approving_review_count: 1 + dismiss_stale_reviews: true + require_code_owner_reviews: true + dismissal_restrictions: + users: [] + teams: + - Admins + - Masters + required_status_checks: + strict: false + contexts: [] + enforce_admins: false + restrictions: + users: [] + teams: + - Admins + - Masters diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..36938bb --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,61 @@ +--- +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before a stale Issue or Pull Request is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - "Status: On hold" + - "Status: In progress" + - "Status: Awaiting response" + - "Status: Blocked" + - "Idea" + - "Security" + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Label to use when marking as stale +staleLabel: "Status: Stale" + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. +unmarkComment: false + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. +closeComment: false + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed diff --git a/.github/support.yml b/.github/support.yml new file mode 100644 index 0000000..4060944 --- /dev/null +++ b/.github/support.yml @@ -0,0 +1,22 @@ +--- +# Configuration for support-requests - https://github.com/dessant/support-requests + +# Label used to mark issues as support requests +supportLabel: "Type: Support" + +# Comment to post on issues marked as support requests. Add a link +# to a support page, or set to `false` to disable +supportComment: > + :wave: We use the issue tracker exclusively for bug reports and feature requests. + However, this issue appears to be a support request. Please use our + support channels to get help with the project. + + Head over to the + [Home Assistant community forum](https://community.home-assistant.io/?u=frenck) + or join our [Discord](https://discord.gg/c5DvZ4e) chat. + +# Close issues marked as support requests +close: true + +# Lock issues marked as support requests +lock: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..35f0cbb --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,365 @@ +--- +image: docker:latest + +variables: + ADDON_GITHUB_REPO: hassio-addons/addon-jupyterlab-lite + ADDON_SLUG: jupyterlablite + ADDON_TARGET: jupyterlab + DOCKER_DRIVER: overlay2 + DOCKER_HUB_ORG: hassioaddons + +stages: + - preflight + - build + - deploy + - manifest + - publish + +# Generic DIND template +.dind: &dind + before_script: + - docker info + services: + - name: docker:dind + command: ["--experimental"] + +# Generic preflight template +.preflight: &preflight + stage: preflight + tags: + - preflight + +# Generic build template +.build: &build + <<: *dind + stage: build + before_script: + - docker info + - | + if [ "$(apk --print-arch)" = "amd64" ]; then + docker run --rm --privileged hassioaddons/qemu-user-static:latest + fi + - | + echo "${CI_JOB_TOKEN}" | docker login \ + --username gitlab-ci-token \ + --password-stdin \ + registry.gitlab.com + - docker pull "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:cache" || true + script: + - | + docker build \ + --build-arg "BUILD_FROM=${FROM}" \ + --build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" \ + --build-arg "BUILD_ARCH=${ADDON_ARCH}" \ + --build-arg "BUILD_REF=${CI_COMMIT_SHA}" \ + --build-arg "BUILD_VERSION=${CI_COMMIT_TAG:-${CI_COMMIT_SHA:0:7}}" \ + --cache-from "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:cache" \ + --tag \ + "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:${CI_COMMIT_SHA}" \ + "${ADDON_TARGET}" + - | + docker push \ + "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:${CI_COMMIT_SHA}" + +# Generic deploy template +.deploy: &deploy + <<: *dind + stage: deploy + before_script: + - docker info + - docker pull "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:${CI_COMMIT_SHA}" + - | + echo "${CI_JOB_TOKEN}" | docker login \ + --username gitlab-ci-token \ + --password-stdin \ + registry.gitlab.com + - | + echo "${DOCKER_PASSWORD}" | docker login \ + --username "${DOCKER_LOGIN}" \ + --password-stdin + script: + - | + docker tag \ + "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:${CI_COMMIT_SHA}" \ + "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:cache" + - docker push "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:cache" + - TAG="${CI_COMMIT_TAG#v}" + - TAG="${TAG:-${CI_COMMIT_SHA:0:7}}" + - | + docker tag \ + "registry.gitlab.com/${CI_PROJECT_PATH}/${ADDON_ARCH}:${CI_COMMIT_SHA}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${ADDON_ARCH}-${TAG}" + - | + docker push \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${ADDON_ARCH}-${TAG}" + tags: + - deploy + only: + - master + - /^v\d+\.\d+\.\d+(?:-(?:beta|rc)(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?)?$/ + except: + - /^(?!master).+@/ + +# Generic manifest template +.manifest: &manifest + <<: *dind + stage: manifest + before_script: + - mkdir -p ~/.docker + - echo '{"experimental":"enabled"}' > ~/.docker/config.json + - docker info + - | + echo "${DOCKER_PASSWORD}" | docker login \ + --username "${DOCKER_LOGIN}" \ + --password-stdin + script: + - TAG="${TAG#v}" + - TAG="${TAG:-${CI_COMMIT_SHA:0:7}}" + - REF="${CI_COMMIT_TAG#v}" + - REF="${REF:-${CI_COMMIT_SHA:0:7}}" + - | + docker manifest create \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:aarch64-${REF}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:amd64-${REF}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:armhf-${REF}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:i386-${REF}" + - | + docker manifest annotate \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:aarch64-${REF}" \ + --os=linux \ + --arch=arm64 \ + --variant=v8 + - | + docker manifest annotate \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:amd64-${REF}" \ + --os=linux \ + --arch=amd64 + - | + docker manifest annotate \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:armhf-${REF}" \ + --os=linux \ + --arch=arm \ + --variant=v6 + - | + docker manifest annotate \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:i386-${REF}" \ + --os=linux \ + --arch=386 + - | + docker manifest push \ + "${DOCKER_HUB_ORG}/${ADDON_SLUG}:${TAG}" + tags: + - manifest + except: + - /^(?!master).+@/ + +# Generic publish template +.publish: &publish + stage: publish + image: + name: hassioaddons/repository-updater:latest + entrypoint: [""] + script: + - | + repository-updater \ + --token "${GITHUB_TOKEN}" \ + --repository "${REPOSITORY}" \ + --addon "${ADDON_GITHUB_REPO}" + tags: + - publish + except: + - /^(?!master).+@/ + +# Preflight jobs +hadolint: + <<: *preflight + image: hadolint/hadolint:latest-debian + before_script: + - hadolint --version + script: + - hadolint "${ADDON_TARGET}/Dockerfile" + +shellcheck: + <<: *preflight + image: + name: koalaman/shellcheck-alpine:stable + entrypoint: [""] + before_script: + - shellcheck --version + - apk --no-cache add grep + - | + find . -type f -print0 | \ + xargs -0 sed -i 's:#!/usr/bin/with-contenv bash:#!/bin/bash:g' + script: + - | + for file in $(grep -IRl "#\!\(/usr/bin/env \|/bin/\)" --exclude-dir ".git" "${ADDON_TARGET}"); do + if ! shellcheck $file; then + export FAILED=1 + else + echo "$file OK" + fi + done + if [ "${FAILED}" = "1" ]; then + exit 1 + fi + +yamllint: + <<: *preflight + image: sdesbure/yamllint + before_script: + - yamllint --version + script: + - yamllint . + +jsonlint: + <<: *preflight + image: sahsu/docker-jsonlint + before_script: + - jsonlint --version || true + script: + - | + for file in $(find . -type f -name "*.json"); do + if ! jsonlint -q $file; then + export FAILED=1 + else + echo "$file OK" + fi + done + if [ "${FAILED}" = "1" ]; then + exit 1 + fi +markdownlint: + <<: *preflight + image: + name: ruby:alpine + entrypoint: [""] + before_script: + - gem install mdl + - mdl --version + script: + - mdl --style all --warnings . + +# Build Jobs +build:armhf: + <<: *build + variables: + ADDON_ARCH: armhf + FROM: hassioaddons/ubuntu-base-armhf:2.2.0 + tags: + - build + - armhf + +build:aarch64: + <<: *build + variables: + ADDON_ARCH: aarch64 + FROM: hassioaddons/ubuntu-base-aarch64:2.2.0 + tags: + - build + - aarch64 + +build:i386: + <<: *build + variables: + ADDON_ARCH: i386 + FROM: hassioaddons/ubuntu-base-i386:2.2.0 + tags: + - build + - i386 + +build:amd64: + <<: *build + variables: + ADDON_ARCH: amd64 + FROM: hassioaddons/ubuntu-base-amd64:2.2.0 + tags: + - build + - amd64 + +# Deploy jobs +deploy:armhf: + <<: *deploy + variables: + ADDON_ARCH: armhf + +deploy:aarch64: + <<: *deploy + variables: + ADDON_ARCH: aarch64 + +deploy:i386: + <<: *deploy + variables: + ADDON_ARCH: i386 + +deploy:amd64: + <<: *deploy + variables: + ADDON_ARCH: amd64 + +# Manifest jobs +manifest:sha: + <<: *manifest + only: + - master + +manifest:version: + <<: *manifest + variables: + TAG: "${CI_COMMIT_TAG}" + only: + - /^v\d+\.\d+\.\d+(?:-(?:beta|rc)(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?)?$/ + +manifest:stable: + <<: *manifest + variables: + TAG: latest + only: + - /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/ + +manifest:beta: + <<: *manifest + variables: + TAG: beta + only: + - /^v\d+\.\d+\.\d+(?:-(?:beta|rc)(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?)?$/ + +manifest:edge: + <<: *manifest + variables: + TAG: edge + only: + - master + +# Publish jobs +publish:stable: + <<: *publish + variables: + REPOSITORY: hassio-addons/repository + only: + - /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/ + environment: + name: stable + +publish:beta: + <<: *publish + variables: + REPOSITORY: hassio-addons/repository-beta + only: + - /^v\d+\.\d+\.\d+(?:-(?:beta|rc)(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?)?$/ + environment: + name: beta + +publish:edge: + <<: *publish + variables: + REPOSITORY: hassio-addons/repository-edge + only: + - master + environment: + name: edge diff --git a/.mdlrc b/.mdlrc new file mode 100644 index 0000000..2b0128d --- /dev/null +++ b/.mdlrc @@ -0,0 +1 @@ +rules "~MD024" \ No newline at end of file diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..f4bc5a4 --- /dev/null +++ b/.yamllint @@ -0,0 +1,66 @@ +--- +rules: + braces: + level: error + min-spaces-inside: 0 + max-spaces-inside: 1 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + brackets: + level: error + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + colons: + level: error + max-spaces-before: 0 + max-spaces-after: 1 + commas: + level: error + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: + level: error + require-starting-space: true + min-spaces-from-content: 2 + comments-indentation: + level: error + document-end: + level: error + present: false + document-start: + level: error + present: true + empty-lines: + level: error + max: 1 + max-start: 0 + max-end: 1 + hyphens: + level: error + max-spaces-after: 1 + indentation: + level: error + spaces: 2 + indent-sequences: true + check-multi-line-strings: false + key-duplicates: + level: error + line-length: + ignore: | + .github/support.yml + level: warning + max: 120 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + new-line-at-end-of-file: + level: error + new-lines: + level: error + type: unix + trailing-spaces: + level: error + truthy: + level: error diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a7ab528 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Community Hass.io Add-ons: JupyterLab Lite + +All notable changes to this add-on will be documented in this file. + +The format is based on [Keep a Changelog][keep-a-changelog] +and this project adheres to [Semantic Versioning][semantic-versioning]. + +## Unreleased + +No are unreleased changes yet. + +[keep-a-changelog]: http://keepachangelog.com/en/1.0.0/ +[semantic-versioning]: http://semver.org/spec/v2.0.0.html diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..0ac232b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Code of conduct + +## Our pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention + or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or + electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate + in a professional setting + +## Our responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project lead at frenck@addons.community. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project lead is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..98f1dd2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish +to make via issue, email, or any other method with the owners of this repository +before making a change. + +Please note we have a code of conduct, please follow it in all your interactions +with the project. + +## Issues and feature requests + +You've found a bug in the source code, a mistake in the documentation or maybe +you'd like a new feature? You can help us by submitting an issue to our +[GitHub Repository][github]. Before you create an issue, make sure you search +the archive, maybe your question was already answered. + +Even better: You could submit a pull request with a fix / new feature! + +## Pull request process + +1. Search our repository for open or closed [pull requests][prs] that relates + to your submission. You don't want to duplicate effort. + +1. You may merge the pull request in once you have the sign-off of two other + developers, or if you do not have permission to do that, you may request + the second reviewer to merge it for you. + +[github]: https://github.com/hassio-addons/addon-jupyterlab-lite/issues +[prs]: https://github.com/hassio-addons/addon-jupyterlab-lite/pulls diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3ea8973 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2018 Franck Nijhof + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4193f1 --- /dev/null +++ b/README.md @@ -0,0 +1,324 @@ +# Community Hass.io Add-ons: JupyterLab Lite + +[![GitHub Release][releases-shield]][releases] +![Project Stage][project-stage-shield] +[![License][license-shield]](LICENSE.md) + +[![GitLab CI][gitlabci-shield]][gitlabci] +![Project Maintenance][maintenance-shield] +[![GitHub Activity][commits-shield]][commits] + +[![Bountysource][bountysource-shield]][bountysource] +[![Discord][discord-shield]][discord] +[![Community Forum][forum-shield]][forum] + +[![Buy me a coffee][buymeacoffee-shield]][buymeacoffee] + +Create documents containing live code, equations, visualizations, +and explanatory text. + +## About + +JupyterLab is an open-source web application that allows you to create and share +documents that contain live code, equations, visualizations and narrative text. +Uses include: data cleaning and transformation, numerical simulation, +statistical modeling, data visualization, machine learning, and much more. + +This add-on runs JupyterLab, which is the next-generation user interface for +Project Jupyter. It is an extensible environment for interactive and +reproducible computing, based on the Jupyter Notebook and Architecture. + +## Installation + +The installation of this add-on is pretty straightforward and not different in +comparison to installing any other Hass.io add-on. + +1. [Add our Hass.io add-ons repository][repository] to your Hass.io instance. +1. Install the "JupyterLab Lite" add-on. +1. Start the "JupyterLab Lite" add-on +1. Check the logs of the "JupyterLab Lite" add-on to see if everything went well. + +**NOTE**: Do not add this repository to Hass.io, please use: +`https://github.com/hassio-addons/repository`. + +## Docker status + +[![Docker Architecture][armhf-arch-shield]][armhf-dockerhub] +[![Docker Version][armhf-version-shield]][armhf-microbadger] +[![Docker Layers][armhf-layers-shield]][armhf-microbadger] +[![Docker Pulls][armhf-pulls-shield]][armhf-dockerhub] + +[![Docker Architecture][aarch64-arch-shield]][aarch64-dockerhub] +[![Docker Version][aarch64-version-shield]][aarch64-microbadger] +[![Docker Layers][aarch64-layers-shield]][aarch64-microbadger] +[![Docker Pulls][aarch64-pulls-shield]][aarch64-dockerhub] + +[![Docker Architecture][amd64-arch-shield]][amd64-dockerhub] +[![Docker Version][amd64-version-shield]][amd64-microbadger] +[![Docker Layers][amd64-layers-shield]][amd64-microbadger] +[![Docker Pulls][amd64-pulls-shield]][amd64-dockerhub] + +[![Docker Architecture][i386-arch-shield]][i386-dockerhub] +[![Docker Version][i386-version-shield]][i386-microbadger] +[![Docker Layers][i386-layers-shield]][i386-microbadger] +[![Docker Pulls][i386-pulls-shield]][i386-dockerhub] + +## Configuration + +**Note**: _Remember to restart the add-on when the configuration is changed._ + +Example add-on configuration: + +```json +{ + "log_level": "info", + "password": "omgpuppies", + "github_access_token": "abcdef1234567890abcdef0123456789abcdef01", + "ssl": true, + "certfile": "fullchain.pem", + "keyfile": "privkey.pem", + "system_packages": [ + "ffmpeg" + ], + "python_packages": [ + "nltk" + ] +``` + +**Note**: _This is just an example, don't copy and past it! Create your own!_ + +### Option: `log_level` + +The `log_level` option controls the level of log output by the addon and can +be changed to be more or less verbose, which might be useful when you are +dealing with an unknown issue. Possible values are: + +- `trace`: Show every detail, like all called internal functions. +- `debug`: Shows detailed debug information. +- `info`: Normal (usually) interesting events. +- `warning`: Exceptional occurrences that are not errors. +- `error`: Runtime errors that do not require immediate action. +- `fatal`: Something went terribly wrong. Add-on becomes unusable. + +Please note that each level automatically includes log messages from a +more severe level, e.g., `debug` also shows `info` messages. By default, +the `log_level` is set to `info`, which is the recommended setting unless +you are troubleshooting. + +### Option: `password` + +Sets the password to authenticate with JupyterLab. Leaving the password +empty, will disable password authentication. + +### Option: `github_access_token` + +Sets an GitHub access token. When making unauthenticated requests to GitHub +(as we must do to get repository data), GitHub imposes fairly strict rate-limits +on how many requests we can make. As such, you are likely to hit that limit +within a few minutes of work. + +There is a chapter in this document with instruction on obtaining such a token. + +### Option: `ssl` + +Enables/Disables SSL (HTTPS) on the web terminal. Set it `true` to enable it, +`false` otherwise. + +### Option: `certfile` + +The certificate file to use for SSL. + +**Note**: _The file MUST be stored in `/ssl/`, which is default for Hass.io_ + +### Option: `keyfile` + +The private key file to use for SSL. + +**Note**: _The file MUST be stored in `/ssl/`, which is default for Hass.io_ + +### Option: `system_packages` + +Allows you to specify additional [Alpine packages][alpine-packages] to be +installed to your Jupyter setup (e.g., `g++`. `make`, `ffmpeg`). + +**Note**: _Adding many packages will result in a longer start-up time +for the add-on._ + +### Option: `python_packages` + +Allows you to specify additional [Python packages][python-packages] to be +installed to your Jupyter setup (e.g., `PyMySQL`. `Requests`, `Pillow`). + +**Note**: _Adding many packages will result in a longer start-up time +for the add-on._ + +## Embedding into Home Assistant + +It is possible to embed Jupyter directly into Home Assistant, allowing +you to access the JupyterLab through the Home Assistant frontend. + +Home Assistant provides the `panel_iframe` component, for these purposes. + +Example configuration: + +```yaml +panel_iframe: + jupyter: + title: JupyterLab + icon: mdi:flask + url: https://addres.to.your.hass.io:8888 +``` + +## Getting a GitHub access token + +You can get an access token by following these steps: + +1. [Verify][github-verify] your email address with GitHub. +1. Go to your account settings on GitHub and select "Developer Settings" + from the left panel. +1. On the left, select "Personal access tokens" +1. Click the "Generate new token" button, and enter your password. +1. Give the token a description, and check the "**repo**" scope box. +1. Click "Generate token" +1. You should be given a string which will be your access token. + +Remember that this token is effectively a password for your GitHub account. +*Do not* share it online or check the token into version control, +as people can use it to access all of your data on GitHub. + +## Changelog & Releases + +This repository keeps a change log using [GitHub's releases][releases] +functionality. The format of the log is based on +[Keep a Changelog][keepchangelog]. + +Releases are based on [Semantic Versioning][semver], and use the format +of ``MAJOR.MINOR.PATCH``. In a nutshell, the version will be incremented +based on the following: + +- ``MAJOR``: Incompatible or major changes. +- ``MINOR``: Backwards-compatible new features and enhancements. +- ``PATCH``: Backwards-compatible bugfixes and package updates. + +## Support + +Got questions? + +You have several options to get them answered: + +- The Home Assistant [Community Forum][forum], we have a + [dedicated topic][forum] on that forum regarding this add-on. +- The Home Assistant [Discord Chat Server][discord] for general Home Assistant + discussions and questions. +- Join the [Reddit subreddit][reddit] in [/r/homeassistant][reddit] + +You could also [open an issue here][issue] GitHub. + +## Contributing + +This is an active open-source project. We are always open to people who want to +use the code or contribute to it. + +We have set up a separate document containing our +[contribution guidelines](CONTRIBUTING.md). + +Thank you for being involved! :heart_eyes: + +## Authors & contributors + +The original setup of this repository is by [Franck Nijhof][frenck]. + +For a full list of all authors and contributors, +check [the contributor's page][contributors]. + +## We have got some Hass.io add-ons for you + +Want some more functionality to your Hass.io Home Assistant instance? + +We have created multiple add-ons for Hass.io. For a full list, check out +our [GitHub Repository][repository]. + +## License + +MIT License + +Copyright (c) 2018 Franck Nijhof + +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. + +[aarch64-anchore-shield]: https://img.shields.io/badge/lorem-ipsum-red.svg +[aarch64-anchore]: http://example.com +[aarch64-arch-shield]: https://img.shields.io/badge/architecture-aarch64-blue.svg +[aarch64-dockerhub]: https://hub.docker.com/r/hassioaddons/jupyterlablite-aarch64 +[aarch64-layers-shield]: https://images.microbadger.com/badges/image/hassioaddons/jupyterlablite-aarch64.svg +[aarch64-microbadger]: https://microbadger.com/images/hassioaddons/jupyterlablite-aarch64 +[aarch64-pulls-shield]: https://img.shields.io/docker/pulls/hassioaddons/jupyterlablite-aarch64.svg +[aarch64-version-shield]: https://images.microbadger.com/badges/version/hassioaddons/jupyterlablite-aarch64.svg +[alpine-packages]: https://pkgs.alpinelinux.org/packages +[amd64-anchore-shield]: https://img.shields.io/badge/lorem-ipsum-red.svg +[amd64-anchore]: https://anchore.io/image/dockerhub/hassioaddons%jupyterlablite-amd64%3Alatest +[amd64-arch-shield]: https://img.shields.io/badge/architecture-amd64-blue.svg +[amd64-dockerhub]: https://hub.docker.com/r/hassioaddons/jupyterlablite-amd64 +[amd64-layers-shield]: https://images.microbadger.com/badges/image/hassioaddons/jupyterlablite-amd64.svg +[amd64-microbadger]: https://microbadger.com/images/hassioaddons/jupyterlablite-amd64 +[amd64-pulls-shield]: https://img.shields.io/docker/pulls/hassioaddons/jupyterlablite-amd64.svg +[amd64-version-shield]: https://images.microbadger.com/badges/version/hassioaddons/jupyterlablite-amd64.svg +[armhf-anchore-shield]: https://img.shields.io/badge/lorem-ipsum-red.svg +[armhf-anchore]: https://anchore.io/image/dockerhub/hassioaddons%jupyterlablite-armhf%3Alatest +[armhf-arch-shield]: https://img.shields.io/badge/architecture-armhf-blue.svg +[armhf-dockerhub]: https://hub.docker.com/r/hassioaddons/jupyterlablite-armhf +[armhf-layers-shield]: https://images.microbadger.com/badges/image/hassioaddons/jupyterlablite-armhf.svg +[armhf-microbadger]: https://microbadger.com/images/hassioaddons/jupyterlablite-armhf +[armhf-pulls-shield]: https://img.shields.io/docker/pulls/hassioaddons/jupyterlablite-armhf.svg +[armhf-version-shield]: https://images.microbadger.com/badges/version/hassioaddons/jupyterlablite-armhf.svg +[bountysource-shield]: https://img.shields.io/bountysource/team/hassio-addons/activity.svg +[bountysource]: https://www.bountysource.com/teams/hassio-addons/issues +[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg +[buymeacoffee]: https://www.buymeacoffee.com/frenck +[commits-shield]: https://img.shields.io/github/commit-activity/y/hassio-addons/addon-jupyterlab-lite.svg +[commits]: https://github.com/hassio-addons/addon-jupyterlab-lite/commits/master +[contributors]: https://github.com/hassio-addons/addon-jupyterlab-lite/graphs/contributors +[discord-shield]: https://img.shields.io/discord/330944238910963714.svg +[discord]: https://discord.gg/c5DvZ4e +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg +[forum]: https://community.home-assistant.io/?u=frenck +[frenck]: https://github.com/frenck +[github-verify]: https://help.github.com/articles/verifying-your-email-address +[gitlabci-shield]: https://gitlab.com/hassio-addons/addon-jupyterlab-lite/badges/master/pipeline.svg +[gitlabci]: https://gitlab.com/hassio-addons/addon-jupyterlab-lite/pipelines +[home-assistant]: https://home-assistant.io +[i386-anchore-shield]: https://img.shields.io/badge/lorem-ipsum-red.svg +[i386-anchore]: https://anchore.io/image/dockerhub/hassioaddons%2Fjupyter-i386%3Alatest +[i386-arch-shield]: https://img.shields.io/badge/architecture-i386-blue.svg +[i386-dockerhub]: https://hub.docker.com/r/hassioaddons/jupyterlablite-i386 +[i386-layers-shield]: https://images.microbadger.com/badges/image/hassioaddons/jupyterlablite-i386.svg +[i386-microbadger]: https://microbadger.com/images/hassioaddons/jupyterlablite-i386 +[i386-pulls-shield]: https://img.shields.io/docker/pulls/hassioaddons/jupyterlablite-i386.svg +[i386-version-shield]: https://images.microbadger.com/badges/version/hassioaddons/jupyterlablite-i386.svg +[issue]: https://github.com/hassio-addons/addon-jupyterlab-lite/issues +[keepchangelog]: http://keepachangelog.com/en/1.0.0/ +[license-shield]: https://img.shields.io/github/license/hassio-addons/addon-jupyterlab-lite.svg +[maintenance-shield]: https://img.shields.io/maintenance/yes/2018.svg +[project-stage-shield]: https://img.shields.io/badge/project%20stage-concept-red.svg +[python-packages]: https://pypi.org/ +[reddit]: https://reddit.com/r/homeassistant +[releases-shield]: https://img.shields.io/github/release/hassio-addons/addon-jupyterlab-lite.svg +[releases]: https://github.com/hassio-addons/addon-jupyterlab-lite/releases +[repository]: https://github.com/hassio-addons/repository +[semver]: http://semver.org/spec/v2.0.0.htm diff --git a/jupyterlab/.README.j2 b/jupyterlab/.README.j2 new file mode 100644 index 0000000..27c19c0 --- /dev/null +++ b/jupyterlab/.README.j2 @@ -0,0 +1,74 @@ +# Community Hass.io Add-ons: JupyterLab Lite + +[![Release][release-shield]][release] ![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield] + +[![Discord][discord-shield]][discord] [![Community Forum][forum-shield]][forum] + +[![Buy me a coffee][buymeacoffee-shield]][buymeacoffee] + +Create documents containing live code, equations, visualizations, +and explanatory text. + +## About + +JupyterLab is an open-source web application that allows you to create and share +documents that contain live code, equations, visualizations and narrative text. +Uses include: data cleaning and transformation, numerical simulation, +statistical modeling, data visualization, machine learning, and much more. + +This add-on runs JupyterLab, which is the next-generation user interface for +Project Jupyter. It is an extensible environment for interactive and +reproducible computing, based on the Jupyter Notebook and Architecture. + +[Click here for the full documentation][docs] + +{% if channel == "edge" %} +## WARNING! THIS IS AN EDGE VERSION! + +This Hass.io Add-ons repository contains edge builds of add-ons. Edge builds +add-ons are based upon the latest development version. + +- They may not work at all. +- They might stop working at any time. +- They could have a negative impact on your system. + +This repository was created for: + +- Anybody willing to test. +- Anybody interested in trying out upcoming add-ons or add-on features. +- Developers. + +If you are more interested in stable releases of our add-ons: + + + +{% endif %} +{% if channel == "beta" %} +## WARNING! THIS IS A BETA VERSION! + +This Hass.io Add-ons repository contains beta releases of add-ons. + +- They might stop working at any time. +- They could have a negative impact on your system. + +This repository was created for: + +- Anybody willing to test. +- Anybody interested in trying out upcoming add-ons or add-on features. + +If you are more interested in stable releases of our add-ons: + + + +{% endif %} +[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg +[buymeacoffee]: https://www.buymeacoffee.com/frenck +[discord-shield]: https://img.shields.io/discord/330944238910963714.svg +[discord]: https://discord.gg/c5DvZ4e +[docs]: {{ repo }}/blob/{{ version }}/README.md +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg +[forum]: https://community.home-assistant.io/?u=frenck +[maintenance-shield]: https://img.shields.io/maintenance/yes/2018.svg +[project-stage-shield]: https://img.shields.io/badge/project%20stage-concept-red.svg +[release-shield]: https://img.shields.io/badge/version-{{ version }}-blue.svg +[release]: {{ repo }}/tree/{{ version }} diff --git a/jupyterlab/Dockerfile b/jupyterlab/Dockerfile new file mode 100644 index 0000000..2ae7a68 --- /dev/null +++ b/jupyterlab/Dockerfile @@ -0,0 +1,125 @@ +ARG BUILD_FROM=hassioaddons/ubuntu-base:2.2.0 +# hadolint ignore=DL3006 +FROM ${BUILD_FROM} + +# Set shell +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Copy Python requirements file +COPY requirements.txt /opt/ + +# Setup base +# hadolint ignore=DL3018 +ARG BUILD_ARCH +RUN \ + MAKEFLAGS="-j$(nproc)" \ + && export MAKEFLAGS \ + \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential=12.4ubuntu1 \ + dirmngr=2.2.4-1ubuntu1.1 \ + git=1:2.17.1-1ubuntu0.4 \ + gpg-agent=2.2.4-1ubuntu1.1 \ + gpg=2.2.4-1ubuntu1.1 \ + libffi-dev=3.2.1-8 \ + libffi6=3.2.1-8 \ + libfreetype6-dev=2.8.1-2ubuntu2 \ + libfreetype6=2.8.1-2ubuntu2 \ + libjpeg-turbo8-dev=1.5.2-0ubuntu5.18.04.1 \ + libpng-dev=1.6.34-1ubuntu0.18.04.1 \ + libpng16-16=1.6.34-1ubuntu0.18.04.1 \ + libssl-dev=1.1.0g-2ubuntu4.1 \ + libtiff5-dev=4.0.9-5 \ + libxml2-dev=2.9.4+dfsg1-6.1ubuntu1.2 \ + libxml2=2.9.4+dfsg1-6.1ubuntu1.2 \ + libxslt1-dev=1.1.29-5 \ + libxslt1.1=1.1.29-5 \ + libzmq3-dev=4.2.5-1 \ + libzmq5=4.2.5-1 \ + pandoc=1.19.2.4~dfsg-1build4 \ + pkg-config=0.29.1-0ubuntu2 \ + python-dev=2.7.15~rc1-1 \ + python3-dev=3.6.7-1~18.04 \ + python3-distutils=3.6.7-1~18.04 \ + python3=3.6.7-1~18.04 \ + zlib1g-dev=1:1.2.11.dfsg-0ubuntu2 \ + \ + && curl -sL https://deb.nodesource.com/setup_8.x | bash - \ + \ + && apt-get install -y --no-install-recommends \ + nodejs=8.14.0-1nodesource1 \ + \ + && curl https://bootstrap.pypa.io/get-pip.py | python3 \ + \ + && pip3 install --no-cache-dir -r /opt/requirements.txt \ + \ + && jupyter labextension install \ + @jupyter-widgets/jupyterlab-manager@0.38 --no-build \ + && jupyter labextension install jupyterlab_bokeh --no-build \ + && jupyter labextension install @jupyterlab/github --no-build \ + && jupyter labextension install @jupyterlab/git --no-build \ + && jupyter labextension install plotlywidget@0.5.2 --no-build \ + && jupyter labextension install \ + @jupyterlab/plotly-extension@0.18.1 --no-build \ + && jupyter labextension install jupyterlab-chart-editor@1.0 --no-build \ + && jupyter lab build \ + \ + && apt-get purge -y --auto-remove \ + build-essential \ + dirmngr \ + gpg \ + gpg-agent \ + libffi-dev \ + libfreetype6-dev \ + libjpeg-turbo8-dev \ + libpng-dev \ + libssl-dev \ + libtiff5-dev \ + libxml2-dev \ + libxslt1-dev \ + libzmq3-dev \ + pkg-config \ + python-dev \ + python3-dev \ + zlib1g-dev \ + \ + && find /usr/local/lib/python3.6/ -type d -name tests -depth -exec rm -rf {} \; \ + && find /usr/local/lib/python3.6/ -type d -name test -depth -exec rm -rf {} \; \ + && find /usr/local/lib/python3.6/ -name __pycache__ -depth -exec rm -rf {} \; \ + && find /usr/local/lib/python3.6/ -name "*.pyc" -depth -exec rm -f {} \; \ + \ + && npm cache clean --force \ + \ + && rm -fr \ + /tmp/* \ + /root/{.cache,.config,.gnupg,.local,.log,.npm} \ + /usr/local/share/.cache \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +# Copy root filesystem +COPY rootfs / + +# Build arugments +ARG BUILD_DATE +ARG BUILD_REF +ARG BUILD_VERSION + +# Labels +LABEL \ + io.hass.name="JupyterLab Lite" \ + io.hass.description="Create and share documents that contain live code, equations, visualizations, and explanatory text directly in your browser" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.type="addon" \ + io.hass.version=${BUILD_VERSION} \ + maintainer="Franck Nijhof " \ + org.label-schema.description="Create and share documents that contain live code, equations, visualizations, and explanatory text directly in your browser" \ + org.label-schema.build-date=${BUILD_DATE} \ + org.label-schema.name="JupyterLab Lite" \ + org.label-schema.schema-version="1.0" \ + org.label-schema.url="https://addons.community" \ + org.label-schema.usage="https://github.com/hassio-addons/addon-jupyterlab-lite/tree/master/README.md" \ + org.label-schema.vcs-ref=${BUILD_REF} \ + org.label-schema.vcs-url="https://github.com/hassio-addons/addon-jupyterlab-lite" \ + org.label-schema.vendor="Community Hass.io Add-ons" diff --git a/jupyterlab/build.json b/jupyterlab/build.json new file mode 100644 index 0000000..ebe53ce --- /dev/null +++ b/jupyterlab/build.json @@ -0,0 +1,9 @@ +{ + "build_from": { + "aarch64": "hassioaddons/ubuntu-base-aarch64:2.2.0", + "amd64": "hassioaddons/ubuntu-base-amd64:2.2.0", + "armhf": "hassioaddons/ubuntu-base-armhf:2.2.0", + "i386": "hassioaddons/ubuntu-base-i386:2.2.0" + }, + "args": {} +} diff --git a/jupyterlab/config.json b/jupyterlab/config.json new file mode 100644 index 0000000..444fd27 --- /dev/null +++ b/jupyterlab/config.json @@ -0,0 +1,65 @@ +{ + "name": "JupyterLab Lite", + "version": "dev", + "slug": "jupyterlablite", + "description": "Create documents containing live code, equations, visualizations, and explanatory text.", + "url": "https://github.com/hassio-addons/addon-jupyterlab-lite", + "webui": "[PROTO:ssl]://[HOST]:[PORT:8888]", + "startup": "application", + "arch": [ + "aarch64", + "amd64", + "armhf", + "i386" + ], + "machine": [ + "intel-nuc", + "qemux86", + "qemux86-64", + "qemuarm", + "qemuarm-64", + "raspberrypi2", + "raspberrypi3", + "raspberrypi3-64", + "tinker", + "odroid-c2", + "odroid-xu" + ], + "boot": "auto", + "hassio_api": true, + "auth_api": false, + "hassio_role": "default", + "homeassistant_api": true, + "host_network": false, + "ports": { + "8888/tcp": 8888 + }, + "map": [ + "config:rw", + "share:rw", + "ssl" + ], + "options": { + "log_level": "info", + "password": "", + "github_access_token": "", + "ssl": false, + "certfile": "fullchain.pem", + "keyfile": "privkey.pem", + "system_packages": [], + "python_packages": [] + }, + "schema": { + "log_level": "match(^(trace|debug|info|notice|warning|error|fatal)$)", + "password": "str", + "github_access_token": "str", + "ssl": "bool", + "certfile": "str", + "keyfile": "str", + "system_packages": ["str"], + "python_packages": ["str"] + }, + "environment": { + "LOG_FORMAT": "{LEVEL}: {MESSAGE}" + } +} diff --git a/jupyterlab/dive.log b/jupyterlab/dive.log new file mode 100644 index 0000000..e69de29 diff --git a/jupyterlab/icon.png b/jupyterlab/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..20e4baec6c4865be8f6c358fce599e8e00bd6316 GIT binary patch literal 4678 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 literal 0 HcmV?d00001 diff --git a/jupyterlab/requirements.txt b/jupyterlab/requirements.txt new file mode 100644 index 0000000..d6e622e --- /dev/null +++ b/jupyterlab/requirements.txt @@ -0,0 +1,19 @@ +beautifulsoup4==4.6.3 +bokeh==1.0.2 +geopy==1.17.0 +HASS-data-detective==0.5 +influxdb==5.2.0 +ipywidgets==7.4.2 +jupyterlab_github==0.7.0 +jupyterlab-git==0.5.0 +jupyterlab==0.35.4 +matplotlib==3.0.2 +nbconvert==5.4.0 +numpy==1.15.4 +pandas-datareader==0.7.0 +pandas==0.23.4 +plotly==3.4.2 +PyMySQL==0.9.2 +python-dateutil==2.7.5 +scrapy==1.5.1 +SQLAlchemy==1.2.14 diff --git a/jupyterlab/rootfs/etc/cont-init.d/10-requirements.sh b/jupyterlab/rootfs/etc/cont-init.d/10-requirements.sh new file mode 100644 index 0000000..e3eb75f --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/10-requirements.sh @@ -0,0 +1,26 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# This files check if all user configuration requirements are met +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +# Check SSL requirements, if enabled +if hass.config.true 'ssl'; then + if ! hass.config.has_value 'certfile'; then + hass.die 'SSL is enabled, but no certfile was specified' + fi + + if ! hass.config.has_value 'keyfile'; then + hass.die 'SSL is enabled, but no keyfile was specified' + fi + + if ! hass.file_exists "/ssl/$(hass.config.get 'certfile')"; then + hass.die 'The configured certfile is not found' + fi + + if ! hass.file_exists "/ssl/$(hass.config.get 'keyfile')"; then + hass.die 'The configured keyfile is not found' + fi +fi diff --git a/jupyterlab/rootfs/etc/cont-init.d/20-notebooks-dir.sh b/jupyterlab/rootfs/etc/cont-init.d/20-notebooks-dir.sh new file mode 100644 index 0000000..0cfc81b --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/20-notebooks-dir.sh @@ -0,0 +1,27 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Ensures the JupyterLab notebooks directory exists +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +if ! hass.directory_exists '/config/notebooks'; then + mkdir -p /config/notebooks \ + || hass.die 'Failed creating notebooks directory' + + git clone -b master --single-branch \ + https://github.com/home-assistant/home-assistant-notebooks.git \ + /config/notebooks/home-assistant \ + || hass.die 'Failed installing Home Assistant example notebooks' + + git clone -b master --single-branch --depth 1 \ + https://github.com/bokeh/bokeh-notebooks.git \ + /config/notebooks/bokeh-examples \ + || hass.die 'Failed installing Bokeh example notebooks' + + git clone -b master --single-branch --depth 1 \ + https://bitbucket.org/hrojas/learn-pandas.git \ + /config/notebooks/learn-pandas \ + || hass.die 'Failed installing learn pandas notebooks' +fi diff --git a/jupyterlab/rootfs/etc/cont-init.d/21-persistent-storage.sh b/jupyterlab/rootfs/etc/cont-init.d/21-persistent-storage.sh new file mode 100644 index 0000000..a0a3927 --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/21-persistent-storage.sh @@ -0,0 +1,23 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Ensure directories in the persistent storage exists +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +if ! hass.directory_exists '/data/user-settings'; then + mkdir -p /data/user-settings \ + || hass.die 'Failed creating persistent user-settings directory' +fi + +if ! hass.directory_exists '/data/workspaces'; then + mkdir -p /data/workspaces \ + || hass.die 'Failed creating persistent workspaces directory' +fi + +if ! hass.directory_exists '/data/local'; then + mkdir -p /data/local \ + || hass.die 'Failed creating persistent local directory' +fi +ln -s /data/local /root/.local diff --git a/jupyterlab/rootfs/etc/cont-init.d/50-password.sh b/jupyterlab/rootfs/etc/cont-init.d/50-password.sh new file mode 100644 index 0000000..0eec784 --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/50-password.sh @@ -0,0 +1,17 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Sets the user set password into JupyterLab +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +readonly CONFIG_PATH="/etc/jupyter/jupyter_notebook_config.py" + +# Set password +if hass.config.has_value 'password'; then + password=$(hass.config.get 'password') + password=$(python3 -c "from notebook.auth.security import passwd;print(passwd('${password}'))") + sed -i "s/c.NotebookApp.password\\ .*/c.NotebookApp.password\\ =\\ '${password}'/" "${CONFIG_PATH}" \ + || hass.die 'Failed setting up password' +fi diff --git a/jupyterlab/rootfs/etc/cont-init.d/51-github.sh b/jupyterlab/rootfs/etc/cont-init.d/51-github.sh new file mode 100644 index 0000000..5ecfe4c --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/51-github.sh @@ -0,0 +1,16 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Install the users GitHub Access token into JupyterLab +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +readonly CONFIG_PATH="/etc/jupyter/jupyter_notebook_config.py" + +# Set password +if hass.config.has_value 'github_access_token'; then + token=$(hass.config.get 'github_access_token') + sed -i "s/c.GitHubConfig.access_token\\ .*/c.GitHubConfig.access_token\\ =\\ '${token}'/" "${CONFIG_PATH}" \ + || hass.die 'Failed setting up GitHub access token' +fi diff --git a/jupyterlab/rootfs/etc/cont-init.d/80-system-packages.sh b/jupyterlab/rootfs/etc/cont-init.d/80-system-packages.sh new file mode 100644 index 0000000..e4626b4 --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/80-system-packages.sh @@ -0,0 +1,17 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Install user configured/requested packages +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +if hass.config.has_value 'system_packages'; then + apt-get update \ + || hass.die 'Failed updating Ubuntu packages repository indexes' + + for package in $(hass.config.get 'system_packages'); do + apt-get install -y --no-install-recommends "$package" \ + || hass.die "Failed installing package ${package}" + done +fi diff --git a/jupyterlab/rootfs/etc/cont-init.d/81-python-packages.sh b/jupyterlab/rootfs/etc/cont-init.d/81-python-packages.sh new file mode 100644 index 0000000..c7a696e --- /dev/null +++ b/jupyterlab/rootfs/etc/cont-init.d/81-python-packages.sh @@ -0,0 +1,14 @@ +#!/usr/bin/with-contenv bash +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Install user configured/requested Python packages +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +if hass.config.has_value 'python_packages'; then + for package in $(hass.config.get 'python_packages'); do + pip3 install "$package" \ + || hass.die "Failed installing package ${package}" + done +fi diff --git a/jupyterlab/rootfs/etc/jupyter/jupyter_notebook_config.py b/jupyterlab/rootfs/etc/jupyter/jupyter_notebook_config.py new file mode 100644 index 0000000..9fce2d1 --- /dev/null +++ b/jupyterlab/rootfs/etc/jupyter/jupyter_notebook_config.py @@ -0,0 +1,29 @@ +# Configuration file for ipython-notebook. + +c = get_config() + +# ------------------------------------------------------------------------------ +# NotebookApp configuration +# ------------------------------------------------------------------------------ + +c.GitHubConfig.access_token = '' +c.JupyterApp.answer_yes = True +c.LabApp.user_settings_dir = '/data/user-settings' +c.LabApp.workspaces_dir = '/data/workspaces' +c.NotebookApp.allow_origin = '*' +c.NotebookApp.allow_password_change = False +c.NotebookApp.allow_root = True +c.NotebookApp.base_url = '/' +c.NotebookApp.ip = '0.0.0.0' +c.NotebookApp.notebook_dir = '/config/notebooks' +c.NotebookApp.open_browser = False +c.NotebookApp.password = '' +c.NotebookApp.port = 8888 +c.NotebookApp.token = '' +c.NotebookApp.tornado_settings = {'static_url_prefix': '/static/'} +c.NotebookApp.trust_xheaders = True +c.NotebookApp.tornado_settings = { + 'headers': { + 'Content-Security-Policy': "frame-ancestors *" + } +} diff --git a/jupyterlab/rootfs/etc/services.d/jupyter/finish b/jupyterlab/rootfs/etc/services.d/jupyter/finish new file mode 100644 index 0000000..8a479a0 --- /dev/null +++ b/jupyterlab/rootfs/etc/services.d/jupyter/finish @@ -0,0 +1,9 @@ +#!/usr/bin/execlineb -S0 +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Take down the S6 supervision tree when the Jupyter daemon fails +# ============================================================================== +if -n { s6-test $# -ne 0 } +if -n { s6-test ${1} -eq 256 } + +s6-svscanctl -t /var/run/s6/services diff --git a/jupyterlab/rootfs/etc/services.d/jupyter/run b/jupyterlab/rootfs/etc/services.d/jupyter/run new file mode 100644 index 0000000..304a811 --- /dev/null +++ b/jupyterlab/rootfs/etc/services.d/jupyter/run @@ -0,0 +1,43 @@ +#!/usr/bin/with-contenv bash +# shellcheck disable=SC2191 +# ============================================================================== +# Community Hass.io Add-ons: JupyterLab Lite +# Runs Jupyter +# ============================================================================== +# shellcheck disable=SC1091 +source /usr/lib/hassio-addons/base.sh + +declare -a options + +hass.log.info 'Starting the Jupyter server' + +# Find the matching Jupyter log level +case "$(hass.string.lower "$(hass.config.get 'log_level')")" in + all|trace|debug) + options+=(--log-level='DEBUG') + ;; + info|notice) + options+=(--log-level='INFO') + ;; + warning) + options+=(--log-level='WARN') + ;; + error) + options+=(--log-level='ERROR') + ;; + fatal|off) + options+=(--log-level='CRITICAL') + ;; +esac + +# SSL Configuration +if hass.config.true 'ssl'; then + options+=(--keyfile="/ssl/$(hass.config.get 'keyfile')") + options+=(--certfile="/ssl/$(hass.config.get 'certfile')") +fi + +# Change current working directory +cd /config/notebooks || hass.die 'Failed changing working directory' + +# Run Juypter Notebook server +exec jupyter lab "${options[@]}"