diff --git a/.github/workflows/tilt.yaml b/.github/workflows/tilt.yaml new file mode 100644 index 0000000..f52783e --- /dev/null +++ b/.github/workflows/tilt.yaml @@ -0,0 +1,44 @@ +name: "Tilt CI" +on: + pull_request: + branches: [ main ] +jobs: + tilt-ci: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: AbsaOSS/k3d-action@v2 + name: "Create Single Cluster" + with: + cluster-name: "k3s-default" + k3d-version: v5.7.4 + args: >- + --image rancher/k3s:v1.30.4-k3s1 + -v "$(pwd):/charts" + --k3s-arg --disable=traefik@server:0 + --k3s-arg --disable=servicelb@server:0 + - name: Tilt CI + run: | + pushd dev + for i in {1..5}; do + echo "Tilt CI attempt $i" + nix develop -c tilt ci && exit 0 || sleep 60 + done + exit 1 + - name: Bitcoin Smoketest + run: | + pushd dev + kubectl -n galoy-dev-smoketest exec smoketest -- bash -c "cd /charts/ci/tasks && \ + CHART=bitcoind ./dev-smoketest-settings.sh && \ + ./bitcoind-smoketest.sh regtest && \ + rm -rf smoketest-settings" + - name: Clean Up + if: always() + run: | + rm -rf ci/tasks/smoketest-settings + k3d cluster delete --all diff --git a/charts/galoy-deps/.helmignore b/charts/galoy-deps/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/galoy-deps/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/galoy-deps/Chart.lock b/charts/galoy-deps/Chart.lock new file mode 100644 index 0000000..4e9f99d --- /dev/null +++ b/charts/galoy-deps/Chart.lock @@ -0,0 +1,18 @@ +dependencies: +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.14.3 +- name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.7.1 +- name: strimzi-kafka-operator + repository: https://strimzi.io/charts/ + version: 0.39.0 +- name: kube-monkey + repository: https://asobti.github.io/kube-monkey/charts/repo + version: 1.5.2 +- name: opentelemetry-collector + repository: https://open-telemetry.github.io/opentelemetry-helm-charts + version: 0.108.0 +digest: sha256:e2b4306eac185d9ee0827e570187a6b88e07ab4454d75153b84d2298d5379393 +generated: "2024-10-04T19:59:12.231527006Z" diff --git a/charts/galoy-deps/Chart.yaml b/charts/galoy-deps/Chart.yaml new file mode 100644 index 0000000..092c9c5 --- /dev/null +++ b/charts/galoy-deps/Chart.yaml @@ -0,0 +1,43 @@ +apiVersion: v2 +name: galoy-deps +description: A Helm chart for Kubernetes +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.10.20-dev +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" +dependencies: + - name: "cert-manager" + repository: "https://charts.jetstack.io" + version: v1.14.3 + condition: cert-manager.enabled + - name: "ingress-nginx" + repository: "https://kubernetes.github.io/ingress-nginx" + version: 4.7.1 + condition: ingress-nginx.enabled + - name: strimzi-kafka-operator + repository: https://strimzi.io/charts/ + version: 0.39.0 + condition: strimzi-kafka-operator.enabled + - name: kube-monkey + alias: kubemonkey + repository: https://asobti.github.io/kube-monkey/charts/repo + version: 1.5.2 + condition: kubemonkey.enabled + - name: opentelemetry-collector + repository: https://open-telemetry.github.io/opentelemetry-helm-charts + version: 0.108.0 + condition: opentelemetry-collector.enabled diff --git a/charts/galoy-deps/templates/NOTES.txt b/charts/galoy-deps/templates/NOTES.txt new file mode 100644 index 0000000..e69de29 diff --git a/charts/galoy-deps/templates/_helpers.tpl b/charts/galoy-deps/templates/_helpers.tpl new file mode 100644 index 0000000..66ea0f5 --- /dev/null +++ b/charts/galoy-deps/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "galoy-deps.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "galoy-deps.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "galoy-deps.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "galoy-deps.labels" -}} +helm.sh/chart: {{ include "galoy-deps.chart" . }} +{{ include "galoy-deps.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "galoy-deps.selectorLabels" -}} +app.kubernetes.io/name: {{ include "galoy-deps.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "galoy-deps.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "galoy-deps.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/galoy-deps/templates/kafka-cluster.yaml b/charts/galoy-deps/templates/kafka-cluster.yaml new file mode 100644 index 0000000..6905f7f --- /dev/null +++ b/charts/galoy-deps/templates/kafka-cluster.yaml @@ -0,0 +1,39 @@ +{{- if (index .Values "strimzi-kafka-operator").enabled -}} +apiVersion: kafka.strimzi.io/v1beta2 +kind: Kafka +metadata: + name: kafka +spec: + kafka: + version: 3.6.0 + replicas: 3 + listeners: + - name: plain + port: 9092 + type: {{ index .Values "strimzi-kafka-operator" "kafka" "listener" "type" }} + tls: false + config: + # https://github.com/strimzi/strimzi-kafka-operator/blob/main/documentation/api/io.strimzi.api.kafka.model.KafkaClusterSpec.adoc + # https://github.com/strimzi/strimzi-kafka-operator/blob/main/documentation/modules/managing/con-broker-config-properties.adoc + auto.create.topics.enable: false + offsets.topic.replication.factor: 3 + transaction.state.log.replication.factor: 3 + transaction.state.log.min.isr: 1 + default.replication.factor: 3 + min.insync.replicas: 2 + log.retention.hours: 72 # 3 days + log.segment.bytes: 100000000 # 100 MB + log.retention.check.interval.ms: 300000 # 5 minutes + storage: + type: ephemeral + resources: + {{ toYaml (index .Values "strimzi-kafka-operator" "kafka" "resources") | nindent 6 }} + zookeeper: + replicas: 3 + storage: + type: ephemeral + resources: + {{ toYaml (index .Values "strimzi-kafka-operator" "zookeeper" "resources") | nindent 6 }} + entityOperator: + userOperator: {} +{{- end -}} diff --git a/charts/galoy-deps/values.yaml b/charts/galoy-deps/values.yaml new file mode 100644 index 0000000..24392a8 --- /dev/null +++ b/charts/galoy-deps/values.yaml @@ -0,0 +1,252 @@ +# Default values for galoy-deps. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +strimzi-kafka-operator: + enabled: true + nameOverride: kafka + watchNamespaces: [] + kafka: + listener: + type: cluster-ip + resources: {} + zookeeper: + resources: {} + resources: {} + +kubemonkey: + enabled: true + fullnameOverride: kubemonkey + resources: {} + +opentelemetry-collector: + image: + repository: "otel/opentelemetry-collector-k8s" + service: + enabled: true + extraEnvs: + - name: K8S_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + enabled: true + clusterRole: + create: true + rules: + - apiGroups: [""] + resources: ["nodes/stats"] + verbs: ["get"] + resources: {} + mode: daemonset + config: + exporters: + debug: {} + otlp: + endpoint: "api.honeycomb.io:443" + headers: + "x-honeycomb-team": ${HONEYCOMB_API_KEY} + "x-honeycomb-dataset": ${HONEYCOMB_DATASET} + extensions: + health_check: {} + processors: + batch: {} + tail_sampling: + policies: + - name: status_code + type: status_code + status_code: + status_codes: [ERROR] + - name: probabilistic + type: probabilistic + probabilistic: { sampling_percentage: 100 } + attributes: + actions: + - key: graphql.variables.input.code + action: update + value: "" + - key: code.function.params.code + action: update + value: "" + - key: code.function.params.token + action: update + value: "" + - key: code.function.params.cookie + action: update + value: "" + - key: code.function.params.authToken + action: update + value: "" + - key: code.function.params.totpCode + action: update + value: "" + - key: code.function.params.body + action: update + value: "" + - key: code.function.params.macaroon + action: update + value: "" + - key: code.function.params.cert + action: update + value: "" + - key: code.function.params.secret + action: update + value: "" + - key: code.function.params.rawHeaders + action: update + value: "" + - key: code.function.params.key + action: update + value: "" + - key: code.function.params.value + action: update + value: "" + - key: args + action: update + value: "" + - key: graphql.variables.input.jwt + action: update + value: "" + - key: code.function.params.jwt + action: update + value: "" + - key: code.function.params.password + action: update + value: "" + - key: graphql.variables.input.totpCode + action: update + value: "" + - key: graphql.variables.input.authToken + action: update + value: "" + # Default memory limiter configuration for the collector based on k8s resource limits. + memory_limiter: + # check_interval is the time between measurements of memory usage. + check_interval: 5s + # By default limit_mib is set to 80% of ".Values.resources.limits.memory" + limit_percentage: 80 + # By default spike_limit_mib is set to 25% of ".Values.resources.limits.memory" + spike_limit_percentage: 25 + resourcedetection: + detectors: [env, gcp] + timeout: 5s + override: false + k8sattributes: + passthrough: true + filter/ottl: + error_mode: ignore + traces: + span: + - 'kind.string != "Server" and IsMatch(resource.attributes["service.name"], ".*-kratos")' + - 'name == "query_planning" and IsMatch(resource.attributes["service.name"], ".*-router")' + spanevent: + - 'name == "" and IsMatch(resource.attributes["service.name"], ".*-router")' + metrics: + metric: + - 'resource.attributes["k8s.namespace.name"] == "kube-system"' + receivers: + kubeletstats: + collection_interval: 60s + auth_type: "serviceAccount" + endpoint: "https://${env:K8S_NODE_NAME}:10250" + insecure_skip_verify: true + jaeger: + protocols: + grpc: + endpoint: 0.0.0.0:14250 + thrift_http: + endpoint: 0.0.0.0:14268 + thrift_compact: + endpoint: 0.0.0.0:6831 + thrift_binary: + endpoint: 0.0.0.0:6832 + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + service: + extensions: + - health_check + pipelines: + logs: + exporters: + - debug + processors: + - memory_limiter + - batch + receivers: + - otlp + metrics: + exporters: + - otlp + - debug + processors: + - filter/ottl + - memory_limiter + - batch + receivers: + - otlp + - kubeletstats + traces: + exporters: + - debug + - otlp + processors: + - filter/ottl + - memory_limiter + - resourcedetection + - attributes + - k8sattributes + - tail_sampling + - batch + receivers: + - jaeger + - otlp + ports: + otlp: + enabled: true + containerPort: 4317 + servicePort: 4317 + hostPort: 4317 + protocol: TCP + jaeger-thrift-b: + enabled: true + containerPort: 6832 + servicePort: 6832 + hostPort: 6832 + protocol: UDP + jaeger-thrift: + enabled: true + containerPort: 14268 + servicePort: 14268 + hostPort: 14268 + protocol: TCP + +cert-manager: + enabled: true + installCRDs: true + resources: {} + +ingress-nginx: + enabled: true + controller: + resources: {} + replicaCount: 2 + ingressClassResource: + default: true + watchIngressWithoutClass: true + config: + enable-opentracing: true + jaeger-propagation-format: "w3c" + log-format-upstream: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id" + service: + externalTrafficPolicy: Local + # service type should be NodePort when deploying locally + type: LoadBalancer + metrics: + enabled: true + service: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "10254" diff --git a/dev/.gitignore b/dev/.gitignore new file mode 100644 index 0000000..4cfaa1d --- /dev/null +++ b/dev/.gitignore @@ -0,0 +1,4 @@ +.terraform +.terraform.lock.hcl +.terraform.tfstate* +terraform.tfstate* diff --git a/dev/Makefile b/dev/Makefile new file mode 100644 index 0000000..0c2de95 --- /dev/null +++ b/dev/Makefile @@ -0,0 +1,22 @@ +REPO:=$(shell git rev-parse --show-toplevel) + +create-cluster: + k3d cluster create --image rancher/k3s:v1.30.4-k3s1 -v "$(REPO):/charts" \ + --k3s-arg "--disable=traefik@server:0" \ + --k3s-arg "--disable=servicelb@server:0" + +delete-cluster: + k3d cluster delete + +all: create-cluster + +run-bitcoin-smoketest: + kubectl -n galoy-dev-smoketest exec smoketest -- bash -c "cd /charts/ci/tasks && \ + CHART=bitcoind ./dev-smoketest-settings.sh && \ + ./bitcoind-smoketest.sh regtest && \ + rm -rf smoketest-settings" + +helm-dep-updates: + for dir in $$(ls ../charts); do \ + cd ../charts/$$dir && helm dependency update && cd -; \ + done diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 0000000..3bb338d --- /dev/null +++ b/dev/README.md @@ -0,0 +1,31 @@ +# Charts dev setup + +Intended as a local environment to test changes to the charts. Not as a dev backend for the mobile app. +Currently successfully brings up charts - no guarantee that everything is working as in prod, but enough to do some refactorings or stuff like that. + +## Dependencies + +### Docker +* choose the install method for your system https://docs.docker.com/desktop/ + +### Nix package manager +* recommended install method using https://github.com/DeterminateSystems/nix-installer + ``` + curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install + ``` + +### direnv >= 2.30.0 +* recommended install method from https://direnv.net/docs/installation.html: + ``` + curl -sfL https://direnv.net/install.sh | bash + echo "eval \"\$(direnv hook bash)\"" >> ~/.bashrc + source ~/.bashrc + ``` + +## Regtest +* run in the `dev` folder: + ``` + direnv allow + make create-cluster + tilt up + ``` diff --git a/dev/Tiltfile b/dev/Tiltfile new file mode 100644 index 0000000..254d0d4 --- /dev/null +++ b/dev/Tiltfile @@ -0,0 +1,4 @@ +include('./galoy-deps/Tiltfile') +include('./bitcoin/Tiltfile') +include('./cala/Tiltfile') +include('./smoketest/Tiltfile') diff --git a/dev/bitcoin/Tiltfile b/dev/bitcoin/Tiltfile new file mode 100644 index 0000000..f5be3bf --- /dev/null +++ b/dev/bitcoin/Tiltfile @@ -0,0 +1,102 @@ +load('ext://helm_resource', 'helm_resource') +load('ext://namespace', 'namespace_create') +load('ext://secret', 'secret_from_dict') +load('../common/Tiltfile', 'helm_release') +update_settings(k8s_upsert_timeout_secs=120) + +name_prefix = 'galoy-dev' +bitcoin_network = 'regtest' +bitcoind_rpcpassword = 'rpcpassword' +bitcoin_namespace = '{}-bitcoin'.format(name_prefix) +smoketest_namespace = '{}-smoketest'.format(name_prefix) + +namespace_create(bitcoin_namespace) + +k8s_yaml(secret_from_dict( + name='bitcoind-rpcpassword', + namespace=bitcoin_namespace, + inputs={'password': bitcoind_rpcpassword}, +)) + +helm_release( + "../../charts/bitcoind", + name="bitcoind", + namespace=bitcoin_namespace, + values=['./bitcoind-regtest-values.yml'] +) + +k8s_resource(workload='bitcoind', labels='bitcoin') + +k8s_yaml(secret_from_dict( + name='bitcoind-onchain-rpcpassword', + namespace=bitcoin_namespace, + inputs={'password': bitcoind_rpcpassword}, +)) + +k8s_yaml(secret_from_dict( + name='bitcoind-signer-descriptor', + namespace=bitcoin_namespace, + inputs={ + 'descriptor_json_base64': local( + "base64 bitcoind_signers_descriptors.json | tr -d '\n\r'" + ) + }, +)) + +helm_release( + '../../charts/bitcoind', + name='bitcoind-onchain', + namespace=bitcoin_namespace, + values=['./bitcoind-regtest-values.yml', './bitcoind-onchain-values.yml'] +) + +k8s_resource(workload='bitcoind-onchain', labels='bitcoin') + +local_resource( + name="bitcoind-block-generator", + cmd='./generateBlock.sh', + labels="bitcoin", + resource_deps=["bitcoind-onchain", "bitcoind"] +) + +k8s_yaml(secret_from_dict( + name='bria-smoketest', + namespace=bitcoin_namespace, + inputs={ + 'key': 'value' + }, +)) + +k8s_yaml(secret_from_dict( + name='bria', + namespace=bitcoin_namespace, + inputs={ + 'pg-con': 'postgres://bria:bria@bria-postgresql:5432/bria', + 'signer-encryption-key': local('openssl rand -hex 32'), + }, +)) + +helm_release( + '../../charts/bria', + name='bria', + namespace=bitcoin_namespace, + values=['./bria-values.yml'], + dependency_build=True, + add_repos=True +) + +k8s_resource(workload='bria', labels='bitcoin') + +# create bitcoind smoketest secret from snippet above +k8s_yaml(secret_from_dict( + name='bitcoind-smoketest', + namespace=smoketest_namespace, + inputs={ + 'bitcoind_rpcpassword': bitcoind_rpcpassword, + 'bitcoind_endpoint': 'bitcoind.{}.svc.cluster.local'.format(bitcoin_namespace), + 'bitcoind_port': '18443', + 'bitcoind_user': 'rpcuser', + 'bitcoind_onchain_rpcpassword': bitcoind_rpcpassword, + 'bitcoind_onchain_endpoint': 'bitcoind.{}.svc.cluster.local'.format(bitcoin_namespace), + } +)) diff --git a/dev/bitcoin/bitcoind-onchain-values.yml b/dev/bitcoin/bitcoind-onchain-values.yml new file mode 100644 index 0000000..a49461f --- /dev/null +++ b/dev/bitcoin/bitcoind-onchain-values.yml @@ -0,0 +1,3 @@ +descriptor: + secretName: bitcoind-signer-descriptor + secretKey: descriptor_json_base64 diff --git a/dev/bitcoin/bitcoind-regtest-values.yml b/dev/bitcoin/bitcoind-regtest-values.yml new file mode 100644 index 0000000..0648b48 --- /dev/null +++ b/dev/bitcoin/bitcoind-regtest-values.yml @@ -0,0 +1,25 @@ +global: + network: regtest + service: + ports: + rpc: 18443 + +secrets: + create: false + +persistence: + enabled: false + +service: + type: ClusterIP + ports: + zmqpubrawtx: 28333 + zmqpubrawblock: 28332 + p2p: 18444 + +bitcoindCustomConfig: + bind: 0.0.0.0 + rpcbind: 0.0.0.0 + rpcallowip: 0.0.0.0/0 + dbcache: 450 + maxmempool: 300 diff --git a/dev/bitcoin/bitcoind-signet-values.yml b/dev/bitcoin/bitcoind-signet-values.yml new file mode 100644 index 0000000..d183465 --- /dev/null +++ b/dev/bitcoin/bitcoind-signet-values.yml @@ -0,0 +1,25 @@ +global: + network: signet + service: + ports: + rpc: 38332 + +secrets: + create: false + +persistence: + enabled: true + accessMode: ReadWriteOnce + size: 2Gi + +service: + type: ClusterIP + ports: + zmqpubrawtx: 28333 + zmqpubrawblock: 28332 + p2p: 38333 + +bitcoindCustomConfig: + bind: 0.0.0.0 + rpcbind: 0.0.0.0 + rpcallowip: 0.0.0.0/0 diff --git a/dev/bitcoin/bitcoind_signers_descriptors.json b/dev/bitcoin/bitcoind_signers_descriptors.json new file mode 100644 index 0000000..fc0a179 --- /dev/null +++ b/dev/bitcoin/bitcoind_signers_descriptors.json @@ -0,0 +1 @@ +[{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/0/*)#88k4937c","timestamp":0},{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/1/*)#knn5cywq","internal":true,"timestamp":0}] diff --git a/dev/bitcoin/bria-values.yml b/dev/bitcoin/bria-values.yml new file mode 100644 index 0000000..811fb9b --- /dev/null +++ b/dev/bitcoin/bria-values.yml @@ -0,0 +1,13 @@ +bria: + devDaemon: + enabled: true + blockchain: + electrumUrl: fulcrum.galoy-dev-bitcoin.svc.cluster.local + replicas: 1 + secrets: + create: false + +postgresql: + primary: + persistence: + enabled: false diff --git a/dev/bitcoin/fulcrum-regtest-values.yml b/dev/bitcoin/fulcrum-regtest-values.yml new file mode 100644 index 0000000..d3b63d8 --- /dev/null +++ b/dev/bitcoin/fulcrum-regtest-values.yml @@ -0,0 +1,4 @@ +persistence: + enabled: false + +bitcoindRpcPort: 18443 diff --git a/dev/bitcoin/fulcrum-signet-values.yml b/dev/bitcoin/fulcrum-signet-values.yml new file mode 100644 index 0000000..590738a --- /dev/null +++ b/dev/bitcoin/fulcrum-signet-values.yml @@ -0,0 +1,5 @@ +persistence: + enabled: true + size: 1Gi + +bitcoindRpcPort: 38332 diff --git a/dev/bitcoin/generateBlock.sh b/dev/bitcoin/generateBlock.sh new file mode 100755 index 0000000..4fdd6f1 --- /dev/null +++ b/dev/bitcoin/generateBlock.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +kubectl -n galoy-dev-bitcoin exec bitcoind-0 -- bitcoin-cli generatetoaddress 1 bcrt1qxcpz7ytf3nwlhjay4n04nuz8jyg3hl4ud02t9t +kubectl -n galoy-dev-bitcoin exec bitcoind-onchain-0 -- bitcoin-cli generatetoaddress 1 bcrt1qxcpz7ytf3nwlhjay4n04nuz8jyg3hl4ud02t9t diff --git a/dev/bitcoin/loop-values.yml b/dev/bitcoin/loop-values.yml new file mode 100644 index 0000000..7edc5c1 --- /dev/null +++ b/dev/bitcoin/loop-values.yml @@ -0,0 +1,5 @@ +global: + network: regtest + +persistence: + enabled: false diff --git a/dev/cala/Tiltfile b/dev/cala/Tiltfile new file mode 100644 index 0000000..341c63d --- /dev/null +++ b/dev/cala/Tiltfile @@ -0,0 +1,38 @@ +load('ext://helm_resource', 'helm_resource') +load('ext://namespace', 'namespace_create') +load('ext://secret', 'secret_from_dict') +load('../common/Tiltfile', 'helm_release') +update_settings(k8s_upsert_timeout_secs=120) + +name_prefix = 'galoy-dev' +cala_namespace = '{}-cala'.format(name_prefix) +smoketest_namespace = '{}-smoketest'.format(name_prefix) + +namespace_create(cala_namespace) + +k8s_yaml(secret_from_dict( + name='cala-smoketest', + namespace=cala_namespace, + inputs={ + 'key': 'value' + }, +)) + +k8s_yaml(secret_from_dict( + name='cala', + namespace=cala_namespace, + inputs={ + 'pg-con': 'postgres://cala:cala@cala-postgresql:5432/cala', + }, +)) + +helm_release( + '../../charts/cala', + name='cala', + namespace=cala_namespace, + values=['./cala-values.yml'], + dependency_build=True, + add_repos=True +) + +k8s_resource(workload='cala', labels='cala') diff --git a/dev/cala/cala-values.yml b/dev/cala/cala-values.yml new file mode 100644 index 0000000..d4b2186 --- /dev/null +++ b/dev/cala/cala-values.yml @@ -0,0 +1,8 @@ +cala: + secrets: + create: false + +postgresql: + primary: + persistence: + enabled: false diff --git a/dev/common/Tiltfile b/dev/common/Tiltfile new file mode 100644 index 0000000..65555e1 --- /dev/null +++ b/dev/common/Tiltfile @@ -0,0 +1,42 @@ +load('ext://secret', 'secret_from_dict') + +def wait_for_secret_creation(waiter_name, secret_name, namespace, resource_deps, timeout_secs=30): + local_resource( + name=waiter_name, + cmd='for i in $(seq {}); do kubectl -n {} get secret {} &>/dev/null && echo "Secret exists" && exit 0; sleep 1; done; exit 1'.format(timeout_secs, namespace, secret_name), + resource_deps=resource_deps + ) + +# TODO: Add labels for better grouping +def copy_secret(source_secret_name, source_namespace, target_namespace, resource_deps, target_secret_name=None): + if target_secret_name == None: + target_secret_name = source_secret_name + + random_suffix=local('openssl rand -hex 1') + waiter_name='{}-secret-copier-waiter-{}'.format(target_secret_name, random_suffix) + wait_for_secret_creation(waiter_name, source_secret_name, source_namespace, resource_deps) + + delete_secret_cmd = 'kubectl -n {} delete secret {} --ignore-not-found=true'.format(target_namespace, target_secret_name) + generate_secret_json_cmd = 'kubectl -n {} get secret {} -o json | jq "del(.metadata.namespace, .metadata.name) | .metadata.name = \\"{}\\""'.format(source_namespace, source_secret_name, target_secret_name) + create_secret_cmd = 'kubectl -n {} apply -f -'.format(target_namespace) + + local_resource( + name='{}-secret-copier'.format(target_secret_name), + cmd='{} && {} | {}'.format(delete_secret_cmd, generate_secret_json_cmd, create_secret_cmd), + resource_deps=['{}'.format(waiter_name)] + ) + +def helm_release(pathToChartDir, name, namespace, values=[], dependency_build=False, add_repos=False): + if add_repos and (config.tilt_subcommand == 'up' or config.tilt_subcommand == 'ci'): + local('../common/add-helm-repos.sh {}/Chart.yaml'.format(pathToChartDir)) + + if dependency_build and (config.tilt_subcommand == 'up' or config.tilt_subcommand == 'ci'): + local('helm dependency build {}'.format(pathToChartDir)) + + k8s_yaml(helm( + pathToChartDir, + name, + namespace, + values, + kube_version='1.27.0', + )) diff --git a/dev/common/add-helm-repos.sh b/dev/common/add-helm-repos.sh new file mode 100755 index 0000000..886fa4b --- /dev/null +++ b/dev/common/add-helm-repos.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +function add_helm_repos() { + yq e '.dependencies[] | select(.repository | test("^oci://") | not) | .name + " " + .repository' "$1" | while read -r name repo; do + helm repo add "$name" "$repo" + done +} + +add_helm_repos "$1" diff --git a/dev/galoy-deps/Tiltfile b/dev/galoy-deps/Tiltfile new file mode 100644 index 0000000..903ecdb --- /dev/null +++ b/dev/galoy-deps/Tiltfile @@ -0,0 +1,52 @@ +load('ext://helm_resource', 'helm_resource') +load('ext://namespace', 'namespace_create') +load('../common/Tiltfile', 'copy_secret', 'helm_release') +update_settings(k8s_upsert_timeout_secs=120) + +name_prefix = "galoy-dev" +ingress_namespace = "{}-ingress".format(name_prefix) +otel_namespace = "{}-otel".format(name_prefix) +kubemonkey_namespace = "{}-kubemonkey".format(name_prefix) + +## cert-manager and ingress-nginx + +namespace_create(ingress_namespace) + +# TODO: decide how to label the ingress namespace +# k8s_resource(workload="cert-manager?", objects=["galoy-dev-ingress:namespace"]) + +helm_release( + '../../charts/galoy-deps', + name='cert-manager', + namespace=ingress_namespace, + values=['./cert-manager-values.yml'], +) + +helm_release( + '../../charts/galoy-deps', + name='ingress-nginx', + namespace=ingress_namespace, + values=['./ingress-nginx-values.yml'], +) + +## opentelemetry-collector + +namespace_create(otel_namespace) + +helm_release( + '../../charts/galoy-deps', + name='opentelemetry-collector', + namespace=otel_namespace, + values=['./otel-values.yml'], +) + +# do we need kubemonkey in local dev? + +# helm_resource( +# name="kubemonkey", +# chart="../../charts/galoy-deps", +# namespace=kubemonkey_namespace, +# flags=['--values=./kubemonkey-values.yml'], +# labels="kubemonkey" +# ) + diff --git a/dev/galoy-deps/cert-manager-values.yml b/dev/galoy-deps/cert-manager-values.yml new file mode 100644 index 0000000..66b03a9 --- /dev/null +++ b/dev/galoy-deps/cert-manager-values.yml @@ -0,0 +1,10 @@ +ingress-nginx: + enabled: false +kubemonkey: + enabled: false +opentelemetry-collector: + enabled: false +strimzi-kafka-operator: + enabled: false +cert-manager: + installCRDs: true diff --git a/dev/galoy-deps/ingress-nginx-values.yml b/dev/galoy-deps/ingress-nginx-values.yml new file mode 100644 index 0000000..d143f40 --- /dev/null +++ b/dev/galoy-deps/ingress-nginx-values.yml @@ -0,0 +1,18 @@ +cert-manager: + enabled: false +kubemonkey: + enabled: false +opentelemetry-collector: + enabled: false +strimzi-kafka-operator: + enabled: false +ingress-nginx: + controller: + admissionWebhooks: + enabled: false + config: + enable-opentracing: true + jaeger-service-name: galoy-dev-ingress + jaeger-collector-host: opentelemetry-collector.galoy-dev-otel.svc.cluster.local + service: + type: NodePort diff --git a/dev/galoy-deps/ingress.tf b/dev/galoy-deps/ingress.tf new file mode 100644 index 0000000..594f8db --- /dev/null +++ b/dev/galoy-deps/ingress.tf @@ -0,0 +1,25 @@ +resource "kubernetes_manifest" "issuer" { + manifest = { + apiVersion = "cert-manager.io/v1" + kind = "ClusterIssuer" + metadata = { + name = "letsencrypt-issuer" + } + spec = { + acme = { + server = "https://acme-v02.api.letsencrypt.org/directory" + email = "dev@galoy.io" + privateKeySecretRef = { + name = "letsencrypt-issuer" + } + solvers = [ + { http01 = { ingress = { class = "nginx" } } } + ] + } + } + } + + depends_on = [ + helm_release.cert_manager + ] +} diff --git a/dev/galoy-deps/kafka-values.yml b/dev/galoy-deps/kafka-values.yml new file mode 100644 index 0000000..69fbcf4 --- /dev/null +++ b/dev/galoy-deps/kafka-values.yml @@ -0,0 +1,12 @@ +cert-manager: + enabled: false +ingress-nginx: + enabled: false +kubemonkey: + enabled: false +opentelemetry-collector: + enabled: false +strimzi-kafka-operator: + kafka: + listener: + type: nodeport diff --git a/dev/galoy-deps/kubemonkey-values.yml.tmpl b/dev/galoy-deps/kubemonkey-values.yml.tmpl new file mode 100644 index 0000000..9a14316 --- /dev/null +++ b/dev/galoy-deps/kubemonkey-values.yml.tmpl @@ -0,0 +1,22 @@ +cert-manager: + enabled: false +ingress-nginx: + enabled: false +strimzi-kafka-operator: + enabled: false +opentelemetry-collector: + enabled: false +kubemonkey: + fullnameOverride: kubemonkey + config: + timeZone: ${time_zone} + whitelistedNamespaces: +%{ for ns in whitelisted_namespaces ~} + - ${ns} +%{ endfor ~} + notifications: + enabled: true + attacks: | + endpoint = "${notification_url}" + message = '{"text":"Attacked `{$name}` of `{$namespace}` on {$date} at {$time}. {$error}"}' + headers = ["Content-Type:application/json"] diff --git a/dev/galoy-deps/otel-values.yml b/dev/galoy-deps/otel-values.yml new file mode 100644 index 0000000..0c855ff --- /dev/null +++ b/dev/galoy-deps/otel-values.yml @@ -0,0 +1,8 @@ +cert-manager: + enabled: false +ingress-nginx: + enabled: false +strimzi-kafka-operator: + enabled: false +kubemonkey: + enabled: false diff --git a/dev/signet.md b/dev/signet.md new file mode 100644 index 0000000..d0171e3 --- /dev/null +++ b/dev/signet.md @@ -0,0 +1,80 @@ +# Testing signet + +Documentation of testing options with the wider network since the dev setup makes is possible to start a signet instance. + +## General notes about signet +### Advantages +- it is a network where intreaction is possible with other instances and projects +- funds are worthless just like on testnet +- the derivation paths are same as on testnet +- reliable blocktimes +- no block-storms +- small blockchain (IBD ~ 10 mins on a laptop) +- Faucets are available (see below) + +### Disadvantages +- no lndmon support yet https://github.com/lightninglabs/lndmon/issues/80 +- no Loop support yet https://github.com/lightninglabs/loop/issues/522 +- fewer nodes on the network +- staging would need an accounting reset when converting (might be an advantage) +- no known 3rd party services / test shops available +- pending in: + - https://github.com/alexbosworth/ln-sync/issues/5 + - https://github.com/bitcoinjs/bitcoinjs-lib/issues/1820 + +## Public Electrum servers +* signet-electrumx.wakiyamap.dev:50002:s +* electrum.emzy.de:53002:s +* node202.fra.mempool.space:60602:s + +## Block explorer +* https://mempool.space/signet +* https://explorer.bc-2.jp/ + +## Faucets +- https://signet.bc-2.jp/ +- https://signetfaucet.bublina.eu.org/ +- https://faucet.sirion.io/ (LN only) + +## LN explorers +- https://mempool.space/signet/lightning +- https://signet-lightning.wakiyamap.dev/ + +## Test payment destinations +* LN address on staging: `test@pay.staging.galoy.io` +* Staging cash resgister: https://pay.staging.galoy.io/test?amount=0&sats=0&unit=CENT&memo=&display=USD¤cy=USD +* BTCPay PoS (LN - BOLT11) https://sigpay.sirion.io/apps/33Au5UDsPWnUL5GVT8q1Yjovw83K/crowdfund +* BTCPay PoS (onchain) https://signet.demo.btcpayserver.org/apps/2SCdQhwFm464BcHxXn44kRVg3iCr/pos + +## Other wallets with signet support +### [OBW](https://github.com/nbd-wtf/obw/releases/) +* for Android only with LN support +* To use a hosted channel scan the QRcode with OBW-signet (should also provide some local balance ): + + + + +### [Sparrow Wallet](https://sparrowwallet.com/download/) +Supports onchain only, but works well with Taproot accounts. To use it with signet open: + menu - Tools - Restart in Testnet and set a Private Electrum server serving signet like: `node202.fra.mempool.space` port: `60602` with SSL.` + +### [Electrum Wallet](https://electrum.org/#download) +* Support onchain (no Taproot yet) and LN, but couldn't send or receive lightning payments on signet with electrum yet. +* start with the signet flag eg: + ``` + electrum-4.4.0-x86_64.AppImage --signet + ``` +* `menu` - `Wallet` - `Information` and restart to activate lightning + +## Galoy staging node to open channels to +* [Galoy staging LND1](https://mempool.space/signet/lightning/node/024e679c1a77143029b806f396f935fa6cd0744970f412667adfc75edbbab54d7a) +* Scan the URI to open a channel: + + + + +## Resources +* General documentation on signet: https://en.bitcoin.it/wiki/Signet +* Plebnet playground using a separate signet: https://www.plebnet.fun/ +* Custom signet by Mutiny: https://blog.mutinywallet.com/mutinynet/?ref=nobsbitcoin.com + diff --git a/dev/smoketest/Tiltfile b/dev/smoketest/Tiltfile new file mode 100644 index 0000000..ff0c8bb --- /dev/null +++ b/dev/smoketest/Tiltfile @@ -0,0 +1,104 @@ +load('ext://namespace', 'namespace_create') +load('ext://secret', 'secret_from_dict') + +name_prefix = 'galoy-dev' +smoketest_namespace = '{}-smoketest'.format(name_prefix) + +namespace_create(smoketest_namespace) + +smoketest_role_yaml=""" +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: smoketest + namespace: galoy-dev-smoketest +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] +""" +k8s_yaml(blob(smoketest_role_yaml)) + +smoketest_serviceaccount_yaml=""" +apiVersion: v1 +kind: ServiceAccount +metadata: + name: smoketest + namespace: galoy-dev-smoketest +""" +k8s_yaml(blob(smoketest_serviceaccount_yaml)) + +smoketest_rolebinding_yaml=""" +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: smoketest + namespace: galoy-dev-smoketest +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: smoketest +subjects: +- kind: ServiceAccount + name: smoketest + namespace: galoy-dev-smoketest +""" +k8s_yaml(blob(smoketest_rolebinding_yaml)) + +smoketest_pv_yaml=""" +apiVersion: v1 +kind: PersistentVolume +metadata: + name: smoketest-tasks +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + capacity: + storage: 1Gi + hostPath: + path: "/charts" +""" +k8s_yaml(blob(smoketest_pv_yaml)) + +smoketest_pvc_yaml=""" +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: smoketest-tasks + namespace: galoy-dev-smoketest +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + volumeName: smoketest-tasks +""" +k8s_yaml(blob(smoketest_pvc_yaml)) + +smoketest_pod_yaml=""" +apiVersion: v1 +kind: Pod +metadata: + name: smoketest + namespace: galoy-dev-smoketest +spec: + containers: + - name: smoketest + image: us.gcr.io/galoy-org/blink-deployments-pipeline + imagePullPolicy: IfNotPresent + command: + - sleep + - "604800" + volumeMounts: + - name: smoketest-tasks + mountPath: /charts + serviceAccountName: smoketest + volumes: + - name: smoketest-tasks + persistentVolumeClaim: + claimName: smoketest-tasks +""" +k8s_yaml(blob(smoketest_pod_yaml))