artifacty 0.10.7 → 0.10.8
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/README.md +4 -0
- package/docs/central-team-deployment-design.md +13 -0
- package/docs/integrations.md +3 -1
- package/package.json +1 -1
- package/src/lib/service.js +2 -0
- package/src/lib/storage.js +51 -28
package/README.md
CHANGED
|
@@ -92,6 +92,10 @@ artifacty serve --host 10.0.0.50 --share-mode team --api-token "$ARTIFACTY_BOOTS
|
|
|
92
92
|
artifacty install all --mcp-url http://10.0.0.50:8787/mcp --api-token "$ARTIFACTY_PERSONAL_TOKEN"
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
When running the central server as a Linux `systemctl --user` service, enable
|
|
96
|
+
lingering for the service account so Artifacty stays up after logout:
|
|
97
|
+
`sudo loginctl enable-linger $USER`.
|
|
98
|
+
|
|
95
99
|
Administrators can create users individually at `/admin/users` or import them
|
|
96
100
|
from CSV. Use `email,name,role,password,password_reset_required` headers. If
|
|
97
101
|
`password` is empty, Artifacty generates a temporary password, shows it once in
|
|
@@ -134,6 +134,19 @@ proxy, keep `ARTIFACTY_ENABLE_REACT_RENDERER` disabled unless the team trusts
|
|
|
134
134
|
all artifact authors, and store `ARTIFACTY_HOME` on local server disk with
|
|
135
135
|
regular backups.
|
|
136
136
|
|
|
137
|
+
For Linux user-service deployments, enable systemd lingering for the service
|
|
138
|
+
account:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
sudo loginctl enable-linger artifacty
|
|
142
|
+
loginctl show-user artifacty -p Linger
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use the actual service account name. Without lingering, `systemctl --user`
|
|
146
|
+
services are tied to the user's login manager and can stop after logout, making
|
|
147
|
+
the central dashboard and `/mcp` endpoint unavailable until that user logs in
|
|
148
|
+
again. A system-level unit is another valid option for central servers.
|
|
149
|
+
|
|
137
150
|
The central server exposes `/mcp` only when explicitly enabled. This keeps the
|
|
138
151
|
local browser/API server behavior unchanged while making team MCP exposure an
|
|
139
152
|
intentional operating mode.
|
package/docs/integrations.md
CHANGED
|
@@ -393,9 +393,11 @@ The generated service runs `src/server.js` with explicit `--host` and `--home` a
|
|
|
393
393
|
Activation is still delegated to the OS service manager:
|
|
394
394
|
|
|
395
395
|
- macOS: `launchctl load ~/Library/LaunchAgents/com.artifacty.server.plist`
|
|
396
|
-
- Linux: `systemctl --user enable --now com.artifacty.server.service`
|
|
396
|
+
- Linux: `sudo loginctl enable-linger $USER`, then `systemctl --user enable --now com.artifacty.server.service`
|
|
397
397
|
- Windows: run the generated PowerShell script, which registers and starts the `ArtifactyServer` scheduled task.
|
|
398
398
|
|
|
399
|
+
On Linux central servers, user services stop when the user's systemd manager exits unless lingering is enabled. Verify durable operation with `loginctl show-user $USER -p Linger`; it should report `Linger=yes`. Without lingering, Artifacty may be unavailable after logout and appear to recover only after the user logs back in.
|
|
400
|
+
|
|
399
401
|
For background services, prefer a stable `ARTIFACTY_API_TOKEN` or `--api-token` value. `serve --generate-token` is intended for temporary interactive sessions; the parent CLI returns the generated token in JSON.
|
|
400
402
|
|
|
401
403
|
## Backup and Audit
|
package/package.json
CHANGED
package/src/lib/service.js
CHANGED
|
@@ -184,9 +184,11 @@ function definitionForPlatform(platform, options = {}, renderOnly) {
|
|
|
184
184
|
path: servicePath(platform, options),
|
|
185
185
|
content: createSystemdUserUnit(options),
|
|
186
186
|
nextSteps: [
|
|
187
|
+
"sudo loginctl enable-linger $USER",
|
|
187
188
|
"systemctl --user daemon-reload",
|
|
188
189
|
`systemctl --user enable --now ${path.basename(servicePath(platform, options))}`,
|
|
189
190
|
`systemctl --user status ${path.basename(servicePath(platform, options))}`,
|
|
191
|
+
"loginctl show-user $USER -p Linger",
|
|
190
192
|
`journalctl --user -u ${path.basename(servicePath(platform, options))} -f`,
|
|
191
193
|
`systemctl --user disable --now ${path.basename(servicePath(platform, options))}`
|
|
192
194
|
],
|
package/src/lib/storage.js
CHANGED
|
@@ -10,6 +10,7 @@ import { assertNoSecrets, securityConfig } from "./security.js";
|
|
|
10
10
|
export const STORE_VERSION = 4;
|
|
11
11
|
export const ARTIFACT_SCHEMA_VERSION = 1;
|
|
12
12
|
export const MAX_ARTIFACT_BYTES = 16 * 1024 * 1024;
|
|
13
|
+
const SOURCE_AGENT_NORMALIZATION_VERSION = "1";
|
|
13
14
|
export const USER_ROLES = ["admin", "user"];
|
|
14
15
|
export const ARTIFACT_FORMATS = [
|
|
15
16
|
"html",
|
|
@@ -123,6 +124,11 @@ export async function writeIndex(store, index) {
|
|
|
123
124
|
insertVersionRecord(db, artifact.id, version);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
normalizeStoredSourceAgents(db, store, {
|
|
128
|
+
force: true,
|
|
129
|
+
inTransaction: true,
|
|
130
|
+
rebuildSearch: false
|
|
131
|
+
});
|
|
126
132
|
rebuildSearchIndexInDb(db, store);
|
|
127
133
|
});
|
|
128
134
|
} finally {
|
|
@@ -1502,9 +1508,26 @@ function migrateJsonIndex(db, store) {
|
|
|
1502
1508
|
});
|
|
1503
1509
|
}
|
|
1504
1510
|
|
|
1511
|
+
function metaValue(db, key) {
|
|
1512
|
+
return db.prepare("SELECT value FROM meta WHERE key = ?").get(key)?.value || "";
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1505
1515
|
const INFERABLE_SOURCE_AGENTS = new Set(["claude", "codex", "copilot", "cursor", "gemini"]);
|
|
1506
1516
|
|
|
1507
|
-
function normalizeStoredSourceAgents(db, store) {
|
|
1517
|
+
function normalizeStoredSourceAgents(db, store, options = {}) {
|
|
1518
|
+
if (!options.force && metaValue(db, "source_agent_normalized_version") === SOURCE_AGENT_NORMALIZATION_VERSION) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const run = () => normalizeStoredSourceAgentsInDb(db, store, options);
|
|
1523
|
+
if (options.inTransaction) {
|
|
1524
|
+
run();
|
|
1525
|
+
} else {
|
|
1526
|
+
transaction(db, run);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function normalizeStoredSourceAgentsInDb(db, store, options = {}) {
|
|
1508
1531
|
const rows = db.prepare(`
|
|
1509
1532
|
SELECT id, source_agent, tags_json
|
|
1510
1533
|
FROM artifacts
|
|
@@ -1512,37 +1535,37 @@ function normalizeStoredSourceAgents(db, store) {
|
|
|
1512
1535
|
const updateArtifact = db.prepare("UPDATE artifacts SET source_agent = ?, tags_json = ? WHERE id = ?");
|
|
1513
1536
|
let changed = false;
|
|
1514
1537
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
changed = true;
|
|
1526
|
-
}
|
|
1538
|
+
for (const row of rows) {
|
|
1539
|
+
const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "unknown" });
|
|
1540
|
+
const inferred = isUnknownSourceAgent(normalized)
|
|
1541
|
+
? inferStoredSourceAgent(db, row)
|
|
1542
|
+
: normalized;
|
|
1543
|
+
const nextSourceAgent = inferred || normalized;
|
|
1544
|
+
const nextTags = normalizeStoredSourceTags(row.tags_json, nextSourceAgent);
|
|
1545
|
+
if (nextSourceAgent !== row.source_agent || nextTags !== row.tags_json) {
|
|
1546
|
+
updateArtifact.run(nextSourceAgent, nextTags, row.id);
|
|
1547
|
+
changed = true;
|
|
1527
1548
|
}
|
|
1549
|
+
}
|
|
1528
1550
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1551
|
+
const auditRows = db.prepare(`
|
|
1552
|
+
SELECT id, source_agent
|
|
1553
|
+
FROM audit_log
|
|
1554
|
+
WHERE source_agent IS NOT NULL
|
|
1555
|
+
AND TRIM(source_agent) != ''
|
|
1556
|
+
`).all();
|
|
1557
|
+
const updateAudit = db.prepare("UPDATE audit_log SET source_agent = ? WHERE id = ?");
|
|
1558
|
+
for (const row of auditRows) {
|
|
1559
|
+
const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "" });
|
|
1560
|
+
if (normalized && normalized !== row.source_agent) {
|
|
1561
|
+
updateAudit.run(normalized, row.id);
|
|
1562
|
+
changed = true;
|
|
1542
1563
|
}
|
|
1543
|
-
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('source_agent_normalized_version', ?)").run(SOURCE_AGENT_NORMALIZATION_VERSION);
|
|
1544
1567
|
|
|
1545
|
-
if (changed) {
|
|
1568
|
+
if (changed && options.rebuildSearch !== false) {
|
|
1546
1569
|
rebuildSearchIndexInDb(db, store);
|
|
1547
1570
|
}
|
|
1548
1571
|
}
|