diff --git a/.cirun.yml b/.cirun.yml deleted file mode 100644 index cdd6307..0000000 --- a/.cirun.yml +++ /dev/null @@ -1,8 +0,0 @@ -runners: - - name: big-runner - cloud: digitalocean - instance_type: c-2 - machine_image: docker-20-04 - region: ams3 - labels: - - cirun-runner diff --git a/.env b/.env new file mode 100644 index 0000000..ed1705a --- /dev/null +++ b/.env @@ -0,0 +1,33 @@ +# Invidious +INV_CPU_CORES=1 +INV_MEM_LIMIT="768m" +INV_MEM_RESERVATION="512m" +INV_IMAGE="invidious-nerdvpn:latest" +INV_RESTART_POLICY="unless-stopped" + +INV_HEALTHCHECK_URL="watch?v=jNQXAC9IVRw" + +BANNER_MSG="

" + +# PSQL +POSTGRES_DB="invidious" +POSTGRES_USER="invidious" +POSTGRES_PASSWORD="" + +INV_PSQL_CPU_CORES=6 +INV_PSQL_MEM_LIMIT="8448m" +INV_PSQL_MEM_RESERVATION="6400m" +INV_PSQL_IMAGE="docker.io/library/postgres:14" + +# Materialious +VITE_DEFAULT_INVIDIOUS_INSTANCE="https://api.invidious.nerdvpn.de" +VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE="https://ryd.invidious.nerdvpn.de" +VITE_DEFAULT_FRONTEND_URL="https://materialious.nerdvpn.de" +VITE_DEFAULT_SPONSERBLOCK_INSTANCE="https://sponsor.ajay.app" +VITE_DEFAULT_DEARROW_INSTANCE="" +VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE="" +VITE_DEFAULT_SYNCIOUS_INSTANCE="https://sync.invidious.nerdvpn.de" +VITE_DEFAULT_PEERJS_HOST="peer.invidious.nerdvpn.de" +VITE_DEFAULT_PEERJS_PATH="/" +VITE_DEFAULT_PEERJS_PORT=443 +VITE_DEFAULT_SETTINGS='{"region": "DE", "darkMode": false, "themeColor": "#FFB81C", "autoPlay": true, "alwaysLoop": false, "proxyVideos": false, "listenByDefault": false, "savePlaybackPosition": true, "dashEnabled": false, "theatreModeByDefault": false, "autoplayNextByDefault": true, "returnYtDislikes": true, "searchSuggestions": true, "previewVideoOnHover": false, "sponsorBlock": true, "sponsorBlockCategories": "sponsor,interaction,selfpromo", "deArrowEnabled": false, "playerMiniPlayer": true, "syncious": true}' diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 08f5bcc..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Docker Image CI - -on: - workflow_dispatch: - - push: - branches: - - master - - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '0 0 * * *' - -jobs: - - build: - - runs-on: 'ubuntu-latest' - - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - if: github.event_name != 'push' - with: - platforms: arm64 - - name: Check Out Repo - uses: actions/checkout@v3 - - - name: Check Out Repo Invidious - uses: actions/checkout@v3 - with: - repository: "iv-org/invidious" - ref: 'master' - path: 'invidious' - - - name: patch invidious main repo - run: | - cd invidious - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - git am ../patches/*.patch - sed -i 's/https:\/\/invidious.io\/donate\//\/donate/g' src/invidious/views/template.ecr - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Set up build commit id - run: echo "commitid=$(cd invidious && git log --format="%H" -n 1)-$(git log --format="%H" -n 1)" >> $GITHUB_ENV - - - name: Build and push alpine docker image - id: docker_build_new - uses: docker/build-push-action@v3 - with: - context: ./ - file: ./Dockerfile.new - build-args: | - release=1 - platforms: linux/arm64/v8 - push: true - tags: unixfox/invidious-custom:new-latest, unixfox/invidious-custom:new-build-${{ env.commitid }} - - # - name: patch invidious API repo - # run: | - # cd invidious - # git config --global user.email "you@example.com" - # git config --global user.name "Your Name" - # git am ../patches-api/*.patch - - # - name: Build and push alpine docker image with API only - # id: docker_build_new_api - # uses: docker/build-push-action@v3 - # with: - # context: ./ - # file: ./Dockerfile.new - # build-args: | - # release=1 - # add_build_args=-Dapi_only - # platforms: linux/amd64,linux/arm64/v8 - # push: true - # tags: unixfox/invidious-custom:api-new-latest, unixfox/invidious-custom:api-new-build-${{ env.commitid }} - - # - name: Build and push official crystal docker image - # id: docker_build - # uses: docker/build-push-action@v3 - # with: - # context: ./ - # build-args: | - # release=1 - # file: ./Dockerfile - # push: true - # tags: unixfox/invidious-custom:latest, unixfox/invidious-custom:build-${{ env.commitid }} diff --git a/.gitignore b/.gitignore index 1213661..567609b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -invidious/ \ No newline at end of file +build/ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4761c5b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -FROM crystallang/crystal:1.4.1-alpine AS builder -RUN apk add --no-cache sqlite-static yaml-static - -WORKDIR /invidious -COPY ./invidious/shard.yml ./shard.yml -COPY ./invidious/shard.lock ./shard.lock -RUN shards update --production && shards install --production - -COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a - -COPY ./invidious/src/ ./src/ -# TODO: .git folder is required for building – this is destructive. -# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION. -COPY ./invidious/.git/ ./.git/ -# Required for fetching player dependencies -COPY ./invidious/scripts/ ./scripts/ -COPY ./invidious/assets/ ./assets/ -COPY ./invidious/videojs-dependencies.yml ./videojs-dependencies.yml -RUN crystal build --release ./src/invidious.cr \ - --static --warnings all \ - --link-flags "-lxml2 -llzma" - -FROM alpine:latest -RUN apk add --no-cache librsvg ttf-opensans tini -WORKDIR /invidious -RUN addgroup -g 1000 -S invidious && \ - adduser -u 1000 -S invidious -G invidious -COPY --chown=invidious ./invidious/config/config.* ./config/ -RUN mv -n config/config.example.yml config/config.yml -RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: postgres/' config/config.yml -COPY ./invidious/config/sql/ ./config/sql/ -COPY ./invidious/locales/ ./locales/ -COPY --from=builder /invidious/assets ./assets/ -COPY --from=builder /invidious/invidious . -RUN chmod o+rX -R ./assets ./config ./locales - -EXPOSE 3000 -USER invidious -ENTRYPOINT ["/sbin/tini", "--"] -CMD [ "/invidious/invidious" ] \ No newline at end of file diff --git a/Dockerfile.new b/Dockerfile.new deleted file mode 100644 index ef1c961..0000000 --- a/Dockerfile.new +++ /dev/null @@ -1,51 +0,0 @@ -FROM alpine:3.19 AS builder -RUN apk add --no-cache 'crystal=1.10.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-static zlib-static openssl-libs-static openssl-dev musl-dev xz-static yq - -ARG add_build_args - -WORKDIR /invidious -COPY ./invidious/shard.yml ./shard.yml -COPY ./invidious/shard.lock ./shard.lock -# Sentry is just for reporting Invidious crashes, no personal data is collected. -#RUN yq e -i '.dependencies.raven.github = "Sija/raven.cr"' shard.yml -#RUN yq e -i '.targets.sentry_crash_handler.main = "lib/raven/src/crash_handler.cr"' shard.yml -RUN shards install --production - -COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a - -COPY ./invidious/src/ ./src/ -# TODO: .git folder is required for building – this is destructive. -# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION. -COPY ./invidious/.git/ ./.git/ -# Required for fetching player dependencies -COPY ./invidious/scripts/ ./scripts/ -COPY ./invidious/assets/ ./assets/ -COPY ./invidious/videojs-dependencies.yml ./videojs-dependencies.yml -RUN crystal build ./src/invidious.cr ${add_build_args} \ - --release \ - -Ddisable_quic \ - --static --warnings all \ - --link-flags "-lxml2 -llzma"; - -#RUN shards build --release --static sentry_crash_handler - -FROM alpine:3.16 -RUN apk add --no-cache librsvg ttf-opensans tini -WORKDIR /invidious -RUN addgroup -g 1000 -S invidious && \ - adduser -u 1000 -S invidious -G invidious -COPY --chown=invidious ./invidious/config/config.* ./config/ -RUN mv -n config/config.example.yml config/config.yml -RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: postgres/' config/config.yml -COPY ./invidious/config/sql/ ./config/sql/ -COPY ./invidious/locales/ ./locales/ -COPY --from=builder /invidious/assets ./assets/ -COPY --from=builder /invidious/invidious . -#COPY --from=builder /invidious/bin/sentry_crash_handler . -RUN chmod o+rX -R ./assets ./config ./locales - -EXPOSE 3000 -USER invidious -#CMD [ "/invidious/sentry_crash_handler", "/invidious/invidious" ] -ENTRYPOINT ["/sbin/tini", "--"] -CMD [ "/invidious/invidious" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..77e7411 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Invidious.nerdvpn.de +Invidious fork with customizations for invidious.nerdvpn.de + +Theme for my instance is based on: https://github.com/Tsyron/Invidious-Theme + +Original repo at: https://github.com/iv-org/invidious + +Based upon patches from: https://github.com/yewtudotbe/invidious-custom + + +# Build instructions + +1. `git clone https://github.com/Sommerwiesel/invidious-nerdvpn .` +2. `git clone https://github.com/iv-org/invidious build` +3. `./patch.sh` +4. `./build.sh development` for the development image or `./build.sh release` for the production image + +# Update instructions + +- Simply run `./update.sh` + +# Run instructions (docker compose) + +- For the database: `docker compose --profile database up -d` +- For the worker: `docker compose --profile worker up -d` +- For the web client: `docker compose --profile web up -d` +- For the tor web client: `docker compose --profile tor up -d` +- For materialious: `docker compose --profile materialious up -d` + +# Run instructions (systemd) + +First copy the invidious.service to /etc/systemd/system/ + +- For the database: `systemctl enable --now invidious@database.service` +- For the worker: `systemctl enable --now invidious@worker.service` +- For the web client: `systemctl enable --now invidious@web.service` +- For the tor web client: `systemctl enable --now invidious@tor.service` +- For materialious: `systemctl enable --now invidious@materialious.service` + diff --git a/assets/css/default.css b/assets/css/default.css new file mode 100644 index 0000000..858c49c --- /dev/null +++ b/assets/css/default.css @@ -0,0 +1,1174 @@ +:root { + --Color1: #BF616A; + --Color1Hover: rgba(191, 97, 106, 0.9); + --Color1Watched: rgba(191, 97, 106, 0.4); + --Color2: #D08770; + --Color2Hover: rgba(208, 135, 112, 0.9); + --Color3: #B48EAD; + --Color3Hover: rgba(180, 142, 173, 0.9); + --Color4: #EBCB8B; + --Color4Hover: rgba(235, 203, 139, 0.9); + --Color5: #A3BE8C; + --Color5Hover: rgba(163, 190, 140, 0.9); + --Color5Border: rgba(163, 190, 140, 0.3); + --Font: Roboto, Arial, sans-serif !important; +} + +/* Themes */ + +/* Dark Theme */ +body.dark-theme { + background: #2E3440; + color: #D8DEE9; +} + +.dark-theme .pure-g, +.dark-theme .pure-form legend { + background: #2E3440; + color: #D8DEE9; +} + +/* Clickable Links */ +.dark-theme a:not(.navbar .index-link), +.dark-theme a:active:not(.navbar .index-link), +.dark-theme a:visited:not(.navbar .index-link), +.dark-theme footer a, +.dark-theme summary, +.dark-theme a:not([data-id]) > .icon, +.dark-theme a:link:not(.pure-button):not(.channel-owner) { + color: #81A1C1; +} + +.dark-theme a:hover, +.dark-theme footer a:hover, +.dark-theme summary:hover, +.dark-theme a:not([data-id]) > .icon:hover, +.dark-theme a:link:not(.pure-button):not(.channel-owner):hover { + color: rgba(129, 161, 193, 0.9) !important; +} + +/* Multiple option Select Box */ + +.pure-form input[type="color"], +.pure-form input[type="date"], +.pure-form input[type="datetime-local"], +.pure-form input[type="datetime"], +.pure-form input[type="email"], +.pure-form input[type="month"], +.pure-form input[type="number"], +.pure-form input[type="password"], +.pure-form input[type="tel"], +.pure-form input[type="text"], +.pure-form input[type="time"], +.pure-form input[type="url"], +.pure-form input[type="week"], +.pure-form select, +.pure-form textarea { + box-shadow: transparent 1px 1px 1px inset; + border: transparent; + border-radius: 5px 5px 5px; + background-color: #3B4252; + color: #D8DEE9; +} + +/* General Button */ +.dark-theme .pure-button-primary, +.dark-theme .pure-button-secondary, +.dark-theme .pure-button-primary:focus, +.dark-theme .pure-button-secondary:focus { + border: transparent; + background-color: #3B4252 !important; + color: #D8DEE9 !important; +} + +.dark-theme .pure-button-primary:hover, +.dark-theme .pure-button-secondary:hover { + background-color: #434C5E !important; + color: #D8DEE9 !important; +} + +/* Checkbox & Radio: Background color */ +.dark-theme input[type="checkbox"], +.dark-theme input[type="checkbox"]:before, +.dark-theme input[type="checkbox"]:checked:before, +.dark-theme input[type="radio"], +.dark-theme input[type="radio"]:before, +.dark-theme input[type="radio"]:checked:before { + color: #434C5E; +} + +.dark-theme input[disabled] { + background-color: rgba(67, 76, 94, 0.3); +} + +/* Range slider: Background */ +.dark-theme input[type="range"] { + background-color: #434C5E; +} + +/* Home Screen: Clickable links colors */ +.dark-theme p, +.dark-theme p.video-data:hover { + color: #D8DEE9; +} + +/* Home Screen: Donate/Popular/Trending */ +.dark-theme a.feed-menu-item.pure-menu-heading { + color: #81A1C1; +} + +/* Home Screen: Only Highlights YouTube Video's */ +.dark-theme a[href^="/watch?v="] p:not(.length):hover { + color: rgba(216, 222, 233, 0.9); +} + +/* Home Screen: Watched */ +.dark-theme .watched { + background-color: #434C5E; + color: #D8DEE9; +} + +/* Home Screen: Length Time*/ +.dark-theme .length, +.dark-theme p.length { + color: #D8DEE9; + background-color: #434C5E; +} + +.dark-theme p.channel-name { + color: #81A1C1; +} + +.dark-theme p.channel-name:hover { + color: rgba(129, 161, 193, 0.9); +} + +/* Search: Filter box */ +.dark-theme #filters-box { + background: #2E3440 !important; +} + +/* Searchbox: background color */ +.dark-theme .pure-form input[type="search"] { + border: transparent; + background-color: #3B4252; + color: #D8DEE9; +} + +/* Video Player */ +/* Video player: play button: color */ +.dark-theme .video-js .vjs-big-play-button .vjs-icon-placeholder { + color: #D8DEE9; +} + +/* Video player: Controls: Color */ +.dark-theme .video-js .vjs-control-bar, +.dark-theme .video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + background-color: #2E3440; + color: #ECEFF4; +} + +/* Button Hover */ +.dark-theme .video-js button:hover { + color: #ECEFF4; +} + +/* Video player: Controls: Hovering Over Selected Option colors */ +.dark-theme .video-js .vjs-menu li.vjs-menu-item:focus, +.dark-theme .video-js .vjs-menu li.vjs-menu-item:hover { + background-color: var(--Color1Hover); + color: #D8DEE9; +} + +/* Video player: Controls: The Selected option colors */ +.dark-theme .video-js .vjs-menu li.vjs-selected, +.dark-theme .video-js .vjs-menu li.vjs-selected:focus, +.dark-theme .video-js .vjs-menu li.vjs-selected:hover { + background-color: var(--Color1); + color: #D8DEE9; +} + +/* Video player: Share Links */ +.dark-theme .video-js .vjs-share__short-link, +.dark-theme .video-js .vjs-share__btn, +.dark-theme .video-js .vjs-share__short-link-wrapper { + font-family: sans-serif; + background-color: #3B4252; + color: #D8DEE9; +} + +.dark-theme .video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button, +.dark-theme .video-js .vjs-share__subtitle, +.dark-theme .video-js .vjs-share__title { + font-family: sans-serif; + color: #D8DEE9; +} + +.dark-theme .video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content, +.dark-theme .video-js .vjs-modal-dialog { + background: #D8DEE9; +} + +.dark-theme .video-js .vjs-modal-dialog .vjs-modal-dialog-content, +.dark-theme .video-js .vjs-modal-dialog, +.dark-theme .video-js .vjs-modal-dialog-content { + background: #2E3440 !important; +} + +/* Video player: Settings */ +.dark-theme .video-js .vjs-text-track-settings legend { + color: #D8DEE9; +} + +/* Video Player: Text */ +.dark-theme .video-js .vjs-icon-placeholder, +.dark-theme .video-js .vjs-icon-share { + color: #D8DEE9; +} + +/* Video Player: Sliders */ +.dark-theme .video-js .vjs-play-progress, +.dark-theme .video-js .vjs-volume-level { + color: #D8DEE9; +} + +/* Video player: Controls: Progress Bar: Color */ +.video-js.player-style-invidious .vjs-play-progress { + background-color: var(--Color1); +} + +/* Subscribe button */ +.dark-theme #subscribe, +.dark-theme #subscribe:hover { + border: transparent; + color: #2E3440 !important; +} + +/* Comment Section: OC Channel: Comment */ +body.dark-theme a.channel-owner, +body.dark-theme a.channel-owner:hover, +.dark-theme .channel-owner:visited, +.dark-theme .channel-owner .icon.ion.ion-md-checkmark-circle { + color: #2E3440 !important; +} + +.dark-theme #subscribe, +.dark-theme #subscribe:hover { + color: #ECEFF4 !important; +} + +body.dark-theme a.channel-owner, +body.dark-theme a.channel-owner:hover, +.channel-owner:visited, +.channel-owner .icon.ion.ion-md-checkmark-circle { + color: #ECEFF4 !important; +} + +/* Description: Download Button */ +.dark-theme input, +.dark-theme select, +.dark-theme textarea { + background-color: #3B4252; + color: #D8DEE9; +} + +/* Scrollbar */ +.dark-theme ::-webkit-scrollbar, +.dark-theme ::-webkit-scrollbar { + background-color: #3B4252; +} + +.dark-theme ::-webkit-scrollbar-thumb, +.dark-theme ::-webkit-scrollbar-thumb { + background-color: #434C5E; +} + +/* Selection of text color */ +.dark-theme ::selection, +.dark-theme ::selection { + background-color: #D8DEE9 !important; + color: #434C5E !important; +} + +/* White Line (Spacer/padding) */ +.dark-theme hr, +.dark-theme .pure-form legend, +.dark-theme .pure-form-aligned legend, +.dark-theme .underlined { + border-color: rgba(76, 86, 106, 1); +} + +/* Settings: Volume Text */ +.dark-theme span#volume-value { + color: #D8DEE9; +} + +/* Placeholder */ +.dark-theme ::placeholder { + color: #D8DEE9; +} + +.dark-theme .pure-form input[type="color"], +.dark-theme .pure-form input[type="date"], +.dark-theme .pure-form input[type="datetime-local"], +.dark-theme .pure-form input[type="datetime"], +.dark-theme .pure-form input[type="email"], +.dark-theme .pure-form input[type="month"], +.dark-theme .pure-form input[type="number"], +.dark-theme .pure-form input[type="password"], +.dark-theme .pure-form input[type="tel"], +.dark-theme .pure-form input[type="text"], +.dark-theme .pure-form input[type="time"], +.dark-theme .pure-form input[type="url"], +.dark-theme .pure-form input[type="week"], +.dark-theme .pure-form input[type="search"], +.dark-theme .pure-form select, +.dark-theme .pure-form textarea { + border: 1px solid #4C566A !important; +} + +.dark-theme .pure-form input[type="range"]::-webkit-slider-thumb { + border-right: 1px solid #4C566A !important; +} + +.dark-theme .pure-form input[type="checkbox"], +.dark-theme .pure-form input[type="radio"], +.dark-theme .pure-form input[type="range"] { + border: 1px solid var(--Color5Border) !important; +} + +.dark-theme .pure-form .pure-button-primary, +.dark-theme .pure-form .pure-button-primary:focus, +.dark-theme .pure-form .pure-button-primary:hover, +.dark-theme .pure-form .pure-button-secondary, +.dark-theme .pure-form .pure-button-secondary:focus, +.dark-theme .pure-form .pure-button-secondary:hover { + border: 1px solid #4C566A !important; +} + +.dark-theme .pure-form #subscribe, +.dark-theme .pure-form #subscribe:hover, +.dark-theme .pure-form a.channel-owner, +.dark-theme .pure-form a.channel-owner:hover { + border: 1px solid var(--Color1) !important; +} + + +/* Light Theme */ +body.light-theme { + background: #ECEFF4; + color: #434C5E; +} + + +.light-theme .pure-g, +.light-theme .pure-form legend { + background: #ECEFF4; + color: #434C5E; +} + +/* Clickable Links */ +.light-theme a:not(.pure-u-md-4-24 .pure-menu-heading), +.light-theme a:active:not(.pure-u-md-4-24 .pure-menu-heading), +.light-theme a:visited:not(.pure-u-md-4-24 .pure-menu-heading), +.light-theme footer a, +.light-theme summary, +.light-theme a:not([data-id]) > .icon, +.light-theme a:link:not(.pure-button):not(.channel-owner) { + color: #5E81AC; +} + +.light-theme a:hover, +.light-theme footer a:hover, +.light-theme summary:hover, +.light-theme a:not([data-id]) > .icon:hover, +.light-theme a:link:not(.pure-button):not(.channel-owner):hover { + color: rgba(94, 129, 172, 0.9) !important; +} + +/* Multiple option Select Box */ +.light-theme .pure-form input[type="color"], +.light-theme .pure-form input[type="date"], +.light-theme .pure-form input[type="datetime-local"], +.light-theme .pure-form input[type="datetime"], +.light-theme .pure-form input[type="email"], +.light-theme .pure-form input[type="month"], +.light-theme .pure-form input[type="number"], +.light-theme .pure-form input[type="password"], +.light-theme .pure-form input[type="tel"], +.light-theme .pure-form input[type="text"], +.light-theme .pure-form input[type="time"], +.light-theme .pure-form input[type="url"], +.light-theme .pure-form input[type="week"], +.light-theme .pure-form select, +.light-theme .pure-form textarea { + box-shadow: transparent 1px 1px 1px inset; + border: transparent; + border-radius: 5px 5px 5px; + background-color: #D8DEE9; + color: #434C5E; +} + +/* General Button */ +.light-theme .pure-button-primary, +.light-theme .pure-button-secondary, +.light-theme .pure-button-primary:focus, +.light-theme .pure-button-secondary:focus { + border: transparent; + background-color: #D8DEE9 !important; + color: #434C5E !important; +} +/* new */ +.light-theme .pure-button-primary:hover, +.light-theme .pure-button-secondary:hover { + background-color: #D8DEE9 !important; + color: #434C5E !important; +} + +/* Checkbox & Radio: Background color */ +.light-theme input[type="checkbox"], +.light-theme input[type="checkbox"]:before, +.light-theme input[type="checkbox"]:checked:before, +.light-theme input[type="radio"], +.light-theme input[type="radio"]:before, +.light-theme input[type="radio"]:checked:before { + color: #434C5E; +} + +.light-theme input[disabled] { + background-color: rgba(216, 222, 233, 0.5); +} + +/* Range slider: Background */ +.light-theme input[type="range"] { + background-color: #D8DEE9; +} + +/* Home Screen: Clickable links colors */ +.light-theme p, +.light-theme p.video-data:hover { + color: #4C566A; +} + +/* Home Screen: Donate/Popular/Trending */ +.light-theme a.feed-menu-item.pure-menu-heading { + color: #5E81AC; +} + +/* Home Screen: Only Highlights YouTube Video's */ +.light-theme a[href^="/watch?v="] p:not(.length):hover { + color: rgba(46, 52, 64, 0.9); +} + +/* Home Screen: Watched */ +.light-theme .watched { + background-color: #ECEFF4; + color: #434C5E; +} + +/* Home Screen: Length Time*/ +.light-theme .length, +.light-theme p.length { + color: #434C5E; + background-color: #ECEFF4; +} + +.light-theme p.channel-name { + color: #5E81AC; +} + +.light-theme p.channel-name:hover { + color: rgba(94, 129, 172, 0.9); +} + +/* Search: Filter box */ +.light-theme #filters-box { + background-color: #ECEFF4 !important; +} + +/* Searchbox: background color */ +.light-theme .pure-form input[type="search"] { + border: transparent; + background-color: #D8DEE9; + color: #434C5E; +} + +/* Video Player */ +/* Video player: play button: color */ +.light-theme .video-js .vjs-big-play-button .vjs-icon-placeholder { + color: #D8DEE9; +} + +/* Video player: Controls: Color */ +.light-theme .video-js .vjs-control-bar, +.light-theme .video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + background-color: #ECEFF4; + color: #434C5E; +} + +/* Button Hover */ +.light-theme .video-js button:hover { + color: #434C5E; +} + +/* Video player: Controls: Hovering Over Selected Option colors */ +.light-theme .video-js .vjs-menu li.vjs-menu-item:focus, +.light-theme .video-js .vjs-menu li.vjs-menu-item:hover { + background-color: var(--Color1Hover); + color: #D8DEE9; +} + +/* Video player: Controls: The Selected option colors */ +.light-theme .video-js .vjs-menu li.vjs-selected, +.light-theme .video-js .vjs-menu li.vjs-selected:focus, +.light-theme .video-js .vjs-menu li.vjs-selected:hover { + background-color: var(--Color1); + color: #ECEFF4; +} + +/* Video player: Share Links */ +.light-theme .video-js .light-theme .video-js .vjs-share__short-link, +.light-theme .video-js .light-theme .video-js .vjs-share__btn, +.light-theme .video-js .vjs-share__short-link-wrapper { + font-family: sans-serif; + background-color: #D8DEE9; + color: #2E3440; +} + +.light-theme .video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button, +.light-theme .video-js .vjs-share__subtitle, +.light-theme .video-js .vjs-share__title { + font-family: sans-serif; + color: #2E3440; +} + +.light-theme .video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content, +.light-theme .video-js .vjs-modal-dialog, +.light-theme .video-js .vjs-modal-dialog-content { + background: #ECEFF4 !important; +} + +.light-theme .video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content, +.light-theme .video-js .vjs-modal-dialog { + background: #ECEFF4; +} + +/* Video player: Settings */ +.light-theme .video-js .vjs-text-track-settings legend { + color: #434C5E; +} + +/* Video Player: Text */ +.light-theme .video-js .vjs-icon-placeholder, +.light-theme .video-js .vjs-icon-share { + color: #4C566A; +} + +/* Video Player: Sliders */ +.light-theme .video-js .vjs-play-progress, +.light-theme .video-js .vjs-volume-level { + color: #4C566A; +} + +/* Video player: Controls: Progress Bar: Color */ +.light-theme .video-js.player-style-invidious .vjs-play-progress { + background-color: var(--Color1); +} + +/* Subscribe button */ +.light-theme #subscribe, +.light-theme #subscribe:hover { + border: transparent; + color: #D8DEE9 !important; +} + +/* Comment Section: OC Channel: Comment */ +body.light-theme a.channel-owner, +body.light-theme a.channel-owner:hover, +.light-theme .channel-owner:visited, +.light-theme .channel-owner .icon.ion.ion-md-checkmark-circle { + color: #D8DEE9 !important; +} + + +/* Description: Download Button */ +.light-theme input, +.light-theme select, +.light-theme textarea { + background-color: #D8DEE9; + color: #434C5E; +} + +/* Scrollbar */ +.light-theme ::-webkit-scrollbar { + background-color: #D8DEE9; +} + +.light-theme ::-webkit-scrollbar-thumb { + background-color: #E5E9F0; +} + +/* Selection of text color */ +.light-theme ::selection { + background-color: #2E3440 !important; + color: #D8DEE9 !important; +} + +/* White Line (Spacer/padding) */ +.light-theme hr, +.light-theme .pure-form legend, +.light-theme .underlined { + border-color: rgba(216, 222, 233, 0.3); +} + +.light-theme .pure-form-aligned legend { + border-color: #D8DEE9; +} + +/* Settings: Volume Text */ +.light-theme span#volume-value { + color: #2E3440; +} + +/* Placeholder */ +.light-theme ::placeholder { + color: #2E3440; +} + +.light-theme .pure-form input[type="color"], +.light-theme .pure-form input[type="date"], +.light-theme .pure-form input[type="datetime-local"], +.light-theme .pure-form input[type="datetime"], +.light-theme .pure-form input[type="email"], +.light-theme .pure-form input[type="month"], +.light-theme .pure-form input[type="number"], +.light-theme .pure-form input[type="password"], +.light-theme .pure-form input[type="tel"], +.light-theme .pure-form input[type="text"], +.light-theme .pure-form input[type="time"], +.light-theme .pure-form input[type="url"], +.light-theme .pure-form input[type="week"], +.light-theme .pure-form input[type="search"], +.light-theme .pure-form select, +.light-theme .pure-form textarea { + border: 1px solid #ECEFF4 !important; +} + +.light-theme .pure-form input[type="range"]::-webkit-slider-thumb { + border-right: 1px solid #ECEFF4 !important; +} + +.light-theme .pure-form input[type="checkbox"], +.light-theme .pure-form input[type="radio"], +.light-theme .pure-form input[type="range"] { + border: 1px solid var(--Color5Border) !important; +} + +.light-theme .pure-form .pure-button-primary, +.light-theme .pure-form .pure-button-primary:focus, +.light-theme .pure-form .pure-button-primary:hover, +.light-theme .pure-form .pure-button-secondary, +.light-theme .pure-form .pure-button-secondary:focus, +.light-theme .pure-form .pure-button-secondary:hover { + border: 1px solid #ECEFF4 !important; +} + +.light-theme .pure-form #subscribe, +.light-theme .pure-form #subscribe:hover, +.light-theme .pure-form a.channel-owner, +.light-theme .pure-form a.channel-owner:hover { + border: 1px solid var(--Color1) !important; +} + + + +/* General Button */ +.pure-button-primary, +.pure-button-secondary { + padding: 0.5em 1em; + margin: 0px; + border-radius: 5px 5px 5px !important; +} + +/* Checkbox & Radio */ +input[type="checkbox"], +input[type="radio"] { + border-radius: 6px; + height: 1.3em; + width: 1.3em; + cursor: pointer; + position: relative; + vertical-align: middle; + -webkit-appearance: none; + -webkit-transition: 0.1s; +} + +/* Checkbox & Radio: Icons */ +input[type="radio"] { + border-radius: 50%; +} + +input[type="checkbox"]:checked:before { + content: "✔"; +} + +/* Color checkbox and radio button */ +input[type="checkbox"]:before, +input[type="checkbox"]:checked:before, +input[type="radio"]:before, +input[type="radio"]:checked:before { + background-color: var(--Color5); + border-radius: 6px; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + line-height: 1.3em; + text-align: center; +} + +/* Checkbox: Autoplay */ +.pure-u-lg-1-5 #continue { + margin-left: 3px; +} + +/* Checkbox & Radio: Color check */ +input[type="checkbox"]:checked, +input[type="radio"]:checked { + background-color: var(--Color5); +} + +/* Range slider: Slider and Round */ +input[type="range"]::-webkit-slider-thumb { + width: 15px; + border-radius: 15px; + -webkit-appearance: none; + height: 15px; + background: var(--Color5); + box-shadow: -80px 0 0 75px var(--Color5Hover); +} + +/* Range slider: Padding */ +input[type="range"] { + cursor: pointer; + overflow: hidden; + -webkit-appearance: none; + border-radius: 1em; +} + +input[type="range"]::-webkit-slider-runnable-track { + height: 15px; + -webkit-appearance: none; +} + +/* Screen Width */ +@media screen and (min-width: 48em) { + .pure-u-md-1-6, + .pure-u-md-1-12, + .pure-u-md-4-24, + .pure-u-md-20-24, + .pure-u-md-12-24 { + width: 0% !important; + } +} + +.pure-u-1.pure-u-md-2-24 { + width: 0%; +} + +#comments { + max-width: 100%; +} + +/* Font */ +html, +body, +.pure-g [class*="pure-u"] { + font-family: var(--Font); + font-size: 1em; +} + +/* Home Screen: Thumbnail */ +img, +div.watched-overlay { + border-radius: 5px 5px 5px; +} + +div.watched-indicator { + border-radius: 0 0 18px 0px 35px 18px 0px 35px; +} + +div.watched-overlay { + background-color: var(--Color1Watched); +} + +div.watched-indicator { + background-color: var(--Color1); +} + +/* Home Screen: Searchbox */ +#search-widget h1 { + color: var(--Color1); +} + +#searchbox { + border: transparent; + box-shadow: rgba(0, 0, 0, 0.35) 1px 1px 1px; + font-size:1.25em; +} + +/* Home Screen: Watched */ +.watched { + border-radius: 5px 5px 5px; +} + +/* Home Screen: Length Time*/ +.length, +p.length { + padding: 5px 8px 5px 8px; + right: 0.4em; + bottom: -0.6em; + font-size: 1em; + border-radius: 5px 5px 5px; +} + +/* Home Screen: Invidious icon / Log in */ +.navbar .index-link, +.pure-menu-heading { + color: var(--Color1); +} + +.navbar .index-link:hover { + color: var(--Color1Hover) !important; +} + +/* Home Screen: Little icons beside video */ +i.icon.ion-md-headset { + color: var(--Color4) !important; +} + +i.icon.ion-md-headset:hover { + color: var(--Color4Hover) !important; +} + +i.icon.ion-logo-youtube { + color: var(--Color1) !important; +} + +i.icon.ion-logo-youtube:hover { + color: var(--Color1Hover) !important; +} + +i.icon.ion-md-jet { + color: var(--Color3) !important; +} + +i.icon.ion-md-jet:hover { + color: var(--Color3Hover) !important; +} + +/* Home Screen: filters */ +#filters-apply { + text-align: right; +} + +/* Searchbox */ +.pure-form input[type="search"] { + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); + border-radius: 5px 5px 5px; + margin: 0px; +} + +/* Zooming */ +.pure-g:not(.h-box) > .pure-u-md-1-4 .h-box, +.pure-g:not(.searchbar) > .searchbar, +.pure-form-aligned { + border-radius: 5px 5px 5px; + padding: 12px; + transition: all 0.2s; +} + +.h-box { + padding-left: 1.1em; + padding-right: 1.1em; +} + +.pure-g:not(.h-box) > .pure-u-md-1-4 .h-box:hover { + transform: scale(1.025); + transition: all 0.2s; + opacity: 1; + z-index: 99999; +} + +/* .video-js */ +/* Video player: play button: Border */ +.video-js .vjs-big-play-button { + border-radius: 5px 5px 5px; + background-color: var(--Color1) !important; + border: 0em solid transparent; + font-size: 4.5em; + top: 50%; + left: 50%; + margin-top: -0.81666em; + margin-left: -1.5em; +} + +/* Volume Slider */ +.video-js .vjs-volume-level { + border-radius: 5px 5px 5px; + background-color: var(--Color1) !important; +} + +/* Video player: Controls: Floating */ +.video-js .vjs-control-bar, +.video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + font-family: sans-serif; + opacity: 1; + border-radius: 18px 0px 35px 18px 0px 35px 0 0; + left: 0.5%; + right: 0.5%; + width: 99%; + margin-bottom: 0.5%; +} + +.video-js .vjs-control-bar { + border-radius: 5px 5px 5px; +} + +/* Video player: Share Links */ +.video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content, +.video-js .vjs-modal-dialog { + width: 80%; + height: 80%; + top: 10%; + left: 10%; + border-radius: 5px 5px 5px; +} + +.video-js .vjs-share__social_fbFeed, +.video-js .vjs-share__social_tw, +.video-js .vjs-share__social_reddit, +.video-js .vjs-share__social_mail, +.video-js .vjs-share__social_email, +.video-js .vjs-share__short-link, +.video-js .vjs-share__btn, +.video-js .vjs-share__short-link-wrapper { + border-radius: 5px 5px 5px; +} + +.video-js .vjs-share__social_fbFeed { + background-color: var(--Color1); +} + +.video-js .vjs-share__social_tw { + background-color: var(--Color3); +} + +.video-js .vjs-share__social_reddit { + background-color: var(--Color2); +} + +.video-js .vjs-share__social_mail, +.video-js .vjs-share__social_email { + background-color: var(--Color4); +} + +/* Video player: Settings */ +.video-js .vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button::before { + display: none; +} + +/* Video player: Size */ +.pure-u-md-20-24 { + width: 94% !important; + max-width: 170em !important; + margin: auto; +} + +/* Video player: Remove black borders */ +.player-dimensions.vjs-fluid { + padding-top: 56.25% !important; +} + +#player-container { + transition: all 0.2s; + padding-bottom: 54.7% !important; +} + +@media screen and (max-width: 48em) { + #player-container { + padding-bottom: 47% !important; + margin-bottom: 8%; + } +} + +/* Theater mode */ +@media screen and (max-width: 170em) { + #player-container { + margin-bottom: 6%; + margin-left: -3.2%; + margin-right: -3.2%; + } +} + +@media screen and (max-width: 767px) { + #player-container { + margin-bottom: 15%; + } +} + + +/* Description: Padding and Form */ +.pure-g:not(.h-box) > .pure-u-md-1-4 .h-box { + margin: 5px; +} + +@media screen and (min-width: 64em) { + .pure-u-lg-3-5 .title { + margin: 0px; + } +} + +/* Description: Title */ +@media screen and (max-width: 767px) { + h1 { + margin: 0.67em 0; + } +} + +/* Description: Subscribe button */ +#subscribe { + border-radius: 5px 5px 5px; + background-color: var(--Color1) !important; + float: right; + padding: 7.5px; + /*margin-top: -55px;*/ +} + +#subscribe:hover { + background-color: var(--Color1Hover) !important; +} + +/* Description: Download Multiple Section Bold */ +select#download_widget { + font-weight: bold; + max-width: 350px !important; + margin-top: 10px; +} + +.pure-form-stacked .pure-button-primary { + margin-top: 5px; +} + +/* Description: Download Button */ +input, +select, +textarea { + font-family: sans-serif; + border: 0px; + border-radius: 5px 5px 5px; +} + +/* Description: Channel Image */ +.channel-profile > img { + width: 36px !important; + margin-right: 6px; + height: auto; +} + +.pure-u-md-2-24 > img { + width: 36px !important; + height: auto; +} + +.pure-u-md-2-24 { + max-width: 46px; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 15px; +} + +/* Comment Section: Heart icon */ +.icon.ion-ios-heart.creator-heart-small-container { + color: var(--Color1); +} + +/* Comment Section: OC Channel: Comment */ +body a.channel-owner { + background-color: var(--Color1); + padding: 4px 8px; + border-radius: 5px 5px 5px; +} + +body a.channel-owner:hover { + background-color: var(--Color1Hover); +} + +/* Banner on Channel */ +.pure-u-md-20-24 > .h-box > img { + border-radius: 5px 5px 5px; + margin: 5px; + opacity: 1; + transition: all 0.2s; +} + +/* Channel, Trending UI Links */ +.pure-g .pure-u-1 .pure-md-1-3 { + margin-bottom: 0.5em; +} + +/* White Line (Spacer/padding) */ +hr { + margin: 1.3em 0; +} + +.pure-form legend { + text-align: center; + margin-bottom: 1.3em; + padding: 1.3em; +} + +/* Settings */ +.pure-form-aligned .pure-control-group label, +.pure-form-aligned .pure-control-group a, +.pure-form-aligned .pure-control-group input { + text-align: left !important; + margin: 6px; +} + +.pure-form-aligned .pure-control-group select { + margin: 0px 0px 0px 6px; +} + +.pure-form-aligned .pure-controls { + margin: 0px; + text-align: right; +} + +#max_results { + width: 209.783px; +} + +@media screen and (max-width: 1280px) { + .pure-form-aligned .pure-control-group label, + .pure-form-aligned .pure-control-group a, + .pure-form-aligned .pure-controls { + padding: 0px; + } +} + +/* Shadow */ +.pure-g:not(.h-box) > .pure-u-md-1-4 .h-box:hover { + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} + +#player-container { + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} + +.pure-u-md-20-24 > .h-box > img { + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c347ceb --- /dev/null +++ b/build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +if [ $# -eq 0 ]; then + echo "Usage: build [development|release]" + exit 1 +fi + +mode=$1 + + +if [ "$mode" == "release" ]; then + docker compose --profile base build --build-arg release=1 +fi + +if [ "$mode" == "development" ]; then + docker compose --profile base build --build-arg release=0 +fi + +yes | docker system prune diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..f8e0122 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,536 @@ +x-volumes: &volumes + volumes: + - /var/run/redis/redis-server.sock:/invidious/redis-server.sock:rw + +services: + + # This service is for building the base image (patch.sh && build.sh) + invidious: + <<: *volumes + env_file: + - .env + profiles: ["base"] + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + build: + context: build + dockerfile: docker/Dockerfile + restart: "${INV_RESTART_POLICY}" + environment: &environment + INVIDIOUS_CONFIG: | + #force_resolve: ipv6 + modified_source_code_url: "https://github.com/Sommerwiesel/invidious-nerdvpn" + hmac_key: + db: + dbname: invidious + user: invidious + password: + host: invidious-pgbouncer + port: 5432 + redis_socket: "/invidious/redis-server.sock" + redis_db: 4 + redis_auth: "" + http_proxy: + host: "192.42.6.30" + port: 80 + check_tables: true + external_port: 443 + domain: "invidious.nerdvpn.de" + https_only: true + hsts: true + pool_size: 60 + disable_proxy: ["local"] + log_level: "Fatal" + popular_enabled: true + statistics_enabled: true + enable_user_notifications: false + channel_threads: 0 + feed_threads: 0 + use_pubsub_feeds: false + banner: "${BANNER_MSG}" + jobs: + clear_expired_items: + enable: false + refresh_channels: + enable: false + refresh_feeds: + enable: false + default_user_preferences: + locale: "de" + region: "DE" + captions: ["","English","English (auto-generated)"] + dark_mode: "dark" + feed_menu: ["Trending", "Subscriptions", "Playlists", "Search"] + default_home: "Search" + comments: ["youtube", ""] + autoplay: true + continue_autoplay: true + quality: "hd720" + quality_dash: "720p" + unseen_only: true + local: false + automatic_instance_redirect: true + networks: + invidious_proxy: + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + retries: 2 + + # Takes care of refreshing the feeds + invidious_worker: + <<: *volumes + env_file: + - .env + profiles: ["worker"] + container_name: invidious_worker + cpus: ${INV_CPU_CORES} + mem_limit: "1536m" + mem_reservation: "768m" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + INVIDIOUS_CONFIG: | + #force_resolve: ipv6 + modified_source_code_url: "https://github.com/Sommerwiesel/invidious-nerdvpn" + hmac_key: + db: + dbname: invidious + user: invidious + password: + host: invidious-pgbouncer + port: 5432 + redis_socket: "/invidious/redis-server.sock" + redis_db: 4 + redis_auth: "" + http_proxy: + host: "192.42.6.30" + port: 80 + check_tables: true + external_port: 443 + domain: "invidious.nerdvpn.de" + https_only: true + hsts: true + pool_size: 100 + disable_proxy: ["livestreams","local"] + log_level: "Fatal" + popular_enabled: true + statistics_enabled: true + enable_user_notifications: false + channel_threads: 4 + feed_threads: 4 + channel_refresh_interval: "15m" + use_pubsub_feeds: true + default_user_preferences: + locale: "de" + region: "DE" + captions: ["","English","English (auto-generated)"] + dark_mode: "dark" + feed_menu: ["Trending", "Subscriptions", "Playlists", "Search"] + default_home: "Search" + comments: ["youtube", ""] + autoplay: true + continue_autoplay: true + quality: "hd720" + quality_dash: "720p" + unseen_only: true + local: false + automatic_instance_redirect: true + networks: + invidious_proxy: + ipv4_address: 192.42.6.9 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 3m + timeout: 5s + retries: 2 + + invidious_1: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_1 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.2 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious_2: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_2 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.3 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious_3: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_3 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.4 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious_4: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_4 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.5 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious_5: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_5 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.6 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious_6: + <<: *volumes + env_file: + - .env + profiles: ["web"] + container_name: invidious_6 + cpus: ${INV_CPU_CORES} + mem_limit: "${INV_MEM_LIMIT}" + mem_reservation: "${INV_MEM_RESERVATION}" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + <<: *environment + networks: + invidious_proxy: + ipv4_address: 192.42.6.7 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + materialious: + env_file: + - .env + profiles: ["materialious"] + container_name: materialious + cpus: "2" + mem_reservation: "32m" + mem_limit: "64m" + image: "wardpearce/materialious" + restart: "unless-stopped" + networks: + invidious_proxy: + ipv4_address: 192.42.6.13 + + # Return YouTube Dislike proxy via tor + materialious_ryd-proxy: + profiles: ["materialious"] + container_name: materialious_ryd-proxy + image: 1337kavin/ryd-proxy:latest + restart: "unless-stopped" + cpus: "2" + mem_reservation: "128m" + mem_limit: "256m" + depends_on: + - materialious_tor + environment: + - PROXY=socks5://materialious_tor:5566 + - WEB_CONCURRENCY=3 + networks: + invidious_proxy: + ipv4_address: 192.42.6.14 + + materialious_api: + profiles: ["materialious"] + container_name: materialious_api + image: wardpearce/invidious_api_extended:latest + restart: "unless-stopped" + cpus: "3" + mem_reservation: "192m" + mem_limit: "320m" + environment: + api_extended_postgre: '{"host": "invidious-db", "port": 5432, "database": "invidious", "user": "invidious", "password": ""}' + api_extended_allowed_origins: '["https://materialious.nerdvpn.de"]' + api_extended_debug: false + api_extended_invidious_instance: "https://api.invidious.nerdvpn.de" + api_extended_production_instance: "https://sync.invidious.nerdvpn.de" + WEB_CONCURRENCY: 3 + networks: + invidious_proxy: + ipv4_address: 192.42.6.15 + + # Opens tor circuits + materialious_tor: + profiles: ["materialious"] + container_name: materialious_tor + image: 1337kavin/alpine-tor:latest + restart: "unless-stopped" + cpus: "2" + mem_reservation: "128m" + mem_limit: "256m" + environment: + - tors=2 + - test_url="http://nerdvpn2awosmfgodp77bavzve3k2kvxw52z364suglul2qaalcdqtad.onion" + networks: + invidious_proxy: + ipv4_address: 192.42.6.20 + + materialious_peerjs: + profiles: ["materialious"] + container_name: materialious_peerjs + build: + context: peerjs + dockerfile: Dockerfile + restart: "unless-stopped" + cpus: "2" + mem_reservation: "32m" + mem_limit: "128m" + labels: + - "com.centurylinklabs.watchtower.enable=false" + networks: + invidious_proxy: + ipv4_address: 192.42.6.19 + + invidious_tor: + <<: *volumes + env_file: + - .env + profiles: ["tor"] + container_name: invidious_tor + cpus: ${INV_CPU_CORES} + mem_limit: "512m" + mem_reservation: "128m" + image: "${INV_IMAGE}" + restart: "${INV_RESTART_POLICY}" + environment: + INVIDIOUS_CONFIG: | + force_resolve: ipv6 + modified_source_code_url: "https://github.com/Sommerwiesel/invidious-nerdvpn" + hmac_key: + db: + dbname: invidious + user: invidious + password: + host: invidious-pgbouncer + port: 5432 + redis_socket: "/invidious/redis-server.sock" + redis_db: 4 + redis_auth: "" + http_proxy: + host: "192.42.6.30" + port: 80 + check_tables: true + external_port: 80 + domain: "jt3wafuew7rumgwp7wfixpbtxc4dw6jvvfjt7dud33xczvwvgih5nfqd.onion" + https_only: false + hsts: false + pool_size: 40 + disable_proxy: ["livestreams","local","dash","downloads"] + log_level: "Fatal" + popular_enabled: false + statistics_enabled: true + enable_user_notifications: false + channel_threads: 0 + feed_threads: 0 + use_pubsub_feeds: false + jobs: + clear_expired_items: + enable: false + refresh_channels: + enable: false + refresh_feeds: + enable: false + default_user_preferences: + locale: "de" + region: "DE" + captions: ["","English","English (auto-generated)"] + dark_mode: "dark" + feed_menu: ["Trending", "Subscriptions", "Playlists", "Search"] + default_home: "Search" + comments: ["youtube", ""] + autoplay: true + continue_autoplay: true + quality: "hd720" + quality_dash: "720p" + unseen_only: true + local: false + automatic_instance_redirect: false + networks: + invidious_proxy: + ipv4_address: 192.42.6.8 + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/${INV_HEALTHCHECK_URL} || exit 1 + interval: 5m + timeout: 5s + retries: 2 + + invidious-pgbouncer: + env_file: + - .env + profiles: ["database"] + container_name: invidious-pgbouncer + mem_limit: "128m" + mem_reservation: "64m" + image: edoburu/pgbouncer + restart: "${INV_RESTART_POLICY}" + environment: + - DB_HOST=invidious-db + - DB_PORT=5432 + - DB_USER=invidious + - DB_PASSWORD= + - DB_NAME=invidious + - ADMIN_USERS=pgbouncer + - POOL_MODE=transaction + - DEFAULT_POOL_SIZE=80 + - MIN_POOL_SIZE=20 + - RESERVE_POOL_SIZE=80 + - RESERVE_POOL_TIMEOUT=5 + - MAX_DB_CONNECTIONS=160 + - MAX_USER_CONNECTIONS=160 + - MAX_CLIENT_CONN=1000 + - AUTH_TYPE=scram-sha-256 + - IGNORE_STARTUP_PARAMETERS=extra_float_digits + volumes: + - ./docker/userlist.txt:/etc/pgbouncer/userlist.txt:rw + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB -h invidious-db"] + interval: 5m + timeout: 15s + retries: 2 + networks: + invidious_proxy: + ipv4_address: 192.42.6.11 + + invidious-db: + env_file: + - .env + profiles: ["database"] + container_name: invidious-db + cpus: ${INV_PSQL_CPU_CORES} + mem_limit: "${INV_PSQL_MEM_LIMIT}" + memswap_limit: "${INV_PSQL_MEM_LIMIT}" + mem_reservation: "${INV_PSQL_MEM_RESERVATION}" + image: "${INV_PSQL_IMAGE}" + restart: "${INV_RESTART_POLICY}" + volumes: + - type: bind + source: /srv/invidious/database + target: /var/lib/postgresql/data + - ./config/sql:/config/sql + - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh + - ./docker/pg-tune.sql:/docker-entrypoint-initdb.d/pg-tune.sql + - ./docker/pgstat.sql:/tmp/pgstat.sql + - ./docker/pg-cleanup.sql:/tmp/pgcleanup.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5m + timeout: 15s + retries: 2 + networks: + invidious_proxy: + ipv4_address: 192.42.6.10 + + invidious-proxy: + profiles: ["web"] + image: 1337kavin/ytproxy:latest + container_name: invidious_proxy + cpus: "4" + mem_reservation: "256m" + mem_limit: "512m" + restart: "unless-stopped" + user: 33:33 + network_mode: "host" + environment: + - DISABLE_WEBP=1 + volumes: + - /srv/ytproxy:/app/socket + + invidious-haproxy: + profiles: ["web"] + image: haproxytech/haproxy-alpine:latest + container_name: invidious_haproxy + cpus: "3" + mem_reservation: "64m" + mem_limit: "128m" + restart: "unless-stopped" + volumes: + - ./docker/haproxy:/usr/local/etc/haproxy:ro + networks: + invidious_proxy: + ipv4_address: 192.42.6.30 + + +networks: + invidious_proxy: + external: true diff --git a/docker/APKBUILD-boringssl b/docker/APKBUILD-boringssl deleted file mode 100644 index 61caa4f..0000000 --- a/docker/APKBUILD-boringssl +++ /dev/null @@ -1,46 +0,0 @@ -# Based on https://aur.archlinux.org/packages/boringssl-git/ -# Maintainer: Omar Roth -pkgname=boringssl -pkgver=1.1.0 -pkgrel=0 -pkgdesc="BoringSSL is a fork of OpenSSL that is designed to meet Google's needs" -url="https://boringssl.googlesource.com/boringssl" -arch="all" -license="MIT" -replaces="openssl libressl" -depends="!openssl-libs-static" -makedepends_host="linux-headers" -makedepends="cmake git go perl" -subpackages="$pkgname-static $pkgname-dev $pkgname-doc" -source="251b516.tar.gz::https://github.com/google/boringssl/tarball/251b516" -builddir="$srcdir/google-boringssl-251b516" - -prepare() { - : -} - -build() { - cmake -DCMAKE_BUILD_TYPE=Release . - make ssl crypto -} - -check() { - make all_tests -} - -package() { - for i in *.md ; do - install -Dm644 $i "$pkgdir/usr/share/doc/$pkgname/$i" - done - install -d "$pkgdir/usr/lib" - install -d "$pkgdir/usr/include" - cp -R include/openssl "$pkgdir/usr/include" - - install -Dm755 crypto/libcrypto.a "$pkgdir/usr/lib/libcrypto.a" - install -Dm755 ssl/libssl.a "$pkgdir/usr/lib/libssl.a" -# install -Dm755 decrepit/libdecrepit.a "$pkgdir/usr/lib/libdecrepit.a" -# install -Dm755 libboringssl_gtest.a "$pkgdir/usr/lib/libboringssl_gtest.a" -} -sha512sums=" -b1d42ed188cf0cce89d40061fa05de85b387ee4244f1236ea488a431536a2c6b657b4f03daed0ac9328c7f5c4c9330499283b8a67f1444dcf9ba5e97e1199c4e 251b516.tar.gz -" diff --git a/docker/APKBUILD-lsquic b/docker/APKBUILD-lsquic deleted file mode 100644 index 51630a0..0000000 --- a/docker/APKBUILD-lsquic +++ /dev/null @@ -1,43 +0,0 @@ -# Maintainer: Omar Roth -pkgname=lsquic -pkgver=2.18.1 -pkgrel=0 -pkgdesc="LiteSpeed QUIC and HTTP/3 Library" -url="https://github.com/litespeedtech/lsquic" -arch="all" -license="MIT" -depends="boringssl-dev boringssl-static zlib-static libevent-static" -makedepends="cmake git go perl bsd-compat-headers linux-headers" -subpackages="$pkgname-static" -source="v$pkgver.tar.gz::https://github.com/litespeedtech/lsquic/tarball/v2.18.1 -ls-qpack-$pkgver.tar.gz::https://github.com/litespeedtech/ls-qpack/tarball/a8ae6ef -ls-hpack-$pkgver.tar.gz::https://github.com/litespeedtech/ls-hpack/tarball/bd5d589" -builddir="$srcdir/litespeedtech-$pkgname-692a910" - -prepare() { - cp -r -T "$srcdir/litespeedtech-ls-qpack-a8ae6ef" "$builddir/src/liblsquic/ls-qpack" - cp -r -T "$srcdir/litespeedtech-ls-hpack-bd5d589" "$builddir/src/lshpack" -} - -build() { - cmake \ - -DCMAKE_BUILD_TYPE=None \ - -DBORINGSSL_INCLUDE=/usr/include/openssl \ - -DBORINGSSL_LIB_crypto=/usr/lib \ - -DBORINGSSL_LIB_ssl=/usr/lib . - make lsquic -} - -check() { - make tests -} - -package() { - install -d "$pkgdir/usr/lib" - install -Dm755 src/liblsquic/liblsquic.a "$pkgdir/usr/lib/liblsquic.a" -} -sha512sums=" -d015a72f1e88750ecb364768a40f532678f11ded09c6447a2e698b20f43fa499ef143a53f4c92a5938dfece0e39e687dc9df4aea97c618faee0c63da771561c3 v2.18.1.tar.gz -c5629085a3881815fb0b72a321eeba8de093eff9417b8ac7bde1ee1264971be0dca6d61d74799b02ae03a4c629b2a9cf21387deeb814935339a8a2503ea33fee ls-qpack-2.18.1.tar.gz -1b9f7ce4c82dadfca8154229a415b0335a61761eba698f814d4b94195c708003deb5cb89318a1ab78ac8fa88b141bc9df283fb1c6e40b3ba399660feaae353a0 ls-hpack-2.18.1.tar.gz -" diff --git a/docker/haproxy/haproxy.cfg b/docker/haproxy/haproxy.cfg new file mode 100644 index 0000000..accdefa --- /dev/null +++ b/docker/haproxy/haproxy.cfg @@ -0,0 +1,31 @@ +global + stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners + log stdout format raw local0 notice + +defaults + log global + mode http + option httplog + option dontlognull + timeout connect 5s + timeout client 10s + timeout server 10s + timeout http-request 10s + + + +frontend invidious + log stdout format raw local0 emerg + bind *:80 + mode http + default_backend proxies + +backend proxies + balance leastconn + http-reuse always + http-request set-header Proxy-Authorization Basic\ base64_here + # Normal proxies go here + server proxy_normal_1 ipaddr_here check fall 3 rise 3 inter 15s observe layer7 error-limit 10 on-error mark-down sni hdr(host) + + # Add at least 1 backup proxy to shortly take over until the others have rotated their ips during YT blockage + server proxy_backup_1 ipaddr_here backup check fall 3 rise 3 inter 15s observe layer7 error-limit 10 on-error mark-down sni hdr(host) diff --git a/docker/pg-cleanup.sql b/docker/pg-cleanup.sql new file mode 100755 index 0000000..23b2d33 --- /dev/null +++ b/docker/pg-cleanup.sql @@ -0,0 +1,8 @@ +BEGIN; + +/* CLEAN DB FROM USERS NOT LONGER IN THE USERS TABLE */ +DELETE FROM session_ids WHERE email NOT IN (SELECT email FROM users); +DELETE FROM playlist_videos WHERE plid IN (SELECT id FROM playlists WHERE author NOT IN (SELECT email FROM users)); +DELETE FROM playlists WHERE author NOT IN (SELECT email FROM users); + +COMMIT; diff --git a/docker/pg-tune.sql b/docker/pg-tune.sql new file mode 100755 index 0000000..66a7bc2 --- /dev/null +++ b/docker/pg-tune.sql @@ -0,0 +1,42 @@ +-- DB Version: 14 +-- OS Type: linux +-- DB Type: oltp +-- Total Memory (RAM): 6 GB +-- CPUs num: 6 +-- Connections num: 200 +-- Data Storage: ssd + +ALTER SYSTEM SET + max_connections = '200'; +ALTER SYSTEM SET + shared_buffers = '1536MB'; +ALTER SYSTEM SET + effective_cache_size = '4608MB'; +ALTER SYSTEM SET + maintenance_work_mem = '384MB'; +ALTER SYSTEM SET + checkpoint_completion_target = '0.9'; +ALTER SYSTEM SET + wal_buffers = '16MB'; +ALTER SYSTEM SET + default_statistics_target = '100'; +ALTER SYSTEM SET + random_page_cost = '1.1'; +ALTER SYSTEM SET + effective_io_concurrency = '200'; +ALTER SYSTEM SET + work_mem = '2621kB'; +ALTER SYSTEM SET + huge_pages = 'off'; +ALTER SYSTEM SET + min_wal_size = '2GB'; +ALTER SYSTEM SET + max_wal_size = '8GB'; +ALTER SYSTEM SET + max_worker_processes = '6'; +ALTER SYSTEM SET + max_parallel_workers_per_gather = '3'; +ALTER SYSTEM SET + max_parallel_workers = '6'; +ALTER SYSTEM SET + max_parallel_maintenance_workers = '3'; diff --git a/docker/pgstat.sql b/docker/pgstat.sql new file mode 100644 index 0000000..c420cdb --- /dev/null +++ b/docker/pgstat.sql @@ -0,0 +1,31 @@ +-- Show psql cache hit statistics for prometheus exporter +with +all_tables as +( +SELECT * +FROM ( + SELECT 'all'::text as table_name, + sum( (coalesce(heap_blks_read,0) + coalesce(idx_blks_read,0) + coalesce(toast_blks_read,0) + coalesce(tidx_blks_read,0)) ) as from_disk, + sum( (coalesce(heap_blks_hit,0) + coalesce(idx_blks_hit,0) + coalesce(toast_blks_hit,0) + coalesce(tidx_blks_hit,0)) ) as from_cache + FROM pg_statio_USER_tables --> change to pg_statio_USER_tables if you want to check only user tables (excluding postgres's own tables) + ) a +WHERE (from_disk + from_cache) > 0 -- discard tables without hits +), +tables as +( +SELECT * +FROM ( + SELECT relname as table_name, + ( (coalesce(heap_blks_read,0) + coalesce(idx_blks_read,0) + coalesce(toast_blks_read,0) + coalesce(tidx_blks_read,0)) ) as from_disk, + ( (coalesce(heap_blks_hit,0) + coalesce(idx_blks_hit,0) + coalesce(toast_blks_hit,0) + coalesce(tidx_blks_hit,0)) ) as from_cache + FROM pg_statio_USER_tables --> change to pg_statio_USER_tables if you want to check only user tables (excluding postgres's own tables) + ) a +WHERE (from_disk + from_cache) > 0 -- discard tables without hits +) +SELECT table_name as "table name", + from_disk as "disk hits", + round((from_disk::numeric / (from_disk + from_cache)::numeric)*100.0,2) as "% disk hits", + round((from_cache::numeric / (from_disk + from_cache)::numeric)*100.0,2) as "% cache hits", + (from_disk + from_cache) as "total hits" +FROM (SELECT * FROM all_tables UNION ALL SELECT * FROM tables) a +ORDER BY (case when table_name = 'all' then 0 else 1 end), "total hits" desc; diff --git a/docker/userlist.txt b/docker/userlist.txt new file mode 100644 index 0000000..72cac7f --- /dev/null +++ b/docker/userlist.txt @@ -0,0 +1,2 @@ +"pgbouncer" "" +"invidious" "" diff --git a/invidious.service b/invidious.service new file mode 100644 index 0000000..0f93b24 --- /dev/null +++ b/invidious.service @@ -0,0 +1,18 @@ +[Unit] +Description=Invidious (%i) +After=syslog.target +After=network.target +Requires=docker.service +After=docker.service + +[Service] +Restart=always +RestartSec=15s +User=invidious +Group=invidious +WorkingDirectory=/srv/invidious +ExecStart=/usr/bin/docker compose --env-file .env --profile %i up +ExecStop=/usr/bin/docker compose --env-file .env --profile %i down + +[Install] +WantedBy=multi-user.target diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..e928907 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,71 @@ +user www-data; +worker_processes 8; +worker_rlimit_nofile 25000; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 4096; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + ssl_session_cache shared:nginx_TLS_default:4m; + client_max_body_size 2M; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + + ## + # SSL Settings + ## + + include snippets/tls12.conf; + include snippets/ocsp.conf; + ssl_dhparam /srv/letsencrypt/dhparam.pem; + + ## + # Logging Settings + ## + + access_log off; + error_log /dev/null emerg; + + + ## + # Maintenance mode + ## + + include snippets/invidious_access.conf; + + ## + # Gzip Settings + ## + + gzip on; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; + + limit_req_status 429; + limit_conn_status 429; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/sites-enabled/*.vhost; +} diff --git a/nginx/sites-enabled/invidious.vhost b/nginx/sites-enabled/invidious.vhost new file mode 100644 index 0000000..513428b --- /dev/null +++ b/nginx/sites-enabled/invidious.vhost @@ -0,0 +1,728 @@ +upstream invidious { + least_conn; + server 192.42.6.2:3000 fail_timeout=3s; + server 192.42.6.3:3000 fail_timeout=3s; + server 192.42.6.4:3000 fail_timeout=3s; + server 192.42.6.5:3000 fail_timeout=3s; + server 192.42.6.6:3000 fail_timeout=3s; + server 192.42.6.7:3000 fail_timeout=3s; + keepalive 30; +} + +upstream invidious_worker { + server 192.42.6.9:3000; + keepalive 1; +} + +upstream invidious_proxy { + server unix:/srv/ytproxy/http-proxy.sock; + keepalive 6; +} + +upstream invidious_tor { + server 192.42.6.8:3000; + keepalive 2; +} + +upstream ryd_proxy { + server 192.42.6.14:3000; + keepalive 3; +} + +upstream invidious_sync { + server 192.42.6.15:80; + keepalive 3; +} + +upstream invidious_peer { + server 192.42.6.19:9000; + keepalive 3; +} + +map $upstream_addr $server_instance { + default "invidious"; + 192.42.6.2:3000 "invidious_1"; + 192.42.6.3:3000 "invidious_2"; + 192.42.6.4:3000 "invidious_3"; + 192.42.6.5:3000 "invidious_4"; + 192.42.6.6:3000 "invidious_5"; + 192.42.6.7:3000 "invidious_6"; +} + +# I use this line to switch to a testing instance of invidious when I test a new docker image +geo $upstream_server { + default invidious; +} + +# Toggle to switch between using team piped ytproxy or invidious to server google asssets and video stream +geo $ivproxy_server { + default invidious_proxy; + #default invidious; +} + +# The api is for internal access only +geo $api_invidious_access { + default 0; + + # locahost + 127.0.0.1/32 1; + + # Docker network + 192.42.0.0/16 1; +} + +# Access to feeds is rate limited except for google ips +geo $feeds_whitelist { + default 0; + # CIDR in the list below are not limited + + # Google + 8.34.208.0/20 1; + 8.35.192.0/20 1; + 23.236.48.0/20 1; + 23.251.128.0/19 1; + 34.0.0.0/15 1; + 34.2.0.0/16 1; + 34.3.0.0/23 1; + 34.3.3.0/24 1; + 34.3.4.0/24 1; + 34.3.8.0/21 1; + 34.3.16.0/20 1; + 34.3.32.0/19 1; + 34.3.64.0/18 1; + 34.4.0.0/14 1; + 34.8.0.0/13 1; + 34.16.0.0/12 1; + 34.32.0.0/11 1; + 34.64.0.0/10 1; + 34.128.0.0/10 1; + 35.184.0.0/13 1; + 35.192.0.0/14 1; + 35.196.0.0/15 1; + 35.198.0.0/16 1; + 35.199.0.0/17 1; + 35.199.128.0/18 1; + 35.200.0.0/13 1; + 35.208.0.0/12 1; + 35.224.0.0/12 1; + 35.240.0.0/13 1; + 64.15.112.0/20 1; + 64.233.160.0/19 1; + 66.22.228.0/23 1; + 66.102.0.0/20 1; + 66.249.64.0/19 1; + 70.32.128.0/19 1; + 72.14.192.0/18 1; + 74.125.0.0/16 1; + 104.154.0.0/15 1; + 104.196.0.0/14 1; + 104.237.160.0/19 1; + 107.167.160.0/19 1; + 107.178.192.0/18 1; + 108.59.80.0/20 1; + 108.170.192.0/18 1; + 108.177.0.0/17 1; + 130.211.0.0/16 1; + 142.250.0.0/15 1; + 146.148.0.0/17 1; + 162.216.148.0/22 1; + 162.222.176.0/21 1; + 172.110.32.0/21 1; + 172.217.0.0/16 1; + 172.253.0.0/16 1; + 173.194.0.0/16 1; + 173.255.112.0/20 1; + 193.186.4.0/24 1; + 199.36.154.0/23 1; + 199.36.156.0/24 1; + 199.192.112.0/22 1; + 199.223.232.0/21 1; + 207.223.160.0/20 1; + 208.65.152.0/22 1; + 208.68.108.0/22 1; + 208.81.188.0/22 1; + 208.117.224.0/19 1; + 209.85.128.0/17 1; + 216.58.192.0/19 1; + 216.73.80.0/20 1; + 216.239.32.0/19 1; + 2001:4860::/32 1; + 2404:6800::/32 1; + 2404:f340::/32 1; + 2600:1900::/28 1; + 2606:73c0::/32 1; + 2607:f8b0::/32 1; + 2620:11a:a000::/40 1; + 2620:120:e000::/40 1; + 2800:3f0::/32 1; + 2a00:1450::/32 1; + 2c0f:fb50::/32 1; +} + +map $feeds_whitelist $feeds_limit { + 0 ""; + 1 $binary_remote_addr; +} + +# Limit login to prevent bruteforce +limit_req_zone $binary_remote_addr zone=invidious_login:5m rate=2r/s; + +# Limit global requests to prevent DoS +limit_req_zone $binary_remote_addr zone=invidious_requests:5m rate=5r/s; + +# Limit webfeed hook +limit_req_zone $feeds_limit zone=invidious_feeds:5m rate=3r/s; + +####### +# HTTP +####### + +server { + listen 80; + listen [::]:80; + + server_name invidious.nerdvpn.de www.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + location /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + alias /srv/nginx/public/acme/; + } + + location / { + return 301 https://invidious.nerdvpn.de$request_uri; + } +} +server { + listen 80; + listen [::]:80; + + server_name api.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + location /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + alias /srv/nginx/public/acme/; + } + + location / { + return 301 https://$host$request_uri; + } +} +server { + listen 80; + listen [::]:80; + + server_name ryd.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + location /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + alias /srv/nginx/public/acme/; + } + + location / { + return 301 https://$host$request_uri; + } +} +server { + listen 80; + listen [::]:80; + + server_name sync.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + location /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + alias /srv/nginx/public/acme/; + } + + location / { + return 301 https://$host$request_uri; + } +} +server { + listen 80; + listen [::]:80; + + server_name peer.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + location /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + alias /srv/nginx/public/acme/; + } + + location / { + return 301 https://$host$request_uri; + } +} + +######## +# HTTPS +######## + +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name api.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + ssl_session_cache shared:nginx_TLS_api_invidious:5m; + + add_header X-Robots-Tag "none"; + + ssl_certificate /srv/letsencrypt/certs/api.invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/api.invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + include snippets/malicious.conf; + + proxy_hide_header Access-Control-Allow-Origin; + add_header Access-Control-Allow-Credentials true; + add_header Access-Control-Allow-Origin "https://materialious.nerdvpn.de" always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE" always; + add_header Access-Control-Allow-Headers "User-Agent, Authorization, Content-Type" always; + + location = /favicon.ico { + allow all; + } + + location = /robots.txt { + add_header Content-Type text/plain; + return 200 "User-agent: *\nDisallow: /"; + } + + location = /css/default.css { + root /srv/nginx/public/invidious; + } + + limit_rate 6000k; + + location / { + if ($api_invidious_access != 1) { + return 302 "https://invidious.nerdvpn.de$request_uri"; + } + + if ($request_method = OPTIONS) { + return 204; + } + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + + proxy_pass http://$upstream_server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ~ /(css|js|fonts|login|authorize_token|api) { + if ($request_method = OPTIONS) { + return 204; + } + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + + proxy_pass http://$upstream_server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name ryd.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + ssl_session_cache shared:nginx_TLS_ryd:5m; + + add_header X-Robots-Tag "none"; + + ssl_certificate /srv/letsencrypt/certs/ryd.invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/ryd.invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + include snippets/malicious.conf; + + location = /favicon.ico { + allow all; + } + + location = /robots.txt { + add_header Content-Type text/plain; + return 200 "User-agent: *\nDisallow: /"; + } + + limit_rate 6000k; + + location / { + add_header Access-Control-Allow-Origin "https://materialious.nerdvpn.de" always; + add_header Access-Control-Allow-Methods "GET, OPTIONS" always; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://ryd_proxy; + } +} +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name sync.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + ssl_session_cache shared:nginx_TLS_sync:5m; + + add_header X-Robots-Tag "none"; + + ssl_certificate /srv/letsencrypt/certs/sync.invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/sync.invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + include snippets/malicious.conf; + + location = /favicon.ico { + allow all; + } + + location = /robots.txt { + add_header Content-Type text/plain; + return 200 "User-agent: *\nDisallow: /"; + } + + limit_rate 6000k; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://invidious_sync; + } +} +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name peer.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + ssl_session_cache shared:nginx_TLS_peer:5m; + + add_header X-Robots-Tag "none"; + + ssl_certificate /srv/letsencrypt/certs/peer.invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/peer.invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + include snippets/invidious-errorpages.conf; + + location = /favicon.ico { + allow all; + } + + location = /robots.txt { + add_header Content-Type text/plain; + return 200 "User-agent: *\nDisallow: /"; + } + + limit_rate 6000k; + + location / { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header "Connection" ""; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://invidious_peer; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name www.invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + ssl_certificate /srv/letsencrypt/certs/invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + location / { + return 301 https://invidious.nerdvpn.de$request_uri; + } +} + +server { + listen 443 ssl; + listen 443 default_server quic reuseport; + listen [::]:443 ssl; + listen [::]:443 default_server quic reuseport; + + http2 on; + http3 on; + quic_retry on; + + server_name invidious.nerdvpn.de; + root /srv/nginx/public/invidious; + + add_header Alt-Svc 'h3=":443"; ma=86400'; + add_header X-server-instance $server_instance always; + add_header X-server-proto $server_protocol always; + add_header Onion-Location http://nerdvpneaggggfdiurknszkbmhvjndks5z5k3g5yp4nhphflh3n3boad.onion$request_uri; + + ssl_session_cache shared:nginx_TLS_invidious:10m; + + add_header X-Robots-Tag "noindex, nofollow, noai, noimageai"; + + ssl_certificate /srv/letsencrypt/certs/invidious.nerdvpn.de/fullchain.pem; + ssl_certificate_key /srv/letsencrypt/certs/invidious.nerdvpn.de/privkey.pem; + include snippets/htst.conf; + + access_log off; + error_log /dev/null emerg; + + include snippets/malicious.conf; + + limit_rate 6000k; + + location = /favicon.ico { + include /etc/nginx/bots.d/ddos.conf; + allow all; + } + + location = /robots.txt { + add_header Content-Type text/plain; + return 200 "User-agent: GPTBot\nDisallow: /\nUser-agent: ChatGPT-User\nDisallow: /\nUser-agent: FacebookBot\nDisallow: /\nUser-agent: archive.org_bot\nDisallow: /\nUser-agent: Amazonbot\nDisallow: /\nUser-agent: *\nDisallow:"; + } + + error_page 503 @maintenance; + location @maintenance { + root /srv/nginx/public/maintenance/; + rewrite ^ "/index.html" break; + } + + location /api/v1/stats { + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://invidious/api/v1/stats; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ~ (^/feed/webhook) { + limit_req zone=invidious_feeds burst=240 delay=80; + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://invidious_worker; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location / { + limit_req zone=invidious_requests burst=240 delay=80; + + if ($invidious_access != 1) { + return 503; + } + if ($request_method = OPTIONS) { + return 204; + } + + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://$upstream_server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # video streaming and downloads + + location ~ (^/videoplayback) { + limit_req zone=invidious_requests burst=240 delay=80; + + # Limit user video streaming to 32 MBit/s + limit_rate 4000k; + + proxy_set_header X-Forwarded-For ""; + proxy_set_header CF-Connecting-IP ""; + proxy_hide_header "alt-svc"; + + sendfile on; + sendfile_max_chunk 512k; + tcp_nopush on; + aio threads=default; + aio_write on; + directio 16m; + + proxy_hide_header Cache-Control; + proxy_hide_header etag; + proxy_set_header Connection keep-alive; + proxy_max_temp_file_size 32m; + + proxy_http_version 1.1; + proxy_pass http://$ivproxy_server; + add_header Cache-Control private always; + } + # static assets from yt + location ~ (^/vi/|^/ggpht/) { + limit_req zone=invidious_requests burst=240 delay=80; + limit_rate 8000k; + + proxy_set_header X-Forwarded-For ""; + proxy_set_header CF-Connecting-IP ""; + proxy_hide_header "alt-svc"; + + sendfile on; + sendfile_max_chunk 512k; + tcp_nopush on; + aio threads=default; + aio_write on; + directio 16m; + + proxy_hide_header Cache-Control; + proxy_hide_header etag; + proxy_set_header Connection keep-alive; + proxy_max_temp_file_size 32m; + + proxy_http_version 1.1; + proxy_pass http://$ivproxy_server; + add_header Cache-Control private always; + } + location ~ /(login) { + limit_req zone=invidious_login burst=4 nodelay; + + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://$upstream_server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ~ /(css|js|fonts|delete_account|data_control|subscription_manager) { + limit_req zone=invidious_requests burst=240 delay=80; + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://$upstream_server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +server { + listen unix:/var/run/invidious-tor.sock; + + server_name nerdvpneaggggfdiurknszkbmhvjndks5z5k3g5yp4nhphflh3n3boad.onion; + root /srv/nginx/public/invidious; + + add_header X-server-instance $server_instance always; + + access_log off; + error_log /dev/null emerg; + + error_page 503 @maintenance; + location @maintenance { + root /srv/nginx/public/maintenance/; + rewrite ^ "/index.html" break; + } + + location / { + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_pass http://invidious_tor; + proxy_set_header Host $host; + } + # video streaming + location ~ (^/videoplayback) { + proxy_set_header X-Forwarded-For ""; + proxy_set_header CF-Connecting-IP ""; + proxy_hide_header "alt-svc"; + + sendfile on; + sendfile_max_chunk 512k; + tcp_nopush on; + aio threads=default; + aio_write on; + directio 16m; + + proxy_hide_header Cache-Control; + proxy_hide_header etag; + proxy_set_header Connection keep-alive; + proxy_max_temp_file_size 32m; + + proxy_http_version 1.1; + proxy_pass http://$ivproxy_server; + add_header Cache-Control private always; + } + # assets from yt + location ~ (^/vi/|^/ggpht/) { + proxy_set_header X-Forwarded-For ""; + proxy_set_header CF-Connecting-IP ""; + proxy_hide_header "alt-svc"; + + sendfile on; + sendfile_max_chunk 512k; + tcp_nopush on; + aio threads=default; + aio_write on; + directio 16m; + + proxy_hide_header Cache-Control; + proxy_hide_header etag; + proxy_set_header Connection keep-alive; + proxy_max_temp_file_size 32m; + + proxy_http_version 1.1; + proxy_pass http://$ivproxy_server; + add_header Cache-Control private always; + } +} diff --git a/nginx/snippets/dhparam.conf b/nginx/snippets/dhparam.conf new file mode 100644 index 0000000..a85b504 --- /dev/null +++ b/nginx/snippets/dhparam.conf @@ -0,0 +1 @@ +ssl_dhparam /srv/letsencrypt/dhparam/dhparam.pem; diff --git a/nginx/snippets/htst.conf b/nginx/snippets/htst.conf new file mode 100644 index 0000000..46ab48e --- /dev/null +++ b/nginx/snippets/htst.conf @@ -0,0 +1 @@ +add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; diff --git a/nginx/snippets/invidious_access.conf b/nginx/snippets/invidious_access.conf new file mode 100644 index 0000000..1fcfb1a --- /dev/null +++ b/nginx/snippets/invidious_access.conf @@ -0,0 +1,86 @@ +geo $invidious_access { + # To enable maintenance mode, set default to 0 to lock out all visitors not whitelisted below + default 1; + + # Always allow Google + 8.34.208.0/20 1; + 8.35.192.0/20 1; + 23.236.48.0/20 1; + 23.251.128.0/19 1; + 34.0.0.0/15 1; + 34.2.0.0/16 1; + 34.3.0.0/23 1; + 34.3.3.0/24 1; + 34.3.4.0/24 1; + 34.3.8.0/21 1; + 34.3.16.0/20 1; + 34.3.32.0/19 1; + 34.3.64.0/18 1; + 34.4.0.0/14 1; + 34.8.0.0/13 1; + 34.16.0.0/12 1; + 34.32.0.0/11 1; + 34.64.0.0/10 1; + 34.128.0.0/10 1; + 35.184.0.0/13 1; + 35.192.0.0/14 1; + 35.196.0.0/15 1; + 35.198.0.0/16 1; + 35.199.0.0/17 1; + 35.199.128.0/18 1; + 35.200.0.0/13 1; + 35.208.0.0/12 1; + 35.224.0.0/12 1; + 35.240.0.0/13 1; + 64.15.112.0/20 1; + 64.233.160.0/19 1; + 66.22.228.0/23 1; + 66.102.0.0/20 1; + 66.249.64.0/19 1; + 70.32.128.0/19 1; + 72.14.192.0/18 1; + 74.125.0.0/16 1; + 104.154.0.0/15 1; + 104.196.0.0/14 1; + 104.237.160.0/19 1; + 107.167.160.0/19 1; + 107.178.192.0/18 1; + 108.59.80.0/20 1; + 108.170.192.0/18 1; + 108.177.0.0/17 1; + 130.211.0.0/16 1; + 142.250.0.0/15 1; + 146.148.0.0/17 1; + 162.216.148.0/22 1; + 162.222.176.0/21 1; + 172.110.32.0/21 1; + 172.217.0.0/16 1; + 172.253.0.0/16 1; + 173.194.0.0/16 1; + 173.255.112.0/20 1; + 193.186.4.0/24 1; + 199.36.154.0/23 1; + 199.36.156.0/24 1; + 199.192.112.0/22 1; + 199.223.232.0/21 1; + 207.223.160.0/20 1; + 208.65.152.0/22 1; + 208.68.108.0/22 1; + 208.81.188.0/22 1; + 208.117.224.0/19 1; + 209.85.128.0/17 1; + 216.58.192.0/19 1; + 216.73.80.0/20 1; + 216.239.32.0/19 1; + 2001:4860::/32 1; + 2404:6800::/32 1; + 2404:f340::/32 1; + 2600:1900::/28 1; + 2606:73c0::/32 1; + 2607:f8b0::/32 1; + 2620:11a:a000::/40 1; + 2620:120:e000::/40 1; + 2800:3f0::/32 1; + 2a00:1450::/32 1; + 2c0f:fb50::/32 1; +} diff --git a/nginx/snippets/malicious.conf b/nginx/snippets/malicious.conf new file mode 100644 index 0000000..f262d46 --- /dev/null +++ b/nginx/snippets/malicious.conf @@ -0,0 +1,14 @@ +# Currently include a list of IPs originating from Russian and Chinese official bureau (i.e. Roskomnadzor) +if ($evilgovernments) { + return 451; +} + +if ($http_user_agent ~* LWP::Simple|BBBike|wget|jorgee) { + return 403; +} + +client_body_timeout 15s; +client_header_timeout 15s; +location ~* .(display_errors|set_time_limit|allow_url_include.*disable_functions.*open_basedir|set_magic_quotes_runtime|webconfig.txt.php|file_put_contentssever_root|wlwmanifest|encode|localhost|loopback|xmlrpc|revslider|roundcube|smtp|http\:|soap|w00tw00t) { + return 404; +} diff --git a/nginx/snippets/ocsp.conf b/nginx/snippets/ocsp.conf new file mode 100644 index 0000000..4f5f253 --- /dev/null +++ b/nginx/snippets/ocsp.conf @@ -0,0 +1,3 @@ +ssl_stapling on; +ssl_stapling_verify on; +ssl_trusted_certificate /srv/letsencrypt/ocsp/BPClass2CA2Bundle.pem; diff --git a/nginx/snippets/tls12.conf b/nginx/snippets/tls12.conf new file mode 100644 index 0000000..f659b16 --- /dev/null +++ b/nginx/snippets/tls12.conf @@ -0,0 +1,5 @@ +ssl_session_timeout 3h; +ssl_session_tickets off; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_prefer_server_ciphers off; diff --git a/patch.sh b/patch.sh new file mode 100755 index 0000000..da33ffc --- /dev/null +++ b/patch.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +cd build + +PATCHES=../patches/*.patch + +for p in $PATCHES; do + echo "applying ${p}..." + git apply --check $p && git apply $p +done + diff --git a/patches-api/0001-reuse-port-by-default.patch b/patches-api/0001-reuse-port-by-default.patch deleted file mode 100644 index beeb091..0000000 --- a/patches-api/0001-reuse-port-by-default.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 3ad7b342cb24b16f9885b18ee5321cd695ac7dcd Mon Sep 17 00:00:00 2001 -From: Emilien Devos -Date: Wed, 23 Feb 2022 23:48:17 +0100 -Subject: [PATCH 1/1] reuse port by default - ---- - src/invidious.cr | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/src/invidious.cr b/src/invidious.cr -index d4878759..5a2cdce0 100644 ---- a/src/invidious.cr -+++ b/src/invidious.cr -@@ -562,4 +562,8 @@ Kemal.config.app_name = "Invidious" - Kemal.config.env = "production" if !ENV.has_key?("KEMAL_ENV") - {% end %} - --Kemal.run -+Kemal.run do |config| -+ server = config.server.not_nil! -+ server.bind_tcp Kemal.config.host_binding, Kemal.config.port, reuse_port: true -+ server.bind_tcp Kemal.config.host_binding, 0 -+end -\ No newline at end of file --- -2.35.1 - diff --git a/patches/001-nerdvpn-branding.patch b/patches/001-nerdvpn-branding.patch new file mode 100644 index 0000000..03b8a42 --- /dev/null +++ b/patches/001-nerdvpn-branding.patch @@ -0,0 +1,29 @@ +diff --git a/src/invidious.cr b/src/invidious.cr +index e0bd0101..bf1a46d1 100644 +--- a/src/invidious.cr ++++ b/src/invidious.cr +@@ -75,9 +75,9 @@ REQUEST_HEADERS_WHITELIST = {"accept", "accept-encoding", "cache-control", "con + RESPONSE_HEADERS_BLACKLIST = {"access-control-allow-origin", "alt-svc", "server"} + HTTP_CHUNK_SIZE = 10485760 # ~10MB + +-CURRENT_BRANCH = {{ "#{`git branch | sed -n '/* /s///p'`.strip}" }} ++CURRENT_BRANCH = "nerdvpn" + CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }} +-CURRENT_VERSION = {{ "#{`git log -1 --format=%ci | awk '{print $1}' | sed s/-/./g`.strip}" }} ++CURRENT_VERSION = {{ "#{`date +%Y.%m.%d`.strip}" }} + + # This is used to determine the `?v=` on the end of file URLs (for cache busting). We + # only need to expire modified assets, so we can use this to find the last commit that changes +diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr +index 9904b4fc..14170acd 100644 +--- a/src/invidious/views/template.ecr ++++ b/src/invidious/views/template.ecr +@@ -147,7 +147,7 @@ +
+ + +- <%= translate(locale, "footer_donate_page") %> ++ <%= translate(locale, "footer_donate_page") %> + + <%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %> +
diff --git a/patches/002-2111.patch.disable b/patches/002-2111.patch.disable deleted file mode 100644 index 9ad24ac..0000000 --- a/patches/002-2111.patch.disable +++ /dev/null @@ -1,45 +0,0 @@ -From 78773d732672d8985795fb040a39dd7e946c7b7c Mon Sep 17 00:00:00 2001 -From: Emilien Devos -Date: Sat, 22 May 2021 17:42:23 +0200 -Subject: [PATCH] add the ability to listen on unix sockets - ---- - src/invidious.cr | 15 ++++++++++++--- - src/invidious/helpers/helpers.cr | 1 + - 2 files changed, 13 insertions(+), 3 deletions(-) - -diff --git a/src/invidious.cr b/src/invidious.cr -index ae20e13e5..65b1091bb 100644 ---- a/src/invidious.cr -+++ b/src/invidious.cr -@@ -3917,6 +3917,15 @@ add_context_storage_type(Preferences) - add_context_storage_type(User) - - Kemal.config.logger = LOGGER --Kemal.config.host_binding = Kemal.config.host_binding != "0.0.0.0" ? Kemal.config.host_binding : CONFIG.host_binding --Kemal.config.port = Kemal.config.port != 3000 ? Kemal.config.port : CONFIG.port --Kemal.run -+ -+Kemal.run do |config| -+ if CONFIG.bind_unix -+ if File.exists?(CONFIG.bind_unix.not_nil!) -+ File.delete(CONFIG.bind_unix.not_nil!) -+ end -+ config.server.not_nil!.bind_unix CONFIG.bind_unix.not_nil! -+ else -+ config.host_binding = config.host_binding != "0.0.0.0" ? config.host_binding : CONFIG.host_binding -+ config.port = config.port != 3000 ? config.port : CONFIG.port -+ end -+end -diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr -index e1d877b78..6a5789a0e 100644 ---- a/src/invidious/helpers/helpers.cr -+++ b/src/invidious/helpers/helpers.cr -@@ -98,6 +98,7 @@ class Config - property force_resolve : Socket::Family = Socket::Family::UNSPEC # Connect to YouTube over 'ipv6', 'ipv4'. Will sometimes resolve fix issues with rate-limiting (see https://github.com/ytdl-org/youtube-dl/issues/21729) - property port : Int32 = 3000 # Port to listen for connections (overrided by command line argument) - property host_binding : String = "0.0.0.0" # Host to bind (overrided by command line argument) -+ property bind_unix : String? = nil # Make Invidious listening on UNIX sockets - Example: /tmp/invidious.sock - property pool_size : Int32 = 100 # Pool size for HTTP requests to youtube.com and ytimg.com (each domain has a separate pool of `pool_size`) - property use_quic : Bool = true # Use quic transport for youtube api - diff --git a/patches/003-proxy-csp.patch b/patches/003-proxy-csp.patch index e3103d7..c6e8bf7 100644 --- a/patches/003-proxy-csp.patch +++ b/patches/003-proxy-csp.patch @@ -16,10 +16,10 @@ index 8e2a253f..5c7cc959 100644 # TODO: check if *.youtube.com can be removed if CONFIG.disabled?("local") || !preferences.local - extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443" -+ extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443 https://*.yewtu.be:443" ++ extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443 https://*.invidious.nerdvpn.de:443" else - extra_media_csp = "" -+ extra_media_csp = " https://*.yewtu.be:443" ++ extra_media_csp = " https://*.invidious.nerdvpn.de:443" end # Only allow the pages at /embed/* to be embedded @@ -28,7 +28,7 @@ index 8e2a253f..5c7cc959 100644 "img-src 'self' data:", "font-src 'self' data:", - "connect-src 'self'", -+ "connect-src 'self' https://*.yewtu.be:443", ++ "connect-src 'self' https://*.invidious.nerdvpn.de:443", "manifest-src 'self'", "media-src 'self' blob:" + extra_media_csp, "child-src 'self' blob:", diff --git a/patches/004-donate-page.patch b/patches/004-donate-page.patch index 6582850..ebe1c7f 100644 --- a/patches/004-donate-page.patch +++ b/patches/004-donate-page.patch @@ -46,7 +46,7 @@ index 3dbeaf37..d84d03d0 100644 @@ -1,4 +1,7 @@
+ -+ Donate ++ <%= translate(locale, "footer_donate_page") %> + <% feed_menu = env.get("preferences").as(Preferences).feed_menu.dup %> <% if !env.get?("user") %> @@ -56,42 +56,33 @@ new file mode 100644 index 00000000..0c7deecc --- /dev/null +++ b/src/invidious/views/donate.ecr -@@ -0,0 +1,34 @@ +@@ -0,0 +1,26 @@ +<% content_for "header" do %> +Donate - Invidious +<% end %> + +
-+

