My Maintenance← Takaisin etusivulle
Suomi

My Maintenance · 0.6.44 · Windows & Android

Ota oma Cloudflare-synkronointi käyttöön

Kuvallinen vaiheittainen ohje My Maintenance 0.6.44:lle Windowsissa ja Androidissa. Pilvi on valinnainen; sovellus ja paikalliset varmuuskopiot toimivat ilman sitä. Kuvissa on englanninkielinen Cloudflare ilman tilitietoja. Nimet ovat esimerkkejä.

Vaiheet
  1. Aktivoi R2 ja tarkista hinnat
  2. Luo yksityinen säiliö
  3. Luo Worker
  4. Lisää My Maintenancen koodi
  5. Yhdistä yksityinen säiliö
  6. Luo ja tallenna yksityinen avain
  7. Kopioi Workerin osoite
  8. Yhdistä ensimmäinen laite
  9. Valitse tiedot ja synkronoi
  10. Yhdistä ja tarkista toinen laite
  11. Säilytä erilliset varmuuskopiot

01 / 11

Aktivoi R2 ja tarkista hinnat

Kirjaudu sisään ja avaa Storage & databases → R2 Object Storage. Aktivoi R2 tarvittaessa ja tarkista maksuehdot itse. R2 sisältää kuukausittaisen maksuttoman käyttömäärän; aktivointi voi vaatia maksutiedot ja ylimenevä käyttö voi maksaa. Tarkista R2:n ja Workersin nykyhinnat sekä käyttöilmoitukset. My Maintenance on maksuton.

02 / 11

Luo yksityinen säiliö

Valitse R2:ssa Create bucket, esimerkiksi nimellä my-maintenance-private. Valitse Standard ja tiedoillesi sopiva alue tai lainkäyttöalue, sitten Create bucket. Pidä julkinen käyttö poissa käytöstä: älä ota r2.dev-osoitetta tai julkista verkkotunnusta käyttöön. Valitset saman säiliön vaiheessa 5.

Luo yksityinen säiliö · Cloudflare English UI

03 / 11

Luo Worker

Avaa Compute → Workers & Pages → Create application → Start with Hello World! Anna yksilöllinen nimi, kuten my-maintenance-sync, ja valitse Deploy. Pidä Protect with Cloudflare Access pois päältä; sovellus käyttää omaa avainta. Aloituspalvelu ei vielä sisällä synkronointikoodia.

Luo Worker · Cloudflare English UI
Luo Worker · Cloudflare English UI

04 / 11

Lisää My Maintenancen koodi

Lataa Worker-koodi alta. Avaa Worker → Edit code → worker.js. Korvaa koko aloituskoodi ladatun tiedoston täydellä sisällöllä. Älä lisää avainta koodiin. Valitse Deploy. Unauthorized-ilmoitus esikatselussa ilman avainta on odotettu.

Näytä 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);}
 }
};
Lisää My Maintenancen koodi · Cloudflare English UI

05 / 11

Yhdistä yksityinen säiliö

Avaa Settings → Bindings → Add binding. Valitse R2 Bucket → Add Binding. Variable name on täsmälleen MAINTENANCE. Valitse vaiheen 2 yksityinen säiliö ja Deploy. Jos nimi on jo käytössä, muokkaa olemassa olevaa sidosta. Kuvan säiliö on testisäiliö; valitse omasi.

Yhdistä yksityinen säiliö · Cloudflare English UI
Yhdistä yksityinen säiliö · Cloudflare English UI
Esimerkki tallennetuista asetuksista. Valitse oma yksityinen säiliösi. Tallennettu salaisuus on piilotettu; anna arvo sitä lisätessäsi.

06 / 11

Luo ja tallenna yksityinen avain

Luo vahva satunnainen vähintään 32 merkin avain tai käytä paikallista generaattoria. Säilytä avain salasananhallinnassa. Avaa Settings → Runtime variables and secrets → Add variable: Type = Secret, Name = SYNC_TOKEN ja Value = avaimesi. Valitse Deploy. Cloudflare ei näytä arvoa enää tallennuksen jälkeen. Kyseessä ei ole Cloudflare-tilin API-tunniste.

