Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Syncify

Syncify copies your Spotify "Liked Songs" to a regular playlist that can be made public and shared with friends. Spotify has never offered this natively, so here we are.

It syncs automatically every 24 hours. You can also trigger a sync manually at any time.

Live at syncify.keval6b.com

How it works

  1. Log in with Spotify and grant access
  2. Press "Enqueue Sync" to copy your liked songs into a playlist (or wait for the automatic 24-hour sync)
  3. Find your new playlist in your Spotify library

Playlists are matched by name, so renaming one causes a fresh playlist to be created on the next sync, which is useful for keeping snapshots.

Architecture

Fully serverless on AWS, running at roughly $1-5/month.

  • Frontend — React SPA (Vite, TanStack Router/Query) deployed to S3 behind CloudFront
  • API — FastAPI + Mangum on Lambda (arm64), behind API Gateway HTTP API (rate limited: 20 req/s, burst 50), JWT cookie sessions
  • Worker — separate Lambda with reserved concurrency of 1, triggered by SQS
  • Scheduling — one EventBridge Schedule per user (rate 24h) feeds the SQS queue automatically; created on signup and deleted on account deletion or revoked Spotify access
  • Database — DynamoDB; sync request history expires after 1 year via TTL
  • IaC — Terraform in infra/
  • CI/CD — GitHub Actions deploys on push to main using OIDC (no stored AWS keys); frontend and Lambda layer builds run in parallel

Deploying your own instance

Prerequisites

  • AWS account
  • Spotify app (create one here) with a redirect URI you will add after the first deploy
  • Terraform >= 1.9
  • A GitHub repo with Actions enabled

1. Create a Terraform state bucket

Create an S3 bucket in your target region for Terraform state, then update the bucket, key, and region in infra/versions.tf.

2. Create a GitHub Actions deploy role

Create an IAM role trusted by GitHub Actions OIDC (token.actions.githubusercontent.com) and scoped to your repository and the prd environment. Attach the following AWS managed policies:

  • AWSLambda_FullAccess
  • AmazonAPIGatewayAdministrator
  • AmazonDynamoDBFullAccess
  • AmazonSQSFullAccess
  • AmazonS3FullAccess
  • CloudFrontFullAccess
  • AmazonEventBridgeFullAccess
  • AmazonEventBridgeSchedulerFullAccess
  • CloudWatchFullAccess
  • AmazonSNSFullAccess

For IAM (needed to manage Lambda execution roles), attach a custom policy scoped to arn:aws:iam::*:role/syncify-* covering iam:CreateRole, iam:DeleteRole, iam:GetRole, iam:TagRole, iam:PutRolePolicy, iam:DeleteRolePolicy, iam:GetRolePolicy, iam:ListRolePolicies, iam:ListAttachedRolePolicies, and iam:PassRole (the latter conditioned on iam:PassedToService of lambda.amazonaws.com and scheduler.amazonaws.com).

3. Configure GitHub environment secrets

Create a prd environment in your GitHub repo settings and add the following secrets:

SecretDescription
AWS_DEPLOY_ROLE_ARNARN of the deploy role created above
SPOTIPY_CLIENT_IDSpotify app client ID
SPOTIPY_CLIENT_SECRETSpotify app client secret
JWT_SECRETSecret for signing session cookies; generate with openssl rand -hex 32
POSTHOG_API_KEYPostHog API key (optional; used for both frontend analytics and server-side event capture)

4. Deploy

Push to main. The workflow builds the frontend (with PostHog key baked in as a VITE_ variable) and the Lambda dependency layer in parallel, then runs terraform apply, syncs the frontend to S3, and invalidates the CloudFront cache.

5. First-time setup after deploy

  1. Custom domain — add your domain and ACM certificate to the CloudFront distribution in the AWS console; Terraform is configured to ignore these fields on subsequent deploys
  2. Spotify redirect URI — register https://your-domain.com/api/v1/auth/callback in your Spotify app's settings
  3. SNS alerts — subscribe to the syncify-alarms SNS topic in AWS to receive email alerts for Lambda errors and worker failures

About

Sync your Spotify 'Liked Songs' playlist to a sharable one.

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Packages

Contributors

Languages