From 2bd6b7f44e41d688116d64f768ecf063c83d5df8 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Thu, 13 Mar 2025 05:20:42 +0000 Subject: [PATCH] finished implementing script and update README. --- README.md | 34 +++++- scripts/cloudflared.sh | 84 +++++++++++++ scripts/install.sh | 263 +++++++++++++---------------------------- 3 files changed, 197 insertions(+), 184 deletions(-) create mode 100755 scripts/cloudflared.sh mode change 100644 => 100755 scripts/install.sh diff --git a/README.md b/README.md index 72b6c49..7849613 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ -# ras-pi -A comprehensive, end-to-end guide to the fundamentals of coding, network protocols, Linux, and the Raspberry Pi. +

ras-pi

+A comprehensive, end-to-end guide to the fundamentals of coding, network +protocols, Linux, and the Raspberry Pi. + +

Getting Started

+Run the following commands on a fresh installation on a Raspberry Pi: +``` +sudo apt install git; +git clone https://github.com/SLCPL-CreativeLab/ras-pi; +cd ras-pi/scripts; +sudo chmod a+x install.sh; +sudo ./install.sh; + +# If you want to expose services to the internet, run this command as well. +sudo ./cloudflared.sh; +``` + +

Expose Services to the Internet

+To enable cloudflared to expose services to the internet, you must + - Make a Cloudflare account & purchase a domain; + - Run the command `cloudflared tunnel login`; + - Run the command ./cloudflared.sh in ~/ras-pi/scripts; + +To temporarily expose a single service to the internet, use this +command: `cloudflared tunnel --url=http://localhost:PORT`, where +PORT is the service you'd like to expose. The ports used with the +standard services are: + - 5090 (the manual mirror of raspi.vintagecoding.net) + - 5091 (the AI web application) + - 5092 (the personal Netflix replacement) + +See `cloudflared.md` for more information. diff --git a/scripts/cloudflared.sh b/scripts/cloudflared.sh new file mode 100755 index 0000000..3d76fc9 --- /dev/null +++ b/scripts/cloudflared.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +DOMAIN="" +MANUAL_PORT=5090 +JELLYFIN_PORT=5091 +OLLAMA_PORT=5092 + +# This function initializes cloudflared. +# TODO: This needs to dynamically insert based upon what the user has entered. +init_cloudflared() { + read -p "What is your domain name? e.g., vintagecoding.net: " DOMAIN; + + echo "export TUNNEL_ORIGIN_CERT=$HOME/.cloudflared/cert.pem" >> $HOME/.bashrc; + source $HOME/.bashrc; + + cloudflared tunnel create raspi; + TUNNEL_ID=$(cloudflared tunnel list | awk '/raspi/ {print $1}'); + + cd; + echo "tunnel: $TUNNEL_ID +credentials-file: $(pwd)/.cloudflared/$TUNNEL_ID.json +ingress: + - hostname: raspi.$DOMAIN + service: http://localhost:$MANUAL_PORT + - hostname: media.$DOMAIN + service: http://localhost:$JELLYFIN_PORT + - hostname: ai.$DOMAIN + service: http://localhost:$OLLAMA_PORT + - service: http_status:404" > $(pwd)/.cloudflared/config.yaml; + + cloudflared tunnel route dns $TUNNEL_ID raspi.$DOMAIN; + cloudflared tunnel route dns $TUNNEL_ID media.$DOMAIN; + cloudflared tunnel route dns $TUNNEL_ID ai.$DOMAIN; + + ACTUAL_USER=$(whoami) + + echo "[Unit] +Description=Cloudflare Tunnel Daemon +After=network.target + +[Service] +Type=simple +User=$ACTUAL_USER +ExecStart=/usr/bin/cloudflared tunnel run raspi +Restart=always + +[Install] +WantedBy=multi-user.target" | sudo tee /etc/systemd/system/cloudflared.service; + + sudo systemctl enable cloudflared.service; + sudo systemctl start cloudflared.service; + + source ~/.bashrc; + + sudo sed -i -e 's/server_name localhost/servername ai.$DOMAIN/' /etc/nginx/sites-available/open-webui; + + kill -9 $(pgrep open-webui); + cd; + source open-webui-env/bin/activate; + + read -p "Do you want to require login to your AI? (n/Y)" REQUIRE_LOGIN + REQUIRE_LOGIN=$(echo "$REQUIRE_LOGIN" | tr '[:upper:]' '[:lower:]') + if [[ -z "$REQUIRE_LOGIN" || "$REQUIRE_LOGIN" == "n" ]]; then + export WEBUI_AUTH=False; + fi + + export OLLAMA_BASE_URL=http://localhost:11434; + export WEBUI_BASE_URL=ai.$DOMAIN; + open-webui serve --port $OLLAMA_PORT & +} + +read -p "Has the command 'cloudflared tunnel login' been executed? (y/N): " LOGGED_IN +LOGGED_IN=$(echo "$LOGGED_IN" | tr '[:upper:]' '[:lower:]') +if [[ -z "$LOGGED_IN" || "$LOGGED_IN" == "y" ]]; then + init_cloudflared; +else + echo "You can still use Cloudflare for free, by generating random URLs." + echo "Enter the following into a terminal:"; + echo " cloudflared tunnel --url=http://localhost:PORT"; + echo "to get a URL a process, for example. Replace PORT with one of the following:"; + echo " $MANUAL_PORT (The manual mirror's port.)"; + echo " $JELLYFIN_PORT (The Jellyfin port.)"; + echo " $OLLAMA_PORT (The Ollama & Open Web-UI port.)"; +fi diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 index 4a75405..5044d01 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # This script gets a Raspberry Pi setup with essential applications. # # For explanations of commands, visit raspi.vintagecoding.net or use Google. @@ -9,47 +11,6 @@ MANUAL_PORT=5090 JELLYFIN_PORT=5091 OLLAMA_PORT=5092 -help() { - echo "This script gets a Raspberry Pi setup with essential applications."; - echo "By default, everything will be installed. But, there are some flags"; - echo "available for the end user. Multiple flags can be used together."; - echo ""; - echo ""; - echo ""; - echo "If you're new to the command line, the structure of these help"; - echo "pages is often like this:"; - echo " command { --full-name-of-flag | -f }"; - echo "Where, the curly braces indicates a grouping of options, typically"; - echo "the full name of a flag, | (or) the short flag."; - echo "Therefore, either of the following commands are valid:"; - echo " command --full-name-of-flag"; - echo "or"; - echo " command -f"; - echo "Furthermore, different flags can be chained together. e.g.,"; - echo " command --flag-1 --flag-2"; - echo ""; - echo ""; - echo ""; - echo "If you're new, checkout the Raspberry Pi manual available at"; - echo "raspi.vintagecoding.net."; - echo ""; - echo ""; - echo ""; - echo "NOTE: This script CANNOT be copied to /usr/local/bin, or anywhere on"; - echo "the PATH environment variable. That interferes with the system-level"; - echo "install command."; - echo ""; - echo ""; - echo ""; - echo "Usage:"; - echo " ./install.sh { --help | -h }"; - echo ""; - echo ""; - echo ""; - echo "The user will be prompted for each feature beyond the the barebones"; - echo "system utilities installation."; -} - # This function initializes core services and packages for getting started. init() { # Update repositories and upgrade installed packages. @@ -58,44 +19,69 @@ init() { # Install requisite packages for cloudflared. sudo apt install curl lsb-release; - # Downloads the GPG key (a package integrity technology). - curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee \ - /usr/share/keyrings/cloudflare-archive-keyring.gpg > /dev/null; + # Add cloudflare gpg key + sudo mkdir -p --mode=0755 /usr/share/keyrings + curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null - # Add the GPG key to the allowed repositories to download packages from. - echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive.keyring.gpg] \ - https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \ - | sudo tee /etc/apt/sources.list.d/cloudflared.list; + # Add this repo to your apt repositories + echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list + + # install cloudflared + sudo apt-get update && sudo apt-get install cloudflared # Install cloudflared. # Install podman, a container technology for easily deploying projects. # Install golang, a server-side programming language. # Install tools for the user - sudo apt update && sudo apt install nginx cloudflared podman golang tldr neovim; + sudo apt update; + sudo apt install cloudflared podman golang tldr neovim; + + # Get user input for some default applications to get started. + read -p "Do you want a mirror of the manual from raspi.vintagecoding.net on this device? (y/N): " MANUAL; + MANUAL=$(echo "$MANUAL" | tr '[:upper:]' '[:lower:]') + if [[ -z "$MANUAL" || "$MANUAL" == "n" ]]; then + MANUAL="n" + else + init_manual; + fi + + read -p "Do you want to setup Jellyfin, a personal Netflix alternative? (y/N): " JELLYFIN; + JELLYFIN=$(echo "$JELLYFIN" | tr '[:upper:]' '[:lower:]') + if [[ -z "$JELLYFIN" || "$JELLYFIN" == "n" ]]; then + JELLYFIN="n" + else + init_jellyfin; + fi + + read -p "Do you want to setup Ollama, a personal ChatGPT alternative? (y/N): " OLLAMA; + OLLAMA=$(echo "$OLLAMA" | tr '[:upper:]' '[:lower:]') + if [[ -z "$OLLAMA" || "$OLLAMA" == "n" ]]; then + OLLAMA="n" + else + init_ollama; + fi } # This function initializes the manual mirror of raspi.vintagecoding.net init_manual() { cd ~/ras-pi/man/pages; - sudo echo " - [Unit] - Description=Raspberry Pi Manual Mirror - After=network.target - StartLimitIntervalSec=0 + echo "[Unit] +Description=Raspberry Pi Manual Mirror +After=network.target +StartLimitIntervalSec=0 - [Service] - Type=simple - Restart=always - RestartSec=1 - User=$(whoami) - WorkingDirectory=$(pwd) - ExecStartPre=/usr/bin/git pull - ExecStart=/usr/bin/python3 -m http.server $MANUAL_PORT +[Service] +Type=simple +Restart=always +RestartSec=1 +User=$(whoami) +WorkingDirectory=$(pwd) +ExecStartPre=/usr/bin/git pull +ExecStart=/usr/bin/python3 -m http.server $MANUAL_PORT - [Install] - WantedBy=multi-user.target - " > /etc/systemd/system/manual.service; +[Install] +WantedBy=multi-user.target" | sudo tee /etc/systemd/system/manual.service; sudo systemctl enable manual.service; sudo systemctl start manual.service; @@ -105,7 +91,7 @@ init_manual() { # This function initializes Jellyfin. init_jellyfin() { # Get the official image of Jellyfin software - podman pull jellyfin/jellyfin; + podman pull ghcr.io/jellyfin/jellyfin; echo "Making media directories where Jellyfin will retrieve content."; echo "It is up to you to provide supported files and build a library."; @@ -119,12 +105,11 @@ init_jellyfin() { --name myjellyfin \ --publish $JELLYFIN_PORT:8096/tcp \ --user $(id -u):$(id -g) \ - --userns keep-id \ --volume jellyfin-cache:/cache:Z \ --volume jellyfin-config:/config:Z \ --mount type=bind,source=/$(pwd)/media,destination=/media,ro=true,relabel=private \ --restart always \ - jellyfin + ghcr.io/jellyfin/jellyfin; echo "Finished creating the Jellyfin container! It is now live on"; echo "http://localhost:$JELLYFIN_PORT. If you enabled it, it'll also be"; @@ -134,130 +119,44 @@ init_jellyfin() { # This function initializes Ollama. init_ollama() { echo "This will take a while..."; - cd; + curl -fsSL https://ollama.com/install.sh | sh; + echo "export OLLAMA_BASE_URL=http://localhost:11434; +export WEBUI_AUTH=False;" >> $HOME/.bashrc; + source $HOME/.bashrc + + # TODO: Write a script so this can be daemonized. python -m venv open-webui-env; source open-webui-env/bin/activate pip install open-webui; - echo " - export OLLAMA_BASE_URL=http://localhost:11434; - export WEBUI_BASE_URL=ai.$DOMAIN; - export WEBUI_AUTH=False; - " >> $(pwd)/.bashrc; - open-webui serve --port $OLLAMA_PORT &; - curl -fsSL https://ollama.com/install.sh | sh; -} + echo "server { + listen 80; #or whatever port your open-webui backend is running on. + server_name localhost; -# This function initializes cloudflared. -# TODO: This needs to dynamically insert based upon what the user has entered. -init_cloudflared() { - cloudflared tunnel login; - cloudflared tunnel create raspi; - TUNNEL_ID=$(cloudflared tunnel list | awk '/raspi/ {print $1}'); - read "?What is your domain name? e.g., vintagecoding.net" $DOMAIN; + location / { + proxy_pass http://localhost:$OLLAMA_PORT/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } - cd; - echo " - tunnel: $TUNNEL_ID - credentials-file: $(pwd)/.cloudflared/$TUNNEL_ID.json - ingress: - - hostname: raspi.$DOMAIN - service: http://localhost:$MANUAL_PORT - - hostname: media.$DOMAIN - service: http://localhost:$JELLYFIN_PORT - - hostname: ai.$DOMAIN - service: http://localhost:$OLLAMA_PORT - - service: http_status:404 - - " > $(pwd)/.cloudflared/config.yaml; - - cloudflared tunnel route dns $TUNNEL_ID raspi.$DOMAIN; - cloudflared tunnel route dns $TUNNEL_ID media.$DOMAIN; - cloudflared tunnel route dns $TUNNEL_ID ai.$DOMAIN; - - echo " - [Unit] - Description=Cloudflare Tunnel Daemon - After=network.target - - [Service] - Type=simple - User=$(whoami) - ExecStart=/usr/bin/cloudflared tunnel run raspi - Restart=always - - [Install] - WantedBy=multi-user.target - " > /etc/systemd/system/cloudflared.service; - - sudo systemctl enable cloudflared.service; - sudo systemctl start cloudflared.service; - - sudo echo " - server { - listen 80; #or whatever port your open-webui backend is running on. - server_name ai.$DOMAIN; - - location /api/ { - proxy_pass http://localhost:$OLLAMA_PORT/api/; # Ensure the trailing slash is present - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - location / { - proxy_pass http://localhost:$OLLAMA_PORT/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - -} - " > /etc/nginx/sites-available/open-webui; + location /api/ { + proxy_pass http://localhost:$OLLAMA_PORT/api/; # Ensure the trailing slash is present + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +}" | sudo tee/etc/nginx/sites-available/open-webui; sudo ln -s /etc/nginx/sites-available/open-webui /etc/nginx/sites-enabled sudo systemctl enable nginx; sudo systemctl start nginx; + + open-webui serve --port $OLLAMA_PORT & } -# Display help if the user requests it. -if [[ $# == 1 && ($1 == "--help" || $1 == "-h") ]]; then - help; - exit; -fi - -# Get user input for some default applications to get started. -read "?Do you have a Cloudflare account and own a domain? (y/N)" CLOUDFLARE; -read "?Do you want a mirror of the manual from raspi.vintagecoding.net\ - on this device? (y/N)" MANUAL; -read "?Do you want to setup Jellyfin, a personal Netflix alternative? (y/N)" JELLYFIN; -read "?Do you want to setup Ollama, a personal ChatGPT alternative? (y/N)" OLLAMA; - init; - -if [[ $MANUAL == "y" ]]; then - init_manual; -fi - -if [[ $JELLYFIN == "y" ]]; then - init_jellyfin; -fi - -if [[ $CLOUDFLARE == "y" ]]; then - init_cloudflare; -else - echo "You can still use Cloudflare for free, by generating random URLs." - echo "Enter the following into a terminal:"; - echo " cloudflared tunnel --url=http://localhost:PORT"; - echo "to get a URL a process, for example. Replace PORT with one of the following:"; - echo " $MANUAL_PORT (The manual mirror's port.)"; - echo " $JELLYFIN_PORT (The Jellyfin port.)"; - echo " $OLLAMA_PORT (The Ollama & Open Web-UI port.)"; -fi - -if [[ $OLLAMA == "y" ]]; then - init_ollama; -fi