3 Commits

Author SHA1 Message Date
d803a58ab8 Add flag to persist builds
Some checks failed
App Image CI / Build app image (push) Successful in -1m2s
NPM Audit Check / Check NPM audit (push) Failing after -2m7s
Docker Image CI / Build build images (arch) (push) Successful in 47s
Docker Image CI / Build build images (artix) (push) Successful in -27s
2025-11-26 18:20:44 -05:00
7bd8bf4d85 adjust arch image workflow trigger
All checks were successful
App Image CI / Build app image (push) Successful in -1m1s
NPM Audit Check / Check NPM audit (push) Successful in -2m7s
Docker Image CI / Build build images (artix) (push) Successful in -36s
Docker Image CI / Build build images (arch) (push) Successful in 52s
2025-11-16 00:57:37 -05:00
a7e72f273f default to all repos
All checks were successful
App Image CI / Build app image (push) Successful in -1m20s
NPM Audit Check / Check NPM audit (push) Successful in -2m9s
2025-11-16 00:43:36 -05:00
12 changed files with 107 additions and 64 deletions

View File

@@ -5,7 +5,7 @@ on:
branches: [ master ]
push:
branches: [ master ]
paths: [ docker/*, docker/scripts/* ]
paths: [ docker/** ]
pull_request:
branches: [ master ]
paths: [ docker/* ]

View File

@@ -3,7 +3,7 @@ use strict;
use warnings;
my $dep = $ENV{DEP} // 'stable';
my $tier = $ENV{TIER} // '1';
my $tier = $ENV{TIER} // '2';
#region header
print <<'EOHEADER';

View File

@@ -3,7 +3,7 @@ use strict;
use warnings;
my $dep = $ENV{DEP} // 'stable';
my $tier = $ENV{TIER} // '1';
my $tier = $ENV{TIER} // '2';
#region header
print <<'EOHEADER';

78
package-lock.json generated
View File

@@ -1,20 +1,20 @@
{
"name": "archery",
"version": "0.2.4",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "archery",
"version": "0.2.4",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"body-parser": "^2.2.0",
"body-parser": "^2.2.1",
"ejs": "3.1.10",
"express": "^5.1.0",
"express-session": "1.18.2",
"express-ws": "^5.0.2",
"ky": "1.10.0",
"ky": "1.14.0",
"passport": "0.7.0",
"passport-openidconnect": "0.1.2",
"pg": "^8.16.3",
@@ -23,9 +23,9 @@
"sqids": "0.3.0"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/express": "^5.0.5",
"@types/express-session": "^1.18.2",
"@types/express-ws": "3.0.5",
"@types/express-ws": "3.0.6",
"@types/node": "^24.10.1",
"@types/passport": "1.0.17",
"@types/passport-openidconnect": "0.1.3",
@@ -376,15 +376,15 @@
}
},
"node_modules/@types/express": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz",
"integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "*"
"@types/serve-static": "^1"
}
},
"node_modules/@types/express-serve-static-core": {
@@ -411,9 +411,9 @@
}
},
"node_modules/@types/express-ws": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.5.tgz",
"integrity": "sha512-lbWMjoHrm/v85j81UCmb/GNZFO3genxRYBW1Ob7rjRI+zxUBR+4tcFuOpKKsYQ1LYTYiy3356epLeYi/5zxUwA==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.6.tgz",
"integrity": "sha512-6ZDt+tMEQgM4RC1sMX1fIO7kHQkfUDlWfxoPddXUeeDjmc+Yt/fCzqXfp8rFahNr5eIxdomrWphLEWDkB2q3UQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -589,23 +589,27 @@
"license": "MIT"
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
"integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
@@ -934,7 +938,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
@@ -1239,15 +1242,19 @@
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/immutable": {
@@ -1342,9 +1349,9 @@
}
},
"node_modules/ky": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/ky/-/ky-1.10.0.tgz",
"integrity": "sha512-YRPCzHEWZffbfvmRrfwa+5nwBHwZuYiTrfDX0wuhGBPV0pA/zCqcOq93MDssON/baIkpYbvehIX5aLpMxrRhaA==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/ky/-/ky-1.14.0.tgz",
"integrity": "sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1608,7 +1615,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
@@ -1819,22 +1825,6 @@
"node": ">= 0.10"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",

View File

@@ -1,31 +1,31 @@
{
"name": "archery",
"version": "0.2.4",
"version": "0.3.0",
"description": "Build Arch packages through a web interface",
"keywords": [
"docker",
"arch",
"artix"
],
"homepage": "https://github.com/CorySanin/archery#readme",
"homepage": "https://git.sanin.dev/corysanin/archery#readme",
"bugs": {
"url": "https://github.com/CorySanin/archery/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/CorySanin/archery.git"
"url": "git+https://git.sanin.dev/corysanin/archery.git"
},
"license": "MIT",
"author": "Cory Sanin",
"type": "module",
"main": "index.ts",
"dependencies": {
"body-parser": "^2.2.0",
"body-parser": "^2.2.1",
"ejs": "3.1.10",
"express": "^5.1.0",
"express-session": "1.18.2",
"express-ws": "^5.0.2",
"ky": "1.10.0",
"ky": "1.14.0",
"passport": "0.7.0",
"passport-openidconnect": "0.1.2",
"pg": "^8.16.3",
@@ -34,9 +34,9 @@
"sqids": "0.3.0"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/express": "^5.0.5",
"@types/express-session": "^1.18.2",
"@types/express-ws": "3.0.5",
"@types/express-ws": "3.0.6",
"@types/node": "^24.10.1",
"@types/passport": "1.0.17",
"@types/passport-openidconnect": "0.1.3",

View File

@@ -1,7 +0,0 @@
document.addEventListener('DOMContentLoaded', function () {
for (let btn of document.getElementsByClassName('copybtn')) {
btn.addEventListener('click', e => {
navigator.clipboard.writeText(e.target.previousElementSibling.innerText);
});
}
});

23
scripts/form.js Normal file
View File

@@ -0,0 +1,23 @@
document.addEventListener('DOMContentLoaded', function () {
for (let btn of document.getElementsByClassName('copybtn')) {
btn.addEventListener('click', e => {
navigator.clipboard.writeText(e.target.previousElementSibling.innerText);
});
}
document.getElementById('persistChk')?.addEventListener('change', async e => {
const sqid = document.getElementById('sqid').textContent;
const persist = !!e.target?.checked;
const resp = await fetch(`/build/${sqid}/persist`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ persist })
});
if (!resp.ok) {
this.location.reload();
}
});
});

View File

@@ -30,6 +30,7 @@ interface Build {
pid?: number;
sqid?: string;
uuid: string;
persist: boolean;
}
interface User {
@@ -128,6 +129,10 @@ class DB extends Store {
uuid: {
type: DataTypes.STRING,
unique: true
},
persist: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
});
@@ -262,6 +267,16 @@ class DB extends Store {
});
}
public async persist(id: number, persist: boolean = true): Promise<void> {
await this.build.update({
persist
}, {
where: {
id
}
});
}
public async appendLog(buildId: number, type: LogType, chunk: string): Promise<void> {
await this.logChunk.create({
buildId,
@@ -366,7 +381,8 @@ class DB extends Store {
public async cleanup(): Promise<void> {
await this.build.destroy({
where: {
startTime: { [Op.lt]: new Date(Date.now() - MONTH * 6) }
startTime: { [Op.lt]: new Date(Date.now() - MONTH * 6) },
persist: { [Op.eq]: false }
},
force: true
});

View File

@@ -297,6 +297,17 @@ class Web {
res.redirect(`/build/${req.params.id}/`);
});
app.post('/build/:id/persist', async (req, res) => {
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
const persist = !! req?.body?.persist;
if (!build) {
res.sendStatus(404);
return;
}
await this.db.persist(build.id, persist);
res.sendStatus(200);
})
this._webserver = this.app.listen(this.port, () => console.log(`archery is running on port ${this.port}`));
}

View File

@@ -268,6 +268,10 @@ a {
}
}
.display-none {
display: none;
}
#followCheckmarkContainer {
display: inline-block;
position: fixed;

View File

@@ -10,6 +10,9 @@
<div class="content">
<h1>Build #<%= build.id %></h1>
<h2 id="buildStatus"><%= build.status %></h2>
<% if (!public) { %>
<h3 id="sqid" class="display-none"><%= build.sqid %></h3>
<% } %>
<div class="overflow-x">
<div class="grid-2col">
<label>Repo</label> <span><span><%= build.repo %></span> <button class="copybtn">Copy</button></span>
@@ -21,6 +24,9 @@
<% if (build.userId && build.userId !== '-1') { %>
<label>Triggered by</label> <span><%= build.user.displayName %> (<%= build.user.username %>)</span>
<% } %>
<% if (!public) { %>
<label>Persist</label> <span><input id="persistChk" type="checkbox" <% if (build.persist) { %>checked<% } %>/></span>
<% } %>
<% if (locals.shareable) { %>
<label>Shareable link</label> <span><a href="<%= shareable %>"><%= shareable %></a> <button class="copybtn">Copy</button></span>
<% } %>
@@ -47,7 +53,7 @@
</div>
<%- include("footer", locals) %>
<script src="/assets/js/timezone.js?v1" nonce="<%= cspNonce %>"></script>
<script src="/assets/js/copy.js?v1" nonce="<%= cspNonce %>"></script>
<script src="/assets/js/form.js?v1" nonce="<%= cspNonce %>"></script>
<% if (!ended) { %>
<script src="/assets/js/build.js?v3" nonce="<%= cspNonce %>"></script>
<% } %>

View File

@@ -14,7 +14,7 @@
<link rel="canonical" href="<%= page.canonical%>"/>
<% } %>
<link rel="shortcut icon" href="/assets/svg/favicon.svg">
<link rel="stylesheet" href="/assets/css/styles.css?v4">
<link rel="stylesheet" href="/assets/css/styles.css?v5">
<script nonce="<%= cspNonce %>">
document.addEventListener("DOMContentLoaded", function() {
document.body.classList.remove('preload');