Donate to YewTube

++

Donate to NerdVPN.de

+

Why donating?

-+

Every day YewTube handles 40 million requests and 2TB (2000GB) of bandwidth. All this traffic has a cost.

-+

By donating, you are helping me cover the costs and also improve the user experience because I'll be able to rent more powerful servers.

-+

I truly thank you if you consider donating to YewTube.

++

In order to provide the best Invidious experience possible, I have rented a very powerful dedicated server with 16 cores, 32 GiB RAM and a 2 GBit/s unmetered network connection. I also use 3 different, fully e2e-encrypted backup locations with a zero-dataloss backup strategy. All of this has its cost.

++

All donations will go directly into paying my monthly infrastructure costs (server, domain, backups).

++

Should the donations at any point fully cover the costs, I will invest the remaining directly into improving my infrastructure and thus improving the user experience.

++

Every little bit helps. Thank you very much for considering to donate!

+

Payment methods

-+

Credit/debit card and bank transfer

++

PayPal, credit/debit card and bank transfer

+ +

Cryptocurrency

++

I currently don't support donations via cryptocurrency, but you can donate to the team behind Invidious directly, if you'd like. I'm sure they'd love the support.

+ -+

Is your prefered payment method not listed?

-+

Feel free to ask me by email at donate[at]yewtu.be to add it.

