From 31942d6fa6f86d9a93bedebf4ff733b98bf5e040 Mon Sep 17 00:00:00 2001 From: simp Date: Thu, 13 Mar 2025 17:29:46 -0400 Subject: [PATCH] readme update to include setup instructions for offloading the music stream from the webapp --- README.md | 240 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 204 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 6321134..0ed283e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a js-free music player. It uses iframes and css for interactivity and pa Live instance at music.simp.i2p -## Features +### Features - Minimal audio player using only html/css - Can load balance streams to a number of b32s, see number of listeners @@ -26,13 +26,16 @@ The `updateplaylists.py` script will compress all found tracks to opus and chang This script will also pack a playlist for torrenting, by hashing the contents of the directory and creating a symlink to a torrent directory. It then copies the .torrent file there. -## To be added +### To be added - Track metadata -## Install Guide +# Install Guide -### Step 1: Make a user for the install +This will install the webapp stack and all dependencies. +wsgi.py runs gunicorn, which runs the flask app. This passes either a .sock file or a port to the webserver. It's recommended to run nginx with gunicorn, generally, though other webservers can be used. For streaming, we want nginx to be doing the heavy lifting. + +## Step 1: Make a user for the install sudo useradd music && sudo mkdir /home/music && sudo chown music:music /home/music && sudo usermod -aG sudo music && sudo passwd -d music && su music @@ -44,7 +47,7 @@ If not, run (check how to install for your OS): sudo apt update && sudo apt install ffmpeg -### Step 2: Download and install dependencies +## Step 2: Download and install dependencies Next, cd to the home directory of the "music" user: @@ -60,7 +63,7 @@ Run this to test that it's running: It should be running on ports 5000 and 5095. -#### ONLY if that doesn't work, otherwise skip this part - Manual install +### ONLY if that doesn't work, otherwise skip this part - Manual install Clone the repo using your i2p http proxy, cd into it, then make a python venv. @@ -76,11 +79,30 @@ After requirements are installed, make sure your venv is activated: Running `python3 wsgi.py` should work, which will run gunicorn. Running `python3 app.py` will just run the flask app withou gunicorn. Useful for debugging. Do not run the flask app alone without wsgi! -### Step 3 - Different ways to run the app +## Step 3 - Edit config + +in the /i2music directory, do + + nano config.txt + +Make sure to change: + + secret_key = + +Worth a look: + +- site_title +- options under [footer] +- bitrate +- if you want to get peers, change get_peercounts to True + +The settings section has descriptions on the other options. To take effect the app needs to be restarted. + +## Step 4 - Run the app Running as a service will keep it running under the "music" user we created. -#### As a service (systemd) with .sock file +### As a service (systemd) with .sock file This config will work for systemd linux. Run: @@ -108,7 +130,7 @@ Now we enable and start the service: sudo systemctl daemon-reload && sudo systemctl enable i2music && sudo systemctl start i2music -#### Run the wsgi.py directly +### Run the wsgi.py directly You can run just the wsgi.py directly, passing the .sock file or port to the webserver. @@ -124,7 +146,7 @@ OR, to get just the port, run: python3 wsgi.py -### Step 4 - Setup Nginx +## Step 5 - Setup Nginx This will pass the .sock file through nginx, listening on port 5050. @@ -150,6 +172,18 @@ Then paste the config, change server_name somedomain.i2p and make sure the path if ($http_user_agent ~* LWP::Simple|BBBike|wget) { return 444; } + keepalive_timeout 320; + proxy_buffering on; + proxy_buffer_size 256k; + proxy_busy_buffers_size 264k; + proxy_buffers 128 8k; + gzip on; + gzip_comp_level 6; + gzip_buffers 32 4k; + gzip_types text/plain text/css application/json image/gif image/jpeg image/png image/webp application/javascript text/xml application/xml application/xml+rss application/atom+xml audio/ogg audio/mp3 font/truetype font/opentype image/svg+xml + reset_timedout_connection on; + client_body_timeout 20; + send_timeout 10; location / { proxy_hide_header Server; proxy_pass http://unix:/home/music/i2music/eepsite.sock; @@ -165,8 +199,28 @@ Now make symlink to sites-enabled. Reload Nginx so it sees the new config: sudo systemctl reload nginx - -### Setup i2p tunnels + +## Step 7 - Nginx optimizations for streaming (optional) + +These can be added to the nginx.conf file + sudo nano /etc/nginx.conf + +Nginx options to add under http: section that should help with streaming performance: + + keepalive_timeout 320; + proxy_buffering on; + proxy_buffer_size 256k; + proxy_busy_buffers_size 264k; + proxy_buffers 128 8k; + gzip on; + gzip_comp_level 6; + gzip_buffers 32 4k; + gzip_types text/plain text/css application/json image/gif image/jpeg image/png image/webp application/javascript text/xml application/xml application/xml+rss application/atom+xml audio/ogg audio/mp3 font/truetype font/opentype image/svg+xml + reset_timedout_connection on; + client_body_timeout 20; + send_timeout 10; + +## Step 6 - Setup i2p tunnels From here you only need to pass the port 5050 from nginx to i2p. Don't use port 5095 (gunicorn) and never run the flask port anywhere other than locally, as its unsafe (5000) @@ -174,7 +228,139 @@ In java, make a new http server tunnel. Make sure to save the private key. start with 4-6 tunnels, you may opt to reduce tunnels to conserve resources. -## Playlist update script: updateplaylists.py +# Increase performance with separate stream tunnels + +This requires more setup, but will give a more performative stream and ability to handle more users. It will also let us optimize a set of tunnels for streaming, and save the ones for the app for http requests. + +The main app will use its own set of tunnels this way, offloading music streaming to other tunnels. You can reduce the tunnel count on your main app, and use "reduce tunnels to conserve resources" option in java to allow the stream tunnels to die down when not in use. + +We will run "wsgi_fileserve.py" alongside the normal "wsgi.py", which will start another gunicorn instances on another port, "gunicorn_stream_port = 5006" under the settings block. All this server instance can do is serve files with GET requests, all others will be dropped. + +## Service config + +This mirrors the setup from step 4. + +Run: + + sudo nano /etc/systemd/system/i2musicstream.service + +Then paste the following: + + [Unit] + Description=Gunicorn instance to serve music streams + After=network.target + + [Service] + User=music + Group=www-data + WorkingDirectory=/home/music/i2music + Environment="PATH=/home/music/i2music/bin" + ExecStart=/home/music/i2music/bin/gunicorn --workers 1 --bind unix:stream.sock -m 007 wsgi_fileserve:app + + [Install] + WantedBy=multi-user.target + +Enable and start the service: + + sudo systemctl daemon-reload && sudo systemctl enable musicstream && sudo systemctl start musicstream + +## Nginx config + +We will have one optimized for streaming, and one for the app + +### Nginx config for streaming + +Go the the nginx directory (path may be different): + + cd /etc/nginx + +Make a new site in sites-enabled + + sudo nano /etc/nginx/sites-available/musicstream + +Then paste the config, change server_name somedomain.i2p and make sure the path in proxy_pass is right: + + server { + listen 5051; + server_name somedomain.i2p; + server_tokens off; + if ($request_method !~ ^(GET)$ ) { + return 444; + } + if ($http_user_agent ~* LWP::Simple|BBBike|wget) { + return 444; + } + keepalive_timeout 320; + proxy_buffering on; + proxy_buffer_size 256k; + proxy_busy_buffers_size 264k; + proxy_buffers 128 8k; + gzip on; + gzip_comp_level 6; + gzip_buffers 32 4k; + gzip_types text/plain text/css application/json image/gif image/jpeg image/png image/webp application/javascript text/xml application/xml application/xml+rss application/atom+xml audio/ogg audio/mp3 font/truetype font/opentype image/svg+xml + reset_timedout_connection on; + client_body_timeout 20; + send_timeout 10; + location / { + proxy_hide_header Server; + proxy_pass http://unix:/home/music/i2music/stream.sock; + } + } + +### Change the nginx config for the webapp + +Edit the i2music config made earlier + + sudo nano /etc/nginx/sites-available/i2music + +Remove the parts between "keepalive_timeout 320;" and "location / {" so it looks like this: + + server { + listen 5050; + server_name somedomain.i2p; + server_tokens off; + if ($request_method !~ ^(GET|POST)$ ) { + return 444; + } + if ($http_user_agent ~* LWP::Simple|BBBike|wget) { + return 444; + } + keepalive_timeout 320; + location / { + proxy_hide_header Server; + proxy_pass http://unix:/home/music/i2music/eepsite.sock; + } + } + +ctrl+s / ctrl+x to save and exit. + + +Reload Nginx so it sees the new config: + + sudo systemctl reload nginx + + +### Make i2p http server tunnels for streaming only + +Make a new http server tunnel, listening for port 5051 (based on nginx config). In java, try starting with 2-3 tunnels for up/down. Enable "reduce tunnels to conserve resources" after 5 mins to 1 tunnel. Save the key file, start automatically when router starts. + +Under custom options, paste this and adjust to your liking: + + i2cp.fastReceive=true i2cp.gzip=true i2p.streaming.bufferSize=256k i2p.streaming.connectTimeout=20*1000 i2p.streaming.inactivityTimeout=600*1000 i2p.streaming.limitAction=http + +Once you have one tunnel made up, copy it so you have 2 or more sets of streaming tunnels, all going to port 5051. You can make as many of these tunnels as needed with different b32's, using identical settings. + +Then copy each of the server b32's, add 1 per line, tabbed. Found in the config.txt file under the "b32streams" block, like this: + + [b32streams] + b32s = + hvyaj6xegbsluf65ob2rdw2yf25me7kavmy2o3crwtijf4yb4zwa.b32.i2p + gkbtx3a2s3pryryiagso7e5rehcbccphyvdm4y2nxops77os56nq.b32.i2p + +After restarting the main webapp, it will keep track of the number of listeners using each b32 and try to use the least crowded one. A user will continue using the same stream server for the session. + +# Playlist update script: updateplaylists.py Note: this script need ffmpeg to work. Make sure that's installed. @@ -200,7 +386,7 @@ Much higher than 56k may result in some stuttering in i2p from my testing. The script won't touch images and music files that have already been converted. BUT IT WILL REPLACE those files, so make sure you have the originals somewhere else. -## Torrent packing with updateplaylists.py +# Torrent packing with updateplaylists.py The options for this are under the "torrent" section. @@ -221,7 +407,7 @@ To enable hashing of torrents, `make_playlist_torrents` should be True. This wil With all features enabled, the updateplaylists.py script will compress the contents of enabled playlist directories, hash them as torrents, send them to your torrent clients watch folder, and save the playlist data as `data.json` -## How it works +# How it works When the webapp is ran it loads the data.json file that was created from running `python3 updateplaylists.py` @@ -231,7 +417,7 @@ These details are kept in memory to determine track controls (next/previous song There are 4 iframes, left is for playlist view, center is the audio player, right top is for settings, right bottom is for info. Using target="framename" we can get some limited interactivity this way, relying on backend. -## Track ordering +# Track ordering Tracks are ordered using natsort, which sorts a-z/numerically. @@ -243,29 +429,11 @@ To manually order tracks, name them with a "xxx." in front of the title: The number in front of the track name won't be shown in the player. -## Browser support +# Browser support This relies entirely on browser suport for playing audio and autoplay especially. Major browsers should all support wav/mp3/ogg(opus), but some may block autoplay. The user may need to enable it to work right. - -## Load balancing, using a separate stream server - -This is entirely optional, but you can offload just the streams to other server tunnels to load balance. The main app will use its own set of tunnels this way, and should be more performative. You can reduce the tunnel count on your main app, and use "reduce tunnels to conserve resources" option in java to allow the stream tunnels to die down when not in use. - -In this case you want to also run "wsgi_fileserve.py" which will start another gunicorn instances on another port, "gunicorn_stream_port = 5006" under the settings block. All this server instance can do is serve files with GET requests, all others will be dropped. - -Then make other http server tunnels, all going to the port set on "gunicorn_stream_port" - -With each of the additional server b32's, add 1 per line and tabbed under the b32streams block: - - [b32streams] - b32s = - hvyaj6xegbsluf65ob2rdw2yf25me7kavmy2o3crwtijf4yb4zwa.b32.i2p - gkbtx3a2s3pryryiagso7e5rehcbccphyvdm4y2nxops77os56nq.b32.i2p - -After restarting the main webapp, it will keep track of the number of listeners using each b32 and try to use the least crowded one. A user will continue using the same stream server for the session. - -## Settings +# Settings Most settings are in config.txt file [settings]