padavan 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Alex Smith
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/main.mjs DELETED
@@ -1,301 +0,0 @@
1
-
2
- import path from 'path';
3
- import fs from 'fs';
4
- import ini from 'ini';
5
- import ssh from 'ssh2';
6
- import jszip from 'jszip';
7
-
8
- export class Padavan {
9
- constructor(credentials={}) {
10
- this.credentials = credentials;
11
- };
12
- exec(SystemCmd) {
13
- /* return this.router('apply.cgi', {
14
- SystemCmd,
15
- action_mode: ' SystemCmd '
16
- }, 'POST').then(res => this.router('console_response.asp').then(res => res.text())); */
17
- return new Promise((res, rej) => {
18
- const stdout = [];
19
- const stderr = [];
20
- const conn = new ssh.Client();
21
- conn.on('error', rej);
22
- conn.on('ready', () => {
23
- conn.exec(SystemCmd, (err, stream) => {
24
- if (err)
25
- return rej(err);
26
- stream.on('close', (code, signal) => {
27
- conn.end();
28
- if (code)
29
- return rej({
30
- code,
31
- message: stderr.join('')
32
- });
33
- return res(stdout.join('').slice(0, -1))
34
- }).on('data', chunk => stdout.push(chunk)).stderr.on('data', chunk => stderr.push(chunk));
35
- });
36
- }).connect({
37
- host: this.credentials.host,
38
- username: this.credentials.username,
39
- password: this.credentials.password
40
- });
41
- });
42
- };
43
- nvram(key, value) {
44
- if (key) {
45
- if (value === undefined)
46
- return this.exec(`nvram get ${key}`);
47
- else if (value === null)
48
- return this.exec(`nvram set ${key}`);
49
- else
50
- return this.exec(`nvram set ${key}=${value}`);
51
- }else
52
- return this.exec('nvram showall').then(ini.parse);
53
- };
54
- async setParams(data) {
55
- if (!data || !Object.keys(data).length)
56
- throw new Error('Input data missing');
57
- if (data.sid_list) {
58
- if (!data.action_mode)
59
- data.action_mode = ' Apply ';
60
- return this.router('start_apply.htm', data, 'POST').then(res => res.text()).then(text => text.includes('<script>done_committing();</script>'));
61
- }else
62
- return Promise.all(Object.entries(data).map(([ key, value ]) => this.nvram(key, value))).then(() => {
63
- return this.exec('nvram commit').then(() => true);
64
- });
65
- };
66
- getParams(keys) {
67
- return this.nvram().then(nvram => {
68
- if (!keys)
69
- return nvram;
70
- return Object.fromEntries([].concat(keys).map(key => [ key, nvram[key] ]));
71
- });
72
- };
73
- getLog() {
74
- return this.router('log_content.asp').then(res => res.text());
75
- };
76
- getStatus() {
77
- return this.router('system_status_data.asp').then(res => res.text()).then(text => {
78
- return JSON.parse(
79
- text.slice(11, -1)
80
- .replace(new RegExp('(\\w+):\\s', 'g'), '"$1": ')
81
- .replace(new RegExp('0x([0-9a-fA-F]+)', 'g'), (match, hex) => parseInt(hex, 16))
82
- );
83
- });
84
- };
85
- getHistory() {
86
- // return this.exec('cat /var/spool/rstats-history.js').then(text => {
87
- return this.router('Main_TrafficMonitor_daily.asp').then(res => res.text()).then(text => {
88
- text = text.substring(text.indexOf('netdev'));
89
- text = text.substring(0, text.indexOf('\n\n'));
90
- text = text.trim()
91
- .replace(new RegExp('\'', 'g'), '"')
92
- .replace(new RegExp('^(.*) =', 'gm'), '"$1":')
93
- .replace(new RegExp(';', 'g'), ',')
94
- .replace(new RegExp(',$', 'g'), '')
95
- .replace(new RegExp('0x([0-9a-fA-F]+)', 'g'), (match, hex) => parseInt(hex, 16));
96
- const json = JSON.parse('{'+text+'}');
97
- for (var k in json) {
98
- if (k.endsWith('_history'))
99
- json[k].forEach(stat => {
100
- stat[0] = [(((stat[0] >> 16) & 0xFF) + 1900), ((stat[0] >>> 8) & 0xFF), (stat[0] & 0xFF)];
101
- });
102
- }
103
- return json;
104
- });
105
- };
106
- getDevices() {
107
- return this.exec('arp -a').then(arp => {
108
- return arp.split('\n').reduce((res, line) => {
109
- const parts = line.match(/^(\S+)\s+\(([\d.]+)\)\s+at\s+([a-fA-F0-9:]+)\s+\[ether\](?:\s+(\S+))?\s+on\s+(\S+)/);
110
- if (parts) {
111
- if (parts[1] === '?')
112
- parts[1] = null;
113
- res[parts[3].toUpperCase()] = parts[1];
114
- }
115
- return res;
116
- }, {});
117
- }).then(arp => {
118
- return this.router('device-map/clients.asp').then(res => res.text()).then(text => {
119
- const ipmonitor = JSON.parse(text.match(new RegExp('^var ipmonitor = (.*?);$', 'm'))[1]);
120
- const wireless = JSON.parse(text.match(new RegExp('^var wireless = (.*?);$', 'm'))[1]);
121
- return ipmonitor.filter(device => device[5] !== '1').map(device => ({
122
- hostname: arp[device[1]] || device[2],
123
- ip: device[0],
124
- mac: device[1],
125
- rssi: wireless[device[1]]
126
- }));
127
- });
128
- });
129
- };
130
- router(path, body, method) {
131
- return fetch(`http://${this.credentials.host}/${path}`, {
132
- method,
133
- headers: {
134
- authorization: 'Basic '+btoa(this.credentials.username+':'+this.credentials.password)
135
- },
136
- body: (body?.constructor.name === 'Object') ? new URLSearchParams(body) : body
137
- }).then(res => {
138
- if (!res.ok)
139
- return Promise.reject({
140
- message: res.statusText,
141
- code: res.status
142
- });
143
- return res;
144
- });
145
- };
146
- github(path, body, method, headers={}) {
147
- if (!path)
148
- path = `https://api.github.com/repos/${this.credentials.repo}`;
149
- else if (!path.startsWith('http'))
150
- path = `https://api.github.com/repos/${this.credentials.repo}/${path}`;
151
- if (body) {
152
- if (method === 'POST')
153
- body = JSON.stringify(body);
154
- else{
155
- path += '?'+new URLSearchParams(body);
156
- body = null;
157
- }
158
- }
159
- if (this.credentials.token)
160
- headers.authorization = `Bearer ${this.credentials.token}`;
161
- return fetch(path, { method, headers, body }).then(res => {
162
- if (!res.ok)
163
- return Promise.reject({
164
- message: res.statusText,
165
- code: res.status
166
- });
167
- return res;
168
- });
169
- };
170
- getCommits(repo, page=1, per_page=100) {
171
- return fetch(`https://gitlab.com/api/v4/projects/${repo}/repository/commits?`+new URLSearchParams({ page, per_page })).then(res => res.json());
172
- };
173
- getWorkflowId() {
174
- return this.github('actions/workflows').then(res => res.json()).then(json => {
175
- return json.workflows?.find(workflow => workflow.path.endsWith('build.yml'))?.id;
176
- });
177
- };
178
- async getRunId(id, code='success', per_page='1') {
179
- if (!id)
180
- id = await this.getWorkflowId();
181
- return this.github(`actions/workflows/${id}/runs`, {
182
- status: 'success',
183
- branch: this.credentials.branch || 'main',
184
- per_page: 1
185
- }).then(res => res.json()).then(json => json.workflow_runs[0]?.id);
186
- };
187
- async getArtifact(id) {
188
- if (!id)
189
- id = await this.getRunId();
190
- return this.github(`actions/runs/${id}/artifacts`).then(res => res.json()).then(json => {
191
- const artifact = json.artifacts?.find(artifact => !artifact.expired);
192
- if (artifact)
193
- return artifact;
194
- throw new Error('Firmware not found');
195
- });
196
- };
197
- async getChangelog(from_id, to_id) {
198
- if (!from_id)
199
- from_id = (await this.nvram('firmver_sub')).split('_')[1];
200
- if (!to_id)
201
- to_id = (await this.getArtifact())?.name.split('-').slice(-1)[0];
202
- if (to_id.length > 7)
203
- to_id = to_id.slice(0, 7);
204
- if (from_id != to_id)
205
- return this.github('contents/build.conf', undefined, undefined, {
206
- accept: 'application/vnd.github.raw+json'
207
- }).then(res => res.text()).then(text => {
208
- let [ padavan_repo ] = text.match(new RegExp('^PADAVAN_REPO="(.*?)"$', 'm'))?.slice(1) || [];
209
- if (!padavan_repo)
210
- throw new Error('PADAVAN_REPO not found in build.conf');
211
- const firstSlashIndex = padavan_repo.indexOf('/', padavan_repo.indexOf('//') + 2);
212
- const host = padavan_repo.substring(padavan_repo.indexOf('//') + 2, firstSlashIndex);
213
- if (host !== 'gitlab.com')
214
- throw new Error('Currently only gitlab.com is supported');
215
- padavan_repo = encodeURIComponent(padavan_repo.substring(firstSlashIndex + 1, padavan_repo.length - 4));
216
- const data = [];
217
- return this.getCommits(padavan_repo).then(arr => {
218
- for (const json of arr) {
219
- if (json.id.startsWith(from_id))
220
- break;
221
- if (json.id.startsWith(to_id) || data.length)
222
- data.push(json.title);
223
- }
224
- return { from_id, to_id, data };
225
- });
226
- });
227
- };
228
- startReboot() {
229
- return this.exec('reboot');
230
- };
231
- startSpeedTest() {
232
- const shell = fs.readFileSync(path.join(import.meta.dirname, 'services', 'speedtest.sh')).toString();
233
- return this.exec(shell).then(ini.parse).then(res => ({
234
- networkDownloadSpeedMbps: Number(parseFloat(res.download / 125000).toFixed(2)),
235
- networkUploadSpeedMbps: Number(parseFloat(res.upload / 125000).toFixed(2))
236
- }));
237
- };
238
- async startBuild(id) {
239
- if (!id)
240
- id = await this.getWorkflowId();
241
- return this.github(`actions/workflows/${id}/dispatches`, {
242
- ref: this.credentials.branch || 'main'
243
- }, 'POST').then(() => id);
244
- };
245
- async startUpgrade(id) {
246
- if (!id)
247
- id = (await this.getArtifact())?.id;
248
- return this.github(`actions/artifacts/${id}/zip`).then(res => res.arrayBuffer()).then(jszip.loadAsync).then(zip => {
249
- const regexp = new RegExp('(bin|trx)$');
250
- const firmware = Object.values(zip.files).find(file => regexp.test(file.name));
251
- return firmware.async('blob').then(blob => {
252
- const data = new FormData();
253
- data.append('file', blob, firmware.name);
254
- return this.router('upgrade.cgi', data, 'POST').then(res => res.text()).then(text => text.includes('showUpgradeBar'));
255
- });
256
- });
257
- };
258
- async getForks(page=1, result=[]) {
259
- const repo = await this.github().then(res => res.json());
260
- const source = repo.source?.full_name || repo.full_name;
261
- const res = await this.github(`https://api.github.com/repos/${source}/forks`, {
262
- page,
263
- per_page: 100
264
- });
265
- const forks = await res.json();
266
- result.push(...forks);
267
- if (res.headers.get('Link')?.includes('rel="next"'))
268
- return await this.getForks(page + 1, result);
269
- return result;
270
- };
271
- async find(id) {
272
- if (!id)
273
- id = await this.nvram('productid');
274
- console.log('get forks list');
275
- return this.getForks().then(async forks => {
276
- const result = [];
277
- for (const fork of forks) {
278
- try {
279
- console.log(`get artifacts: ${fork.full_name}`);
280
- const res = await this.github(`https://api.github.com/repos/${fork.full_name}/actions/artifacts`);
281
- const { artifacts } = await res.json();
282
- result.push(...artifacts.filter((artifact, i) => {
283
- return artifacts.findIndex(cur => artifact.workflow_run.head_branch === cur.workflow_run.head_branch) === i;
284
- }).filter(artifact => artifact.name.includes(id)).map(artifact => {
285
- return {
286
- repo: fork.full_name,
287
- branch: artifact.workflow_run.head_branch,
288
- active: !artifact.expired,
289
- created_at: artifact.created_at,
290
- name: artifact.name
291
- };
292
- }));
293
- } catch (e) {
294
- console.error(e);
295
- }
296
- }
297
- return result;
298
- });
299
- };
300
- };
301
- export default Padavan;
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- size=25000000;
4
- iterations=5;
5
- json=$(curl -s https://www.speedtest.net/api/js/servers?limit=1);
6
- url_upload=$(echo "${json}" | grep -o '"url":"[^"]*"' | sed 's/"url":"\([^"]*\)".*/\1/' | sed 's/\\//g');
7
- url_download=$(echo "${url_upload}" | sed 's@\(/\([^/]*\)/\([^/]*\)/\).*@\1download?size=@');
8
- speed_download=0;
9
- speed_upload=0;
10
- speed_upload_total=0;
11
- speed_download_total=0;
12
- available_space=$(df /tmp/ | awk 'NR==2 {print $4}');
13
- available_space=$((available_space * 1024 - 1048576));
14
- if [ -n "$available_space" ] && [ "$available_space" -lt "$size" ]; then
15
- size=$available_space
16
- fi
17
- for i in $(seq 1 $iterations); do
18
- speed_download=$(curl --connect-timeout 8 "${url_download}${size}" -w "%{speed_download}" -o /tmp/speedtest.bin -s);
19
- speed_upload=$(curl --connect-timeout 8 -F "file=@/tmp/speedtest.bin" "${url_upload}" -w "%{speed_upload}" -o /dev/null -s);
20
- speed_upload_total=$((speed_upload_total + speed_upload));
21
- speed_download_total=$((speed_download_total + speed_download));
22
- rm -f /tmp/speedtest.bin;
23
- done
24
- echo "download=$((speed_download_total / iterations))";
25
- echo "upload=$((speed_upload_total / iterations))";