My Maintenance← Tagasi avalehele
Eesti

My Maintenance · 0.6.44 · Windows & Android

Seadista oma Cloudflare’i sünkroonimine

Piltidega samm-sammuline juhend My Maintenance’i versioonile 0.6.44 Windowsis ja Androidis. Pilv on valikuline; äpp ja kohalikud varukoopiad töötavad ka ilma selleta. Piltidel on Cloudflare’i ingliskeelne liides, kontoandmed on välja lõigatud. Näidisnimed võivad sinu omadest erineda.

Sammud
  1. Aktiveeri R2 ja kontrolli hindu
  2. Loo privaatne konteiner
  3. Loo Worker
  4. Lisa My Maintenance’i kood
  5. Seo privaatne konteiner
  6. Loo ja salvesta pääsuvõti
  7. Kopeeri Workeri aadress
  8. Ühenda esimene seade
  9. Vali andmed ja sünkrooni
  10. Ühenda ja kontrolli teist seadet
  11. Säilita sõltumatud varukoopiad

01 / 11

Aktiveeri R2 ja kontrolli hindu

Logi Cloudflare’i sisse ning ava Storage & databases → R2 Object Storage. Vajadusel aktiveeri R2 tellimus ja vaata maksekinnituse tingimused ise üle. R2-l on igakuine tasuta kasutusmaht, kuid aktiveerimine võib nõuda makseandmeid ja piiri ületav kasutus võib olla tasuline. Kontrolli R2 ning Workersi kehtivaid hindu ja seadista kasutusmahu teavitused. My Maintenance on tasuta.

02 / 11

Loo privaatne konteiner

Vali R2 vaates Create bucket. Näidisnimi on my-maintenance-private. Vali Standard ning oma andmetele sobiv piirkond või jurisdiktsioon. Vajuta Create bucket. Hoia avalik ligipääs väljas: ära luba r2.dev avalikku aadressi ega lisa avalikku domeeni. Sama konteineri valid 5. sammus.

Loo privaatne konteiner · Cloudflare English UI

03 / 11

Loo Worker

Ava Compute → Workers & Pages → Create application ning vali Start with Hello World! Sisesta kordumatu nimi, näiteks my-maintenance-sync, ja vajuta Deploy. Jäta Protect with Cloudflare Access välja: äpp kasutab oma pääsuvõtit. Esialgne teenus ei sisalda veel sünkroonimise koodi.

Loo Worker · Cloudflare English UI
Loo Worker · Cloudflare English UI

04 / 11

Lisa My Maintenance’i kood

Laadi allolevalt lingilt Worker-kood alla. Ava loodud Worker ja vali Edit code. Ava worker.js, vali kogu näidiskood ning asenda see allalaaditud faili täieliku sisuga. Ära lisa võtit koodi. Vajuta Deploy. Tavalises brauseri eelvaates võib näha Unauthorized, sest seal ei saadeta äpi pääsuvõtit; see on ootuspärane.