Luo ja tallenna yksityinen avain · Cloudflare English UI
Luo ja tallenna yksityinen avain · Cloudflare English UI
Esimerkki tallennetuista asetuksista. Valitse oma yksityinen säiliösi. Tallennettu salaisuus on piilotettu; anna arvo sitä lisätessäsi.

07 / 11

Kopioi Workerin osoite

Kopioi Overview-sivulta HTTPS workers.dev -osoite, esimerkiksi https://my-maintenance-sync.your-subdomain.workers.dev/. Käytä juuriosoitetta ilman tiedostopolkua, kyselyä tai hallintapaneelin osoitetta. Selain ilman avainta saa Unauthorized-vastauksen. Älä julkaise avainta osoitteen yhteydessä.

08 / 11

Yhdistä ensimmäinen laite

Avaa My Maintenancessa Asetukset → Valinnainen pilvisynkronointi ja valitse Cloudflare. Anna HTTPS-osoite ja SYNC_TOKEN-avain. Hyväksy tietojen lähettäminen, tallenna yhteys ja odota vahvistusta. Tarkista yhteys. Tallennus tai tarkistus ei vielä yhdistä tietueita; synkronointi tarkistaa myös turvallisen kirjoittamisen.

Yhdistä ensimmäinen laite · Cloudflare English UI

09 / 11

Valitse tiedot ja synkronoi

Valitse vain huoltotietokanta tai lisäksi Ostopaikka, jolloin molempien moduulien kategoriat, tuotteet, kuvat ja asiakirjat sisältyvät. Valitse Synkronoi nyt ja odota tulosta. Vertaa ristiriidoissa molempia puolia ja päätä säilytettävät tietueet. Poistotkin siirtyvät. Sijaintimuistutukset, oikeudet, varmuuskopiointi ja avaimet pysyvät laitekohtaisina.

10 / 11

Yhdistä ja tarkista toinen laite

Anna toisessa laitteessa sama osoite ja avain. Valitse sama laajuus ja synkronoi. Jos molemmissa oli muutoksia, synkronoi ensimmäinen uudelleen. Tarkista kummassakin kohde, suunnitelma, tuote, kuva ja asiakirja. Vain huolto ei muuta ostotietoja. Käytä ajantasaisia yhteensopivia versioita.

11 / 11

Säilytä erilliset varmuuskopiot

Synkronointi ei ole varmuuskopio: poisto voi siirtyä kaikkiin laitteisiin. Säilytä paikalliset varmuuskopiot ja toinen turvallinen kopio. Pilven poistaminen käytöstä säilyttää paikalliset tiedot. Yhteyden poistaminen poistaa avaimen tästä laitteesta, ei pilvitiedostoja. Vaihda vuotanut SYNC_TOKEN ja päivitä kaikki laitteet.

Vianmääritys

Unauthorized / 401

Sovelluksen avaimen on vastattava SYNC_TOKEN-arvoa täsmälleen ilman lisävälilyöntejä. Tyypin on oltava Secret; julkaise Deploy-painikkeella. Selaimessa ilman avainta Unauthorized on normaali.

Puuttuva sidos tai palvelinvirhe

Tarkista MAINTENANCE, yksityinen säiliö, koko koodi ja viimeisin julkaisu. D1-tietokantaa ei tarvita.

Yhteys toimii, synkronointi ei

Lue koko virhe. Tarkista R2, käyttörajat, liiterajat ja versiot. Yhteystesti ei takaa myöhemmän kirjoituksen onnistumista. Säilytä virhe ja varmuuskopio; älä tyhjennä sovelluksen tietoja.

Kuva tai asiakirja puuttuu

Sisällytä Ostopaikka ostoliitteitä varten. Avaa alkuperäinen lähettäjällä. Synkronointi ei voi luoda puuttuvaa paikallista tiedostoa. Älä poista koko tietuetta yhden tiedoston vuoksi.

Viralliset ohjeet ja nykyhinnat

Kuvat Cloudflaren hallintapaneelista 25.–26. lokakuuta 2026. Jos ulkoasu muuttuu, seuraa vaiheiden kenttien nimiä ja arvoja.

Käyttöopas →