From c539c993af9ee61f64c6088def9fd25fbf6e149d Mon Sep 17 00:00:00 2001 From: Sam Peters Date: Fri, 1 Dec 2023 13:04:44 -0700 Subject: [PATCH] chore(pay): change env variable pattern --- apps/pay/Dockerfile | 68 ++++++++----------- .../app/lnurlp/[username]/callback/route.ts | 21 +++--- apps/pay/app/lnurlp/[username]/graphql.ts | 4 +- apps/pay/app/lnurlp/[username]/route.ts | 13 ++-- apps/pay/components/Layouts/AppLayout.tsx | 4 +- .../ParsePOSPayment/Receive-Invoice.tsx | 5 +- apps/pay/config/config.ts | 2 - apps/pay/env.ts | 33 +++++++++ apps/pay/lib/config.ts | 43 ------------ apps/pay/lib/graphql.tsx | 6 +- apps/pay/package.json | 14 ++-- apps/pay/pages/[username]/print.tsx | 4 +- apps/pay/pages/index.tsx | 4 +- dev/Tiltfile | 7 +- pnpm-lock.yaml | 6 ++ 15 files changed, 109 insertions(+), 125 deletions(-) create mode 100644 apps/pay/env.ts delete mode 100644 apps/pay/lib/config.ts diff --git a/apps/pay/Dockerfile b/apps/pay/Dockerfile index 9ebf2c6058..c7b6c044bb 100644 --- a/apps/pay/Dockerfile +++ b/apps/pay/Dockerfile @@ -1,42 +1,32 @@ -# Install dependencies only when needed -FROM node:20-alpine AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat python3 make g++ -WORKDIR /app -COPY package.json yarn.lock ./ -RUN yarn global add node-gyp \ - && yarn install --frozen-lockfile - -# Rebuild the source code only when needed -FROM node:20-alpine AS builder -WORKDIR /app -COPY . . -COPY --from=deps /app/node_modules ./node_modules -RUN yarn build && yarn install --production --ignore-scripts --prefer-offline - -# Production image, copy all the files and run next -FROM node:20-alpine AS runner -WORKDIR /app - -ENV NODE_ENV production - -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nextjs -u 1001 - -# You only need to copy next.config.js if you are NOT using the default configuration -# COPY --from=builder /app/next.config.js ./ -COPY --from=builder /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/package.json ./package.json - -USER nextjs - -EXPOSE 3000 - -ARG BUILDTIME +FROM nixos/nix:latest AS builder +ARG APP=pay + +COPY . /workdir +WORKDIR /workdir + +RUN set -eux; \ + nix \ + --extra-experimental-features "nix-command flakes impure-derivations ca-derivations" \ + --option filter-syscalls false \ + build \ + ".#$APP"; + +RUN mkdir -p /tmp/nix-store-closure /tmp/local-bin +RUN cp -R $(nix-store --query --requisites result/) /tmp/nix-store-closure +RUN ln -snf $(nix-store --query result/)/bin/* /tmp/local-bin/ + +FROM gcr.io/distroless/static-debian11 AS final +ARG APP=pay + +WORKDIR /app/$APP +COPY --from=builder /tmp/nix-store-closure /nix/store +COPY --from=builder /tmp/local-bin/* /usr/local/bin/ + +USER 1000 + ARG COMMITHASH -ENV BUILDTIME ${BUILDTIME} ENV COMMITHASH ${COMMITHASH} -CMD ["node_modules/.bin/next", "start"] +CMD [ \ + "/usr/local/bin/run" \ +] diff --git a/apps/pay/app/lnurlp/[username]/callback/route.ts b/apps/pay/app/lnurlp/[username]/callback/route.ts index 11a2c71812..7a00fd1c74 100644 --- a/apps/pay/app/lnurlp/[username]/callback/route.ts +++ b/apps/pay/app/lnurlp/[username]/callback/route.ts @@ -8,8 +8,7 @@ import { gql } from "@apollo/client" import Redis from "ioredis" -import { URL_HOST_DOMAIN } from "../../../../config/config" -import { NOSTR_PUBKEY } from "../../../../lib/config" +import { env } from "../../../../env" import { AccountDefaultWalletDocument, AccountDefaultWalletQuery, @@ -42,29 +41,29 @@ gql` } ` -const nostrEnabled = !!NOSTR_PUBKEY +const nostrEnabled = !!env.NOSTR_PUBKEY let redis: Redis | null = null if (nostrEnabled) { const connectionObj = { - sentinelPassword: process.env.REDIS_PASSWORD, + sentinelPassword: env.REDIS_PASSWORD, sentinels: [ { - host: `${process.env.REDIS_0_DNS}`, + host: `${env.REDIS_0_DNS}`, port: 26379, }, { - host: `${process.env.REDIS_1_DNS}`, + host: `${env.REDIS_1_DNS}`, port: 26379, }, { - host: `${process.env.REDIS_2_DNS}`, + host: `${env.REDIS_2_DNS}`, port: 26379, }, ], - name: process.env.REDIS_MASTER_NAME ?? "mymaster", - password: process.env.REDIS_PASSWORD, + name: env.REDIS_MASTER_NAME ?? "mymaster", + password: env.REDIS_PASSWORD, } redis = new Redis(connectionObj) @@ -76,8 +75,6 @@ export async function GET( request: Request, { params }: { params: { username: string } }, ) { - console.log(NOSTR_PUBKEY) - const { searchParams } = new URL(request.url) const username = params.username @@ -119,7 +116,7 @@ export async function GET( const metadata = JSON.stringify([ ["text/plain", `Payment to ${username}`], - ["text/identifier", `${username}@${URL_HOST_DOMAIN}`], + ["text/identifier", `${username}@${env.NEXT_PUBLIC_PAY_DOMAIN}`], ]) // lnurl generate invoice diff --git a/apps/pay/app/lnurlp/[username]/graphql.ts b/apps/pay/app/lnurlp/[username]/graphql.ts index a13ec92224..4a6beb096d 100644 --- a/apps/pay/app/lnurlp/[username]/graphql.ts +++ b/apps/pay/app/lnurlp/[username]/graphql.ts @@ -7,7 +7,7 @@ import { InMemoryCache, } from "@apollo/client" -import { GRAPHQL_URL_INTERNAL } from "../../../lib/config" +import { env } from "../../../env" const ipForwardingMiddleware = new ApolloLink((operation, forward) => { operation.setContext(({ headers = {} }) => ({ @@ -25,7 +25,7 @@ export const client = new ApolloClient({ link: concat( ipForwardingMiddleware, new HttpLink({ - uri: GRAPHQL_URL_INTERNAL, + uri: env.CORE_GQL_URL_INTRANET, fetchOptions: { cache: "no-store" }, }), ), diff --git a/apps/pay/app/lnurlp/[username]/route.ts b/apps/pay/app/lnurlp/[username]/route.ts index 523061490c..293d7523b7 100644 --- a/apps/pay/app/lnurlp/[username]/route.ts +++ b/apps/pay/app/lnurlp/[username]/route.ts @@ -1,7 +1,6 @@ import { NextResponse } from "next/server" -import { URL_HOST_DOMAIN } from "../../../config/config" -import { NOSTR_PUBKEY, PAY_SERVER } from "../../../lib/config" +import { env } from "../../../env" import { AccountDefaultWalletDocument, AccountDefaultWalletQuery, @@ -13,14 +12,12 @@ import { client } from "./graphql" const COMMENT_SIZE = 2000 // 2000 characters max for GET -const nostrEnabled = !!NOSTR_PUBKEY +const nostrEnabled = !!env.NOSTR_PUBKEY export async function GET( request: Request, { params }: { params: { username: string } }, ) { - console.log(NOSTR_PUBKEY) - const { searchParams } = new URL(request.url) const username = params.username @@ -73,10 +70,10 @@ export async function GET( const metadata = JSON.stringify([ ["text/plain", `Payment to ${username}`], - ["text/identifier", `${username}@${URL_HOST_DOMAIN}`], + ["text/identifier", `${username}@${env.NEXT_PUBLIC_PAY_DOMAIN}`], ]) - const callback = `${PAY_SERVER}/lnurlp/${username}/callback` + const callback = `${env.PAY_URL}/lnurlp/${username}/callback` let minSendable = 1000 // 1 sat in millisat let maxSendable = 100000000000 // 1 BTC in millisat @@ -96,7 +93,7 @@ export async function GET( ...(nostrEnabled ? { allowsNostr: true, - nostrPubkey: NOSTR_PUBKEY, + nostrPubkey: env.NOSTR_PUBKEY, } : {}), }) diff --git a/apps/pay/components/Layouts/AppLayout.tsx b/apps/pay/components/Layouts/AppLayout.tsx index fbf113ad7c..47519b8701 100644 --- a/apps/pay/components/Layouts/AppLayout.tsx +++ b/apps/pay/components/Layouts/AppLayout.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router" import React from "react" import { Image, OverlayTrigger, Tooltip } from "react-bootstrap" -import { URL_HOST_DOMAIN } from "../../config/config" +import { env } from "../../env" import styles from "./app-layout.module.css" @@ -19,7 +19,7 @@ const AppLayout = ({ children, username }: Props) => { const [openSideBar, setOpenSideBar] = React.useState(false) const [copied, setCopied] = React.useState(false) const lightningAddr = username - ? `${username?.toString().toLowerCase()}@${URL_HOST_DOMAIN}` + ? `${username?.toString().toLowerCase()}@${env.NEXT_PUBLIC_PAY_DOMAIN}` : "" const cashRegisterLink = username ? `/${username}` : "#" diff --git a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx index 506db28f9c..8be66b27d7 100644 --- a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx +++ b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx @@ -9,7 +9,8 @@ import Tooltip from "react-bootstrap/Tooltip" import { QRCode } from "react-qrcode-logo" import { useScreenshot } from "use-react-screenshot" -import { URL_HOST_DOMAIN, USD_INVOICE_EXPIRE_INTERVAL } from "../../config/config" +import { USD_INVOICE_EXPIRE_INTERVAL } from "../../config/config" +import { env } from "../../env" import useCreateInvoice from "../../hooks/use-Create-Invoice" import { LnInvoiceObject } from "../../lib/graphql/index.types.d" import useSatPrice from "../../lib/use-sat-price" @@ -52,7 +53,7 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: const shareUrl = !amount && !unit && !memo - ? `https://${URL_HOST_DOMAIN}/${username}?amount=${ + ? `https://${env.NEXT_PUBLIC_PAY_DOMAIN}/${username}?amount=${ state.currentAmount }&sats=${usdToSats( state.currentAmount, diff --git a/apps/pay/config/config.ts b/apps/pay/config/config.ts index b91dc54fdb..66095752d7 100644 --- a/apps/pay/config/config.ts +++ b/apps/pay/config/config.ts @@ -1,5 +1,3 @@ export const USD_INVOICE_EXPIRE_INTERVAL = 60 * 5 export const MAX_INPUT_VALUE_LENGTH = 14 -export const CASH_REGISTER_DESCRIPTION = "Blink POS Cash Register" export const APP_DESCRIPTION = "Blink official lightning network node" -export const URL_HOST_DOMAIN = "blink.sv" diff --git a/apps/pay/env.ts b/apps/pay/env.ts new file mode 100644 index 0000000000..843f776331 --- /dev/null +++ b/apps/pay/env.ts @@ -0,0 +1,33 @@ +import { createEnv } from "@t3-oss/env-nextjs" +import { z } from "zod" + +export const env = createEnv({ + server: { + CORE_GQL_URL_INTRANET: z.string(), // Use intranet URL to preserve tracing headers + PAY_URL: z.string(), + NOSTR_PUBKEY: z.string().optional(), + REDIS_PASSWORD: z.string().optional(), + REDIS_MASTER_NAME: z.string().optional(), + REDIS_0_DNS: z.string().optional(), + REDIS_1_DNS: z.string().optional(), + REDIS_2_DNS: z.string().optional(), + }, + client: { + NEXT_PUBLIC_CORE_GQL_URL: z.string(), + NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL: z.string(), + NEXT_PUBLIC_PAY_DOMAIN: z.string(), + }, + runtimeEnv: { + CORE_GQL_URL_INTRANET: process.env.CORE_GQL_URL_INTRANET, + NEXT_PUBLIC_CORE_GQL_URL: process.env.NEXT_PUBLIC_CORE_GQL_URL, + NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL: process.env.NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL, + PAY_URL: process.env.PAY_URL, + NEXT_PUBLIC_PAY_DOMAIN: process.env.NEXT_PUBLIC_PAY_DOMAIN, + NOSTR_PUBKEY: process.env.NOSTR_PUBKEY, // Optional but required for Nostr Zaps + REDIS_PASSWORD: process.env.REDIS_PASSWORD, // Optional but required for Nostr Zaps + REDIS_MASTER_NAME: process.env.REDIS_MASTER_NAME, // Optional but required for Nostr Zaps + REDIS_0_DNS: process.env.REDIS_0_DNS, // Optional but required for Nostr Zaps + REDIS_1_DNS: process.env.REDIS_1_DNS, // Optional but required for Nostr Zaps + REDIS_2_DNS: process.env.REDIS_2_DNS, // Optional but required for Nostr Zaps + }, +}) diff --git a/apps/pay/lib/config.ts b/apps/pay/lib/config.ts deleted file mode 100644 index a856745e75..0000000000 --- a/apps/pay/lib/config.ts +++ /dev/null @@ -1,43 +0,0 @@ -let GRAPHQL_URL = process.env.NEXT_PUBLIC_GRAPHQL_URL as string -let GRAPHQL_WEBSOCKET_URL = process.env.NEXT_PUBLIC_GRAPHQL_WEBSOCKET_URL as string - -// we need an internal dns to properly propagate the ip related headers to api -// if we use the api endpoints, nginx will rewrite the header to prevent spoofing -// for example: "api.galoy-name-galoy.svc.cluster.local" -const GRAPHQL_URL_INTERNAL = process.env.GRAPHQL_URL_INTERNAL - -// from https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables -// Note: After being built, your app will no longer respond to changes to these environment variables. -// For instance, if you use a Heroku pipeline to promote slugs built in one environment to another environment, -// or if you build and deploy a single Docker image to multiple environments, all NEXT_PUBLIC_ variables will be frozen with the value evaluated at build time, -// so these values need to be set appropriately when the project is built. If you need access to runtime environment values, -// you'll have to setup your own API to provide them to the client (either on demand or during initialization). - -// so we always assume the api is on the same domain as the frontend -if (!GRAPHQL_URL || !GRAPHQL_WEBSOCKET_URL) { - if (typeof window !== "undefined") { - const hostname = new URL(window.location.href).hostname - const hostPartsApi = hostname.split(".") - const hostPartsWs = hostname.split(".") - - hostPartsApi[0] = "api" - hostPartsWs[0] = "ws" - - GRAPHQL_URL = `https://${hostPartsApi.join(".")}/graphql` - GRAPHQL_WEBSOCKET_URL = `wss://${hostPartsWs.join(".")}/graphql` - } else { - console.log("window is undefined") - } -} - -const NOSTR_PUBKEY = process.env.NOSTR_PUBKEY - -const PAY_SERVER = GRAPHQL_URL?.replace("/graphql", "")?.replace("api", "pay") - -export { - GRAPHQL_URL, - GRAPHQL_WEBSOCKET_URL, - GRAPHQL_URL_INTERNAL, - NOSTR_PUBKEY, - PAY_SERVER, -} diff --git a/apps/pay/lib/graphql.tsx b/apps/pay/lib/graphql.tsx index f5e4d3b8be..5b446d57ee 100644 --- a/apps/pay/lib/graphql.tsx +++ b/apps/pay/lib/graphql.tsx @@ -14,15 +14,15 @@ import { onError } from "@apollo/client/link/error" import { createClient } from "graphql-ws" -import { GRAPHQL_URL, GRAPHQL_WEBSOCKET_URL } from "./config" +import { env } from "../env" const httpLink = new HttpLink({ - uri: GRAPHQL_URL, + uri: env.NEXT_PUBLIC_CORE_GQL_URL, }) const wsLink = new GraphQLWsLink( createClient({ - url: GRAPHQL_WEBSOCKET_URL, + url: env.NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL, retryAttempts: 12, connectionParams: {}, shouldRetry: (errOrCloseEvent) => { diff --git a/apps/pay/package.json b/apps/pay/package.json index cd21d6e322..9f0903ece3 100644 --- a/apps/pay/package.json +++ b/apps/pay/package.json @@ -14,6 +14,7 @@ "dependencies": { "@apollo/client": "^3.7.12", "@galoymoney/client": "^0.2.2", + "@t3-oss/env-nextjs": "^0.6.1", "bech32": "^2.0.0", "bitcoinjs-lib": "5.0.5", "bolt11": "1.3.4", @@ -35,7 +36,8 @@ "react-qrcode-logo": "^2.9.0", "react-to-print": "^2.14.12", "use-debounce": "^8.0.4", - "use-react-screenshot": "^3.0.0" + "use-react-screenshot": "^3.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@galoy/eslint-config": "workspace:^", @@ -47,12 +49,17 @@ "@graphql-codegen/typescript-react-apollo": "^3.3.7", "@types/eslint": "^8.2.0", "@types/lodash.debounce": "^4.0.6", + "@types/node": "^20.8.9", "@types/original-url": "^1.2.0", "@types/prettier": "^2.7.0", + "@types/react": "^18.2.33", "@types/react-dev-utils": "^9.0.11", + "@types/react-dom": "^18.2.14", "@types/react-lottie": "^1.2.6", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.51.0", + "eslint": "^8.52.0", + "eslint-config-next": "13.5.6", "eslint-config-react-app": "^7.0.1", "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.26.0", @@ -65,11 +72,6 @@ "prettier": "^2.4.1", "react-dev-utils": "^12.0.1", "ts-pnp": "1.2.0", - "@types/node": "^20.8.9", - "@types/react": "^18.2.33", - "@types/react-dom": "^18.2.14", - "eslint": "^8.52.0", - "eslint-config-next": "13.5.6", "typescript": "5.2.2" }, "eslintConfig": { diff --git a/apps/pay/pages/[username]/print.tsx b/apps/pay/pages/[username]/print.tsx index ad71eeb7c1..0d62f35f79 100644 --- a/apps/pay/pages/[username]/print.tsx +++ b/apps/pay/pages/[username]/print.tsx @@ -10,7 +10,7 @@ import { useRef } from "react" import { NextRequest } from "next/server" import originalUrl from "original-url" -import { URL_HOST_DOMAIN } from "../../config/config" +import { env } from "../../env" export async function getServerSideProps({ req, @@ -43,7 +43,7 @@ export async function getServerSideProps({ props: { qrCodeURL, username, - userHeader: `Pay ${username}@${URL_HOST_DOMAIN}`, + userHeader: `Pay ${username}@${env.NEXT_PUBLIC_PAY_DOMAIN}`, }, } } diff --git a/apps/pay/pages/index.tsx b/apps/pay/pages/index.tsx index 94134df7da..4ea4f593cb 100644 --- a/apps/pay/pages/index.tsx +++ b/apps/pay/pages/index.tsx @@ -9,7 +9,7 @@ import { gql, useQuery } from "@apollo/client" import { useRouter } from "next/router" -import { GRAPHQL_URL } from "../lib/config" +import { env } from "../env" import CurrencyDropdown from "../components/Currency/currency-dropdown" const GET_NODE_STATS = gql` @@ -21,7 +21,7 @@ const GET_NODE_STATS = gql` ` function Home() { - const nodeUrl = GRAPHQL_URL.includes("staging") + const nodeUrl = env.NEXT_PUBLIC_CORE_GQL_URL.includes("staging") ? `https://mempool.space/signet/lightning/node/` : `https://mempool.space/lightning/node/` const { loading, error, data } = useQuery(GET_NODE_STATS) diff --git a/dev/Tiltfile b/dev/Tiltfile index dd1b4a5633..0527db3bb9 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -74,9 +74,12 @@ local_resource( cmd = "buck2 build {}".format(pay_target), serve_cmd = "buck2 run {}".format(pay_target), serve_env = { - "NEXTAUTH_URL": "http://localhost:3002", - "NEXTAUTH_SECRET": "secret", "PORT": "3002", + "CORE_GQL_URL_INTRANET": "http://localhost:4455/graphql", + "NEXT_PUBLIC_CORE_GQL_URL": "http://localhost:4455/graphql", + "NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL": "wss://localhost:4455/graphql", + "PAY_URL":"http://localhost:3002", + "NEXT_PUBLIC_PAY_DOMAIN":"http://localhost:3002", }, deps = _buck2_dep_inputs(pay_target), allow_parallel = True, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac328d692d..aabc443e90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,6 +302,9 @@ importers: '@galoymoney/client': specifier: ^0.2.2 version: 0.2.6(@bitcoinerlab/secp256k1@1.0.5)(bitcoinjs-lib@5.0.5)(bolt11@1.3.4)(lnurl-pay@1.0.1)(url@0.11.3) + '@t3-oss/env-nextjs': + specifier: ^0.6.1 + version: 0.6.1(typescript@5.2.2)(zod@3.22.4) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -368,6 +371,9 @@ importers: use-react-screenshot: specifier: ^3.0.0 version: 3.0.0(html2canvas@1.4.1)(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@galoy/eslint-config': specifier: workspace:^