A web application for nocturne-image and the Spotify Car Thing.
How To Use • Spotify Developer Setup • Local Development Setup • Donate • Credits • Related • License
- First, follow the steps in Spotify Developer Setup
- Follow the steps for your operating system in the image's repo
- Once running on your Car Thing, press "Use Custom Credentials" and enter your Spotify Client ID and Client Secret using VNC Viewer
- Your credentials will be encrypted and stored securely
- Authorize with Spotify
- Start using the app
It is not recommended to use default credentials at this point in time. Unless you know what you're doing, please use custom credentials
- First, follow the steps in Spotify Developer Setup
- Follow the steps for your operating system in the image's repo.
- Once running on your Car Thing, hold the back button (under the knob) until the "Use Default Credentials (Beta)" button appears
- Press the "Use Default Credentials (Beta)" button
- Authorize with Spotify
- Start using the app
- Hold one of the top hardware preset buttons while on a playlist page to map it to the button
- Press the mapped buttons to quickly play playlists
- Hold the right-most top hardware button to access settings
- Triple-press the right-most top hardware button to access brightness control
- Press the hardware button underneath of the knob to go back in the application
-
Go to the Spotify Developer Dashboard
-
Log in with your Spotify account (create one if needed)
-
Click "Create App"
- App name: Choose a name (e.g., "Nocturne")
- App description: Brief description of your app
- Redirect URI: Add
https://nocturne.brandons.place
for non-development usage, orhttps://your.local.ip.address:port
for development usage - Select "Web API" and "Web Playback SDK" as the API's you will be using
-
After creating the app, you'll see your:
- Client ID (shown on the dashboard)
- Client Secret (click "Show Client Secret")
-
Required Scopes: The app needs these Spotify scopes:
user-read-recently-played user-read-private user-top-read user-read-playback-state user-modify-playback-state user-read-currently-playing user-library-read user-library-modify playlist-read-private playlist-read-collaborative playlist-modify-public playlist-modify-private
First, set up the configuration and database:
- Clone the repository:
git clone https://github.com/yourusername/nocturne-ui.git
cd nocturne
- Install dependencies:
npm install
# or
yarn install
# or
bun install
- Copy the environment example file:
cp .env.example .env.local
- Set up your Supabase project:
- Create a new project at Supabase
- Create a new table named
spotify_credentials
with the following schema:
create table spotify_credentials (
id uuid default uuid_generate_v4() primary key,
temp_id text not null,
client_id text not null,
encrypted_client_secret text not null,
refresh_token text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
expires_at timestamp with time zone,
last_used timestamp with time zone,
first_used_at timestamp with time zone,
token_refresh_count integer default 0,
user_agent text
);
-- Add policies for Row Level Security (RLS)
alter table spotify_credentials enable row level security;
-- Cleanup expired records
create policy "Cleanup expired records"
on spotify_credentials for all
using (expires_at < current_timestamp or refresh_token = current_setting('app.current_refresh_token'::text, true));
-- Cleanup unused temp IDs
create policy "cleanup_unused_temp_ids"
on spotify_credentials for all
using (created_at < current_timestamp - interval '1 hour' and refresh_token is null);
-- Enforce expiration
create policy "Enforce expiration"
on spotify_credentials for all
using (expires_at is null or expires_at > current_timestamp);
-- Update restrictions
create policy "Restricted updates"
on spotify_credentials for update
using (temp_id is not null)
with check (
client_id = (select client_id from spotify_credentials where id = id)
and encrypted_client_secret = (select encrypted_client_secret from spotify_credentials where id = id)
and temp_id = (select temp_id from spotify_credentials where id = id)
and created_at = (select created_at from spotify_credentials where id = id)
);
-- Insert validation
create policy "Validated inserts"
on spotify_credentials for insert
with check (
temp_id is not null
and client_id is not null
and encrypted_client_secret is not null
and length(temp_id) >= 7
and created_at <= current_timestamp
and (expires_at is null or expires_at > current_timestamp)
and not exists (
select 1 from spotify_credentials sc
where sc.temp_id = spotify_credentials.temp_id
)
);
- Generate encryption keys:
# Generate 32-byte key for AES-256
openssl rand -hex 32
# Generate 16-byte IV
openssl rand -hex 16
- Update your
.env.local
with the required values:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
# Spotify
NEXT_PUBLIC_SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
NEXT_PUBLIC_REDIRECT_URI=https://your.local.ip.address:port
# Encryption (from step 5)
ENCRYPTION_KEY=your_32_byte_hex_key
ENCRYPTION_IV=your_16_byte_hex_key
Now, it's time to:
Nocturne requires HTTPS to make API requests, so you need to set up HTTPS on the local server. This makes two different ways to run the dev server:
Using Caddy
This will use Caddy as a reverse proxy in front of Next.js's dev server.
On the last step where you start the development server, use npx next dev
and then, in another tab/
window, caddy run
to start. It will run on https://localhost:3443.
-
Install Caddy: this can be done with your OS package manager, or by downloading the executable from https://caddyserver.com/download.
-
Start the Next.js dev server:
npx next dev
- In a different tab/window, start Caddy. This will run at https://localhost:3443.
caddy run
This may prompt you for a sudo
password to install Caddy's certificate on your system, to make it automatically trusted.
First, install mkcert
.
# macOS
brew install mkcert
# Windows (using chocolatey)
choco install mkcert
# Linux
apt install mkcert
Trust the mkcert CA system-wide.
mkcert -install
Generate a certificate for localhost (and local IP addresses).
mkcert localhost 127.0.0.1 ::1 your.local.ip.address
Copy the certificate to the current dir.
# Rename the generated certificates
mv localhost+3.pem cert.crt
mv localhost+3-key.pem cert.key
# Copy CA certificates (locations vary by OS)
# macOS
cp "$(mkcert -CAROOT)/rootCA.pem" ca.crt
cp "$(mkcert -CAROOT)/rootCA-key.pem" ca.key
# Windows
copy "%LOCALAPPDATA%\mkcert\rootCA.pem" ca.crt
copy "%LOCALAPPDATA%\mkcert\rootCA-key.pem" ca.key
# Linux
cp "$(mkcert -CAROOT)/rootCA.pem" ca.crt
cp "$(mkcert -CAROOT)/rootCA-key.pem" ca.key
Create custom server file at ./server.js
:
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const httpsOptions = {
key: fs.readFileSync('./cert.key'),
cert: fs.readFileSync('./cert.crt')
};
app.prepare().then(() => {
createServer(httpsOptions, (req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(3000, (err) => {
if (err) throw err;
console.log('> Ready on https://localhost:3000');
console.log('> Also available on https://your.local.ip.address:3000');
});
});
Run the server:
npm run dev
# or
yarn dev
# or
bun dev
- Install Wrangler CLI:
npm install -g wrangler
- Login to Cloudflare:
wrangler login
- Create a KV namespace for encryption keys:
cd workers/key-rotation
npx wrangler kv:namespace create ENCRYPTION_KEYS
- Update the KV namespace ID in your root
wrangler.toml
:
name = "nocturne"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
kv_namespaces = [
{ binding = "ENCRYPTION_KEYS", id = "your_namespace_id_here" }
]
[vars]
NEXT_PUBLIC_REDIRECT_URI = "your_production_url"
NEXT_PUBLIC_SUPABASE_URL = "your_supabase_url"
NEXT_PUBLIC_SPOTIFY_CLIENT_ID = "your_client_id"
- Set up environment secrets:
# For the main app
npx wrangler secret put SUPABASE_ANON_KEY
npx wrangler secret put SPOTIFY_CLIENT_SECRET
npx wrangler secret put ENCRYPTION_KEY
npx wrangler secret put ENCRYPTION_IV
# For the key rotation worker
cd workers/key-rotation
npx wrangler secret put ENCRYPTION_KEY
npx wrangler secret put ENCRYPTION_IV
- Deploy the key rotation worker:
cd workers/key-rotation
npx wrangler deploy
The project includes automatic key rotation for encrypted credentials. The rotation worker runs every 7 days and:
- Generates new encryption keys
- Re-encrypts existing credentials
- Maintains backups during rotation
- Handles failures gracefully
The rotation schedule can be modified in workers/key-rotation/wrangler.toml
.
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-feature
- Commit your changes:
git commit -m 'Add new feature'
- Push to the branch:
git push origin feature/new-feature
- Open a Pull Request
- Client secrets are encrypted using AES-256-CBC
- Keys are automatically rotated every 7 days
- Credentials are stored with expiration dates
- Unused credentials are automatically cleaned up
- All sensitive operations happen server-side
Nocturne is a massive endeavor, and the team have spent everyday over the last few months making it a reality out of our passion for creating something that people like you love to use.
All donations are split between the four members of the Nocturne team, and go towards the development of future features. We are so grateful for your support!
This software was made possible only through the following individuals and open source programs:
- Benjamin McGill, for giving me a Car Thing to develop with
- shadow, for OS development, testing, troubleshooting, and crazy good repo maintainence
- Dominic Frye, for OS development, debugging, testing, and marketing
- bbaovanc, for OS development, debugging, and testing
- bishopdynamics, for creating the original superbird-tool, and superbird-debian-kiosk
- Car Thing Hax Community's fork of superbird-tool, for their contributions on the original superbird-tool
nocturne-image - The Debian image that runs this web application
This project is licensed under the GPL-3.0 license.
We kindly ask that any modifications or distributions made outside of direct forks from this repository include attribution to the original project in the README, as we have worked hard on this. :)
brandons.place · GitHub @brandonsaldan · Twitter @brandonsaldan