jskelet 0.6.10 → 0.6.11
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/CHANGELOG.md +3 -2
- package/docs/07-yapilandirma.md +7 -7
- package/docs/08-build.md +2 -2
- package/docs/10-dagitim.md +1 -1
- package/docs/en/07-configuration.md +8 -7
- package/docs/en/08-build.md +2 -2
- package/docs/en/10-deployment.md +1 -1
- package/package.json +1 -1
- package/src/build/tasks/assets-upload.mjs +14 -11
- package/src/config/index.js +6 -26
- package/src/server/logs/pipeline.js +4 -3
- package/src/server/logs/s3-credentials.js +195 -0
- package/src/server/logs/s3-put.js +2 -3
- package/types/config/index.d.ts +2 -13
- package/types/server/logs/s3-credentials.d.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,8 +16,9 @@ one is listed under a **Breaking** heading.
|
|
|
16
16
|
Production `jskelet build` PutObjects `public/assets/` (and optional
|
|
17
17
|
`include` dirs) after precompress. Only credentials are read from env
|
|
18
18
|
(`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, then
|
|
19
|
-
`JSKELET_S3_
|
|
20
|
-
when off it logs a
|
|
19
|
+
`JSKELET_S3_*`, then ECS task-role / EC2 instance-profile metadata). The
|
|
20
|
+
Assets upload step always appears on production builds; when off it logs a
|
|
21
|
+
one-line reason (`skipped (no build.assets.upload.bucket)`,
|
|
21
22
|
`skipped (credentials missing)`, …). A URL in `upload.bucket` is rejected
|
|
22
23
|
with a warning (CDN origin belongs in `prefix`). Watch mode never uploads.
|
|
23
24
|
`.br` / `.gz` siblings are skipped.
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -638,13 +638,13 @@ değişmez** — asset'ler origin'den servis edilir.
|
|
|
638
638
|
| `assets.upload.include` | `string[]` | `["assets"]` | `public/` altı dizinler (`assets`, isteğe `fonts`, …). |
|
|
639
639
|
| `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
|
|
640
640
|
|
|
641
|
-
**
|
|
642
|
-
`AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (yoksa `JSKELET_S3_*`)
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
yok. `.br` / `.gz` yüklenmez.
|
|
647
|
-
[10-dagitim.md](./10-dagitim.md).
|
|
641
|
+
**Credential config'e yazılmaz.** Zincir: `AWS_ACCESS_KEY_ID` /
|
|
642
|
+
`AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (yoksa `JSKELET_S3_*`) → ECS/Fargate
|
|
643
|
+
task role (`AWS_CONTAINER_CREDENTIALS_*`) → EC2 instance profile (IMDSv2).
|
|
644
|
+
Prefix, bucket, region vb. config'te yazılır. Task role varken env key şart
|
|
645
|
+
değil. Production build'de Assets upload adımı her zaman görünür; kapalıysa
|
|
646
|
+
`skipped (…)` ile sebep basılır. Watch'ta adım yok. `.br` / `.gz` yüklenmez.
|
|
647
|
+
Ayrıntı: [08-build.md](./08-build.md), [10-dagitim.md](./10-dagitim.md).
|
|
648
648
|
|
|
649
649
|
```js
|
|
650
650
|
build: {
|
package/docs/08-build.md
CHANGED
|
@@ -84,8 +84,8 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
|
84
84
|
|
|
85
85
|
### CDN prefix ve S3 upload
|
|
86
86
|
|
|
87
|
-
Varsayılan kapalıdır. Prefix / bucket / keyPrefix config'te;
|
|
88
|
-
|
|
87
|
+
Varsayılan kapalıdır. Prefix / bucket / keyPrefix config'te; credential
|
|
88
|
+
env → ECS task role → EC2 IMDS zincirinden. Açılınca:
|
|
89
89
|
|
|
90
90
|
1. Production build `public/assets/` (ve `include` listesindeki diğer dizinler)
|
|
91
91
|
altındaki dosyaları S3'e PutObject ile kopyalar.
|
package/docs/10-dagitim.md
CHANGED
|
@@ -651,13 +651,14 @@ changes** — assets stay on the origin.
|
|
|
651
651
|
| `assets.upload.include` | `string[]` | `["assets"]` | Directories under `public/` (`assets`, optionally `fonts`, …). |
|
|
652
652
|
| `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
|
|
653
653
|
|
|
654
|
-
**
|
|
655
|
-
`AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (else `JSKELET_S3_*`)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
No upload on watch passes. `.br` / `.gz` siblings are
|
|
660
|
-
[08-build.md](./08-build.md),
|
|
654
|
+
**Credentials are not written in the config.** Chain: `AWS_ACCESS_KEY_ID` /
|
|
655
|
+
`AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (else `JSKELET_S3_*`) → ECS/Fargate
|
|
656
|
+
task role (`AWS_CONTAINER_CREDENTIALS_*`) → EC2 instance profile (IMDSv2).
|
|
657
|
+
Prefix, bucket, region, etc. live in the config. With a task role, env keys are
|
|
658
|
+
not required. On a production build the Assets upload step always appears; when
|
|
659
|
+
off it logs `skipped (…)`. No upload on watch passes. `.br` / `.gz` siblings are
|
|
660
|
+
not uploaded. Details: [08-build.md](./08-build.md),
|
|
661
|
+
[10-deployment.md](./10-deployment.md).
|
|
661
662
|
|
|
662
663
|
```js
|
|
663
664
|
build: {
|
package/docs/en/08-build.md
CHANGED
|
@@ -87,8 +87,8 @@ They are passed to templates automatically; in server code,
|
|
|
87
87
|
|
|
88
88
|
### CDN prefix and S3 upload
|
|
89
89
|
|
|
90
|
-
Off by default. Prefix / bucket / keyPrefix come from config;
|
|
91
|
-
|
|
90
|
+
Off by default. Prefix / bucket / keyPrefix come from config; credentials
|
|
91
|
+
resolve from env → ECS task role → EC2 IMDS. When enabled:
|
|
92
92
|
|
|
93
93
|
1. A production build PutObjects files under `public/assets/` (and any other
|
|
94
94
|
directories in `include`) to S3.
|
package/docs/en/10-deployment.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.11",
|
|
4
4
|
"description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Production build sonunda hash'li varlıkları S3'e PutObject ile kopyalar.
|
|
3
3
|
*
|
|
4
|
-
* Watch turunda çalışmaz (çağıran sorumlu). Credential env
|
|
5
|
-
*
|
|
6
|
-
* kapalıysa adım yine görünür: `skipped (…)` ile sebep basılır
|
|
4
|
+
* Watch turunda çalışmaz (çağıran sorumlu). Credential: env (`AWS_*` /
|
|
5
|
+
* `JSKELET_S3_*`) → ECS task role → EC2 instance profile. Bucket yoksa veya
|
|
6
|
+
* upload kapalıysa adım yine görünür: `skipped (…)` ile sebep basılır.
|
|
7
|
+
* Credential yalnızca env+metadata zinciri boşsa skip; task role varken
|
|
8
|
+
* env key şart değil.
|
|
7
9
|
*
|
|
8
10
|
* `.br` / `.gz` kardeşleri yüklenmez: edge kendi sıkıştırmasını yapsın;
|
|
9
11
|
* origin precompress Express için kalır.
|
|
10
12
|
*/
|
|
11
13
|
import fs from "node:fs";
|
|
12
14
|
import path from "node:path";
|
|
13
|
-
import {
|
|
15
|
+
import { resolveS3Credentials } from "../../server/logs/s3-credentials.js";
|
|
14
16
|
import { putObject } from "../../server/logs/s3-put.js";
|
|
15
17
|
import * as log from "../../log.mjs";
|
|
16
18
|
import { paths } from "../paths.mjs";
|
|
@@ -88,7 +90,7 @@ function resolveIncludeDir(name) {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
|
-
*
|
|
93
|
+
* Config kapısı — credential burada bakılmaz (task role async).
|
|
92
94
|
*
|
|
93
95
|
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
94
96
|
* @returns {string | null}
|
|
@@ -105,14 +107,13 @@ export function assetsUploadSkipReason(config) {
|
|
|
105
107
|
|
|
106
108
|
if (!upload.bucket) return "no build.assets.upload.bucket";
|
|
107
109
|
|
|
108
|
-
if (!readS3CredentialsFromEnv()) return "credentials missing";
|
|
109
|
-
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
115
|
-
* @param {{ put?: typeof putObject
|
|
115
|
+
* @param {{ put?: typeof putObject,
|
|
116
|
+
* resolveCredentials?: typeof resolveS3Credentials }} [options]
|
|
116
117
|
* @returns {Promise<void>}
|
|
117
118
|
*/
|
|
118
119
|
export async function uploadAssets(config, options = {}) {
|
|
@@ -123,15 +124,17 @@ export async function uploadAssets(config, options = {}) {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
const upload = config.build.assets.upload;
|
|
126
|
-
const
|
|
127
|
-
|
|
127
|
+
const resolve = options.resolveCredentials ?? resolveS3Credentials;
|
|
128
|
+
const credentials = await resolve();
|
|
128
129
|
if (!upload.bucket || !credentials) {
|
|
129
130
|
log.detail("skipped (credentials missing)");
|
|
130
131
|
return;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
const put = options.put ?? putObject;
|
|
134
|
-
|
|
135
|
+
// Normalize bazen atlanır (test / elle config); önek `/` ile bitsin.
|
|
136
|
+
let keyPrefix = (upload.keyPrefix || "").replace(/^\/+/, "");
|
|
137
|
+
if (keyPrefix && !keyPrefix.endsWith("/")) keyPrefix += "/";
|
|
135
138
|
|
|
136
139
|
/** @type {{ file: string, key: string }[]} */
|
|
137
140
|
const jobs = [];
|
package/src/config/index.js
CHANGED
|
@@ -33,6 +33,10 @@ import path from "node:path";
|
|
|
33
33
|
import process from "node:process";
|
|
34
34
|
import { pathToFileURL } from "node:url";
|
|
35
35
|
import { compilePattern, matchPattern } from "./pattern.js";
|
|
36
|
+
import {
|
|
37
|
+
readS3CredentialsFromEnv,
|
|
38
|
+
resolveS3Credentials,
|
|
39
|
+
} from "../server/logs/s3-credentials.js";
|
|
36
40
|
import {
|
|
37
41
|
DEFAULT_ADMIN,
|
|
38
42
|
DEFAULT_AUTH,
|
|
@@ -64,6 +68,8 @@ import {
|
|
|
64
68
|
DEFAULT_UPSTREAM_LIMIT,
|
|
65
69
|
} from "./defaults.js";
|
|
66
70
|
|
|
71
|
+
export { readS3CredentialsFromEnv, resolveS3Credentials };
|
|
72
|
+
|
|
67
73
|
/** Framework paketinin kökü — kendi şablonlarına ve varlıklarına erişir. */
|
|
68
74
|
export const FRAMEWORK_ROOT = path.resolve(import.meta.dirname, "..", "..");
|
|
69
75
|
|
|
@@ -527,32 +533,6 @@ function envText(value) {
|
|
|
527
533
|
return typeof value === "string" && value ? value : null;
|
|
528
534
|
}
|
|
529
535
|
|
|
530
|
-
/**
|
|
531
|
-
* S3 imza credential'ları. AWS deploy'da standart env önce gelir; R2/MinIO
|
|
532
|
-
* için `JSKELET_S3_*` yedek. IAM instance/task role (IMDS) desteklenmez —
|
|
533
|
-
* CI'da `configure-aws-credentials` gibi araçlar zaten `AWS_*` basar.
|
|
534
|
-
*
|
|
535
|
-
* @returns {{ accessKeyId: string, secretAccessKey: string,
|
|
536
|
-
* sessionToken: string | null } | null}
|
|
537
|
-
*/
|
|
538
|
-
export function readS3CredentialsFromEnv() {
|
|
539
|
-
const accessKeyId =
|
|
540
|
-
envText(process.env.AWS_ACCESS_KEY_ID) ??
|
|
541
|
-
envText(process.env.JSKELET_S3_ACCESS_KEY_ID);
|
|
542
|
-
const secretAccessKey =
|
|
543
|
-
envText(process.env.AWS_SECRET_ACCESS_KEY) ??
|
|
544
|
-
envText(process.env.JSKELET_S3_SECRET_ACCESS_KEY) ??
|
|
545
|
-
envText(process.env.JSKELET_S3_ACCESS_SECRET);
|
|
546
|
-
if (!accessKeyId || !secretAccessKey) return null;
|
|
547
|
-
return {
|
|
548
|
-
accessKeyId,
|
|
549
|
-
secretAccessKey,
|
|
550
|
-
sessionToken:
|
|
551
|
-
envText(process.env.AWS_SESSION_TOKEN) ??
|
|
552
|
-
envText(process.env.JSKELET_S3_SESSION_TOKEN),
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
536
|
/**
|
|
557
537
|
* Log hedefi. Öncelik: `JSKELET_LOG_BUCKET` → `JSKELET_S3_BUCKET`
|
|
558
538
|
* (+ isteğe bağlı `JSKELET_S3_KEY_PREFIX`).
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import * as log from "../../log.mjs";
|
|
9
|
-
import { readS3CredentialsFromEnv } from "../../config/index.js";
|
|
10
9
|
import { createFileSink } from "./file-sink.js";
|
|
11
10
|
import { createS3Sink } from "./s3-sink.js";
|
|
11
|
+
import { resolveS3Credentials } from "./s3-credentials.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {import('../../config/index.js').LogsConfig} LogsConfig
|
|
@@ -73,14 +73,15 @@ export async function configureLogs(config) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (logs.s3.enabled) {
|
|
76
|
-
const credentials =
|
|
76
|
+
const credentials = await resolveS3Credentials();
|
|
77
77
|
if (!logs.s3.bucket) {
|
|
78
78
|
console.warn(
|
|
79
79
|
"[logs] s3.enabled but no bucket (set JSKELET_LOG_BUCKET, JSKELET_S3_BUCKET or logs.s3.bucket)",
|
|
80
80
|
);
|
|
81
81
|
} else if (!credentials) {
|
|
82
82
|
console.warn(
|
|
83
|
-
"[logs] s3.enabled but credentials missing — set AWS_ACCESS_KEY_ID/
|
|
83
|
+
"[logs] s3.enabled but credentials missing — set AWS_ACCESS_KEY_ID/" +
|
|
84
|
+
"AWS_SECRET_ACCESS_KEY or JSKELET_S3_*, or run with an ECS/EC2 IAM role",
|
|
84
85
|
);
|
|
85
86
|
} else {
|
|
86
87
|
const region = logs.s3.region || "auto";
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3 imza credential zinciri — SDK yok.
|
|
3
|
+
*
|
|
4
|
+
* 1. Env: `AWS_*` sonra `JSKELET_S3_*`
|
|
5
|
+
* 2. ECS/Fargate task role: `AWS_CONTAINER_CREDENTIALS_*`
|
|
6
|
+
* 3. EC2 instance profile: IMDSv2 (kısa timeout; local'de asılı kalmaz)
|
|
7
|
+
*
|
|
8
|
+
* EKS IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`) bilinçli olarak yok — STS çağrısı
|
|
9
|
+
* ayrı yüzey; CI genelde zaten `AWS_*` basar.
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {{ accessKeyId: string, secretAccessKey: string,
|
|
16
|
+
* sessionToken: string | null }} AwsCredentials
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const METADATA_TIMEOUT_MS = 400;
|
|
20
|
+
const ECS_METADATA_HOST = "http://169.254.170.2";
|
|
21
|
+
const EC2_METADATA_HOST = "http://169.254.169.254";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {unknown} value
|
|
25
|
+
* @returns {string | null}
|
|
26
|
+
*/
|
|
27
|
+
function envText(value) {
|
|
28
|
+
return typeof value === "string" && value ? value : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @returns {AwsCredentials | null}
|
|
33
|
+
*/
|
|
34
|
+
export function readS3CredentialsFromEnv() {
|
|
35
|
+
const accessKeyId =
|
|
36
|
+
envText(process.env.AWS_ACCESS_KEY_ID) ??
|
|
37
|
+
envText(process.env.JSKELET_S3_ACCESS_KEY_ID);
|
|
38
|
+
const secretAccessKey =
|
|
39
|
+
envText(process.env.AWS_SECRET_ACCESS_KEY) ??
|
|
40
|
+
envText(process.env.JSKELET_S3_SECRET_ACCESS_KEY) ??
|
|
41
|
+
envText(process.env.JSKELET_S3_ACCESS_SECRET);
|
|
42
|
+
if (!accessKeyId || !secretAccessKey) return null;
|
|
43
|
+
return {
|
|
44
|
+
accessKeyId,
|
|
45
|
+
secretAccessKey,
|
|
46
|
+
sessionToken:
|
|
47
|
+
envText(process.env.AWS_SESSION_TOKEN) ??
|
|
48
|
+
envText(process.env.JSKELET_S3_SESSION_TOKEN),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Record<string, unknown>} data
|
|
54
|
+
* @returns {AwsCredentials | null}
|
|
55
|
+
*/
|
|
56
|
+
function fromAwsCredentialJson(data) {
|
|
57
|
+
const accessKeyId =
|
|
58
|
+
typeof data.AccessKeyId === "string" ? data.AccessKeyId : null;
|
|
59
|
+
const secretAccessKey =
|
|
60
|
+
typeof data.SecretAccessKey === "string" ? data.SecretAccessKey : null;
|
|
61
|
+
if (!accessKeyId || !secretAccessKey) return null;
|
|
62
|
+
const token =
|
|
63
|
+
(typeof data.Token === "string" && data.Token) ||
|
|
64
|
+
(typeof data.SessionToken === "string" && data.SessionToken) ||
|
|
65
|
+
null;
|
|
66
|
+
return { accessKeyId, secretAccessKey, sessionToken: token };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} url
|
|
71
|
+
* @param {RequestInit} [init]
|
|
72
|
+
* @param {number} [timeoutMs]
|
|
73
|
+
* @returns {Promise<unknown | null>}
|
|
74
|
+
*/
|
|
75
|
+
async function fetchJson(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
80
|
+
if (!response.ok) return null;
|
|
81
|
+
return await response.json();
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} url
|
|
91
|
+
* @param {RequestInit} [init]
|
|
92
|
+
* @param {number} [timeoutMs]
|
|
93
|
+
* @returns {Promise<string | null>}
|
|
94
|
+
*/
|
|
95
|
+
async function fetchText(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
100
|
+
if (!response.ok) return null;
|
|
101
|
+
return (await response.text()).trim() || null;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
} finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* ECS task role / container credential provider.
|
|
111
|
+
*
|
|
112
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
113
|
+
*/
|
|
114
|
+
export async function readS3CredentialsFromEcs() {
|
|
115
|
+
const relative = envText(process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
|
|
116
|
+
const full = envText(process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI);
|
|
117
|
+
if (!relative && !full) return null;
|
|
118
|
+
|
|
119
|
+
const url = full ?? `${ECS_METADATA_HOST}${relative}`;
|
|
120
|
+
|
|
121
|
+
/** @type {Record<string, string>} */
|
|
122
|
+
const headers = {};
|
|
123
|
+
const token =
|
|
124
|
+
envText(process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN) ??
|
|
125
|
+
readTokenFile(process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE);
|
|
126
|
+
if (token) headers.Authorization = token;
|
|
127
|
+
|
|
128
|
+
const data = await fetchJson(url, { headers });
|
|
129
|
+
if (!data || typeof data !== "object") return null;
|
|
130
|
+
return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {unknown} pathValue
|
|
135
|
+
* @returns {string | null}
|
|
136
|
+
*/
|
|
137
|
+
function readTokenFile(pathValue) {
|
|
138
|
+
const file = envText(pathValue);
|
|
139
|
+
if (!file) return null;
|
|
140
|
+
try {
|
|
141
|
+
const text = fs.readFileSync(file, "utf8").trim();
|
|
142
|
+
return text || null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* EC2 instance profile via IMDSv2 (IMDSv1'e düşmez).
|
|
150
|
+
*
|
|
151
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
152
|
+
*/
|
|
153
|
+
export async function readS3CredentialsFromImds() {
|
|
154
|
+
const token = await fetchText(
|
|
155
|
+
`${EC2_METADATA_HOST}/latest/api/token`,
|
|
156
|
+
{
|
|
157
|
+
method: "PUT",
|
|
158
|
+
headers: { "X-aws-ec2-metadata-token-ttl-seconds": "21600" },
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
if (!token) return null;
|
|
162
|
+
|
|
163
|
+
const headers = { "X-aws-ec2-metadata-token": token };
|
|
164
|
+
const roleName = await fetchText(
|
|
165
|
+
`${EC2_METADATA_HOST}/latest/meta-data/iam/security-credentials/`,
|
|
166
|
+
{ headers },
|
|
167
|
+
);
|
|
168
|
+
if (!roleName) return null;
|
|
169
|
+
|
|
170
|
+
// Birden fazla satır olursa ilki.
|
|
171
|
+
const role = roleName.split("\n")[0]?.trim();
|
|
172
|
+
if (!role || role.includes("..") || role.includes("/")) return null;
|
|
173
|
+
|
|
174
|
+
const data = await fetchJson(
|
|
175
|
+
`${EC2_METADATA_HOST}/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`,
|
|
176
|
+
{ headers },
|
|
177
|
+
);
|
|
178
|
+
if (!data || typeof data !== "object") return null;
|
|
179
|
+
return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Env → ECS task role → EC2 instance profile.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
186
|
+
*/
|
|
187
|
+
export async function resolveS3Credentials() {
|
|
188
|
+
const fromEnv = readS3CredentialsFromEnv();
|
|
189
|
+
if (fromEnv) return fromEnv;
|
|
190
|
+
|
|
191
|
+
const fromEcs = await readS3CredentialsFromEcs();
|
|
192
|
+
if (fromEcs) return fromEcs;
|
|
193
|
+
|
|
194
|
+
return readS3CredentialsFromImds();
|
|
195
|
+
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Minimal S3 PutObject — yalnızca SigV4 + `fetch`.
|
|
3
3
|
*
|
|
4
4
|
* `@aws-sdk/client-s3` taşınmıyor: tek operasyon için SDK'nın maliyeti ve
|
|
5
|
-
* peer yüzeyi gereksiz. Credential
|
|
6
|
-
*
|
|
7
|
-
* `configure-aws-credentials` gibi araçlar key basar.
|
|
5
|
+
* peer yüzeyi gereksiz. Credential `resolveS3Credentials` ile gelir
|
|
6
|
+
* (env → ECS task role → EC2 IMDS).
|
|
8
7
|
*/
|
|
9
8
|
import crypto from "node:crypto";
|
|
10
9
|
|
package/types/config/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readS3CredentialsFromEnv, resolveS3Credentials } from "../server/logs/s3-credentials.js";
|
|
1
2
|
import { DEFAULT_ADMIN, DEFAULT_CLOUDFLARE, DEFAULT_UPSTREAM_LIMIT } from "./defaults.js";
|
|
3
|
+
export { readS3CredentialsFromEnv, resolveS3Credentials };
|
|
2
4
|
/** Framework paketinin kökü — kendi şablonlarına ve varlıklarına erişir. */
|
|
3
5
|
export declare const FRAMEWORK_ROOT: string;
|
|
4
6
|
export type Eagerness = "conservative" | "moderate" | "eager";
|
|
@@ -278,19 +280,6 @@ export declare function splitS3BucketPath(value: string | null): {
|
|
|
278
280
|
bucket: string | null;
|
|
279
281
|
prefix: string | null;
|
|
280
282
|
};
|
|
281
|
-
/**
|
|
282
|
-
* S3 imza credential'ları. AWS deploy'da standart env önce gelir; R2/MinIO
|
|
283
|
-
* için `JSKELET_S3_*` yedek. IAM instance/task role (IMDS) desteklenmez —
|
|
284
|
-
* CI'da `configure-aws-credentials` gibi araçlar zaten `AWS_*` basar.
|
|
285
|
-
*
|
|
286
|
-
* @returns {{ accessKeyId: string, secretAccessKey: string,
|
|
287
|
-
* sessionToken: string | null } | null}
|
|
288
|
-
*/
|
|
289
|
-
export declare function readS3CredentialsFromEnv(): {
|
|
290
|
-
accessKeyId: string;
|
|
291
|
-
secretAccessKey: string;
|
|
292
|
-
sessionToken: string | null;
|
|
293
|
-
} | null;
|
|
294
283
|
/**
|
|
295
284
|
* Kalıcı log sink'leri. Bozuk bir `kinds` listesi siteyi düşürmemeli —
|
|
296
285
|
* bilinmeyen girdiler atılır; hiç geçerli tür kalmazsa varsayılana dönülür.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type AwsCredentials = {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
sessionToken: string | null;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* @returns {AwsCredentials | null}
|
|
8
|
+
*/
|
|
9
|
+
export declare function readS3CredentialsFromEnv(): AwsCredentials | null;
|
|
10
|
+
/**
|
|
11
|
+
* ECS task role / container credential provider.
|
|
12
|
+
*
|
|
13
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
14
|
+
*/
|
|
15
|
+
export declare function readS3CredentialsFromEcs(): Promise<AwsCredentials | null>;
|
|
16
|
+
/**
|
|
17
|
+
* EC2 instance profile via IMDSv2 (IMDSv1'e düşmez).
|
|
18
|
+
*
|
|
19
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
20
|
+
*/
|
|
21
|
+
export declare function readS3CredentialsFromImds(): Promise<AwsCredentials | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Env → ECS task role → EC2 instance profile.
|
|
24
|
+
*
|
|
25
|
+
* @returns {Promise<AwsCredentials | null>}
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveS3Credentials(): Promise<AwsCredentials | null>;
|