Create website for APK download #2
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
MTM_VPN_APK_URL=https://git.vdb.to/cerc-io/mtm-vpn-client-public/releases/download/mtm-vpn-android-v1.8.0-mtm-0.1.0/mtmvpn-general-release-v1.8.0-mtm-0.1.1.apk
|
||||
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals"]
|
||||
}
|
||||
33
.gitea/workflows/build.yaml
Normal file
33
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
name: Build the Webapp
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
CERC_REGISTRY_USER_KEY: ${{ secrets.CICD_LACONIC_USER_KEY }}
|
||||
CERC_REGISTRY_BOND_ID: ${{ secrets.CICD_LACONIC_BOND_ID }}
|
||||
|
||||
jobs:
|
||||
build_webapp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Clone project repository"
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: "Install Yarn"
|
||||
run: npm install -g yarn
|
||||
- name: "Install registry CLI"
|
||||
run: |
|
||||
npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/
|
||||
yarn global add @cerc-io/laconic-registry-cli
|
||||
- name: "Install jq"
|
||||
run: apt -y update && apt -y install jq
|
||||
- name: "Install dependencies"
|
||||
run: yarn install
|
||||
- name: "Lint code"
|
||||
run: npm run lint
|
||||
- name: "Build application"
|
||||
run: npm run build
|
||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Build outputs
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
44
CLAUDE.md
Normal file
44
CLAUDE.md
Normal file
@ -0,0 +1,44 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `npm run dev` - Start development server
|
||||
- `npm run build` - Build the application for production
|
||||
- `npm start` - Start production server
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a Next.js Progressive Web App (PWA) built with TypeScript that demonstrates PWA capabilities using the `next-pwa` plugin powered by Workbox.
|
||||
|
||||
### Key Technologies
|
||||
- **Next.js** - React framework with SSR/SSG capabilities
|
||||
- **next-pwa** - PWA integration with Workbox service worker
|
||||
- **TypeScript** - Type safety throughout the application
|
||||
|
||||
### Project Structure
|
||||
- `pages/` - Next.js pages using file-based routing
|
||||
- `_app.tsx` - Global app component with PWA meta tags and manifest
|
||||
- `index.tsx` - Main landing page displaying environment variables
|
||||
- `api/` - API routes
|
||||
- `public/` - Static assets including PWA icons and manifest
|
||||
- `styles/` - CSS modules and global styles
|
||||
- `scripts/` - Deployment and publishing scripts
|
||||
|
||||
### Environment Configuration
|
||||
The app reads three environment variables that are displayed on the main page:
|
||||
- `MTM_VPN_APK_URL`
|
||||
|
||||
These are configured in `next.config.js` and exposed to the client-side.
|
||||
|
||||
### PWA Configuration
|
||||
- Service worker configured via `next-pwa` with destination set to `public/`
|
||||
- Manifest file at `public/manifest.json`
|
||||
- Icon set includes various sizes (16x16 to 512x512) in `public/icons/`
|
||||
- PWA meta tags configured in `_app.tsx`
|
||||
|
||||
### Deployment
|
||||
The repository includes scripts for deployment:
|
||||
- `scripts/publish-app-record.sh` - Publishes app record
|
||||
- `scripts/request-app-deployment.sh` - Requests deployment
|
||||
77
components/LandingPage.tsx
Normal file
77
components/LandingPage.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import Image from 'next/image';
|
||||
import styles from '../styles/LandingPage.module.css'
|
||||
|
||||
const LandingPage = () => {
|
||||
const handleDownload = () => {
|
||||
const downloadUrl = process.env.MTM_VPN_APK_URL
|
||||
|
||||
if (!downloadUrl) {
|
||||
console.error('MTM_VPN_APK_URL environment variable not set')
|
||||
return
|
||||
}
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = downloadUrl
|
||||
link.download = 'mtm-vpn.apk'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
<div className={styles.navContent}>
|
||||
<div>
|
||||
<span className={styles.logo}>MTM VPN</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.textSection}>
|
||||
<h1 className={styles.title}>
|
||||
<span className={styles.titleWhite}>Secure VPN Service</span>
|
||||
<br />
|
||||
<span className={styles.titleGradient}>Pay with MTM Tokens</span>
|
||||
</h1>
|
||||
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className={styles.downloadButton}
|
||||
>
|
||||
<svg className={styles.phoneIcon} viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 0 0 6 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h7.5A2.25 2.25 0 0 0 18 20.25V3.75a2.25 2.25 0 0 0-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" />
|
||||
</svg>
|
||||
Download for Android
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.imageSection}>
|
||||
<div className={styles.imageContainer}>
|
||||
<Image
|
||||
src="/mtm_vpn.png"
|
||||
alt="MTM VPN App Screenshot"
|
||||
className={styles.appImage}
|
||||
width={300}
|
||||
height={600}
|
||||
/>
|
||||
<div className={styles.imageEffect1}></div>
|
||||
<div className={styles.imageEffect2}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className={styles.backgroundEffects}>
|
||||
<div className={styles.bgEffect1}></div>
|
||||
<div className={styles.bgEffect2}></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingPage
|
||||
10
deploy/.registry.env.example
Normal file
10
deploy/.registry.env.example
Normal file
@ -0,0 +1,10 @@
|
||||
# ENV for registry operations
|
||||
|
||||
# Bond to use
|
||||
REGISTRY_BOND_ID=5d82586d156fb6671a9170d92f930a72a49a29afb45e30e16fff2100e30776e2
|
||||
|
||||
# Target deployer LRN
|
||||
DEPLOYER_LRN=lrn://vaasl-provider/deployers/webapp-deployer-api.apps.vaasl.io
|
||||
|
||||
# Authority to deploy the app under
|
||||
AUTHORITY=laconic-deploy
|
||||
40
deploy/Dockerfile
Normal file
40
deploy/Dockerfile
Normal file
@ -0,0 +1,40 @@
|
||||
ARG VARIANT=20-bullseye
|
||||
FROM node:${VARIANT}
|
||||
|
||||
ARG USERNAME=node
|
||||
ARG NPM_GLOBAL=/usr/local/share/npm-global
|
||||
|
||||
# Add NPM global to PATH.
|
||||
ENV PATH=${NPM_GLOBAL}/bin:${PATH}
|
||||
|
||||
RUN \
|
||||
# Configure global npm install location, use group to adapt to UID/GID changes
|
||||
if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \
|
||||
&& usermod -a -G npm ${USERNAME} \
|
||||
&& umask 0002 \
|
||||
&& mkdir -p ${NPM_GLOBAL} \
|
||||
&& touch /usr/local/etc/npmrc \
|
||||
&& chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \
|
||||
&& chmod g+s ${NPM_GLOBAL} \
|
||||
&& npm config -g set prefix ${NPM_GLOBAL} \
|
||||
&& su ${USERNAME} -c "npm config -g set prefix ${NPM_GLOBAL}" \
|
||||
# Install eslint
|
||||
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
|
||||
&& npm cache clean --force > /dev/null 2>&1
|
||||
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends jq bash
|
||||
|
||||
# laconic-so
|
||||
RUN curl -LO https://git.vdb.to/cerc-io/stack-orchestrator/releases/download/latest/laconic-so && \
|
||||
chmod +x ./laconic-so && \
|
||||
mv ./laconic-so /usr/bin/laconic-so
|
||||
|
||||
# Configure the npm registry
|
||||
RUN npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/
|
||||
|
||||
# DEBUG, remove
|
||||
RUN yarn info @cerc-io/laconic-registry-cli
|
||||
|
||||
# Globally install the cli package
|
||||
RUN yarn global add @cerc-io/laconic-registry-cli
|
||||
83
deploy/README.md
Normal file
83
deploy/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Deploy
|
||||
|
||||
## Setup
|
||||
|
||||
- Clone the repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/deep-stack/mtm-vpn-client-public.git
|
||||
cd mtm-vpn-client-public/deploy
|
||||
```
|
||||
|
||||
- Build registry CLI image:
|
||||
|
||||
```bash
|
||||
docker build -t cerc/laconic-registry-cli .
|
||||
|
||||
# Builds image cerc/laconic-registry-cli:latest
|
||||
```
|
||||
|
||||
- Configure `userKey` and `bondId` in the [registry CLI config](./config.yml):
|
||||
|
||||
- User key should be of the account that owns the `laconic-deploy` authority (owner account address: `laconic1kwx2jm6vscz38qlyujvq6msujmk8l3zangqahs`)
|
||||
- The bond should also be owned by same user (owned bond's ID: `5d82586d156fb6671a9170d92f930a72a49a29afb45e30e16fff2100e30776e2`)
|
||||
- If the authority is not available, follow [these steps to reserve a new authority](./reserve-new-authority.md)
|
||||
|
||||
```bash
|
||||
nano config.yml
|
||||
```
|
||||
|
||||
- Add configuration for registry operations:
|
||||
|
||||
```bash
|
||||
cp .registry.env.example .registry.env
|
||||
|
||||
# Update values if required
|
||||
nano .registry.env
|
||||
```
|
||||
|
||||
- Add configuration for the app:
|
||||
|
||||
- Copy the example environment file:
|
||||
|
||||
```bash
|
||||
cp ../.env.example .app.env
|
||||
```
|
||||
|
||||
- Open .app.env and update the `MTM_VPN_APK_URL` variable if required:
|
||||
|
||||
NOTE: Use the download URL of APK from the latest stable release available at https://git.vdb.to/cerc-io/mtm-vpn-client-public/releases
|
||||
|
||||
```
|
||||
nano .app.env
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
- Deploy MTM VPN App:
|
||||
|
||||
```bash
|
||||
# In mtm-vpn-client-public/deploy dir
|
||||
docker run -it \
|
||||
-v ./:/app/deploy -w /app/deploy \
|
||||
-e DEPLOYMENT_DNS=markto.market \
|
||||
cerc/laconic-registry-cli:latest \
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
- Check deployment logs on deployer UI: <https://webapp-deployer-ui.apps.vaasl.io/>
|
||||
|
||||
- Visit deployed app: <https://markto.market>
|
||||
|
||||
### Remove deployment
|
||||
|
||||
- Remove deployment:
|
||||
|
||||
```bash
|
||||
# In mtm-vpn-client-public/deploy dir
|
||||
docker run -it \
|
||||
-v ./:/app/deploy -w /app/deploy \
|
||||
-e DEPLOYMENT_RECORD_ID=<deploment-record-id-to-be-removed> \
|
||||
cerc/laconic-registry-cli:latest \
|
||||
./remove-deployment.sh
|
||||
```
|
||||
9
deploy/config.yml
Normal file
9
deploy/config.yml
Normal file
@ -0,0 +1,9 @@
|
||||
# Registry CLI config
|
||||
services:
|
||||
registry:
|
||||
rpcEndpoint: 'https://laconicd-mainnet-1.laconic.com'
|
||||
gqlEndpoint: 'https://laconicd-mainnet-1.laconic.com/api'
|
||||
userKey:
|
||||
bondId: 5d82586d156fb6671a9170d92f930a72a49a29afb45e30e16fff2100e30776e2
|
||||
chainId: laconic-mainnet
|
||||
gasPrice: 0.001alnt
|
||||
128
deploy/deploy.sh
Executable file
128
deploy/deploy.sh
Executable file
@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fail on error
|
||||
set -e
|
||||
|
||||
source .registry.env
|
||||
echo "Using REGISTRY_BOND_ID: $REGISTRY_BOND_ID"
|
||||
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
||||
echo "Using AUTHORITY: $AUTHORITY"
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://github.com/deep-stack/mtm-vpn-client-public"
|
||||
|
||||
# Get the latest commit hash for a branch
|
||||
BRANCH_NAME="main"
|
||||
LATEST_HASH=$(git ls-remote $REPO_URL refs/heads/$BRANCH_NAME | awk '{print $1}')
|
||||
|
||||
PACKAGE_VERSION=$(curl -s $REPO_URL/raw/main/package.json | jq -r .version)
|
||||
|
||||
APP_NAME=mtm-vpn
|
||||
|
||||
echo "Repo: ${REPO_URL}"
|
||||
echo "Latest hash: ${LATEST_HASH}"
|
||||
echo "App version: ${PACKAGE_VERSION}"
|
||||
echo "Deployment DNS: ${DEPLOYMENT_DNS}"
|
||||
|
||||
# Current date and time for note
|
||||
CURRENT_DATE_TIME=$(date -u)
|
||||
|
||||
CONFIG_FILE=config.yml
|
||||
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "$APP_NAME" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
NEW_APPLICATION_VERSION=0.0.1
|
||||
fi
|
||||
|
||||
# Generate application-record.yml with incremented version
|
||||
mkdir -p records
|
||||
RECORD_FILE=./records/application-record.yml
|
||||
|
||||
cat >$RECORD_FILE <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
repository_ref: $LATEST_HASH
|
||||
repository: ["$REPO_URL"]
|
||||
app_type: webapp
|
||||
name: $APP_NAME
|
||||
app_version: $PACKAGE_VERSION
|
||||
EOF
|
||||
|
||||
echo "Application record generated successfully: $RECORD_FILE"
|
||||
|
||||
# Publish ApplicationRecord
|
||||
publish_response=$(laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to publish record"
|
||||
exit $rc
|
||||
fi
|
||||
RECORD_ID=$(echo $publish_response | jq -r '.id')
|
||||
echo "ApplicationRecord published, setting names next"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/$APP_NAME"
|
||||
|
||||
name1="$REGISTRY_APP_LRN@${PACKAGE_VERSION}"
|
||||
sleep 2
|
||||
laconic -c $CONFIG_FILE registry name set "$name1" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set name: $REGISTRY_APP_LRN@${PACKAGE_VERSION}"
|
||||
exit $rc
|
||||
fi
|
||||
echo "$name1 set for ApplicationRecord"
|
||||
|
||||
name2="$REGISTRY_APP_LRN@${LATEST_HASH}"
|
||||
sleep 2
|
||||
laconic -c $CONFIG_FILE registry name set "$name2" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set hash"
|
||||
exit $rc
|
||||
fi
|
||||
echo "$name2 set for ApplicationRecord"
|
||||
|
||||
name3="$REGISTRY_APP_LRN"
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
laconic -c $CONFIG_FILE registry name set "$name3" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set release"
|
||||
exit $rc
|
||||
fi
|
||||
echo "$name3 set for ApplicationRecord"
|
||||
|
||||
# Check if record found for REGISTRY_APP_LRN
|
||||
query_response=$(laconic -c $CONFIG_FILE registry name resolve "$REGISTRY_APP_LRN")
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query name"
|
||||
exit $rc
|
||||
fi
|
||||
APP_RECORD=$(echo $query_response | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_LRN."
|
||||
exit 1
|
||||
fi
|
||||
echo "Name resolution successful"
|
||||
|
||||
sleep 2
|
||||
echo "Requesting a webapp deployment for $name2, using deployer $DEPLOYER_LRN"
|
||||
laconic-so request-webapp-deployment \
|
||||
--laconic-config $CONFIG_FILE \
|
||||
--deployer $DEPLOYER_LRN \
|
||||
--app $name2 \
|
||||
--env-file ./.app.env \
|
||||
--dns $DEPLOYMENT_DNS \
|
||||
--make-payment auto
|
||||
|
||||
echo "Done"
|
||||
50
deploy/laconic-cli.sh
Executable file
50
deploy/laconic-cli.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laconic Registry CLI Docker wrapper script
|
||||
# This script wraps the Docker command to run laconic registry CLI commands
|
||||
# Run this script from the deploy directory
|
||||
|
||||
# Check if docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Error: Docker is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the cerc/laconic-registry-cli image exists
|
||||
if ! docker image inspect cerc/laconic-registry-cli &> /dev/null; then
|
||||
echo "Error: cerc/laconic-registry-cli Docker image not found"
|
||||
echo "Please build the image first: docker build -t cerc/laconic-registry-cli ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current directory (should be deploy directory)
|
||||
CURRENT_DIR="$(pwd)"
|
||||
PROJECT_ROOT="$(dirname "$CURRENT_DIR")"
|
||||
|
||||
# Verify we're in the deploy directory
|
||||
if [ ! -f "config.yml" ] || [ ! -f "laconic-cli.sh" ]; then
|
||||
echo "Error: This script must be run from the deploy directory"
|
||||
echo "Current directory: $CURRENT_DIR"
|
||||
echo "Please cd to the deploy directory and run: ./laconic-cli.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set up volume mounts
|
||||
DEPLOY_MOUNT="-v $CURRENT_DIR:/app/deploy"
|
||||
OUT_MOUNT=""
|
||||
|
||||
# Create out directory if it doesn't exist and always mount it
|
||||
if [ ! -d "out" ]; then
|
||||
mkdir -p "out"
|
||||
fi
|
||||
OUT_MOUNT="-v $CURRENT_DIR/out:/app/out"
|
||||
|
||||
# Run the Docker command with processed arguments
|
||||
docker run --rm \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
$DEPLOY_MOUNT \
|
||||
$OUT_MOUNT \
|
||||
-w /app/deploy \
|
||||
cerc/laconic-registry-cli \
|
||||
laconic registry -c config.yml \
|
||||
"$@"
|
||||
0
deploy/records/.gitkeep
Normal file
0
deploy/records/.gitkeep
Normal file
63
deploy/remove-deployment.sh
Executable file
63
deploy/remove-deployment.sh
Executable file
@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ -z $DEPLOYMENT_RECORD_ID ]]; then
|
||||
echo "Error: please pass the deployment record ID" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source .registry.env
|
||||
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
||||
|
||||
echo "Deployment record ID: $DEPLOYMENT_RECORD_ID"
|
||||
|
||||
# Generate application-deployment-removal-request.yml
|
||||
REMOVAL_REQUEST_RECORD_FILE=./records/application-deployment-removal-request.yml
|
||||
|
||||
cat > $REMOVAL_REQUEST_RECORD_FILE <<EOF
|
||||
record:
|
||||
deployer: $DEPLOYER_LRN
|
||||
deployment: $DEPLOYMENT_RECORD_ID
|
||||
type: ApplicationDeploymentRemovalRequest
|
||||
version: 1.0.0
|
||||
EOF
|
||||
|
||||
CONFIG_FILE=config.yml
|
||||
|
||||
sleep 2
|
||||
REMOVAL_REQUEST_ID=$(laconic -c $CONFIG_FILE registry record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRemovalRequest published"
|
||||
echo $REMOVAL_REQUEST_ID
|
||||
|
||||
# Deployment checks
|
||||
RETRY_INTERVAL=30
|
||||
MAX_RETRIES=20
|
||||
|
||||
# Check that an ApplicationDeploymentRemovalRecord is published
|
||||
retry_count=0
|
||||
while true; do
|
||||
removal_records_response=$(laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
|
||||
len_removal_records=$(echo $removal_records_response | jq 'length')
|
||||
|
||||
# Check if number of records returned is 0
|
||||
if [ $len_removal_records -eq 0 ]; then
|
||||
# Check if retries are exhausted
|
||||
if [ $retry_count -eq $MAX_RETRIES ]; then
|
||||
echo "Retries exhausted"
|
||||
echo "ApplicationDeploymentRemovalRecord for deployment removal request $REMOVAL_REQUEST_ID not found"
|
||||
exit 1
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
|
||||
sleep $RETRY_INTERVAL
|
||||
retry_count=$((retry_count+1))
|
||||
fi
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord found"
|
||||
REMOVAL_RECORD_ID=$(echo $removal_records_response | jq -r '.[0].id')
|
||||
echo $REMOVAL_RECORD_ID
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Deployment removal successful"
|
||||
50
deploy/reserve-new-authority.md
Normal file
50
deploy/reserve-new-authority.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Reserve a New Authority
|
||||
|
||||
The following steps are used to reserve `laconic-deploy` authority
|
||||
|
||||
- Reserve authority:
|
||||
|
||||
```bash
|
||||
./laconic-cli.sh authority reserve laconic-deploy
|
||||
```
|
||||
|
||||
- After reserving authority, commit phase begins which lasts for 1 minute so please commit the bid following below steps within that time period
|
||||
|
||||
- Check authority info:
|
||||
|
||||
```bash
|
||||
./laconic-cli.sh authority whois laconic-deploy
|
||||
```
|
||||
|
||||
- Note down auction ID from authority info as it is required in next steps
|
||||
|
||||
- Get auction info:
|
||||
|
||||
```bash
|
||||
./laconic-cli.sh auction get <auction-id>
|
||||
```
|
||||
|
||||
- Commit an auction bid:
|
||||
|
||||
```bash
|
||||
# 5000000 alnt is the minimum bid amount for authority auction
|
||||
|
||||
./laconic-cli.sh auction bid commit <auction-id> 5000000 alnt
|
||||
|
||||
# Example file path inside container
|
||||
Reveal file: /app/deploy/out/bafyreiay2rccax64yn4ljhvzvm3jkbebvzheyucuma5jlbpzpzd5i5gjuy.json
|
||||
```
|
||||
|
||||
- The reveal phase starts as soon as commit phase ends and lasts for 1 minute so please reveal the bid within this time period
|
||||
|
||||
- Reveal bid:
|
||||
|
||||
```bash
|
||||
./laconic-cli.sh auction bid reveal <auction-id> /app/deploy/out/<reaveal-file>.json
|
||||
```
|
||||
|
||||
- Set authority bond after winning auction as it is required to use the published authority:
|
||||
|
||||
```bash
|
||||
./laconic-cli.sh authority bond set laconic-deploy <bond-id>
|
||||
```
|
||||
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
10
next.config.js
Normal file
10
next.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const withPWA = require('next-pwa')({
|
||||
dest: 'public',
|
||||
})
|
||||
|
||||
module.exports = withPWA({
|
||||
env: {
|
||||
MTM_VPN_APK_URL: process.env.MTM_VPN_APK_URL,
|
||||
},
|
||||
})
|
||||
9341
package-lock.json
generated
Normal file
9341
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "mtm-vpn-client-public",
|
||||
"version": "1.8.0-mtm-0.1.1",
|
||||
"repository": "https://github.com/deep-stack/mtm-vpn-client-public",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"next-pwa": "^5.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "17.0.4",
|
||||
"@types/react": "17.0.38",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-config-next": "^15.4.6",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
31
pages/_app.tsx
Normal file
31
pages/_app.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import Head from 'next/head'
|
||||
import '../styles/globals.css'
|
||||
import { AppProps } from 'next/app'
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
|
||||
/>
|
||||
<meta name="description" content="Description" />
|
||||
<meta name="keywords" content="Keywords" />
|
||||
<title>MTM VPN</title>
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link
|
||||
href="/mtm-vpn-logo.png"
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/mtm-vpn-logo.png"></link>
|
||||
<meta name="theme-color" content="#317EFB" />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
5
pages/index.tsx
Normal file
5
pages/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import LandingPage from '../components/LandingPage'
|
||||
|
||||
export default function Home() {
|
||||
return <LandingPage />
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
19
public/manifest.json
Normal file
19
public/manifest.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "MTM VPN",
|
||||
"short_name": "MTM VPN",
|
||||
"theme_color": "#54C3D4",
|
||||
"background_color": "#1e293b",
|
||||
"display": "fullscreen",
|
||||
"orientation": "portrait",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "mtm-vpn-logo.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"splash_pages": null
|
||||
}
|
||||
BIN
public/mtm-vpn-logo.png
Normal file
BIN
public/mtm-vpn-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
BIN
public/mtm_vpn.png
Normal file
BIN
public/mtm_vpn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
100
public/sw.js
Normal file
100
public/sw.js
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// If the loader is already loaded, just stop.
|
||||
if (!self.define) {
|
||||
let registry = {};
|
||||
|
||||
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||
let nextDefineUri;
|
||||
|
||||
const singleRequire = (uri, parentUri) => {
|
||||
uri = new URL(uri + ".js", parentUri).href;
|
||||
return registry[uri] || (
|
||||
|
||||
new Promise(resolve => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
let promise = registry[uri];
|
||||
if (!promise) {
|
||||
throw new Error(`Module ${uri} didn’t register its module`);
|
||||
}
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
self.define = (depsNames, factory) => {
|
||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
if (registry[uri]) {
|
||||
// Module is already loading or loaded.
|
||||
return;
|
||||
}
|
||||
let exports = {};
|
||||
const require = depUri => singleRequire(depUri, uri);
|
||||
const specialDeps = {
|
||||
module: { uri },
|
||||
exports,
|
||||
require
|
||||
};
|
||||
registry[uri] = Promise.all(depsNames.map(
|
||||
depName => specialDeps[depName] || require(depName)
|
||||
)).then(deps => {
|
||||
factory(...deps);
|
||||
return exports;
|
||||
});
|
||||
};
|
||||
}
|
||||
define(['./workbox-e43f5367'], (function (workbox) { 'use strict';
|
||||
|
||||
importScripts();
|
||||
self.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
workbox.registerRoute("/", new workbox.NetworkFirst({
|
||||
"cacheName": "start-url",
|
||||
plugins: [{
|
||||
cacheWillUpdate: async ({
|
||||
request,
|
||||
response,
|
||||
event,
|
||||
state
|
||||
}) => {
|
||||
if (response && response.type === 'opaqueredirect') {
|
||||
return new Response(response.body, {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: response.headers
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}]
|
||||
}), 'GET');
|
||||
workbox.registerRoute(/.*/i, new workbox.NetworkOnly({
|
||||
"cacheName": "dev",
|
||||
plugins: []
|
||||
}), 'GET');
|
||||
|
||||
}));
|
||||
2455
public/workbox-e43f5367.js
Normal file
2455
public/workbox-e43f5367.js
Normal file
File diff suppressed because it is too large
Load Diff
198
styles/LandingPage.module.css
Normal file
198
styles/LandingPage.module.css
Normal file
@ -0,0 +1,198 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #1e293b 0%, #1e40af 50%, #54c3d4 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.backgroundEffects {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bgEffect1 {
|
||||
position: absolute;
|
||||
top: -160px;
|
||||
right: -160px;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
background: rgba(84, 195, 212, 0.1);
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
}
|
||||
|
||||
.bgEffect2 {
|
||||
position: absolute;
|
||||
bottom: -160px;
|
||||
left: -160px;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
background: rgba(34, 211, 238, 0.1);
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.nav {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.navContent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.content {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.textSection {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.textSection {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.title {
|
||||
font-size: 3.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.titleWhite {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.titleGradient {
|
||||
background: linear-gradient(45deg, #54c3d4, #22d3ee);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.downloadButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.downloadButton:hover {
|
||||
background: #f3f4f6;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.phoneIcon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.imageSection {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.imageSection {
|
||||
justify-content: flex-end;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
width: 288px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.appImage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 1.5rem;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
border: 4px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.imageEffect1 {
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
right: -1rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
filter: blur(12px);
|
||||
}
|
||||
|
||||
.imageEffect2 {
|
||||
position: absolute;
|
||||
bottom: -1rem;
|
||||
left: -1rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
filter: blur(12px);
|
||||
}
|
||||
16
styles/globals.css
Normal file
16
styles/globals.css
Normal file
@ -0,0 +1,16 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user