Näita Worker-koodi
// Deploy into YOUR Cloudflare account. The R2 bucket must remain private.
const STATE='maintenance-state-v1.json';
const TABLES=['object_types','properties','maintenance_plans','spare_parts','maintenance_history','attachments'];
const PURCHASE_TABLES=['purchase_categories','purchased_products','purchase_attachments'];
const STATE_LIMIT=16*1024*1024,FILE_LIMIT=50*1024*1024;
const reply=(message,status=400)=>new Response(message,{status,headers:{'Cache-Control':'no-store','Content-Type':'text/plain; charset=utf-8'}});
async function authorized(request,secret){
 if(typeof secret!=='string'||secret.length<32)return false;
 const supplied=request.headers.get('Authorization')||'';
 if(!supplied.startsWith('Bearer ')||supplied.length>1024)return false;
 const hash=async s=>new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(s)));
 const [a,b]=await Promise.all([hash(supplied.slice(7)),hash(secret)]);let different=0;for(let i=0;i<32;i++)different|=a[i]^b[i];return different===0;
}
async function limitedText(request,limit){
 const reader=request.body?.getReader();if(!reader)return '';
 let total=0;const chunks=[];
 try{while(true){const {done,value}=await reader.read();if(done)break;total+=value.length;if(total>limit){await reader.cancel();throw new Error('too-large')}chunks.push(value);}}finally{reader.releaseLock();}
 const bytes=new Uint8Array(total);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length;}return new TextDecoder('utf-8',{fatal:true}).decode(bytes);
}
function conditional(headers){
 const match=headers.get('If-Match'),none=headers.get('If-None-Match');
 if(none==='*'&&!match)return true;
 return !!match&&!none&&/^"[^"\r\n]+"$/.test(match);
}
export default {
 async fetch(request,env){
  if(!await authorized(request,env.SYNC_TOKEN))return reply('Unauthorized',401);
  const url=new URL(request.url),name=url.pathname.slice(1),isState=name===STATE;
  const blob=/^blob-([a-f0-9]{64})\.bin$/.exec(name);
  const probe=/^sync-probe-[a-f0-9]{64}\.json$/.test(name);
  if(url.search||(!isState&&!blob&&!probe))return reply('Not found',404);
  if(probe&&request.method==='DELETE'){await env.MAINTENANCE.delete(name);return new Response(null,{status:204});}
  if(!['GET','HEAD','PUT'].includes(request.method))return reply('Method not allowed',405);
  if(request.method==='GET'||request.method==='HEAD'){
   const object=request.method==='HEAD'?await env.MAINTENANCE.head(name):await env.MAINTENANCE.get(name);
   if(!object)return reply('Not found',404);
   const headers=new Headers({'ETag':object.httpEtag,'Content-Length':String(object.size),'Cache-Control':'no-store','Content-Type':isState?'application/json':'application/octet-stream','X-Content-Type-Options':'nosniff'});
   return new Response(request.method==='HEAD'?null:object.body,{headers});
  }
  if(!conditional(request.headers))return reply('A strong If-Match or If-None-Match: * is required',428);
  if(blob&&request.headers.get('If-None-Match')!=='*')return reply('Attachment objects are immutable',428);
  const length=request.headers.get('Content-Length');
  if(length===null||!/^\d+$/.test(length))return reply('Content-Length required',411);
  if(Number(length)>(isState?STATE_LIMIT:probe?1024:FILE_LIMIT))return reply('Too large',413);
  let body=request.body;
  if(isState){
   try {const text=await limitedText(request,STATE_LIMIT),doc=JSON.parse(text);
    const tables=doc.version===2?[...TABLES,...PURCHASE_TABLES]:TABLES;
    if(doc?.format!=='my-maintenance-sync'||![1,2].includes(doc.version)||!Array.isArray(doc.devices)||!doc.tables||Object.keys(doc.tables).sort().join()!==[...tables].sort().join()||tables.some(t=>!Array.isArray(doc.tables[t])))return reply('Invalid maintenance document',422);
    body=text;
   }catch(e){return reply(e.message==='too-large'?'Too large':'Invalid JSON',e.message==='too-large'?413:422);}
  }else if(probe){try{body=await limitedText(request,1024);}catch(_){return reply('Too large',413);}}
  else{
   if(!body)return reply('Attachment body required',400);
   // Keep the original HTTP request body: R2 requires a stream with known length.
   // Cloudflare's HTTP runtime enforces Content-Length; R2 independently verifies SHA-256.
  }
  try {
   const object=await env.MAINTENANCE.put(name,body,{onlyIf:request.headers,httpMetadata:{contentType:isState?'application/json':'application/octet-stream'},...(blob?{sha256:blob[1]}:{})});
   if(!object)return reply('Precondition failed',412);
   return new Response(null,{status:204,headers:{ETag:object.httpEtag,'Cache-Control':'no-store'}});
  } catch (_) {return reply('Storage write failed; no success was acknowledged',502);}
 }
};
Lisa My Maintenance’i kood · Cloudflare English UI

05 / 11

Seo privaatne konteiner

Ava Workeri Settings → Bindings → Add binding. Vali R2 Bucket ja Add Binding. Variable name peab olema täpselt MAINTENANCE, suurte tähtedega. Vali 2. sammus loodud privaatne konteiner ja vajuta Deploy. Korduva nime vea korral muuda olemasolevat MAINTENANCE seost. Pildil on testkonteiner; vali enda oma.

Seo privaatne konteiner · Cloudflare English UI
Seo privaatne konteiner · Cloudflare English UI
Salvestatud seadete näide. Vali enda privaatne andmekonteiner. Salvestatud saladus on peidetud; väärtus sisestatakse selle lisamisel.

06 / 11

Loo ja salvesta pääsuvõti

Genereeri vähemalt 32 märgi pikkune tugev juhuslik võti või kasuta allolevat kohalikku generaatorit. Hoia võti paroolihalduris. Vali Workeri Settings → Runtime variables and secrets → Add variable. Type on Secret ja Name on SYNC_TOKEN. Kleebi võti väljale Value ning vajuta Deploy. Hiljem Cloudflare võtit enam ei näita. See on äpi pääsuvõti, mitte Cloudflare’i konto API-pääsutunnus.

