shugoi 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/lib/shugoi/api_client.rb +13 -0
- data/lib/shugoi/core.rb +34 -4
- data/lib/shugoi/locales.rb +2 -0
- data/lib/shugoi/notice.rb +69 -57
- data/lib/shugoi/pow.rb +1 -1
- data/lib/shugoi/rack/middleware.rb +32 -10
- data/lib/shugoi/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f63abeeff1710b4f01a153682e1e2f4f5dce888b9d983a32029edf88e72340a
|
|
4
|
+
data.tar.gz: 74343d50af7a65a38d71ad7aed4467c03add9eb30b7716267bcec6691484beec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93796f86c5b561da96e506970bc5b9c820467b886fb336fbf88d57fb1153d9e48ef962d5836671e7038eecabd4bf529b7d186d15547e9d0e98d2e01025d74e34
|
|
7
|
+
data.tar.gz: 2d0802d5ae1c2f16319312a1c052b2ee036e225e75a7bc3661204f22d84ecccf1c778de548b3df2e91c5c8e832b056c489e0ea49497bca7f07008b56abbfa6ad
|
data/lib/shugoi/api_client.rb
CHANGED
|
@@ -54,6 +54,19 @@ module Shugoi
|
|
|
54
54
|
{ "valid" => false, "reason" => "network" }
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# POST /rate-limit-check — vérifie le quota edge par IP (parité core.ts).
|
|
58
|
+
def check_rate_limit(site_key, ip, user_agent = "")
|
|
59
|
+
payload = {
|
|
60
|
+
siteKey: site_key,
|
|
61
|
+
scope: "edge_ip",
|
|
62
|
+
ip: ip,
|
|
63
|
+
metadata: { ip: ip, userAgent: user_agent || "", middleware: true }
|
|
64
|
+
}
|
|
65
|
+
post_json("/rate-limit-check", payload)
|
|
66
|
+
rescue StandardError
|
|
67
|
+
{ "allowed" => true }
|
|
68
|
+
end
|
|
69
|
+
|
|
57
70
|
private
|
|
58
71
|
|
|
59
72
|
def get(path, params)
|
data/lib/shugoi/core.rb
CHANGED
|
@@ -15,10 +15,11 @@ module Shugoi
|
|
|
15
15
|
CHALLENGE_MAX_BLOCK_MS = 15 * 60 * 1000
|
|
16
16
|
VALIDATION_WARN_INTERVAL = 3_600_000
|
|
17
17
|
|
|
18
|
-
def initialize(config, pow, api_client)
|
|
18
|
+
def initialize(config, pow, api_client, config_cache = nil)
|
|
19
19
|
@config = config
|
|
20
20
|
@pow = pow
|
|
21
21
|
@api_client = api_client
|
|
22
|
+
@config_cache = config_cache
|
|
22
23
|
@validation_valid = false
|
|
23
24
|
@validation_failed = false
|
|
24
25
|
@validation_warned_at = 0
|
|
@@ -66,12 +67,41 @@ module Shugoi
|
|
|
66
67
|
|
|
67
68
|
loc = Locales.resolve_locale(@config.locale, ctx[:accept_language])
|
|
68
69
|
msgs = Locales.messages(loc)
|
|
69
|
-
return Decision.new(status: 429, content_type: "text/html", body: shield_page(msgs[:rate_limit_title], msgs[:rate_limit_body], msgs[:rate_limit_badge], ctx[:host].to_s, 60, loc), headers: {})
|
|
70
|
+
return Decision.new(status: 429, content_type: "text/html", body: shield_page(msgs[:rate_limit_title], msgs[:rate_limit_body].call("1 min"), msgs[:rate_limit_badge], ctx[:host].to_s, 60, loc), headers: {})
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Flags de détection (whitelist + skipPaths), parité fetchConfigForSiteKey.
|
|
75
|
+
flags = @config_cache ? @config_cache.fetch(@config.site_key)[:flags] : {}
|
|
76
|
+
headless_enabled = flags["enableHeadlessCheck"] != false
|
|
77
|
+
|
|
78
|
+
# Rate limit check — activé uniquement si le flag est explicitement vrai.
|
|
79
|
+
if flags["enableRateLimit"] == true
|
|
80
|
+
rl = @api_client.check_rate_limit(@config.site_key, ctx[:ip].to_s, ctx[:ua].to_s)
|
|
81
|
+
if rl && rl["allowed"] == false
|
|
82
|
+
reset_at = rl["resetAt"].to_i
|
|
83
|
+
reset_at = (reset_at / 1000.0).ceil if reset_at > 1_000_000_000_000
|
|
84
|
+
remain = [0, reset_at - Utils.now_sec].max
|
|
85
|
+
mins = remain / 60
|
|
86
|
+
secs = remain % 60
|
|
87
|
+
time_str = if mins.positive?
|
|
88
|
+
"#{mins} min#{mins > 1 ? 's' : ''}#{secs.positive? ? " #{secs} s" : ''}"
|
|
89
|
+
else
|
|
90
|
+
"#{secs} seconde#{secs > 1 ? 's' : ''}"
|
|
91
|
+
end
|
|
92
|
+
loc = @config.locale || Locales.resolve_locale(nil, ctx[:accept_language])
|
|
93
|
+
msgs = Locales.messages(loc)
|
|
94
|
+
body = if @config.block_page.respond_to?(:call)
|
|
95
|
+
@config.block_page.call(reason: "rate_limit", title: msgs[:rate_limit_title], message: msgs[:rate_limit_body].call(time_str), badge: msgs[:rate_limit_badge], host: ctx[:host].to_s, remainingSeconds: remain, locale: loc)
|
|
96
|
+
else
|
|
97
|
+
shield_page(msgs[:rate_limit_title], msgs[:rate_limit_body].call(time_str), msgs[:rate_limit_badge], ctx[:host].to_s, remain, loc)
|
|
98
|
+
end
|
|
99
|
+
return Decision.new(status: 429, content_type: "text/html", body: body, headers: {})
|
|
70
100
|
end
|
|
71
101
|
end
|
|
72
102
|
|
|
73
103
|
# Blocage headless (UA). Actif par défaut, y compris sans configuration chargée.
|
|
74
|
-
if !ua.empty? && !is_trusted_bot?(ua, ctx[:ip].to_s) && @config.is_headless?(ua)
|
|
104
|
+
if headless_enabled && !ua.empty? && !is_trusted_bot?(ua, ctx[:ip].to_s) && @config.is_headless?(ua)
|
|
75
105
|
@api_client.post_event(@config.site_key, "headless", "")
|
|
76
106
|
return Decision.new(status: @config.block_status, content_type: "text/plain", body: BLOCK_PAGE, headers: {})
|
|
77
107
|
end
|
|
@@ -93,7 +123,7 @@ module Shugoi
|
|
|
93
123
|
loc = Locales.resolve_locale(@config.locale, ctx[:accept_language])
|
|
94
124
|
if !allow_challenge?(ctx[:ip].to_s)
|
|
95
125
|
msgs = Locales.messages(loc)
|
|
96
|
-
return Decision.new(status: 429, content_type: "text/html", body: shield_page(msgs[:rate_limit_title], msgs[:rate_limit_body], msgs[:rate_limit_badge], ctx[:host].to_s, 60, loc), headers: {})
|
|
126
|
+
return Decision.new(status: 429, content_type: "text/html", body: shield_page(msgs[:rate_limit_title], msgs[:rate_limit_body].call("1 min"), msgs[:rate_limit_badge], ctx[:host].to_s, 60, loc), headers: {})
|
|
97
127
|
end
|
|
98
128
|
js = <<~JS
|
|
99
129
|
(function(){
|
data/lib/shugoi/locales.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Shugoi
|
|
|
5
5
|
module Locales
|
|
6
6
|
FR = {
|
|
7
7
|
rate_limit_title: "Trop de requêtes",
|
|
8
|
+
rate_limit_body: ->(t) { "Vous avez effectué trop de requêtes en peu de temps. Il reste #{t} avant de pouvoir réessayer." },
|
|
8
9
|
rate_limit_badge: "Rate Limit",
|
|
9
10
|
blocked_title: "Accès bloqué",
|
|
10
11
|
blocked_badge: "Blocage",
|
|
@@ -16,6 +17,7 @@ module Shugoi
|
|
|
16
17
|
|
|
17
18
|
EN = {
|
|
18
19
|
rate_limit_title: "Too Many Requests",
|
|
20
|
+
rate_limit_body: ->(t) { "You have made too many requests in a short time. #{t} remaining before you can try again." },
|
|
19
21
|
rate_limit_badge: "Rate Limit",
|
|
20
22
|
blocked_title: "Access Blocked",
|
|
21
23
|
blocked_badge: "Blocked",
|
data/lib/shugoi/notice.rb
CHANGED
|
@@ -6,71 +6,83 @@ module Shugoi
|
|
|
6
6
|
# dans le HTML APRÈS le split-render (elle ne disparaît pas quand le render remplace
|
|
7
7
|
# le document). L'ack est décidé côté SERVEUR via /notice (machineId), pas de cookie.
|
|
8
8
|
class Notice
|
|
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
|
-
|
|
9
|
+
# Script renforcé — PARITÉ EXACTE avec NOTICE_SCRIPT de render.ts (module Node) :
|
|
10
|
+
# - overlay bloquant z-index max (position:fixed;inset:0;pointer-events:auto)
|
|
11
|
+
# - MutationObserver anti-bypass ciblé + garde _applying et mo.takeRecords()
|
|
12
|
+
# (anti-boucle infinie : nos propres ré-applications ne re-déclenchent pas le MO)
|
|
13
|
+
# - PAS de setInterval, PAS de piège clavier (version légère du module Node)
|
|
14
|
+
# - seul OK ferme (ack serveur via POST /notice, lié au machineId)
|
|
15
|
+
# Les placeholders `mid` et `sk` (et `base`) sont remplacés à l'injection.
|
|
16
|
+
SCRIPT = <<~'JS'
|
|
17
|
+
<script>
|
|
18
|
+
(function(){
|
|
19
|
+
var mid=window.__sg_mid||'';
|
|
20
|
+
var sk=window.__sg_siteKey||'';
|
|
21
|
+
if(!mid||!sk||window.__sg_noticeEnabled===false)return;
|
|
22
|
+
var base=window.__sg_baseUrl||'';
|
|
23
|
+
var origin=base.replace(/\/api\/v1\/?$/,'');
|
|
24
|
+
var _OVERLAY_CSS='position:fixed!important;inset:0!important;z-index:2147483647!important;background:rgba(0,0,0,.6)!important;display:flex!important;align-items:center!important;justify-content:center!important;padding:1.2rem!important;pointer-events:auto!important';
|
|
25
|
+
var _CARD_CSS='background:#fff!important;border:4px solid #000!important;border-radius:28px 6px 32px 10px!important;box-shadow:14px 14px 0 #000!important;padding:0!important;max-width:720px!important;width:100%!important;text-align:center!important;font-family:Arial,sans-serif!important;display:flex!important;overflow:hidden!important';
|
|
26
|
+
var _CARD_HTML='<div style="flex:0 0 320px;display:flex;align-items:center;justify-content:center;padding:1.5rem 1rem 1.5rem 3rem;overflow:hidden"><img src="'+origin+'/favicon.png" alt="" style="width:100%;height:auto;max-width:220px;pointer-events:none"></div><div style="flex:1;padding:1.6rem 1.8rem;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center"><img src="'+origin+'/brand-block.png" alt="Shugoi" style="display:block;margin:0 0 .3rem;pointer-events:none;max-width:100%;height:auto;max-height:40px"><div style="border:2px solid #000;display:inline-block;border-radius:8px 2px 12px 4px;padding:.2rem .6rem;font-size:.5rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#E87090;margin-bottom:.6rem">Protection anti-abus</div><p style="font-size:.8rem;color:#555;line-height:1.7;margin:0 .4rem .6rem;max-width:280px">Ce site utilise Shugoi pour se prot\\u00e9ger contre les abus et la fraude. Des caract\\u00e9ristiques techniques de votre navigateur sont analys\\u00e9es pour d\\u00e9tecter les scripts automatis\\u00e9s, Tor, les VPN et les environnements virtuels. Aucune donn\\u00e9e personnelle n\\'est collect\\u00e9e.</p><div style="margin-top:.5rem"><button id="__sg_ok" style="background:#E87090;color:#fff;border:3px solid #000;border-radius:12px 3px 14px 5px;padding:.4rem 2rem;font-size:.8rem;font-weight:700;cursor:pointer">OK</button></div><div style="margin-top:.5rem;font-size:.5rem;color:#ccc"><a href="'+origin+'/legal/shugoi-notice" target="_blank" style="color:#E87090;text-decoration:underline">En savoir plus \\u00b7 shugoi.com</a></div></div>';
|
|
27
|
+
var _closed=false;
|
|
28
|
+
var mo=null;
|
|
29
|
+
var _applying=false;
|
|
30
|
+
function ack(){try{fetch(base+'/notice',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({machineId:mid,siteKey:sk}),keepalive:true,signal:AbortSignal.timeout(4000)}).catch(function(){})}catch(e){}}
|
|
31
|
+
function buildOverlay(){
|
|
32
|
+
var o=document.createElement('div');o.id='__sg_o';o.style.cssText=_OVERLAY_CSS;
|
|
33
|
+
var c=document.createElement('div');c.id='__sg_cd';c.style.cssText=_CARD_CSS;
|
|
34
|
+
c.innerHTML=_CARD_HTML;
|
|
35
|
+
o.appendChild(c);return o;
|
|
36
|
+
}
|
|
37
|
+
function close(){_closed=true;try{if(mo)mo.disconnect()}catch(e){}var el=document.getElementById('__sg_o');if(el&&el.parentNode)el.parentNode.removeChild(el);document.body.style.overflow='';document.documentElement.style.overflow='';}
|
|
38
|
+
function okHandler(){ack();close();}
|
|
39
|
+
function rebind(){var b=document.getElementById('__sg_ok');if(b)b.onclick=okHandler;}
|
|
40
|
+
// Anti-bypass 100% MUTATION OBSERVER (aucun setInterval).
|
|
41
|
+
// CRITIQUE anti-freeze : le flag _applying + mo.takeRecords() cassent la boucle MO —
|
|
42
|
+
// nos propres modifications (style/innerHTML re-appliqués) ne re-déclenchent PAS le MO
|
|
43
|
+
// (le navigateur normalise cssText/innerHTML, donc la comparaison échoue toujours et
|
|
44
|
+
// on ré-appliquerait à l'infini). takeRecords() vide la file des mutations que NOS
|
|
45
|
+
// changements ont générée → une seule passe par altération réelle, jamais de gel.
|
|
46
|
+
function enforce(){
|
|
47
|
+
if(_closed||_applying)return;
|
|
48
|
+
_applying=true;
|
|
49
|
+
try{
|
|
39
50
|
var o=document.getElementById('__sg_o');
|
|
40
|
-
if(!o){
|
|
41
|
-
document.documentElement.appendChild(buildOverlay());
|
|
42
|
-
document.body.style.overflow='hidden';document.documentElement.style.overflow='hidden';
|
|
43
|
-
rebindOk();return;
|
|
44
|
-
}
|
|
51
|
+
if(!o){o=buildOverlay();document.documentElement.appendChild(o);}
|
|
45
52
|
if(o.style.cssText!==_OVERLAY_CSS)o.style.cssText=_OVERLAY_CSS;
|
|
46
53
|
var c=document.getElementById('__sg_cd');
|
|
47
|
-
if(!c){
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
if(!c){o.innerHTML='';o.appendChild(buildOverlay().firstChild);}
|
|
55
|
+
else{
|
|
56
|
+
if(c.style.cssText!==_CARD_CSS)c.style.cssText=_CARD_CSS;
|
|
57
|
+
if(c.innerHTML!==_CARD_HTML)c.innerHTML=_CARD_HTML;
|
|
51
58
|
}
|
|
52
|
-
|
|
53
|
-
if(c.innerHTML!==_CARD_HTML){c.innerHTML=_CARD_HTML;rebindOk();}
|
|
59
|
+
rebind();
|
|
54
60
|
if(document.body.style.overflow!=='hidden')document.body.style.overflow='hidden';
|
|
55
61
|
if(document.documentElement.style.overflow!=='hidden')document.documentElement.style.overflow='hidden';
|
|
62
|
+
try{
|
|
63
|
+
if(o&&!o.__sgObserved){o.__sgObserved=true;mo&&mo.observe(o,{childList:true,subtree:true,attributes:true,characterData:true,attributeFilter:['style','class','id']});}
|
|
64
|
+
}catch(e){}
|
|
65
|
+
}finally{
|
|
66
|
+
_applying=false;
|
|
67
|
+
try{if(mo)mo.takeRecords();}catch(e){}
|
|
56
68
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
window.addEventListener('wheel',function(e){e.preventDefault()},{passive:false});
|
|
65
|
-
rebindOk();
|
|
69
|
+
}
|
|
70
|
+
function show(){
|
|
71
|
+
_closed=false;
|
|
72
|
+
var o=buildOverlay();document.documentElement.appendChild(o);
|
|
73
|
+
document.body.style.overflow='hidden';document.documentElement.style.overflow='hidden';
|
|
74
|
+
rebind();
|
|
75
|
+
try{
|
|
66
76
|
mo=new MutationObserver(function(){enforce();});
|
|
67
|
-
mo.observe(document.documentElement,{childList:true
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
mo.observe(document.documentElement,{childList:true});
|
|
78
|
+
try{o.__sgObserved=true;mo.observe(o,{childList:true,subtree:true,attributes:true,characterData:true,attributeFilter:['style','class','id']});}catch(e){}
|
|
79
|
+
}catch(e){}
|
|
80
|
+
}
|
|
81
|
+
function init(){if(document.body)show();else if(document.addEventListener)document.addEventListener('DOMContentLoaded',show);else setTimeout(init,50)}
|
|
82
|
+
fetch(base+'/notice?machineId='+encodeURIComponent(mid)+'&siteKey='+encodeURIComponent(sk),{signal:AbortSignal.timeout(4000)}).then(function(r){return r.json()}).then(function(d){if(!d.acknowledged)init()}).catch(function(){init()});
|
|
83
|
+
})();
|
|
84
|
+
</script>
|
|
85
|
+
JS
|
|
74
86
|
|
|
75
87
|
# Injecte la notice dans le HTML rendu (avant </body>).
|
|
76
88
|
# @param html [String] HTML rendu
|
data/lib/shugoi/pow.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Shugoi
|
|
|
15
15
|
def initialize(app, options = {})
|
|
16
16
|
@app = app
|
|
17
17
|
@config = Config.new(options)
|
|
18
|
+
@disk_path_option = options[:disk_path]
|
|
18
19
|
@api_client = ApiClient.new(@config.base_url, debug: @config.debug)
|
|
19
20
|
# Config (whitelist + flags + skipPaths) et render : via internalUrl pour éviter
|
|
20
21
|
# le deadlock (appels serveur→serveur qui repasseraient par le middleware public).
|
|
@@ -22,11 +23,13 @@ module Shugoi
|
|
|
22
23
|
@guard_cache = GuardCache.new(@api_client)
|
|
23
24
|
@config_cache = ConfigCache.new(@api_internal)
|
|
24
25
|
@token_signer = TokenSigner.new(@config.signing_secret)
|
|
25
|
-
|
|
26
|
+
# multi_process → stockage disque du HTML (nécessaire en cluster PM2, parité
|
|
27
|
+
# enableDiskStore(true) du module Node).
|
|
28
|
+
@html_store = HtmlStore.new(disk_path: disk_path)
|
|
26
29
|
@pow = Pow.new(@config.signing_secret, @config.pow_difficulty, @config.pow_ttl_ms)
|
|
27
30
|
@skeleton = SkeletonGenerator.new(@config, @guard_cache, @config_cache, @token_signer)
|
|
28
31
|
@render = RenderHandler.new(@config, @token_signer, @html_store, @config_cache, @pow)
|
|
29
|
-
@core = Core.new(@config, @pow, @api_client)
|
|
32
|
+
@core = Core.new(@config, @pow, @api_client, @config_cache)
|
|
30
33
|
@csp = Csp.build(site_key: @config.site_key, api_origin: Csp.origin_of(@config.base_url), extra_directives: @config.extra_directives, split_render: @config.split_render)
|
|
31
34
|
end
|
|
32
35
|
|
|
@@ -46,6 +49,16 @@ module Shugoi
|
|
|
46
49
|
return method_not_allowed
|
|
47
50
|
end
|
|
48
51
|
|
|
52
|
+
# SkipPaths (SSR direct) : on laisse l'app servir la page sans challenge ni skeleton.
|
|
53
|
+
# Parité middleware.ts : le check est AVANT core.evaluate (un skipPath hors allowlist
|
|
54
|
+
# ne doit PAS passer par le challenge PoW ni la protection des assets).
|
|
55
|
+
if @config.auto_inject && @config.site_key
|
|
56
|
+
cfg = @config_cache.fetch(@config.site_key)
|
|
57
|
+
if cfg[:skip_paths].include?(path)
|
|
58
|
+
return @app.call(env)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
ctx = build_ctx(env, query)
|
|
50
63
|
decision = @core.evaluate(ctx)
|
|
51
64
|
|
|
@@ -57,14 +70,6 @@ module Shugoi
|
|
|
57
70
|
return [decision.status, h, [decision.body]]
|
|
58
71
|
end
|
|
59
72
|
|
|
60
|
-
# SkipPaths (SSR direct) : on laisse l'app servir la page sans challenge ni skeleton.
|
|
61
|
-
if @config.auto_inject && @config.site_key
|
|
62
|
-
cfg = @config_cache.fetch(@config.site_key)
|
|
63
|
-
if cfg[:skip_paths].include?(path)
|
|
64
|
-
return @app.call(env)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
73
|
status, resp_headers, body = @app.call(env)
|
|
69
74
|
|
|
70
75
|
# Rack 3 exige des noms de headers en minuscules → on normalise.
|
|
@@ -186,6 +191,23 @@ module Shugoi
|
|
|
186
191
|
def parse_query(qs)
|
|
187
192
|
::Rack::Utils.parse_nested_query(qs)
|
|
188
193
|
end
|
|
194
|
+
|
|
195
|
+
# Chemin du stockage disque (parité enableDiskStore du module Node).
|
|
196
|
+
# multi_process → répertoire tmp partagé (cluster PM2) ; sinon l'option disk_path
|
|
197
|
+
# explicite éventuelle.
|
|
198
|
+
def disk_path
|
|
199
|
+
return @disk_path_option if @disk_path_option
|
|
200
|
+
return nil unless @config.multi_process
|
|
201
|
+
|
|
202
|
+
dir = File.join(Dir.tmpdir, "shugoi-render-#{Process.uid}")
|
|
203
|
+
begin
|
|
204
|
+
Dir.mkdir(dir, 0o700) unless Dir.exist?(dir)
|
|
205
|
+
File.chmod(0o700, dir)
|
|
206
|
+
rescue StandardError
|
|
207
|
+
return nil
|
|
208
|
+
end
|
|
209
|
+
dir
|
|
210
|
+
end
|
|
189
211
|
end
|
|
190
212
|
end
|
|
191
213
|
end
|
data/lib/shugoi/version.rb
CHANGED