commit 35c54263a92a8c30e7559cb4b3eef25b53cf6df1 Author: kenny Date: Sun Dec 7 14:35:40 2025 +0200 First commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..cc5c18b --- /dev/null +++ b/.envrc @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +# `use devenv` supports the same options as the `devenv shell` command. +# +# To silence all output, use `--quiet`. +# +# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true +use devenv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae49879 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..60da709 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Spotify liked playlist downloader + + - [X] Working SpotifyAPI with ngrok+HTTPS +- [ ] Fix this in the openssl call: +/nix/store/dzz1xf4nxx8v42pz5sxxx62r0jjpl0qf-craftcerts-script: line 5: 2: command not found +req: Extra option: "1" +req: Use -help for summary. +- [ ] Make geturl.py needs to return playlist URL to the program diff --git a/access_token.txt b/access_token.txt new file mode 100644 index 0000000..f4f68c1 --- /dev/null +++ b/access_token.txt @@ -0,0 +1 @@ +BQBNPC2WwzpQqZTz_CO2pjKj16j0hMKLGBv0xEQbUD1Ezf7e4fUT1oj2YKH7cyfRv5WiJ5TtQiNhBDfSONPgfnwfNV1IXkVWiNveGWCOkgmlGyV2wJV6TbXZEGjLFCMwpZZTR2GdP8vswc_veA2MH-T5A-Uooz3DYADjtwZz3_WuHN2W-6XwVCRHfRd4EPwG2FwtHvsC2GmZIILMYU7g0YFbDWnXIgGafA2_wEl7S-3Yuon2lNMfmmoc0LHBfupwyxdn9oD-Og \ No newline at end of file diff --git a/current b/current new file mode 100644 index 0000000..b2c9af4 --- /dev/null +++ b/current @@ -0,0 +1,450 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1764927628, + "owner": "cachix", + "repo": "devenv", + "rev": "247d7027f91368054fb0eefbd755a73d42b66fee", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1764712249, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "3b279e4317ccfa4865356387935310531357d919", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1765016596, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "548fc44fca28a5e81c5d6b846e555e6b9c2a5a3c", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762808025, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1764580874, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} +{ pkgs, lib, config, inputs, ... }: { + env.GREET = "SpotDL devenv"; + packages = [pkgs.spotdl pkgs.python313Packages.spotipy pkgs.python313Packages.flask pkgs.openssl]; + + scripts.hello.exec = ''echo hello from $GREET ! ''; + + scripts.craftcerts.exec = '' + mkdir -p ssl # Use -p to avoid error if the directory exists + cd ssl + if [ ! -f ../openssl.cnf ]; then + echo "Please create 'openssl.cnf' in the parent directory." + exit 1 + fi + openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.cert -days 365 -nodes -config ../openssl.cnf >/dev/null + ''; + + scripts.download.exec = '' + while true; do + url=$(python ./geturl.py) # Capture the output of geturl.py + if [ $? -eq 0 ]; then + spotdl "$url" + break + fi + echo "Error downloading, retrying..." + sleep 2 + done + ''; + + enterShell = '' + hello + craftcerts + download + ''; +} +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend +from flask import Flask, request, redirect +import spotipy +from spotipy.oauth2 import SpotifyOAuth +import subprocess +import os +import time +from datetime import datetime + +app = Flask(__name__) + +client_id = "c0020cc0e05245efb2ddb61b7045e4f2" +client_secret = "2e2e7c98f849403fbacc219c1f01c17d" +redirect_uri = "https://localhost:8888/callback" + +sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read") + +@app.route('/') +def index(): + return "Welcome! Login to Spotify" + +@app.route('/login') +def login(): + return redirect(sp_oauth.get_authorize_url()) + +@app.route('/callback') +def callback(): + code = request.args.get('code') + token_info = sp_oauth.get_access_token(code) + access_token = token_info['access_token'] + sp = spotipy.Spotify(auth=access_token) + + print("Fetching liked songs...") + liked_songs = [] + results = sp.current_user_saved_tracks() + + while results: + for item in results['items']: + liked_songs.append(item['track']['id']) + if results['next']: + results = sp.next(results) + time.sleep(1) + else: + break + + playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S") + user_id = sp.me()['id'] + new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False) + + print(f"Created playlist: {playlist_name}") + + for i in range(0, len(liked_songs), 100): + batch = liked_songs[i:i + 100] + sp.playlist_add_items(new_playlist['id'], batch) + print(f"Added {len(batch)} songs...") + time.sleep(1) + + playlist_url = new_playlist['external_urls']['spotify'] + print(f"Playlist URL: {playlist_url}") + + # Write playlist URL for the download script + with open("last_playlist_url.txt", "w") as f: + f.write(playlist_url) + + return f"{playlist_url}" + +if __name__ == "__main__": + print("Starting Flask server...") + os.execvp('flask', [ + 'flask', + 'run', + '--host=0.0.0.0', + '--port=8888', + '--cert=ssl/server.cert', + '--key=ssl/server.key' + ]) +from flask import Flask, request, redirect +import spotipy +from spotipy.oauth2 import SpotifyOAuth +import threading +import os +import sys +import time # Import time module +from datetime import datetime + +app = Flask(__name__) + +client_id = "c0020cc0e05245efb2ddb61b7045e4f2" +client_secret = "2e2e7c98f849403fbacc219c1f01c17d" +redirect_uri = "https://localhost:8888/callback" + +sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read") + +@app.route('/') +def index(): + return "Welcome! Login to Spotify" + +@app.route('/login') +def login(): + return redirect(sp_oauth.get_authorize_url()) + +@app.route('/callback') +def callback(): + code = request.args.get('code') + token_info = sp_oauth.get_access_token(code) + access_token = token_info['access_token'] + + with open("access_token.txt", "w") as f: + f.write(access_token) + + print("Authorization complete. You can close this window.", file=sys.stderr) + return "Authorization complete. You can close this window." + +def create_playlist(): + if not os.path.exists("access_token.txt"): + print("No access token found.", file=sys.stderr) + return + + with open("access_token.txt", "r") as f: + access_token = f.read().strip() + + sp = spotipy.Spotify(auth=access_token) + + print("Fetching liked songs...", file=sys.stderr) + liked_songs = [] + results = sp.current_user_saved_tracks() + + while results: + for item in results['items']: + liked_songs.append(item['track']['id']) + if results['next']: + results = sp.next(results) + else: + break + + playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S") + user_id = sp.me()['id'] + new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False) + + print(f"Created playlist: {playlist_name}", file=sys.stderr) + + for i in range(0, len(liked_songs), 100): + batch = liked_songs[i:i + 100] + sp.playlist_add_items(new_playlist['id'], batch) + print(f"Added {len(batch)} songs...", file=sys.stderr) + + playlist_url = new_playlist['external_urls']['spotify'] + print(f"Playlist URL: {playlist_url}", file=sys.stderr) + + return playlist_url + +def run_flask(): + app.run(ssl_context=('ssl/server.cert', 'ssl/server.key'), host="0.0.0.0", port=8888) + +def main(): + # Start the Flask app in a background thread + flask_thread = threading.Thread(target=run_flask, daemon=True) + flask_thread.start() + + # Inform the user to authorize access via the URL shown in the terminal + print("Please authorize access via the following URL:", sp_oauth.get_authorize_url()) + + # Wait until the access token is available + while not os.path.exists("access_token.txt"): + time.sleep(1) # Sleep for a while, then check again + + # Now that we have the token, proceed with creating the playlist + playlist_url = create_playlist() + print(f"Playlist URL returned: {playlist_url}") + +if __name__ == "__main__": + main() +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +prompt = no +[req_distinguished_name] +C = US +ST = California +L = San Francisco +O = Example Company +OU = IT Department +CN = localhost +emailAddress = email@example.com +To implement HTTPS using Let's Encrypt and ngrok while modifying your Flask application, follow these structured steps: + +## Step 1: Install and Set Up ngrok + +1. **Download ngrok**: + - Go to the [ngrok website](https://ngrok.com/download) and download the appropriate version for your OS. + - Install ngrok by following the instructions on the site. + +2. **Authenticate ngrok**: + - Sign up for a free account at ngrok.com. + - After signing up, you’ll receive an authtoken. Run the following command to set it up: + ```bash + ngrok config add-authtoken + ``` + +## Step 2: Modify Flask to Accept HTTPS + +Update your Flask application to use ngrok to tunnel HTTPS traffic: + +1. **Change `run_flask()` to bind to HTTP**: + This is so ngrok can handle the SSL termination. Update the endpoint by removing the `ssl_context`: + ```python + def run_flask(): + app.run(host="0.0.0.0", port=8888) + ``` + +2. **Expose the Flask app via ngrok**: + You can run ngrok to expose your Flask app by executing: + ```bash + ngrok http 8888 + ``` + This will give you a public HTTPS URL that tunnels to your local Flask app. + +## Step 3: Modify the Development Environment (devenv.nix) + +You’ll need to add the ngrok installation to your `devenv.nix` configuration, if it's not already installed. + +1. **Edit `devenv.nix`**: + +```nix +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = [ + pkgs.ngrok + ]; + + shellHook = '' + # Set up ngrok to run automatically + ngrok http 8888 & + ''; +} +``` + +This snippet sets up ngrok to run in the background whenever you start your development environment. + +## Step 4: Implement the PKI Chain in `geturl.py` + +To implement an HTTPS PKI chain, consider using the Let's Encrypt certificate. For local development, you can't use Let's Encrypt directly without a publicly reachable domain, but you can still prepare your code as if you're using secure connections. + +1. **Modify `geturl.py`**: + +Assuming your application logic has not changed, implement a function to fetch the ngrok URL: + +```python +import os +import requests +import threading +import time + +def get_ngrok_url(): + response = requests.get("http://localhost:4040/api/tunnels") + tunnels = response.json()["tunnels"] + for tunnel in tunnels: + if tunnel["name"] == "http": + return tunnel["public_url"] + return None + +def create_playlist(): + # Your existing code to create a playlist + ... + +def run_flask(): + app.run(host="0.0.0.0", port=8888) + +def main(): + # Start Flask in a background thread + flask_thread = threading.Thread(target=run_flask, daemon=True) + flask_thread.start() + + # Wait for ngrok to be ready + time.sleep(5) # Allow some time for ngrok to initialize + + ngrok_url = get_ngrok_url() + print(f"Ngrok URL: {ngrok_url}") + + print("Please authorize access via the following URL:", sp_oauth.get_authorize_url()) + + while not os.path.exists("access_token.txt"): + time.sleep(1) + + playlist_url = create_playlist() + print(f"Playlist URL returned: {playlist_url}") + +if __name__ == "__main__": + main() +``` + +In this code: +- `get_ngrok_url()` fetches the ngrok tunnel URL dynamically. +- You can then safely use this URL to inform other services of your HTTPS endpoint. + +## Summary + +This setup will let you run a local Flask application using HTTPS through an ngrok tunnel. You process the playlist creation and output the URL correctly while working in a secure environment. If you later transition to production, you can directly implement Let's Encrypt for your domain, maintaining the same architecture with minimal changes. diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..2a6f32f --- /dev/null +++ b/devenv.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1764927628, + "owner": "cachix", + "repo": "devenv", + "rev": "247d7027f91368054fb0eefbd755a73d42b66fee", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1764712249, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "3b279e4317ccfa4865356387935310531357d919", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1765016596, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "548fc44fca28a5e81c5d6b846e555e6b9c2a5a3c", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762808025, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1764580874, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..9ac5b4e --- /dev/null +++ b/devenv.nix @@ -0,0 +1,38 @@ +{ pkgs, lib, config, inputs, ... }: { + env.GREET = "SpotDL devenv"; + packages = [pkgs.spotdl pkgs.python313Packages.spotipy pkgs.python313Packages.flask pkgs.openssl pkgs.ngrok ]; + + scripts.hello.exec = ''echo hello from $GREET ! ''; + + scripts.craftcerts.exec = '' + mkdir -p ssl # Use -p to avoid error if the directory exists + cd ssl + if [ ! -f ./openssl.cnf ]; then + echo "Creating openssl..." + openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.cert -days 365 -nodes -config ../openssl.cnf 1&2 > /dev/null + exit 1 + fi + ''; + + scripts.download.exec = '' + ngrok http 8888 > /dev/null & + python ./geturl.py > geturl_logfile + url=$(cat -p geturl_logfile) # Capture the output of geturl.py + echo "STATEMENT AFTER GETURL" + while true; do + if [ $? -eq 0 ]; then + echo "SPOTDL ATTEMPT:" + spotdl "$url" + break + fi + echo "Error downloading, retrying..." + sleep 2 + done + ''; + + enterShell = '' + hello + craftcerts + download + ''; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..116a2ad --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend diff --git a/geturl.old b/geturl.old new file mode 100644 index 0000000..56e5bd3 --- /dev/null +++ b/geturl.old @@ -0,0 +1,75 @@ +from flask import Flask, request, redirect +import spotipy +from spotipy.oauth2 import SpotifyOAuth +import subprocess +import os +import time +from datetime import datetime + +app = Flask(__name__) + +client_id = "c0020cc0e05245efb2ddb61b7045e4f2" +client_secret = "2e2e7c98f849403fbacc219c1f01c17d" +redirect_uri = "https://localhost:8888/callback" + +sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read") + +@app.route('/') +def index(): + return "Welcome! Login to Spotify" + +@app.route('/login') +def login(): + return redirect(sp_oauth.get_authorize_url()) + +@app.route('/callback') +def callback(): + code = request.args.get('code') + token_info = sp_oauth.get_access_token(code) + access_token = token_info['access_token'] + sp = spotipy.Spotify(auth=access_token) + + print("Fetching liked songs...") + liked_songs = [] + results = sp.current_user_saved_tracks() + + while results: + for item in results['items']: + liked_songs.append(item['track']['id']) + if results['next']: + results = sp.next(results) + time.sleep(1) + else: + break + + playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S") + user_id = sp.me()['id'] + new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False) + + print(f"Created playlist: {playlist_name}") + + for i in range(0, len(liked_songs), 100): + batch = liked_songs[i:i + 100] + sp.playlist_add_items(new_playlist['id'], batch) + print(f"Added {len(batch)} songs...") + time.sleep(1) + + playlist_url = new_playlist['external_urls']['spotify'] + print(f"Playlist URL: {playlist_url}") + + # Write playlist URL for the download script + with open("last_playlist_url.txt", "w") as f: + f.write(playlist_url) + + return f"{playlist_url}" + +if __name__ == "__main__": + print("Starting Flask server...") + os.execvp('flask', [ + 'flask', + 'run', + '--host=0.0.0.0', + '--port=8888', + '--cert=ssl/server.cert', + '--key=ssl/server.key' + ]) diff --git a/geturl.py b/geturl.py new file mode 100644 index 0000000..30c6997 --- /dev/null +++ b/geturl.py @@ -0,0 +1,135 @@ +from flask import Flask, request, redirect +import spotipy +from spotipy.oauth2 import SpotifyOAuth +import threading +import os +import sys +import time +import requests +from datetime import datetime +import subprocess + +app = Flask(__name__) +stop_flask = threading.Event() + +client_id = "c0020cc0e05245efb2ddb61b7045e4f2" +client_secret = "2e2e7c98f849403fbacc219c1f01c17d" +redirect_uri = "https://elease-uncapsuled-yosef.ngrok-free.dev/callback" # Hardcoded ngrok URL + +sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read") + +# This will store whether the access token was acquired +access_token_obtained = threading.Event() + +@app.route('/') +def index(): + return "Welcome! Login to Spotify" + +@app.route('/login') +def login(): + return redirect(sp_oauth.get_authorize_url()) + +@app.route('/callback') +def callback(): + code = request.args.get('code') + token_info = sp_oauth.get_access_token(code) + access_token = token_info['access_token'] + + with open("access_token.txt", "w") as f: + f.write(access_token) + + #print("Authorization complete. You can close this window.", file=sys.stderr) + + # Signal that the access token has been obtained + access_token_obtained.set() + + return "Authorization complete. You can close this window." + +def create_playlist(): + if not os.path.exists("access_token.txt"): + print("No access token found.", file=sys.stderr) + return + + with open("access_token.txt", "r") as f: + access_token = f.read().strip() + + sp = spotipy.Spotify(auth=access_token) + + print("Fetching liked songs...", file=sys.stderr) + liked_songs = [] + results = sp.current_user_saved_tracks() + + while results: + for item in results['items']: + liked_songs.append(item['track']['id']) + if results['next']: + results = sp.next(results) + else: + break + + playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S") + user_id = sp.me()['id'] + new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False) + + print(f"Created playlist: {playlist_name}", file=sys.stderr) + + for i in range(0, len(liked_songs), 100): + batch = liked_songs[i:i + 100] + sp.playlist_add_items(new_playlist['id'], batch) + print(f"Added {len(batch)} songs...", file=sys.stderr) + + playlist_url = new_playlist['external_urls']['spotify'] + print(f"Playlist URL: {playlist_url}", file=sys.stderr) + + return playlist_url + +def run_flask(): + with open(os.devnull, 'w') as fnull: + # Redirect both stdout and stderr to devnull + sys.stdout = fnull + sys.stderr = fnull + subprocess.run(["xdg-open", "http://192.168.0.100:8888"]) + app.run(host="0.0.0.0", port=8888, use_reloader=False) + # sys.stdout = sys.__stdout__ # Restore original stdout + # sys.stderr = sys.__stderr__ # Restore original stderr + +def kill_ngrok(): + try: + # Find ngrok process and kill it + subprocess.call(["pkill", "ngrok"]) + except Exception as e: + print(f"Error stopping ngrok: {str(e)}", file=sys.stderr) + + +def main(): + kill_ngrok() + + # Start Flask in a background thread + flask_thread = threading.Thread(target=run_flask, daemon=True) + flask_thread.start() + + # Allow some time for Flask to start + time.sleep(2) + + # Start ngrok dynamically + os.system("ngrok http 8888 &") + + # Allow time for ngrok to initialize + time.sleep(5) + + # Print out the hardcoded ngrok URL + print(f"Ngrok URL: {redirect_uri}", file=sys.stderr ) + print("Please authorize access via the following URL:", sp_oauth.get_authorize_url(), file=sys.stderr) + + # Wait for the access token to be acquired + access_token_obtained.wait() # Wait until the access token is provided + + # Now that we have the token, proceed with creating the playlist + playlist_url = create_playlist() + print(playlist_url) + stop_flask.set() # Signal Flask to stop + flask_thread.join() # Wait for the Flask thread to complete + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/geturl_logfile b/geturl_logfile new file mode 100644 index 0000000..e69de29 diff --git a/openssl.cnf b/openssl.cnf new file mode 100644 index 0000000..d212a4d --- /dev/null +++ b/openssl.cnf @@ -0,0 +1,12 @@ +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +prompt = no +[req_distinguished_name] +C = US +ST = California +L = San Francisco +O = Example Company +OU = IT Department +CN = localhost +emailAddress = email@example.com diff --git a/ssl/server.cert b/ssl/server.cert new file mode 100644 index 0000000..ce8a736 --- /dev/null +++ b/ssl/server.cert @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID9TCCAt2gAwIBAgIUOI3ulMeyDqE64sr8SIkZ9U7SmDIwDQYJKoZIhvcNAQEL +BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxFjAUBgNV +BAsMDUlUIERlcGFydG1lbnQxEjAQBgNVBAMMCWxvY2FsaG9zdDEgMB4GCSqGSIb3 +DQEJARYRZW1haWxAZXhhbXBsZS5jb20wHhcNMjUxMjA3MTE1OTQxWhcNMjYxMjA3 +MTE1OTQxWjCBojELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoMD0V4YW1wbGUgQ29tcGFueTEW +MBQGA1UECwwNSVQgRGVwYXJ0bWVudDESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJ +KoZIhvcNAQkBFhFlbWFpbEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJZvtJTQQvIvJSxC6WMQ8L0fRvhIud3wFFZ21kaMT0Ptetjh +akdF8gdBu6RA4mI8mY/P67G6xsHxQG7rQIeGWl8ds0Y5TS1i9pGbxmim6DxEw/wr +EL3npG+bnKGMrYv2hwUg4r6+bfjvhPW9TgUCJN9clvwixAf6tSHK04epNPHmKXiv +aYaxGgWghSrTLIBXp22TiLfTYczCR8F4Banhkvm3wJkrvA2LsRCb9LHHnKVkXuw6 +mFd8WdTZT3+s85D4pEJPiZ8U76tH6fJm0f8BZigibx7Ag3nPt6FeiDrlVznuzl5q +c9uPX6tu73nFA1/x141HgW1Aw8D1YR5iIzcfnbECAwEAAaMhMB8wHQYDVR0OBBYE +FEfxzkXqyVwA87RNe6CuiKzZVDsmMA0GCSqGSIb3DQEBCwUAA4IBAQBfJPkqzyvz +7vq6fqBOWzUc8swPGx54DCwjzALd4ujEOjhOQCktxrX2VesEk6B5yjNQd6VuOMHU +V8qo1q82UG3LKcCIkaFNlJ9d1H2pzBLVjsMshTPABEEiq1EKuKNX6G+wk7DyuBDH +0PW/OWGP0OaASYv+X8s/WF5JwQMdIsmDBgyeUQd9yMMiuluDqGFFhZ7xM77peqHj +I4GtM847rJn1lTdpJygACWlteBnMCeUd/cyLNA5q3LZFPa3JJ86Dwl3kdEgQtZ6F +FZ2KRQr84QUdPo2Wt+MSMrWv2dWCu66KuE4qGmhtzTbTnWa9D1fR0EV4y0XV/nza +VAFapoxF0keh +-----END CERTIFICATE----- diff --git a/ssl/server.key b/ssl/server.key new file mode 100644 index 0000000..26b3515 --- /dev/null +++ b/ssl/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWb7SU0ELyLyUs +QuljEPC9H0b4SLnd8BRWdtZGjE9D7XrY4WpHRfIHQbukQOJiPJmPz+uxusbB8UBu +60CHhlpfHbNGOU0tYvaRm8Zopug8RMP8KxC956Rvm5yhjK2L9ocFIOK+vm3474T1 +vU4FAiTfXJb8IsQH+rUhytOHqTTx5il4r2mGsRoFoIUq0yyAV6dtk4i302HMwkfB +eAWp4ZL5t8CZK7wNi7EQm/Sxx5ylZF7sOphXfFnU2U9/rPOQ+KRCT4mfFO+rR+ny +ZtH/AWYoIm8ewIN5z7ehXog65Vc57s5eanPbj1+rbu95xQNf8deNR4FtQMPA9WEe +YiM3H52xAgMBAAECggEABvrO8mRdBczwjOacGtzCaajrx1li98hTvj0AZTitdQ+t +tmYzgtPsxj/LUaQxyLhqcMrnIulxPzZ2/Ip5VYxjESSQ8Lw3/6gGvAPh61E6p1Cy +teaG3ePDbCr4dAGhm/PDyRXw9UQDONR+mxL13as/PBaI+hDwHtVnzeYB5VqBPoui +scQ8ha8WqYrHlKskFG3dzTZQmLgajDZYB6hY+E/UYKwzCa1+GsONDkS2l8sij5pL +6zEuvcfU4WyiWZm4JyHX+v39lyjmdfhGcD/u4vKJvKZB2Ir7dKf8OdP8l+qagyBN +A11LXOmb7XkUPAK2oYNjFAAXs+go4rmcWkHSbF++wQKBgQDPqdHx5j4RQFP47/5R +4t1Pzwi+w0HBo3upi5NEfwr7Z7bbd2cH6/KcraIYYkExOke+3xtTHK1OrHTY6Nkf +/DRgrUB+ujsb4AJGm98bPrEtbVnUsfg6ssfSqEB1rYbWFhCP3wFDRlUWzzhvxt+s +cb4a8JGRJpdy1qihBrSvpGil8QKBgQC5c9wkrbSYdXLCp0hy8s9832g6hZJjH0LM +aRTwLMPlaRHGBIkb8FU1zgd2w74lRFJytF7bPFmCXKXV/BPhURvK6FelPxLIge03 +6FbsiIhHEYhsY5agkSc77qz1ESo03UStes/2kscn2wHgoet9Ssz6h1ff08BoQwL5 +7sGdrGOzwQKBgQCB2dqhvCsLdoILo2YPpiBlCzyYrFet0aA+ADzyE862LcA1s5AF +cBCg0CIPxUCmm57JR5E6gzALheL6z38VdQr7eNpfY+waHhTOOiFkU+tOUJZfXXmu +mqRAoVzNONibfeiVTgjoHE6QmLrdiinLFsSc69jaPpts/7UqG5tYSSH2wQKBgQCo +2y1e2CrPhmDvi5ET2LHDaUdlwakAR75ykFtYM+pKP9jHC+orXjC3xNhW8vN2yzam +6kRUKib145W7uMIBLfC4V8U12LApkOOFPC+pPseWrgghaKwFlyS6FR+2I+LiL3YQ +3vnr8MkVKPwUpFnewvTQR9tjGVLfm+Rh3Vq9TfGaAQKBgB0NX3wpmrhzDBs6C6M3 +FqsiKN5vthUVeI9CCaPS13hXJArrFyV3wmc3pSA5ey5zXx46hGPYvcpUVxftFDBo +kFDu/5T7OgfqY7CtunrEcu6g6rFFjsPCxgJZpQD/WN+inp+WCAWubOu7AvNd/bwV +hAHaAz9nR9Ljz77qHEvxhmWr +-----END PRIVATE KEY----- diff --git a/whatsnext.md b/whatsnext.md new file mode 100644 index 0000000..8294bbf --- /dev/null +++ b/whatsnext.md @@ -0,0 +1,115 @@ +To implement HTTPS using Let's Encrypt and ngrok while modifying your Flask application, follow these structured steps: + +## Step 1: Install and Set Up ngrok + +1. **Download ngrok**: + - Go to the [ngrok website](https://ngrok.com/download) and download the appropriate version for your OS. + - Install ngrok by following the instructions on the site. + +2. **Authenticate ngrok**: + - Sign up for a free account at ngrok.com. + - After signing up, you’ll receive an authtoken. Run the following command to set it up: + ```bash + ngrok config add-authtoken + ``` + +## Step 2: Modify Flask to Accept HTTPS + +Update your Flask application to use ngrok to tunnel HTTPS traffic: + +1. **Change `run_flask()` to bind to HTTP**: + This is so ngrok can handle the SSL termination. Update the endpoint by removing the `ssl_context`: + ```python + def run_flask(): + app.run(host="0.0.0.0", port=8888) + ``` + +2. **Expose the Flask app via ngrok**: + You can run ngrok to expose your Flask app by executing: + ```bash + ngrok http 8888 + ``` + This will give you a public HTTPS URL that tunnels to your local Flask app. + +## Step 3: Modify the Development Environment (devenv.nix) + +You’ll need to add the ngrok installation to your `devenv.nix` configuration, if it's not already installed. + +1. **Edit `devenv.nix`**: + +```nix +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = [ + pkgs.ngrok + ]; + + shellHook = '' + # Set up ngrok to run automatically + ngrok http 8888 & + ''; +} +``` + +This snippet sets up ngrok to run in the background whenever you start your development environment. + +## Step 4: Implement the PKI Chain in `geturl.py` + +To implement an HTTPS PKI chain, consider using the Let's Encrypt certificate. For local development, you can't use Let's Encrypt directly without a publicly reachable domain, but you can still prepare your code as if you're using secure connections. + +1. **Modify `geturl.py`**: + +Assuming your application logic has not changed, implement a function to fetch the ngrok URL: + +```python +import os +import requests +import threading +import time + +def get_ngrok_url(): + response = requests.get("http://localhost:4040/api/tunnels") + tunnels = response.json()["tunnels"] + for tunnel in tunnels: + if tunnel["name"] == "http": + return tunnel["public_url"] + return None + +def create_playlist(): + # Your existing code to create a playlist + ... + +def run_flask(): + app.run(host="0.0.0.0", port=8888) + +def main(): + # Start Flask in a background thread + flask_thread = threading.Thread(target=run_flask, daemon=True) + flask_thread.start() + + # Wait for ngrok to be ready + time.sleep(5) # Allow some time for ngrok to initialize + + ngrok_url = get_ngrok_url() + print(f"Ngrok URL: {ngrok_url}") + + print("Please authorize access via the following URL:", sp_oauth.get_authorize_url()) + + while not os.path.exists("access_token.txt"): + time.sleep(1) + + playlist_url = create_playlist() + print(f"Playlist URL returned: {playlist_url}") + +if __name__ == "__main__": + main() +``` + +In this code: +- `get_ngrok_url()` fetches the ngrok tunnel URL dynamically. +- You can then safely use this URL to inform other services of your HTTPS endpoint. + +## Summary + +This setup will let you run a local Flask application using HTTPS through an ngrok tunnel. You process the playlist creation and output the URL correctly while working in a secure environment. If you later transition to production, you can directly implement Let's Encrypt for your domain, maintaining the same architecture with minimal changes.