Loo ja salvesta pääsuvõti · Cloudflare English UI
Loo ja salvesta pääsuvõti · Cloudflare English UI
Salvestatud seadete näide. Vali enda privaatne andmekonteiner. Salvestatud saladus on peidetud; väärtus sisestatakse selle lisamisel.

07 / 11

Kopeeri Workeri aadress

Ava Workeri Overview ja kopeeri HTTPS workers.dev aadress, näiteks https://my-maintenance-sync.your-subdomain.workers.dev/. Kasuta juuraadressi ilma failitee, päringu või Cloudflare’i juhtpaneeli aadressita. Ilma võtmeta avatud brauseris peab vastuseks olema Unauthorized. Ära avalda võtit koos aadressiga.

08 / 11

Ühenda esimene seade

Ava My Maintenance’is Seaded → Valikuline pilvesünkroniseerimine. Vali Cloudflare ning sisesta Workeri HTTPS-aadress ja SYNC_TOKEN võti. Märgi nõusolek, vajuta Salvesta ühendus ja oota kinnitust. Vajuta Kontrolli ühendust. Salvestamine või kontrollimine kirjeid veel ei ühenda. Kontroll kinnitab ligipääsu; sünkroonimine kontrollib ka turvalist kirjutamist.

Ühenda esimene seade · Cloudflare English UI

09 / 11

Vali andmed ja sünkrooni

Vali Ainult Hoolduse andmebaas või Koos „Kust ostetud” andmebaasiga, et kaasata mõlema mooduli kategooriad, tooted, fotod ja dokumendid. Vajuta Sünkrooni nüüd ning oota tulemust. Vastuolude korral võrdle mõlemat poolt ja vali teadlikult säilitatavad kirjed. Kustutamised kanduvad samuti üle. Asukohameeldetuletused, load, varundusgraafik ja ühendusvõtmed jäävad seadmekohaseks.

10 / 11

Ühenda ja kontrolli teist seadet

Sisesta teises seadmes sama Workeri aadress ja võti, vali sama andmete ulatus ning vajuta Sünkrooni nüüd. Kui mõlemas oli eraldi muudatusi, sünkrooni esimest veel kord. Kontrolli mõlemas näidisobjekti, hooldusplaani, toodet, fotot ja dokumenti. Ainult Hoolduse valik jätab ostuandmed muutmata. Hoia mõlemad äpid uusimal ühilduval versioonil.

11 / 11

Säilita sõltumatud varukoopiad

Pilvesünkroonimine ei asenda varukoopiat: kustutamine võib jõuda igasse seadmesse. Hoia automaatseid kohalikke varukoopiaid ning kopeeri olulised koopiad teise turvalisse kohta. Pilve väljalülitamisel jäävad kohalikud andmed alles. Ühenduse eemaldamine kustutab sellest seadmest võtme, mitte pilvefailid. Lekkinud võtme tühistamiseks muuda Cloudflare’is SYNC_TOKEN ja uuenda kõiki seadmeid.

Kui midagi ei õnnestu

Unauthorized / 401

Kontrolli, et äpi võti ja Workeri SYNC_TOKEN kattuvad täpselt ning kõrval pole tühikuid. Type peab olema Secret. Avalda muudatus Deploy-nupuga. Tavalises võtmeta brauseris on Unauthorized ootuspärane.

Puuduv seos või serveriviga

Kontrolli nime MAINTENANCE, valitud privaatset R2-konteinerit, täielikku Worker-koodi ja viimast avaldamist. D1 andmebaasi vaja ei ole.

Ühenduse kontroll õnnestub, sünkroonimine mitte

Loe äpi täielikku veateadet. Kontrolli R2 kättesaadavust ja kasutuspiire, manuste mahtu ning äppide ühilduvust. Ühenduse kontroll ei kinnita, et hilisem kirjutamine õnnestub. Säilita veateade ja varukoopia; ära tühjenda äpi andmeid.

Foto või dokument puudub

Ostumanuste jaoks peab valitud olema ka „Kust ostetud” andmebaas. Ava originaal saatvas seadmes. Sünkroonimine ei saa puuduvat faili taastada. Ühe faili pärast ära kustuta tervet kirjet.

Cloudflare’i ametlikud juhendid ja kehtivad hinnad

Pildid on tehtud Cloudflare’i juhtpaneelis 25.–26. oktoobril 2026. Cloudflare võib siltide nimetusi ja asukohti muuta. Vajadusel järgi siin toodud väljade nimesid ja väärtusi.

Kasutusjuhend →