++

If you have any questions, feel free to contact me via matrix at: @weidenwiesel:matrix.org

+
\ No newline at end of file -- -2.36.1 - +2.26.1 diff --git a/patches/005-decrease-buffer-seconds-for-saving-bandwidth.patch.disabled b/patches/005-decrease-buffer-seconds-for-saving-bandwidth.patch.disabled deleted file mode 100644 index 35457f9..0000000 --- a/patches/005-decrease-buffer-seconds-for-saving-bandwidth.patch.disabled +++ /dev/null @@ -1,26 +0,0 @@ -From a0c481387a701c85b0a1b70af3d642e3fe375e1f Mon Sep 17 00:00:00 2001 -From: Emilien Devos -Date: Fri, 10 Jun 2022 17:38:49 +0200 -Subject: [PATCH 1/1] decrease buffer seconds for saving bandwidth - ---- - assets/js/player.js | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/assets/js/player.js b/assets/js/player.js -index 7d099e66..02c8ae26 100644 ---- a/assets/js/player.js -+++ b/assets/js/player.js -@@ -50,6 +50,9 @@ videojs.Vhs.xhr.beforeRequest = function(options) { - return options; - }; - -+videojs.Vhs.GOAL_BUFFER_LENGTH = 20; -+videojs.Vhs.MAX_GOAL_BUFFER_LENGTH = 30; -+ - var player = videojs('player', options); - - player.on('error', function () { --- -2.36.1 - diff --git a/patches/008-have-video-data-separated-per-node-name.patch.disable b/patches/008-have-video-data-separated-per-node-name.patch.disable deleted file mode 100644 index 25669d4..0000000 --- a/patches/008-have-video-data-separated-per-node-name.patch.disable +++ /dev/null @@ -1,65 +0,0 @@ -From f3f1b080fc0a6a0414cff988cf60f155dc13df15 Mon Sep 17 00:00:00 2001 -From: Emilien Devos -Date: Thu, 13 Oct 2022 19:18:55 +0200 -Subject: [PATCH 1/1] have video data separated per node name - ---- - src/invidious/database/videos.cr | 8 ++++---- - src/invidious/videos.cr | 2 +- - 2 files changed, 5 insertions(+), 5 deletions(-) - -diff --git a/src/invidious/database/videos.cr b/src/invidious/database/videos.cr -index 695f5b33..2524f167 100644 ---- a/src/invidious/database/videos.cr -+++ b/src/invidious/database/videos.cr -@@ -10,7 +10,7 @@ module Invidious::Database::Videos - ON CONFLICT (id) DO NOTHING - SQL - -- PG_DB.exec(request, video.id, video.info.to_json, video.updated) -+ PG_DB.exec(request, video.id + "-" + ENV.fetch("NODE_NAME", ""), video.info.to_json, video.updated) - end - - def delete(id) -@@ -19,7 +19,7 @@ module Invidious::Database::Videos - WHERE id = $1 - SQL - -- PG_DB.exec(request, id) -+ PG_DB.exec(request, id + "-" + ENV.fetch("NODE_NAME", "")) - end - - def delete_expired -@@ -38,7 +38,7 @@ module Invidious::Database::Videos - WHERE id = $1 - SQL - -- PG_DB.exec(request, video.id, video.info.to_json, video.updated) -+ PG_DB.exec(request, video.id + "-" + ENV.fetch("NODE_NAME", ""), video.info.to_json, video.updated) - end - - def select(id : String) : Video? -@@ -47,6 +47,6 @@ module Invidious::Database::Videos - WHERE id = $1 - SQL - -- return PG_DB.query_one?(request, id, as: Video) -+ return PG_DB.query_one?(request, id + "-" + ENV.fetch("NODE_NAME", ""), as: Video) - end - end -diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr -index c0ed6e85..36f5a0e0 100644 ---- a/src/invidious/videos.cr -+++ b/src/invidious/videos.cr -@@ -1117,7 +1117,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) - video = fetch_video(id, region) - Invidious::Database::Videos.insert(video) if !region - end -- -+ video.id = id - return video - rescue DB::Error - # Avoid common `DB::PoolRetryAttemptsExceeded` error and friends --- -2.38.0 - diff --git a/patches/010-use-redis-for-video-cache.patch b/patches/010-use-redis-for-video-cache.patch index 57a8b01..8c208de 100644 --- a/patches/010-use-redis-for-video-cache.patch +++ b/patches/010-use-redis-for-video-cache.patch @@ -69,7 +69,7 @@ index 3804197e..e2d40597 100644 -PG_DB = DB.open CONFIG.database_url +PG_DB = DB.open CONFIG.database_url -+REDIS_DB = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil) ++REDIS_DB = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil, database: CONFIG.redis_db || 0, password: CONFIG.redis_auth || nil) + +if REDIS_DB.ping + puts "Connected to redis" @@ -81,12 +81,14 @@ diff --git a/src/invidious/config.cr b/src/invidious/config.cr index c4ddcdb3..5b6c576c 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr -@@ -77,6 +77,9 @@ class Config +@@ -77,6 +77,11 @@ class Config # Used for crawling channels: threads should check all videos uploaded by a channel property full_refresh : Bool = false + property redis_url : String? + property redis_socket : String? ++ property redis_auth : String? ++ property redis_db : Int32? + # Jobs config structure. See jobs.cr and jobs/base_job.cr property jobs = Invidious::Jobs::JobsConfig.new diff --git a/patches/011-2469.patch b/patches/011-2469.patch index 639caee..7524596 100644 --- a/patches/011-2469.patch +++ b/patches/011-2469.patch @@ -6,7 +6,6 @@ Subject: [PATCH] limit feeds and delete materialized views --- config/migrate-scripts/migrate-db-8bc91ce.sh | 6 ++ config/sql/channel_videos.sql | 8 +-- - kubernetes/values.yaml | 60 ++++++++++++++++ src/invidious.cr | 12 ---- src/invidious/config.cr | 2 - src/invidious/jobs/refresh_feeds_job.cr | 75 -------------------- @@ -14,9 +13,8 @@ Subject: [PATCH] limit feeds and delete materialized views src/invidious/routes/login.cr | 3 - src/invidious/search/processors.cr | 18 ++--- src/invidious/users.cr | 39 +++++----- - 10 files changed, 101 insertions(+), 124 deletions(-) + 9 files changed, 41 insertions(+), 124 deletions(-) create mode 100644 config/migrate-scripts/migrate-db-8bc91ce.sh - create mode 100644 kubernetes/values.yaml delete mode 100644 src/invidious/jobs/refresh_feeds_job.cr diff --git a/config/migrate-scripts/migrate-db-8bc91ce.sh b/config/migrate-scripts/migrate-db-8bc91ce.sh @@ -53,72 +51,6 @@ index cd4e0ffdb..f2ac4876c 100644 - (ucid COLLATE pg_catalog."default"); + (ucid COLLATE pg_catalog."default", published); -diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml -new file mode 100644 -index 000000000..17d69b5d7 ---- /dev/null -+++ b/kubernetes/values.yaml -@@ -0,0 +1,60 @@ -+name: invidious -+ -+image: -+ repository: quay.io/invidious/invidious -+ tag: latest -+ pullPolicy: Always -+ -+replicaCount: 1 -+ -+autoscaling: -+ enabled: false -+ minReplicas: 1 -+ maxReplicas: 16 -+ targetCPUUtilizationPercentage: 50 -+ -+service: -+ type: ClusterIP -+ port: 3000 -+ #loadBalancerIP: -+ -+resources: {} -+ #requests: -+ # cpu: 100m -+ # memory: 64Mi -+ #limits: -+ # cpu: 800m -+ # memory: 512Mi -+ -+securityContext: -+ allowPrivilegeEscalation: false -+ runAsUser: 1000 -+ runAsGroup: 1000 -+ fsGroup: 1000 -+ -+# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql -+postgresql: -+ image: -+ tag: 13 -+ auth: -+ username: kemal -+ password: kemal -+ database: invidious -+ primary: -+ initdb: -+ username: kemal -+ password: kemal -+ scriptsConfigMap: invidious-postgresql-init -+ -+# Adapted from ../config/config.yml -+config: -+ channel_threads: 1 -+ db: -+ user: kemal -+ password: kemal -+ host: invidious-postgresql -+ port: 5432 -+ dbname: invidious -+ full_refresh: false -+ https_only: false -+ domain: diff --git a/src/invidious.cr b/src/invidious.cr index 3804197e3..961ae8721 100644 --- a/src/invidious.cr diff --git a/patches/013-increase-db-timeout.patch b/patches/013-increase-db-timeout.patch new file mode 100644 index 0000000..ff3f72e --- /dev/null +++ b/patches/013-increase-db-timeout.patch @@ -0,0 +1,13 @@ +diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr +index 0ac785e6..c71117f0 100644 +--- a/src/invidious/yt_backend/connection_pool.cr ++++ b/src/invidious/yt_backend/connection_pool.cr +@@ -19,7 +19,7 @@ struct YoutubeConnectionPool + property! timeout : Float64 + property pool : DB::Pool(HTTP::Client) + +- def initialize(url : URI, @capacity = 5, @timeout = 5.0) ++ def initialize(url : URI, @capacity = 5, @timeout = 12.0) + @url = url + @pool = build_pool() + end diff --git a/patches/020-use-separate-domain-for-webhook-requests.patch b/patches/020-use-separate-domain-for-webhook-requests.patch deleted file mode 100644 index 522c00d..0000000 --- a/patches/020-use-separate-domain-for-webhook-requests.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 99e3e9f420cfcaddd98ea3b7fbe93d2d8db1a324 Mon Sep 17 00:00:00 2001 -From: Emilien Devos -Date: Sat, 4 Mar 2023 18:48:06 +0100 -Subject: [PATCH 1/1] use separate domain for webhook requests - ---- - src/invidious/helpers/utils.cr | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr -index 500a2582..7f6e7691 100644 ---- a/src/invidious/helpers/utils.cr -+++ b/src/invidious/helpers/utils.cr -@@ -291,7 +291,7 @@ def subscribe_pubsub(topic, key) - signature = "#{time}:#{nonce}" - - body = { -- "hub.callback" => "#{HOST_URL}/feed/webhook/v1:#{time}:#{nonce}:#{OpenSSL::HMAC.hexdigest(:sha1, key, signature)}", -+ "hub.callback" => "https://webhook.yewtu.be/feed/webhook/v1:#{time}:#{nonce}:#{OpenSSL::HMAC.hexdigest(:sha1, key, signature)}", - "hub.topic" => "https://www.youtube.com/xml/feeds/videos.xml?#{topic}", - "hub.verify" => "async", - "hub.mode" => "subscribe", --- -2.39.2 - diff --git a/patches/021-message-registration.patch b/patches/021-message-registration.patch deleted file mode 100644 index bc9c6ad..0000000 --- a/patches/021-message-registration.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 943effeb4dc0235ae825db3df59843771f3f2106 Mon Sep 17 00:00:00 2001 -From: Emilien Devos <4016501+unixfox@users.noreply.github.com> -Date: Tue, 15 Aug 2023 00:00:45 +0200 -Subject: [PATCH 1/1] message registration disabled - ---- - src/invidious/routes/login.cr | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr -index add9f75d..9fb21dfa 100644 ---- a/src/invidious/routes/login.cr -+++ b/src/invidious/routes/login.cr -@@ -73,7 +73,7 @@ module Invidious::Routes::Login - end - else - if !CONFIG.registration_enabled -- return error_template(400, "Registration has been disabled by administrator.") -+ return error_template(400, "Please read why you can't use an account anymore: https://gist.github.com/yewtudotbe/c16a69ddad88a37c2a364a5ff5359197") - end - - if password.empty? --- -2.41.0 - diff --git a/revert.sh b/revert.sh new file mode 100755 index 0000000..ea8ff92 --- /dev/null +++ b/revert.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cd build + +git checkout . +git restore . +git reset +git clean -fd diff --git a/scripts/deploy-database.sh b/scripts/deploy-database.sh new file mode 100755 index 0000000..f61113c --- /dev/null +++ b/scripts/deploy-database.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +# +# Parameters +# + +interactive=true + +if [ "$1" = "--no-interactive" ]; then + interactive=false +fi + +# +# Enable and start Postgres +# + +sudo systemctl start postgresql.service +sudo systemctl enable postgresql.service + +# +# Create databse and user +# + +if [ "$interactive" = "true" ]; then + sudo -u postgres -- createuser -P invidious + sudo -u postgres -- createdb -O invidious invidious +else + # Generate a DB password + if [ -z "$POSTGRES_PASS" ]; then + echo "Generating database password" + POSTGRES_PASS=$(tr -dc 'A-Za-z0-9.;!?{[()]}\\/' < /dev/urandom | head -c16) + fi + + # hostname:port:database:username:password + echo "Writing .pgpass" + echo "127.0.0.1:*:invidious:invidious:${POSTGRES_PASS}" > "$HOME/.pgpass" + + sudo -u postgres -- psql -c "CREATE USER invidious WITH PASSWORD '$POSTGRES_PASS';" + sudo -u postgres -- psql -c "CREATE DATABASE invidious WITH OWNER invidious;" + sudo -u postgres -- psql -c "GRANT ALL ON DATABASE invidious TO invidious;" +fi + + +# +# Instructions for modification of pg_hba.conf +# + +if [ "$interactive" = "true" ]; then + echo + echo "-------------" + echo " NOTICE " + echo "-------------" + echo + echo "Make sure that your postgreSQL's pg_hba.conf file contains the follwong" + echo "lines before previous 'host' configurations:" + echo + echo "host invidious invidious 127.0.0.1/32 md5" + echo "host invidious invidious ::1/128 md5" + echo +fi diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..fbe7ab4 --- /dev/null +++ b/update.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -ex + +cd ./build + +UPSTREAM=${1:-'@{u}'} +LOCAL=$(git rev-parse @) +REMOTE=$(git rev-parse "$UPSTREAM") +BASE=$(git merge-base @ "$UPSTREAM") + +cd .. + +if [ $LOCAL = $REMOTE ]; then + echo "Up-to-date" +elif [ $LOCAL = $BASE ]; then + echo "Update available" + ./revert.sh + ./patch.sh + ./build.sh + sudo systemctl restart invidious@worker.service + sudo systemctl restart invidious@web.service + sudo systemctl restart invidious@tor.service +else + echo "Diverged" +fi +