├── .do └── deploy.template.yaml ├── .dockerignore ├── .github └── workflows │ └── multireg.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.legacy ├── README.md ├── docker-compose.yaml ├── env.js ├── flightcontrol.json ├── package.json ├── scripts └── postinstall.js └── start.sh /.do/deploy.template.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | name: open-wa-easy-api 3 | services: 4 | - image: 5 | registry: openwa 6 | registry_type: DOCKER_HUB 7 | repository: wa-automate 8 | tag: latest 9 | name: open-wa-easy-api 10 | instance_count: 1 11 | envs: 12 | - key: "WA_LICENSE_KEY" 13 | value: '' 14 | type: GENERAL 15 | scope: RUN_AND_BUILD_TIME 16 | - key: "API_KEY" 17 | type: SECRET 18 | scope: RUN_AND_BUILD_TIME 19 | - key: "WA_KEEP_ALIVE" 20 | value: 'false' 21 | type: GENERAL 22 | scope: RUN_AND_BUILD_TIME 23 | - key: "WA_WEBHOOK" 24 | value: 'false' 25 | type: GENERAL 26 | scope: RUN_AND_BUILD_TIME 27 | - key: "WA_MULTI_DEVICE" 28 | value: 'true' 29 | type: GENERAL 30 | scope: RUN_AND_BUILD_TIME 31 | - key: "WA_KEEP_UPDATED" 32 | value: 'false' 33 | type: GENERAL 34 | scope: RUN_AND_BUILD_TIME 35 | - key: "WA_EV" 36 | value: 'false' 37 | type: GENERAL 38 | scope: RUN_AND_BUILD_TIME 39 | - key: "WA_SOCKET" 40 | value: 'true' 41 | type: GENERAL 42 | scope: RUN_AND_BUILD_TIME 43 | - key: "WA_DEBUG" 44 | value: 'false' 45 | type: GENERAL 46 | scope: RUN_AND_BUILD_TIME 47 | - key: "WA_BOT_PRESS_URL" 48 | value: 'false' 49 | type: GENERAL 50 | scope: RUN_AND_BUILD_TIME 51 | - key: "PORT" 52 | type: GENERAL 53 | value: '8080' 54 | scope: RUN_AND_BUILD_TIME 55 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docker-compose.yaml 3 | Dockerfile 4 | Dockerfile.legacy 5 | flightcontrol.json 6 | README.md 7 | .git* -------------------------------------------------------------------------------- /.github/workflows/multireg.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: [library_updated] 7 | push: 8 | branches: [master] 9 | paths-ignore: 10 | - '**/README.md' 11 | 12 | jobs: 13 | multi: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v2.5.0 19 | # with: 20 | # persist-credentials: false 21 | # - name: Reconfigure git to use HTTP authentication 22 | # run: > 23 | # git config --global url."https://github.com/".insteadOf 24 | # ssh://git@github.com/ 25 | - 26 | name: Docker meta 27 | id: meta 28 | uses: docker/metadata-action@v4.1.1 29 | with: 30 | # list of Docker images to use as base name for tags 31 | images: | 32 | ghcr.io/open-wa/wa-automate 33 | openwa/wa-automate 34 | # generate Docker tags based on the following events/attributes 35 | tags: | 36 | type=ref,event=pr 37 | type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} 38 | type=raw,value=latest 39 | flavor: | 40 | latest=false 41 | - 42 | name: Set up QEMU 43 | uses: docker/setup-qemu-action@v2.1.0 44 | - 45 | name: Set up Docker Buildx 46 | uses: docker/setup-buildx-action@v2.2.1 47 | - 48 | name: Login to Docker Hub 49 | uses: docker/login-action@v2.1.0 50 | with: 51 | username: ${{ secrets.DOCKERHUB_USERNAME }} 52 | password: ${{ secrets.DOCKERHUB_TOKEN }} 53 | - 54 | name: Login to GitHub Container Registry 55 | uses: docker/login-action@v2.1.0 56 | with: 57 | registry: ghcr.io 58 | username: ${{ github.repository_owner }} 59 | password: ${{ secrets.CR_PAT }} 60 | - 61 | name: Build and push 62 | uses: docker/build-push-action@v3.2.0 63 | with: 64 | context: . 65 | file: ./Dockerfile 66 | # platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x 67 | push: true 68 | tags: ${{ steps.meta.outputs.tags }} 69 | labels: ${{ steps.meta.outputs.labels }} 70 | platforms: linux/amd64,linux/arm64 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.data.json 3 | *ignore* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1.3-labs 2 | FROM node:current-bullseye-slim 3 | ENV APP_DIR=/usr/src/app 4 | 5 | 6 | ENV SKIP_GITIGNORE_CHECK true 7 | ENV NODE_ENV production 8 | 9 | # Add your custom ENV vars here: 10 | ENV WA_POPUP true 11 | ENV IS_DOCKER=true 12 | ENV WA_DISABLE_SPINS true 13 | ENV WA_EXECUTABLE_PATH=/usr/bin/google-chrome 14 | ENV WA_CLI_CONFIG=/config 15 | ENV CHROME_PATH=${WA_EXECUTABLE_PATH} 16 | ENV WA_USE_CHROME=true 17 | 18 | ENV CLOUDFLARED_BIN="/usr/src/bin/cloudflared" 19 | ENV PUPPETEER_CHROMIUM_REVISION=${PUPPETEER_CHROMIUM_REVISION} 20 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true 21 | ENV PLAYWRIGHT_BROWSERS_PATH=${APP_DIR} 22 | 23 | COPY . $APP_DIR 24 | 25 | WORKDIR $APP_DIR 26 | 27 | RUN < /dev/null 2>&1; then \ 81 | npx cloudflared@latest bin install; \ 82 | fi 83 | # test with root later 84 | USER owauser 85 | 86 | ENTRYPOINT ["/usr/bin/dumb-init", "--", "./start.sh", "./node_modules/@open-wa/wa-automate/bin/server.js", "--use-chrome", "--in-docker", "--qr-timeout", "0", "--popup", "--debug", "--force-port"] -------------------------------------------------------------------------------- /Dockerfile.legacy: -------------------------------------------------------------------------------- 1 | FROM node:current-stretch 2 | 3 | RUN mkdir -p /usr/src/app 4 | RUN mkdir -p /sessions 5 | 6 | WORKDIR /usr/src/app 7 | 8 | #install chrome 9 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list 10 | 11 | RUN apt update 12 | 13 | RUN apt install google-chrome-stable fonts-freefont-ttf libxss1 --no-install-recommends -y \ 14 | && rm -rf /var/lib/apt/lists/* 15 | 16 | RUN apt upgrade -y 17 | 18 | # skip the puppeteer browser download 19 | ENV PUPPETEER_SKIP_DOWNLOAD true 20 | 21 | RUN npm i @open-wa/wa-automate@latest 22 | # Add user so we don't need --no-sandbox. 23 | # same layer as npm install to keep re-chowned files from using up several hundred MBs more space 24 | RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \ 25 | && mkdir -p /home/pptruser/Downloads \ 26 | && chown -R pptruser:pptruser /home/pptruser \ 27 | && chown -R pptruser:pptruser /usr/src/app \ 28 | && chown -R pptruser:pptruser /sessions \ 29 | && chown -R pptruser:pptruser /usr/src/app/node_modules 30 | 31 | USER pptruser 32 | 33 | COPY . /usr/src/app 34 | 35 | ENV NODE_ENV production 36 | ENV PORT 8080 37 | 38 | # Add your custom ENV vars here: 39 | ENV WA_USE_CHROME true 40 | ENV WA_POPUP true 41 | ENV WA_DISABLE_SPINS true 42 | ENV WA_PORT $PORT 43 | ENV WA_EXECUTABLE_PATH /usr/bin/google-chrome-stable 44 | 45 | EXPOSE $PORT 46 | 47 | ENTRYPOINT [ "node", "./node_modules/@open-wa/wa-automate/bin/server.js", "--in-docker", "--qr-timeout", "0", "--popup", "--debug"] 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wa-automate-docker 2 | 3 | This repo shows a simple way to create a memory efficient API out of your WhatsApp account using docker. 4 | 5 | ## Instructions 6 | 7 | ```bash 8 | > docker run [ ... docker flags] --init openwa/wa-automate [... cli flags] 9 | ``` 10 | 11 | It is important to add the `--init` flag to make sure zombie processes are garbage collected properly 12 | 13 | The default internal port is 8080. Set `-e PORT=3000` in the `docker flags` section to set the internal port to `3000`. 14 | 15 | For example: 16 | 17 | ```bash 18 | # Defaults 19 | > docker run -p 8080:8080 --init openwa/wa-automate 20 | 21 | # Devices with arm64 processors (Raspberry Pi`s, Apple M1, etc...) 22 | > docker run --platform linux/arm64 -p 8080:8080 openwa/wa-automate 23 | 24 | # Custom webhook, port & socket mode enabled for easy integration with node-red 25 | > docker run -p 8080:8085 --init openwa/wa-automate -w -p 8085 https://webhook.site.... --socket 26 | 27 | # You can also use environment variables to set the port 28 | > docker run -e PORT=8085 -p 8080:8085 --init openwa/wa-automate -w https://webhook.site.... --socket 29 | ``` 30 | 31 | ## Versioning 32 | 33 | The only tag for this docker image is `latest`. On launch of the docker container, the latest version of the main library is checked and updated. So all you need to do to use the latest [wa-automate](https://github.com/open-wa/wa-automate-nodejs) code is to restart your container. 34 | 35 | Sometimes you may want to use a previous version of the library, or just pin it due to stability reasons, in this case you can set the environment variable `W_A_V` to your [desired wa-automate library version](https://github.com/open-wa/wa-automate-nodejs/releases). For example: 36 | 37 | ```bash 38 | # Same setup as above but with library version 4.42.1 39 | > docker run -e W_A_V=4.42.1 -p 8080:8080 --init openwa/wa-automate -w https://webhook.site.... --socket 40 | ``` 41 | 42 | See here for more information on [cli flags - https://github.com/open-wa/wa-automate-nodejs/blob/82ecae471e9cdf0013b81f53c2f83d2b33d6fa42/src/cli/setup.ts#L27](https://github.com/open-wa/wa-automate-nodejs/blob/82ecae471e9cdf0013b81f53c2f83d2b33d6fa42/src/cli/setup.ts#L27) 43 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | 4 | owa: 5 | image: openwa/wa-automate:latest 6 | hostname: owa 7 | init: true 8 | restart: on-failure 9 | volumes: 10 | - "sessions:/sessions" 11 | ports: 12 | - "8080:8080" 13 | environment: 14 | WA_DISABLE_SPINS: 'true' # Example cli config as ENV VAR. Use any flag from https://docs.openwa.dev/docs/configuration/command-line-options and convert to capital snake case with `WA_` prefix 15 | WA_SESSION_ID: "MY_SESSION" # Change this 16 | 17 | volumes: 18 | sessions: -------------------------------------------------------------------------------- /env.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable no-useless-escape */ 3 | /** 4 | * Taken from https://github.com/browserless/chrome/blob/master/env.js 5 | * 6 | * By @joelgriffith 7 | */ 8 | import os from 'os' 9 | import playwright from 'playwright-core' 10 | import puppeteer from 'puppeteer' 11 | const IS_DOCKER = process.env.IS_DOCKER === 'true'; 12 | 13 | const USE_CHROME_STABLE = process.env.USE_CHROME_STABLE && process.env.USE_CHROME_STABLE === 'true'; 14 | 15 | const MAC = 'MAC'; 16 | const WINDOWS = 'WINDOWS'; 17 | const LINUX = 'LINUX'; 18 | const LINUX_ARM64 = 'LINUX_ARM64'; 19 | 20 | const CHROME_BINARY_PATHS = { 21 | LINUX: '/usr/bin/google-chrome', 22 | MAC: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome', 23 | WIN: 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe', 24 | }; 25 | 26 | const PLATFORM = os.platform() === 'win32' ? 27 | WINDOWS : 28 | os.platform() === 'darwin' ? 29 | MAC : 30 | os.arch() === 'arm64' ? 31 | LINUX_ARM64 : 32 | LINUX; 33 | 34 | /* 35 | * Assess which chromium revision to install. 36 | * Note that in docker we do our own install, and 37 | * ignore puppeteer's install.js file. 38 | */ 39 | const PUPPETEER_CHROMIUM_REVISION = (() => { 40 | return puppeteer._preferredRevision; 41 | })(); 42 | 43 | /* 44 | * Sometimes we don't use puppeteer's built-in chromium 45 | * for compatibility reasons 46 | */ 47 | const PUPPETEER_BINARY_LOCATION = (() => { 48 | if (PLATFORM === LINUX_ARM64) { 49 | return playwright.chromium.executablePath(); 50 | } 51 | 52 | const browserFetcher = puppeteer.createBrowserFetcher(); 53 | return browserFetcher.revisionInfo(PUPPETEER_CHROMIUM_REVISION).executablePath; 54 | })(); 55 | 56 | /* 57 | * Tells puppeteer, in its install script, what revision to download. 58 | * This is set in our deploy.js file in our docker build. If 59 | * PUPPETEER_SKIP_CHROMIUM_DOWNLOAD is true, then this is ignored 60 | */ 61 | const CHROME_BINARY_LOCATION = (() => { 62 | if (process.env.CHROME_BINARY_LOCATION) { 63 | return process.env.CHROME_BINARY_LOCATION; 64 | } 65 | 66 | // In docker we symlink any chrome installs to the default install location 67 | // so that chromedriver can do its thing 68 | if (IS_DOCKER) { 69 | return CHROME_BINARY_PATHS.LINUX; 70 | } 71 | 72 | // If using chrome-stable, default to it's natural habitat 73 | if (USE_CHROME_STABLE) { 74 | return CHROME_BINARY_PATHS[PLATFORM]; 75 | } 76 | 77 | // All else uses pptr's bin 78 | return PUPPETEER_BINARY_LOCATION; 79 | })(); 80 | 81 | /* 82 | * Tells the chromedriver library to download the appropriate chromedriver binary. 83 | * The only time this should be false is when building chrome stable in docker. 84 | */ 85 | const CHROMEDRIVER_SKIP_DOWNLOAD = (() => { 86 | if (process.env.CHROMEDRIVER_SKIP_DOWNLOAD) { 87 | return process.env.CHROMEDRIVER_SKIP_DOWNLOAD; 88 | } 89 | 90 | if (IS_DOCKER) { 91 | return !USE_CHROME_STABLE; 92 | } 93 | 94 | return true; 95 | })(); 96 | 97 | /* 98 | * Tells puppeteer to skip downloading the appropriate chrome revision. This is generally 99 | * not the case, however when installing chrome-stable in docker we want to skip it as 100 | * we'll download google-chrome-stable from a deb package instead. 101 | */ 102 | const PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = (() => { 103 | if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) { 104 | return process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD; 105 | } 106 | 107 | if (IS_DOCKER) { 108 | return USE_CHROME_STABLE; 109 | } 110 | 111 | return false; 112 | })(); 113 | 114 | const e = { 115 | IS_DOCKER, 116 | USE_CHROME_STABLE, 117 | PUPPETEER_CHROMIUM_REVISION, 118 | CHROME_BINARY_LOCATION, 119 | CHROMEDRIVER_SKIP_DOWNLOAD, 120 | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD, 121 | PUPPETEER_BINARY_LOCATION, 122 | PLATFORM, 123 | WINDOWS, 124 | MAC, 125 | LINUX, 126 | LINUX_ARM64, 127 | }; 128 | 129 | export default e -------------------------------------------------------------------------------- /flightcontrol.json: -------------------------------------------------------------------------------- 1 | { 2 | "environments": [ 3 | { 4 | "id": "production", 5 | "name": "Production", 6 | "region": "us-west-2", 7 | "source": { 8 | "branch": "master" 9 | }, 10 | "services": [ 11 | { 12 | "id": "my-webapp", 13 | "name": "my-webapp", 14 | "type": "fargate", 15 | "cpu": 0.5, 16 | "memory": 1024, 17 | "domain": "example.com", 18 | "dockerfilePath": "Dockerfile", 19 | "port": 8080, 20 | "minInstances": 1, 21 | "maxInstances": 1, 22 | "envVariables": { 23 | "SOMETHING_STATIC": "123", 24 | } 25 | } 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wa-automate-docker", 3 | "version": "1.0.0", 4 | "description": "This is a simple app that will expose an API that you can use to control your whatsapp instance.", 5 | "type": "module", 6 | "scripts": { 7 | "postinstall": "node ./scripts/postinstall.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "npm update && npx @open-wa/wa-automate --in-docker -p 8080 --npm-options=--ignore-scripts", 10 | "sessiondata" : "npx @open-wa/wa-automate --disable-spins --session-data-only" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/open-wa/wa-automate-docker.git" 15 | }, 16 | "author": "SMASHAH (smashah.dev)", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/open-wa/wa-automate-docker/issues" 20 | }, 21 | "homepage": "https://github.com/open-wa/wa-automate-docker#readme", 22 | "dependencies": { 23 | "@open-wa/wa-automate": "latest", 24 | "npm": "^7.12.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/postinstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | /** 4 | * Taken from https://github.com/browserless/chrome/blob/master/scripts/postinstall.js 5 | * 6 | * By @joelgriffith 7 | */ 8 | import * as cp from 'child_process' 9 | const nodeExec = cp.default.exec 10 | import os from 'os' 11 | import { dirname } from 'path'; 12 | import { fileURLToPath } from 'url'; 13 | import * as path from 'path' 14 | import { promisify } from 'util' 15 | import extract from 'extract-zip'; 16 | import fs from 'fs-extra' 17 | import chain from 'lodash/chain.js'; 18 | import isNil from 'lodash/isNil.js'; 19 | 20 | import fetch from 'node-fetch'; 21 | import { installBrowsersForNpmInstall } from 'playwright-core/lib/server' 22 | import puppeteer from 'puppeteer' 23 | import {rimraf} from 'rimraf' 24 | const __dirname = dirname(fileURLToPath(import.meta.url)); 25 | 26 | const execAsync = promisify(nodeExec); 27 | const hostsJson = path.join(__dirname, '..', 'hosts.json'); 28 | 29 | const exec = async (command) => { 30 | const { stdout, stderr } = await execAsync(command); 31 | 32 | if (stderr.trim().length) { 33 | console.error(stderr); 34 | return process.exit(1); 35 | } 36 | 37 | return stdout.trim(); 38 | }; 39 | import _env from '../env.js' 40 | console.log(_env) 41 | const { 42 | CHROME_BINARY_LOCATION, 43 | IS_DOCKER, 44 | USE_CHROME_STABLE, 45 | PUPPETEER_CHROMIUM_REVISION, 46 | PUPPETEER_BINARY_LOCATION, 47 | PLATFORM, 48 | WINDOWS, 49 | MAC, 50 | LINUX_ARM64, 51 | } = _env 52 | 53 | const browserlessTmpDir = path.join( 54 | os.tmpdir(), 55 | `browserless-devtools-${Date.now()}`, 56 | ); 57 | 58 | const IS_LINUX_ARM64 = PLATFORM === LINUX_ARM64; 59 | 60 | // @TODO: Fix this revision once devtools app works again 61 | const devtoolsUrl = `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2F848005%2Fdevtools-frontend.zip?alt=media`; 62 | const chromedriverUrl = (() => { 63 | if (PLATFORM === MAC) 64 | return `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2F${PUPPETEER_CHROMIUM_REVISION}%2Fchromedriver_mac64.zip?alt=media`; 65 | if (PLATFORM === WINDOWS) 66 | return `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Win%2F${PUPPETEER_CHROMIUM_REVISION}%2Fchromedriver_win32.zip?alt=media`; 67 | 68 | // Linux 69 | return `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${PUPPETEER_CHROMIUM_REVISION}%2Fchromedriver_linux64.zip?alt=media`; 70 | })(); 71 | 72 | const downloadUrlToDirectory = (url, dir) => 73 | fetch(url).then( 74 | (response) => 75 | new Promise((resolve, reject) => { 76 | response.body 77 | .pipe(fs.createWriteStream(dir)) 78 | .on('error', reject) 79 | .on('finish', resolve); 80 | }), 81 | ); 82 | 83 | const unzip = async (source, target) => extract(source, { dir: target }); 84 | const move = async (src, dest) => fs.move(src, dest, { overwrite: true }); 85 | const waitForFile = async (filePath) => 86 | new Promise((resolve, reject) => { 87 | let responded = false; 88 | const done = (error) => { 89 | if (responded) return; 90 | responded = true; 91 | clearInterval(interval); 92 | clearTimeout(timeout); 93 | return error ? reject(error) : resolve(); 94 | }; 95 | 96 | const interval = setInterval(() => fs.existsSync(filePath) && done(), 100); 97 | const timeout = setTimeout( 98 | () => done(`Timeout waiting for file ${filePath}`), 99 | 5000, 100 | ); 101 | }); 102 | 103 | const downloadAdBlockList = () => { 104 | console.log(`Downloading ad-blocking list`); 105 | 106 | return fetch( 107 | 'https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts', 108 | ) 109 | .then((res) => res.text()) 110 | .then((raw) => 111 | chain(raw) 112 | .split('\n') 113 | .map((line) => { 114 | const fragments = line.split(' '); 115 | if (fragments.length > 1 && fragments[0] === '0.0.0.0') { 116 | return fragments[1].trim(); 117 | } 118 | return null; 119 | }) 120 | .reject(isNil) 121 | .value(), 122 | ) 123 | .then((hostsArr) => { 124 | fs.writeFileSync(hostsJson, JSON.stringify(hostsArr, null, ' ')); 125 | }); 126 | }; 127 | 128 | const downloadChromium = () => { 129 | if (USE_CHROME_STABLE && IS_LINUX_ARM64) { 130 | throw new Error(`Chrome stable isn't supported for linux-arm64`); 131 | } 132 | 133 | if (IS_LINUX_ARM64) { 134 | return installBrowsersForNpmInstall(['chromium']); 135 | } 136 | 137 | if (USE_CHROME_STABLE) { 138 | console.log('Using chrome stable, not proceeding with chromium download'); 139 | return Promise.resolve(); 140 | } 141 | 142 | console.log( 143 | `Downloading chromium for revision ${PUPPETEER_CHROMIUM_REVISION}`, 144 | ); 145 | 146 | return puppeteer 147 | .createBrowserFetcher({ product: 'chrome' }) 148 | .download(PUPPETEER_CHROMIUM_REVISION); 149 | }; 150 | 151 | const downloadChromedriver = () => { 152 | if (USE_CHROME_STABLE) { 153 | console.log( 154 | 'chromedriver binary already installed, not proceeding with chromedriver', 155 | ); 156 | return Promise.resolve(); 157 | } 158 | 159 | console.log( 160 | `Downloading chromedriver for revision ${PUPPETEER_CHROMIUM_REVISION}`, 161 | ); 162 | 163 | const chromedriverZipFolder = (() => { 164 | if (PLATFORM === MAC) return 'chromedriver_mac64'; 165 | if (PLATFORM === WINDOWS) return 'chromedriver_win32'; 166 | return 'chromedriver_linux64'; // Linux 167 | })(); 168 | 169 | const chromedriverTmpZip = path.join(browserlessTmpDir, `chromedriver`); 170 | const chromedriverBin = `chromedriver${PLATFORM === WINDOWS ? '.exe' : ''}`; 171 | const chromedriverUnzippedPath = path.join( 172 | browserlessTmpDir, 173 | chromedriverZipFolder, 174 | chromedriverBin, 175 | ); 176 | const chromedriverFinalPath = path.join( 177 | __dirname, 178 | '..', 179 | 'node_modules', 180 | 'chromedriver', 181 | 'lib', 182 | 'chromedriver', 183 | 'chromedriver', 184 | ); 185 | 186 | return downloadUrlToDirectory(chromedriverUrl, chromedriverTmpZip) 187 | .then(() => waitForFile(chromedriverTmpZip)) 188 | .then(() => unzip(chromedriverTmpZip, browserlessTmpDir)) 189 | .then(() => waitForFile(chromedriverUnzippedPath)) 190 | .then(() => move(chromedriverUnzippedPath, chromedriverFinalPath)) 191 | .then(() => fs.chmod(chromedriverFinalPath, '755')) 192 | .then(() => waitForFile(chromedriverFinalPath)); 193 | }; 194 | 195 | const downloadDevTools = () => { 196 | console.log( 197 | `Downloading devtools assets for revision ${PUPPETEER_CHROMIUM_REVISION}`, 198 | ); 199 | const devtoolsTmpZip = path.join(browserlessTmpDir, 'devtools'); 200 | const devtoolsUnzippedPath = path.join( 201 | browserlessTmpDir, 202 | 'devtools-frontend', 203 | 'resources', 204 | 'inspector', 205 | ); 206 | const devtoolsFinalPath = path.join(__dirname, '..', 'devtools'); 207 | 208 | return downloadUrlToDirectory(devtoolsUrl, devtoolsTmpZip) 209 | .then(() => waitForFile(devtoolsTmpZip)) 210 | .then(() => unzip(devtoolsTmpZip, browserlessTmpDir)) 211 | .then(() => waitForFile(devtoolsUnzippedPath)) 212 | .then(() => move(devtoolsUnzippedPath, devtoolsFinalPath)) 213 | .then(() => waitForFile(devtoolsFinalPath)); 214 | }; 215 | 216 | (() => 217 | new Promise(async (resolve, reject) => { 218 | try { 219 | await fs.mkdir(browserlessTmpDir); 220 | 221 | await Promise.all([ 222 | downloadChromium(), 223 | // downloadChromedriver(), 224 | // downloadDevTools(), 225 | // downloadAdBlockList(), 226 | ]); 227 | 228 | // If we're in docker, and this isn't a chrome-stable build, 229 | // symlink where chrome-stable should be back to puppeteer's build 230 | if (IS_DOCKER && !fs.existsSync(CHROME_BINARY_LOCATION)) { 231 | if (!USE_CHROME_STABLE && !fs.existsSync(PUPPETEER_BINARY_LOCATION)) { 232 | throw new Error( 233 | `Couldn't find chromium at path: "${PUPPETEER_BINARY_LOCATION}"`, 234 | ); 235 | } 236 | 237 | (async () => { 238 | console.log( 239 | `Symlinking chrome from ${CHROME_BINARY_LOCATION} to ${PUPPETEER_BINARY_LOCATION}`, 240 | ); 241 | await exec( 242 | `ln -s ${PUPPETEER_BINARY_LOCATION} ${CHROME_BINARY_LOCATION}`, 243 | ); 244 | })(); 245 | } 246 | } catch (err) { 247 | console.error(`Error unpacking assets:\n${err.message}\n${err.stack}`); 248 | reject(err); 249 | process.exit(1); 250 | } finally { 251 | try { 252 | await rimraf(browserlessTmpDir) 253 | console.log('Done unpacking chromedriver and devtools assets'); 254 | } catch (err) { 255 | console.warn( 256 | `Error removing temporary directory ${browserlessTmpDir}`, 257 | ); 258 | } 259 | resolve(); 260 | } 261 | }))(); -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # When docker restarts, this file is still there, 4 | # so we need to kill it just in case 5 | [ -f /tmp/.X99-lock ] && rm -f /tmp/.X99-lock 6 | 7 | _kill_procs() { 8 | kill -TERM $node 9 | # kill -TERM $xvfb 10 | } 11 | 12 | # Relay quit commands to processes 13 | trap _kill_procs SIGTERM SIGINT 14 | 15 | # Xvfb :99 -screen 0 1024x768x16 -nolisten tcp -nolisten unix & 16 | # xvfb=$! 17 | 18 | #Set the desired version, fallback to latest 19 | WA_AUTOMATE_VERSION="${W_A_V:-latest}" 20 | 21 | export DISPLAY=:99 22 | 23 | echo "Starting the application" 24 | echo "Using version: $WA_AUTOMATE_VERSION" 25 | echo $@ 26 | if [[ $@ == *"--no-update"* ]]; then 27 | echo "Skipping update" 28 | else eval "PUPPETEER_SKIP_DOWNLOAD=true npm i @open-wa/wa-automate@$WA_AUTOMATE_VERSION --ignore-scripts" 29 | fi 30 | exec node $@ 31 | # exec dumb-init -- node ./node_modules/@open-wa/wa-automate/bin/server.js $@ 32 | # node=$! 33 | # echo "PID: $node" 34 | # wait $node 35 | # wait $xvfb --------------------------------------------------------------------------------