File size: 5,937 Bytes
b39afbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**
 * Copyright (c) 2023 MERCENARIES.AI PTE. LTD.
 * All rights reserved.
 */

const fs = require('node:fs');
const path = require('node:path');
const { exec } = require('child_process');
const assert = require('node:assert');
const { execSync } = require('node:child_process');
const { getSHA256Checksum, statuslogger, omniCwd } = require('./utils.js');
const { extract } = require('tar');

const config = JSON.parse(
  fs.readFileSync(path.join(omniCwd(), 'package.json'), { encoding: 'utf-8' }));
const path_destination_folder = path.join(omniCwd(), 'setup', 'updates');
const remote_releases_url = config.dependenciesBin.updates_base_url;

function _get_updater_url() {
  const platform = process.platform;
  const arch = process.arch;
  assert(platform === 'win32' || platform === 'darwin' || platform === 'linux');
  assert(arch === 'x64' || arch === 'arm64');
  return `${remote_releases_url}/${_get_updater_file()}`;
}

function _get_updater_checksum_url() {
  return `${remote_releases_url}/${_get_updater_checksum_file()}`;
}

function _get_updater_checksum_file() {
  const platform = process.platform;
  const arch = process.arch;
  assert(platform === 'win32' || platform === 'darwin' || platform === 'linux');
  assert(arch === 'x64' || arch === 'arm64');
  return `${platform}_${arch}_updates_checksum.txt`;
}

function _get_updater_file() {
  const platform = process.platform;
  const arch = process.arch;
  assert(platform === 'win32' || platform === 'darwin' || platform === 'linux');
  assert(arch === 'x64' || arch === 'arm64');
  return `${platform}_${arch}_updates.tar.gz`;
}

/**
 * Fetches the contents of a remote file using curl.
 * @param {string} url - The URL of the file to fetch.
 * @returns {Promise<string>} - Resolves with the file contents as a string.
 */
function curl_remote_textfile(url) {
  return new Promise((resolve, reject) => {
    const cmd = `curl -L "${url}"`;

    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        console.error(`exec error: ${error}`);
        reject(error);
        return;        
      }

      // test for SHA256
      const regexExp = /^[a-f0-9]{64}$/gi;
      if (!regexExp.test(stdout)) {
        reject('Failed to retrieve valid SHA256 checksum.');
        return;
      }
      console.log('Remote found: ' + stdout);
      resolve(stdout);
    });
  });
}

async function get_remote_checksum() {
  // update if different
  console.log('Checking for updates...' + _get_updater_checksum_url());
  return await curl_remote_textfile(_get_updater_checksum_url());
}

function verify_checksum(incoming) {
  const checksum_file_path = path.join(path_destination_folder, _get_updater_checksum_file());
  if (!fs.existsSync(path_destination_folder)) {
    fs.mkdirSync(path_destination_folder);
    return false;
  }
  // if local file doesn't exist create and return checksum fail
  if (!fs.existsSync(checksum_file_path)) {
    return false;
  }
  const local_checksum = fs.readFileSync(checksum_file_path, { encoding: 'ascii' });
  return local_checksum === incoming;
}

async function run_update(new_checksum) {
  // download bundle
  let retries = 2;
  const bundle_file = _get_updater_file();
  console.log('Downloading update bundle ' + bundle_file);
  while (retries > 0) {
    const download_cmd = `curl -L "${_get_updater_url()}" -o ${bundle_file}`;
    execSync(download_cmd, { cwd: path_destination_folder, stdio: 'inherit' });
    // verify checksum
    const download_checksum = await getSHA256Checksum(path.join(path_destination_folder, bundle_file));
    if (new_checksum !== download_checksum) {
      console.log(`Checksum failed expected ${new_checksum} got ${download_checksum}`);
      fs.rmSync(path.join(path_destination_folder, bundle_file), { force: true });
      retries--;
      console.log(`Retrying download...${retries} retries`);
      if (retries === 0) {
        throw new Error(`Update bundle checksum failed too many times. Aborting.`);
      }
    } else {
      console.log(`Downloaded ${path.join(path_destination_folder, bundle_file)}`);
      break;
    }
  }
  // copy to project base dir and unpack
  fs.copyFileSync(path.join(path_destination_folder, bundle_file), path.join(omniCwd(), bundle_file));
  console.log(`Extracting ${bundle_file}`);
  const exclude_executable = process.platform === 'win32' ? 'omnitool.exe' : 'omnitool';
  await extract({
    file: bundle_file,
    filter: (p) => {
      const filepath = path.parse(p);
      if (p === exclude_executable) {
        return false;
      } else {
        void statuslogger(`Unpacking ${filepath.name}${filepath.ext}`);
        return true;
      }
    }
  });
  // cleanup
  fs.rmSync(path.join(path_destination_folder, bundle_file));
}

function seal_update(new_checksum) {
  assert(new_checksum !== null);
  const checksum_file_path = path.join(path_destination_folder, _get_updater_checksum_file());
  fs.writeFileSync(checksum_file_path, new_checksum);
}

async function update_build() {
  // skip updates if there's any remote failures
  let need_update = false;
  let remote_checksum = null;
  try {
    remote_checksum = await get_remote_checksum();
    need_update = !verify_checksum(remote_checksum);
  } catch (e) {
    console.warn('Error fetching update checksum. Skipping updates.');
    console.error(e);
    // continue
    need_update = false;
  }
  if (need_update) {
    try {
      console.log('New version found! Updating...');
      await run_update(remote_checksum);
      seal_update(remote_checksum);
      console.log(`Build updated to ${remote_checksum}`);
      return true;
    } catch (e) {
      console.log('Update failed, cleaning up...');
      // always reset on any update error
      fs.rmSync(path_destination_folder, { recursive: true, force: true });
      throw e;
    }
  } else {
    console.log('Current version is the latest available.');
    return false;
  }
}

module.exports = {
  update_build
};