[{"data":1,"prerenderedAt":1596},["ShallowReactive",2],{"page:\u002Fdrangler":3},{"id":4,"title":5,"accent":6,"body":7,"coming":1584,"description":1585,"extension":1586,"headline":1587,"intro":1588,"meta":1589,"navigation":1590,"path":1591,"repos":1592,"seo":1593,"stats":1594,"statsDetail":6,"statsNote":6,"stem":5,"__hash__":1595},"content\u002Fdrangler.md","drangler",null,{"type":8,"value":9,"toc":1568},"minimark",[10,15,45,70,74,81,101,119,122,228,232,243,252,262,277,287,296,309,318,325,340,365,378,393,399,408,414,423,427,496,500,574,578,684,687,808,812,818,828,880,886,921,927,961,975,981,990,996,1016,1107,1121,1125,1197,1201,1243,1258,1272,1277,1295,1350,1354,1364,1416,1421,1485,1508,1512,1564],[11,12,14],"h2",{"id":13},"install","📦 Install",[16,17,22],"pre",{"className":18,"code":19,"language":20,"meta":21,"style":21},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","bun add -g @drupflare\u002Fdrangler\n# or\nnpm i -g @drupflare\u002Fdrangler\n","sh","",[23,24,25,33,39],"code",{"__ignoreMap":21},[26,27,30],"span",{"class":28,"line":29},"line",1,[26,31,32],{},"bun add -g @drupflare\u002Fdrangler\n",[26,34,36],{"class":28,"line":35},2,[26,37,38],{},"# or\n",[26,40,42],{"class":28,"line":41},3,[26,43,44],{},"npm i -g @drupflare\u002Fdrangler\n",[46,47,48,49,53,54,57,58,61,62,65,66,69],"p",{},"Every release also ships ",[50,51,52],"pitch",{},"a single-file binary for Linux and macOS on x64 and arm64, and Windows on x64",", with a\n",[23,55,56],{},"SHA256SUMS"," file beside it. drangler needs ",[23,59,60],{},"ssh"," and ",[23,63,64],{},"wrangler","; ",[23,67,68],{},"drangler doctor"," checks both.",[11,71,73],{"id":72},"first-time","🚀 First Time",[46,75,76,77,80],{},"drangler runs on your own computer. Before the first deploy, install it with wrangler, sign in to\nCloudflare, and let ",[23,78,79],{},"doctor"," check the setup:",[16,82,84],{"className":18,"code":83,"language":20,"meta":21,"style":21},"npm i -g @drupflare\u002Fdrangler wrangler\nwrangler login     # opens a browser to approve access to your Cloudflare account\ndrangler doctor    # checks ssh, wrangler and the sign-in, and says how to fix what is missing\n",[23,85,86,91,96],{"__ignoreMap":21},[26,87,88],{"class":28,"line":29},[26,89,90],{},"npm i -g @drupflare\u002Fdrangler wrangler\n",[26,92,93],{"class":28,"line":35},[26,94,95],{},"wrangler login     # opens a browser to approve access to your Cloudflare account\n",[26,97,98],{"class":28,"line":41},[26,99,100],{},"drangler doctor    # checks ssh, wrangler and the sign-in, and says how to fix what is missing\n",[46,102,103,104,107,108,107,111,114,115,118],{},"Commands that read an existing server (",[23,105,106],{},"migrate",", ",[23,109,110],{},"preview",[23,112,113],{},"doctor --source",") connect over SSH\nwith your usual keys. Commands that act on a deployed site take its address, and the owner token\nthat ",[23,116,117],{},"site claim"," stored in your system keychain.",[46,120,121],{},"Every command accepts these flags:",[123,124,125,138],"table",{},[126,127,128],"thead",{},[129,130,131,135],"tr",{},[132,133,134],"th",{},"Flag",[132,136,137],{},"What it does",[139,140,141,152,162,175,188,202,215],"tbody",{},[129,142,143,149],{},[144,145,146],"td",{},[23,147,148],{},"--dry-run",[144,150,151],{},"print what the command would do, and do none of it",[129,153,154,159],{},[144,155,156],{},[23,157,158],{},"--json",[144,160,161],{},"print the report as one JSON object, for scripts",[129,163,164,172],{},[144,165,166,107,169],{},[23,167,168],{},"--yes",[23,170,171],{},"-y",[144,173,174],{},"consent for anything that changes a live site; without it those refuse",[129,176,177,182],{},[144,178,179],{},[23,180,181],{},"--site \u003Corigin>",[144,183,184,185],{},"the site to act on, such as ",[23,186,187],{},"https:\u002F\u002Fmy-site.example",[129,189,190,195],{},[144,191,192],{},[23,193,194],{},"--token \u003Ctoken>",[144,196,197,198,201],{},"the owner token when it is not saved; ",[23,199,200],{},"DRUPFLARE_OWNER_TOKEN"," also works",[129,203,204,212],{},[144,205,206,107,209],{},[23,207,208],{},"--verbose",[23,210,211],{},"-v",[144,213,214],{},"print every command and request drangler makes",[129,216,217,225],{},[144,218,219,107,222],{},[23,220,221],{},"--quiet",[23,223,224],{},"-q",[144,226,227],{},"hide progress; the report still prints",[11,229,231],{"id":230},"common-jobs","🎬 Common Jobs",[46,233,234,238,239,242],{},[235,236,237],"strong",{},"Try Drupal on your laptop."," One command clones the worker, downloads Drupal and the PHP\ninterpreter, checks the result and serves it at ",[23,240,241],{},"http:\u002F\u002Flocalhost:8787",". Run it again and it reuses\nthe workspace.",[16,244,246],{"className":18,"code":245,"language":20,"meta":21,"style":21},"drangler dev\n",[23,247,248],{"__ignoreMap":21},[26,249,250],{"class":28,"line":29},[26,251,245],{},[46,253,254,257,258,261],{},[235,255,256],{},"Put it on Cloudflare."," Deploy the same workspace, then claim the site to get the administrator\npassword and the owner token. The token goes into your system keychain without asking, so later\ncommands find it; ",[23,259,260],{},"--save"," also writes it to your user config.",[16,263,265],{"className":18,"code":264,"language":20,"meta":21,"style":21},"drangler deploy\ndrangler site claim my-site.example --title \"My Site\" --save\n",[23,266,267,272],{"__ignoreMap":21},[26,268,269],{"class":28,"line":29},[26,270,271],{},"drangler deploy\n",[26,273,274],{"class":28,"line":35},[26,275,276],{},"drangler site claim my-site.example --title \"My Site\" --save\n",[46,278,279,282,283,286],{},[235,280,281],{},"Lost the owner token."," It is shown once, at the claim. If you can write to the Cloudflare account\nthe site is deployed on, you can get it back, and ",[23,284,285],{},"--store"," saves it to the keychain:",[16,288,290],{"className":18,"code":289,"language":20,"meta":21,"style":21},"drangler recover-token my-site.example --store\n",[23,291,292],{"__ignoreMap":21},[26,293,294],{"class":28,"line":29},[26,295,289],{},[46,297,298,301,302,304,305,308],{},[235,299,300],{},"Copy an existing site to see it running."," ",[23,303,110],{}," reads your current server with read-only\ncommands and brings up a working duplicate. ",[50,306,307],{},"The live site is not touched."," Available from drangler\n0.3; from 0.4 it finds the Drupal root itself, so the host is all it needs.",[16,310,312],{"className":18,"code":311,"language":20,"meta":21,"style":21},"drangler preview --host deploy@old.example\n",[23,313,314],{"__ignoreMap":21},[26,315,316],{"class":28,"line":29},[26,317,311],{},[46,319,320,321,324],{},"Every choice it makes has a flag, a ",[23,322,323],{},"DRANGLER_PREVIEW_\u003CNAME>"," variable and a prompt on a terminal, so\na script answers it without flags and a person answers it by typing. A flag wins over a variable, and\na question nobody can answer is refused rather than left waiting.",[16,326,328],{"className":18,"code":327,"language":20,"meta":21,"style":21},"DRANGLER_PREVIEW_CONFIRM=old.example DRANGLER_PREVIEW_FOLLOW=mymodule=my-org\u002Fmymodule \\\n  drangler preview --host deploy@old.example\n",[23,329,330,335],{"__ignoreMap":21},[26,331,332],{"class":28,"line":29},[26,333,334],{},"DRANGLER_PREVIEW_CONFIRM=old.example DRANGLER_PREVIEW_FOLLOW=mymodule=my-org\u002Fmymodule \\\n",[26,336,337],{"class":28,"line":35},[26,338,339],{},"  drangler preview --host deploy@old.example\n",[46,341,342,343,346,347,350,351,354,355,358,359,364],{},"Credentials the database holds, such as payment keys, mail logins and OAuth secrets, are blanked in\nthe duplicate and listed by name, so a copy cannot act as your live site. ",[23,344,345],{},"--carry-key \u003Cname>"," brings\none back. ",[23,348,349],{},"--follow module=owner\u002Frepo"," keeps a module current from git, ",[23,352,353],{},"--check \u003Cpath>"," compares more\npages with the live site, and ",[23,356,357],{},"--deploy"," puts the copy on your Cloudflare account behind a Cloudflare\nAccess login that only you can pass. The full list is in ",[360,361,363],"a",{"href":362},"\u002Fmigrate","Migrate",".",[46,366,367,301,370,373,374,377],{},[235,368,369],{},"Keep an eye on it.",[23,371,372],{},"status"," says what is deployed, ",[23,375,376],{},"health"," says whether it is serving. Both\nexit non-zero on a problem, so either can go in a monitor or a CI job.",[16,379,381],{"className":18,"code":380,"language":20,"meta":21,"style":21},"drangler status my-site.example\ndrangler health my-site.example\n",[23,382,383,388],{"__ignoreMap":21},[26,384,385],{"class":28,"line":29},[26,386,387],{},"drangler status my-site.example\n",[26,389,390],{"class":28,"line":35},[26,391,392],{},"drangler health my-site.example\n",[46,394,395,398],{},[235,396,397],{},"Upgrade."," Move the checkout to the latest release, deploy it, wait for the site to pick it up\nand run Drupal's database updates, in one step.",[16,400,402],{"className":18,"code":401,"language":20,"meta":21,"style":21},"drangler site upgrade my-site.example\n",[23,403,404],{"__ignoreMap":21},[26,405,406],{"class":28,"line":29},[26,407,401],{},[46,409,410,413],{},[235,411,412],{},"Get your data out."," The whole database, as a file you own.",[16,415,417],{"className":18,"code":416,"language":20,"meta":21,"style":21},"drangler migrate export --url my-site.example --out site.sql\n",[23,418,419],{"__ignoreMap":21},[26,420,421],{"class":28,"line":29},[26,422,416],{},[11,424,426],{"id":425},"set-up-and-run-locally","🧰 Set Up and Run Locally",[123,428,429,438],{},[126,430,431],{},[129,432,433,436],{},[132,434,435],{},"Command",[132,437,137],{},[139,439,440,449,459,469,487],{},[129,441,442,446],{},[144,443,444],{},[23,445,79],{},[144,447,448],{},"Checks the toolchain, the Cloudflare credential and the config",[129,450,451,456],{},[144,452,453],{},[23,454,455],{},"init",[144,457,458],{},"Connects this machine to a site and records where it went",[129,460,461,466],{},[144,462,463],{},[23,464,465],{},"build",[144,467,468],{},"Clones the worker and builds it into a deployable tree",[129,470,471,476],{},[144,472,473],{},[23,474,475],{},"validate",[144,477,478,479,482,483,486],{},"Everything that has to hold before ",[23,480,481],{},"dev"," or ",[23,484,485],{},"deploy"," works",[129,488,489,493],{},[144,490,491],{},[23,492,481],{},[144,494,495],{},"Builds if needed, checks, then runs Drupal on your machine",[11,497,499],{"id":498},"deploy-and-update","🚀 Deploy and Update",[123,501,502,510],{},[126,503,504],{},[129,505,506,508],{},[132,507,435],{},[132,509,137],{},[139,511,512,521,531,544,554,564],{},[129,513,514,518],{},[144,515,516],{},[23,517,485],{},[144,519,520],{},"Builds if needed, checks, then deploys to your account",[129,522,523,528],{},[144,524,525],{},[23,526,527],{},"site claim \u003Ctarget>",[144,529,530],{},"Mints the administrator password and the owner token",[129,532,533,538],{},[144,534,535],{},[23,536,537],{},"recover-token \u003Ctarget>",[144,539,540,541,543],{},"Gets a lost owner token back; ",[23,542,285],{}," keeps it",[129,545,546,551],{},[144,547,548],{},[23,549,550],{},"update [worker]",[144,552,553],{},"Moves a checkout to another version, and its worker",[129,555,556,561],{},[144,557,558],{},[23,559,560],{},"site upgrade \u003Ctarget>",[144,562,563],{},"Deploys, waits for the database replay, runs the updates",[129,565,566,571],{},[144,567,568],{},[23,569,570],{},"site updb \u003Ctarget>",[144,572,573],{},"Reads Drupal's update chain and drives one step of it",[11,575,577],{"id":576},"operate-a-site","🩺 Operate a Site",[123,579,580,588],{},[126,581,582],{},[129,583,584,586],{},[132,585,435],{},[132,587,137],{},[139,589,590,600,610,620,630,640,650,664,674],{},[129,591,592,597],{},[144,593,594],{},[23,595,596],{},"status \u003Ctarget>",[144,598,599],{},"What is deployed: plan, version, claim state",[129,601,602,607],{},[144,603,604],{},[23,605,606],{},"health \u003Ctarget>",[144,608,609],{},"Probes a site and reports which cache tier answered",[129,611,612,617],{},[144,613,614],{},[23,615,616],{},"heal \u003Ctarget>",[144,618,619],{},"Reports the repair ladder and performs the repairs it can",[129,621,622,627],{},[144,623,624],{},[23,625,626],{},"reconcile \u003Ctarget>",[144,628,629],{},"What a site still owes the shipped release, and drives it",[129,631,632,637],{},[144,633,634],{},[23,635,636],{},"sweep \u003Ctarget>",[144,638,639],{},"How much of the site is cached, and what the scheduler decided",[129,641,642,647],{},[144,643,644],{},[23,645,646],{},"site invalidate",[144,648,649],{},"Purges cached pages by tag, or retires them all at once",[129,651,652,657],{},[144,653,654],{},[23,655,656],{},"setup cloudflare \u003Ctarget>",[144,658,659,660,663],{},"Whether a Cloudflare account grant is connected; ",[23,661,662],{},"--disconnect"," revokes it",[129,665,666,671],{},[144,667,668],{},[23,669,670],{},"setup mail \u003Ctarget>",[144,672,673],{},"Sending-domain onboarding: what is set up, and takes the next step",[129,675,676,681],{},[144,677,678],{},[23,679,680],{},"setup identity \u003Ctarget>",[144,682,683],{},"The OpenID Connect provider: read it, set it or clear it",[11,685,686],{"id":106},"🚚 Migrate",[123,688,689,697],{},[126,690,691],{},[129,692,693,695],{},[132,694,435],{},[132,696,137],{},[139,698,699,708,718,728,738,748,758,768,778,788,798],{},[129,700,701,705],{},[144,702,703],{},[23,704,110],{},[144,706,707],{},"Surveys, converts and brings up a working duplicate of a site",[129,709,710,715],{},[144,711,712],{},[23,713,714],{},"migrate survey",[144,716,717],{},"Reads a server-hosted Drupal over SSH, read-only",[129,719,720,725],{},[144,721,722],{},[23,723,724],{},"migrate eligibility",[144,726,727],{},"Can this site move today, and what would have to change",[129,729,730,735],{},[144,731,732],{},[23,733,734],{},"migrate plan",[144,736,737],{},"Scores a survey and orders the work, in either direction",[129,739,740,745],{},[144,741,742],{},[23,743,744],{},"migrate convert",[144,746,747],{},"Converts a SQL dump between MySQL and SQLite",[129,749,750,755],{},[144,751,752],{},[23,753,754],{},"migrate install",[144,756,757],{},"Lands a converted database in a workspace, with a backup",[129,759,760,765],{},[144,761,762],{},[23,763,764],{},"migrate restore",[144,766,767],{},"Puts a backup set back where it came from",[129,769,770,775],{},[144,771,772],{},[23,773,774],{},"migrate export",[144,776,777],{},"Pulls a deployed site's database out",[129,779,780,785],{},[144,781,782],{},[23,783,784],{},"migrate delta",[144,786,787],{},"The second pass of content that changed during a move",[129,789,790,795],{},[144,791,792],{},[23,793,794],{},"migrate cutover",[144,796,797],{},"The checklist a person confirms while traffic switches",[129,799,800,805],{},[144,801,802],{},[23,803,804],{},"migrate files",[144,806,807],{},"Writes the managed files in a dump back onto a filesystem",[11,809,811],{"id":810},"develop-a-module","🧩 Develop a Module",[46,813,814,817],{},[23,815,816],{},"drangler modify"," puts a custom module or theme on a live site one revision at a time. Each\nrevision is stored on the site itself, so sending a one-file change sends one file and a rollback\nsends nothing.",[46,819,820,823,824,827],{},[235,821,822],{},"1. Link the project to a site."," Run it inside the module's directory. It detects the project\nshape from the file it names, and writes ",[23,825,826],{},"drangler.json"," for the project and the owner token to your\nuser config.",[16,829,833],{"className":830,"code":831,"language":832,"meta":21,"style":21},"language-console shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","$ cd ~\u002Fwork\u002Fevents\n$ drangler modify init --site https:\u002F\u002Fmy-site.example\ndetected      module-project (\u002Fwork\u002Fevents\u002Fevents.info.yml)\npackage       events\nmounts to     modules\u002Fcustom\u002Fevents\nfiles         24\nsite          https:\u002F\u002Fmy-site.example (claimed)\nowner token   set\n","console",[23,834,835,840,845,850,856,862,868,874],{"__ignoreMap":21},[26,836,837],{"class":28,"line":29},[26,838,839],{},"$ cd ~\u002Fwork\u002Fevents\n",[26,841,842],{"class":28,"line":35},[26,843,844],{},"$ drangler modify init --site https:\u002F\u002Fmy-site.example\n",[26,846,847],{"class":28,"line":41},[26,848,849],{},"detected      module-project (\u002Fwork\u002Fevents\u002Fevents.info.yml)\n",[26,851,853],{"class":28,"line":852},4,[26,854,855],{},"package       events\n",[26,857,859],{"class":28,"line":858},5,[26,860,861],{},"mounts to     modules\u002Fcustom\u002Fevents\n",[26,863,865],{"class":28,"line":864},6,[26,866,867],{},"files         24\n",[26,869,871],{"class":28,"line":870},7,[26,872,873],{},"site          https:\u002F\u002Fmy-site.example (claimed)\n",[26,875,877],{"class":28,"line":876},8,[26,878,879],{},"owner token   set\n",[46,881,882,885],{},[235,883,884],{},"2. Check it before anything leaves your machine."," Every PHP file is linted with the PHP you\nname, and paths that would collide on the site are caught here.",[16,887,889],{"className":830,"code":888,"language":832,"meta":21,"style":21},"$ drangler modify check --php php\npackage     events\nmounts to   modules\u002Fcustom\u002Fevents\nfiles       24 kept, 3 skipped\nlint        24 of 24 ok (php 8.4.12)\npaths       no collision inside this project\n",[23,890,891,896,901,906,911,916],{"__ignoreMap":21},[26,892,893],{"class":28,"line":29},[26,894,895],{},"$ drangler modify check --php php\n",[26,897,898],{"class":28,"line":35},[26,899,900],{},"package     events\n",[26,902,903],{"class":28,"line":41},[26,904,905],{},"mounts to   modules\u002Fcustom\u002Fevents\n",[26,907,908],{"class":28,"line":852},[26,909,910],{},"files       24 kept, 3 skipped\n",[26,912,913],{"class":28,"line":858},[26,914,915],{},"lint        24 of 24 ok (php 8.4.12)\n",[26,917,918],{"class":28,"line":864},[26,919,920],{},"paths       no collision inside this project\n",[46,922,923,926],{},[235,924,925],{},"3. Upload."," Only the files the site does not already hold are sent. The site then boots Drupal\nwith the new code before making it live.",[16,928,930],{"className":830,"code":929,"language":832,"meta":21,"style":21},"$ drangler modify upload --message \"add the event listing\"\npackage     events\nplan        24 files, 2 not on the site, 22 already there\nuploading   2 blob(s), 6.1 kB, 1 batch(es)\ncommit      rev 9f2c1ab4\nverify      kernel booted\n",[23,931,932,937,941,946,951,956],{"__ignoreMap":21},[26,933,934],{"class":28,"line":29},[26,935,936],{},"$ drangler modify upload --message \"add the event listing\"\n",[26,938,939],{"class":28,"line":35},[26,940,900],{},[26,942,943],{"class":28,"line":41},[26,944,945],{},"plan        24 files, 2 not on the site, 22 already there\n",[26,947,948],{"class":28,"line":852},[26,949,950],{},"uploading   2 blob(s), 6.1 kB, 1 batch(es)\n",[26,952,953],{"class":28,"line":858},[26,954,955],{},"commit      rev 9f2c1ab4\n",[26,957,958],{"class":28,"line":864},[26,959,960],{},"verify      kernel booted\n",[46,962,963,966,967,970,971,974],{},[23,964,965],{},"verify kernel booted"," is the line that matters. If the new code would stop Drupal from booting,\n",[50,968,969],{},"the site restores the previous revision on its own"," and the command exits ",[23,972,973],{},"1"," naming the error.",[46,976,977,980],{},[235,978,979],{},"4. Turn it on",", the first time only:",[16,982,984],{"className":18,"code":983,"language":20,"meta":21,"style":21},"drangler modify enable events\n",[23,985,986],{"__ignoreMap":21},[26,987,988],{"class":28,"line":29},[26,989,983],{},[46,991,992,995],{},[235,993,994],{},"5. Undo if you need to."," The last five revisions stay on the site.",[16,997,999],{"className":18,"code":998,"language":20,"meta":21,"style":21},"drangler modify revisions        # newest first\ndrangler modify rollback --yes   # back to the one before\ndrangler modify activate 3d81ee07 --yes\n",[23,1000,1001,1006,1011],{"__ignoreMap":21},[26,1002,1003],{"class":28,"line":29},[26,1004,1005],{},"drangler modify revisions        # newest first\n",[26,1007,1008],{"class":28,"line":35},[26,1009,1010],{},"drangler modify rollback --yes   # back to the one before\n",[26,1012,1013],{"class":28,"line":41},[26,1014,1015],{},"drangler modify activate 3d81ee07 --yes\n",[123,1017,1018,1027],{},[126,1019,1020],{},[129,1021,1022,1024],{},[132,1023,435],{},[132,1025,1026],{},"Use it to",[139,1028,1029,1039,1053,1063,1073,1083,1093],{},[129,1030,1031,1036],{},[144,1032,1033],{},[23,1034,1035],{},"modify status",[144,1037,1038],{},"compare what is live on the site with what is on disk",[129,1040,1041,1046],{},[144,1042,1043],{},[23,1044,1045],{},"modify diff",[144,1047,1048,1049,1052],{},"list the files an upload would send; exits ",[23,1050,1051],{},"3"," if any",[129,1054,1055,1060],{},[144,1056,1057],{},[23,1058,1059],{},"modify release --tag",[144,1061,1062],{},"upload from a tagged commit, refusing uncommitted edits",[129,1064,1065,1070],{},[144,1066,1067],{},[23,1068,1069],{},"modify list",[144,1071,1072],{},"uploaded packages, and the git repositories the site follows",[129,1074,1075,1080],{},[144,1076,1077],{},[23,1078,1079],{},"modify require \u003Cname>",[144,1081,1082],{},"install contrib from drupal.org or Packagist",[129,1084,1085,1090],{},[144,1086,1087],{},[23,1088,1089],{},"modify drop \u003Crev>",[144,1091,1092],{},"delete an old revision and the files only it used",[129,1094,1095,1100],{},[144,1096,1097],{},[23,1098,1099],{},"modify dev",[144,1101,1102,1103,1106],{},"run ",[23,1104,1105],{},"drangler dev"," with this project mounted, live-reloading",[46,1108,1109,1110,1113,1114,1117,1118,364],{},"Contrib modules come from the registry rather than an upload:\n",[23,1111,1112],{},"drangler modify require drupal\u002Fpathauto drupal\u002Fmetatag --enable",". Pin a version with\n",[23,1115,1116],{},"--constraint",", as in ",[23,1119,1120],{},"drangler modify require phpmailer\u002Fphpmailer --constraint v6.12.0",[11,1122,1124],{"id":1123},"️-cloudflare-helpers","☁️ Cloudflare Helpers",[123,1126,1127,1135],{},[126,1128,1129],{},[129,1130,1131,1133],{},[132,1132,435],{},[132,1134,137],{},[139,1136,1137,1147,1157,1167,1177,1187],{},[129,1138,1139,1144],{},[144,1140,1141],{},[23,1142,1143],{},"cf whoami",[144,1145,1146],{},"Which Cloudflare credential drangler would use",[129,1148,1149,1154],{},[144,1150,1151],{},[23,1152,1153],{},"cf workers",[144,1155,1156],{},"Lists the account's workers against a saved list",[129,1158,1159,1164],{},[144,1160,1161],{},[23,1162,1163],{},"cf versions \u003Cworker>",[144,1165,1166],{},"The versions the platform still holds",[129,1168,1169,1174],{},[144,1170,1171],{},[23,1172,1173],{},"cf rollback \u003Cworker> \u003Cv>",[144,1175,1176],{},"Points a worker back at a version it already holds",[129,1178,1179,1184],{},[144,1180,1181],{},[23,1182,1183],{},"cf secret \u003Cworker> …",[144,1185,1186],{},"Lists, sets or removes a secret without printing it",[129,1188,1189,1194],{},[144,1190,1191],{},[23,1192,1193],{},"secrets scan \u003Cpaths…>",[144,1195,1196],{},"Finds credentials in a dump or a tree, masked",[11,1198,1200],{"id":1199},"️-configuration","⚙️ Configuration",[123,1202,1203,1211],{},[126,1204,1205],{},[129,1206,1207,1209],{},[132,1208,435],{},[132,1210,137],{},[139,1212,1213,1223,1233],{},[129,1214,1215,1220],{},[144,1216,1217],{},[23,1218,1219],{},"config where",[144,1221,1222],{},"Which file supplied each setting",[129,1224,1225,1230],{},[144,1226,1227],{},[23,1228,1229],{},"config check \u003Cfile>",[144,1231,1232],{},"Scores a wrangler config against known-bad setups",[129,1234,1235,1240],{},[144,1236,1237],{},[23,1238,1239],{},"config levers \u003Cfile>",[144,1241,1242],{},"The optional features a config declares, and each state",[46,1244,1245,1246,1248,1249,1251,1252,1254,1255,364],{},"Settings resolve from a flag, then an environment variable, then a ",[23,1247,826],{}," in the nearest\nparent directory, then your user config. The owner token never goes in ",[23,1250,826],{},", which is\ncommitted. ",[23,1253,117],{}," stores it in the system keychain, and a machine with no keychain falls back\nto your user config with ",[50,1256,1257],{},"owner-only permissions",[46,1259,1260,1261,1264,1265,1267,1268,1271],{},"Every command finds the owner token in this order: ",[23,1262,1263],{},"--token",", then ",[23,1266,200],{},", then the\nkeychain, then the global config. A token already in the config is not copied into the keychain on\nfirst use; ",[23,1269,1270],{},"drangler recover-token --store"," is the explicit way to move one.",[1273,1274,1276],"h3",{"id":1275},"recovering-the-owner-token","Recovering the Owner Token",[46,1278,1279,1282,1283,1286,1287,1290,1291,1294],{},[23,1280,1281],{},"recover-token"," proves you can write to the site's Cloudflare account instead of presenting the\ntoken. drangler writes a one-minute, single-use proof into the deployment's ",[23,1284,1285],{},"CONFIG_KV"," namespace\nthrough your own wrangler login, then presents it to the site's ",[23,1288,1289],{},"\u002Frecover-token"," route, which checks\nit, spends it and answers with the token. ",[50,1292,1293],{},"Recovery does not rotate the token",", so anything else\nholding it keeps working.",[1296,1297,1298,1314,1331,1334,1343],"ul",{},[1299,1300,1301,1302,1305,1306,1309,1310,1313],"li",{},"It needs a ",[23,1303,1304],{},"wrangler login",", or ",[23,1307,1308],{},"CLOUDFLARE_API_TOKEN",", that can edit KV. ",[23,1311,1312],{},"--account"," picks the\naccount when the login reaches several.",[1299,1315,1316,1317,1320,1321,1323,1324,1327,1328,1330],{},"The namespace is ",[23,1318,1319],{},"--kv-namespace",", else the ",[23,1322,1285],{}," binding in a ",[23,1325,1326],{},"wrangler.jsonc"," in the\nworking directory, else the one account namespace with ",[23,1329,1285],{}," in its title.",[1299,1332,1333],{},"A new proof can take up to a minute to reach the site. drangler retries with growing pauses, at\nmost eight times, because the site refuses an address after 12 failed attempts in a minute.",[1299,1335,1336,1338,1339,1342],{},[23,1337,158],{}," puts the token in the ",[23,1340,1341],{},"ownerToken"," field.",[1299,1344,1345,1346,1349],{},"It is not ",[23,1347,1348],{},"drangler recover",", which reads the 30-day database recovery window.",[11,1351,1353],{"id":1352},"reading-the-output","🚦 Reading the Output",[46,1355,1356,1357,1359,1360,1363],{},"Every command prints a short two-column report on stdout. ",[23,1358,158],{}," prints ",[50,1361,1362],{},"the same fields as one JSON object",", and progress goes to stderr, so piping a command never mixes the two.",[123,1365,1366,1376],{},[126,1367,1368],{},[129,1369,1370,1373],{},[132,1371,1372],{},"Exit Code",[132,1374,1375],{},"Meaning",[139,1377,1378,1388,1397,1407],{},[129,1379,1380,1385],{},[144,1381,1382],{},[23,1383,1384],{},"0",[144,1386,1387],{},"done",[129,1389,1390,1394],{},[144,1391,1392],{},[23,1393,973],{},[144,1395,1396],{},"the check could not run, such as a network failure",[129,1398,1399,1404],{},[144,1400,1401],{},[23,1402,1403],{},"2",[144,1405,1406],{},"the input was wrong, such as a missing flag",[129,1408,1409,1413],{},[144,1410,1411],{},[23,1412,1051],{},[144,1414,1415],{},"it ran and found something: a blocker, a secret, a difference",[46,1417,1418,1420],{},[23,1419,376],{}," gives one verdict:",[123,1422,1423,1433],{},[126,1424,1425],{},[129,1426,1427,1430],{},[132,1428,1429],{},"Verdict",[132,1431,1432],{},"What it means",[139,1434,1435,1445,1455,1465,1475],{},[129,1436,1437,1442],{},[144,1438,1439],{},[23,1440,1441],{},"ok",[144,1443,1444],{},"the site answered normally",[129,1446,1447,1452],{},[144,1448,1449],{},[23,1450,1451],{},"warming",[144,1453,1454],{},"a new site unpacking its database, or a page queued to render; wait",[129,1456,1457,1462],{},[144,1458,1459],{},[23,1460,1461],{},"degraded",[144,1463,1464],{},"an error status, or the site is shedding load on purpose to stay in its limits",[129,1466,1467,1472],{},[144,1468,1469],{},[23,1470,1471],{},"unreachable",[144,1473,1474],{},"nothing answered",[129,1476,1477,1482],{},[144,1478,1479],{},[23,1480,1481],{},"not-drupflare",[144,1483,1484],{},"something answered, and it is not a Drupflare site",[46,1486,1487,1488,1491,1492,1495,1496,1499,1500,1503,1504,1507],{},"Its ",[23,1489,1490],{},"tier"," line says what answered: ",[23,1493,1494],{},"EDGE"," is Cloudflare's cache in front of the site, ",[23,1497,1498],{},"HIT"," is the\nsite's own stored copy, ",[23,1501,1502],{},"KV"," is a copy shared across locations, and ",[23,1505,1506],{},"RENDER"," means Drupal built the\npage for this request.",[11,1509,1511],{"id":1510},"️-what-writes","✍️ What Writes",[46,1513,1514,107,1516,107,1518,61,1521,1523,1524,1527,1528,107,1531,107,1534,1537,1538,61,1541,1544,1545,1547,1548,1550,1551,1553,1554,1557,1558,364],{},[23,1515,465],{},[23,1517,754],{},[23,1519,1520],{},"update",[23,1522,110],{}," write to a local workspace; ",[23,1525,1526],{},"preview --deploy","\nalso creates a Cloudflare Access application and deploys the duplicate as a new worker, which needs\na Cloudflare API token that can edit Access. ",[23,1529,1530],{},"site",[23,1532,1533],{},"heal",[23,1535,1536],{},"modify",",\n",[23,1539,1540],{},"reconcile --run",[23,1542,1543],{},"sweep --run"," change a live site, so each needs the owner token, and anything\nthat changes what visitors see also needs ",[23,1546,168],{},". ",[23,1549,1281],{}," writes one record to the site's\n",[23,1552,1285],{}," namespace that expires in a minute, and needs your Cloudflare login instead of the token. ",[50,1555,1556],{},"Nothing in drangler deletes a file or a directory."," The full reference is in the\n",[360,1559,1563],{"href":1560,"rel":1561},"https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fdrangler#readme",[1562],"nofollow","drangler README",[1565,1566,1567],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":21,"searchDepth":35,"depth":35,"links":1569},[1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1582,1583],{"id":13,"depth":35,"text":14},{"id":72,"depth":35,"text":73},{"id":230,"depth":35,"text":231},{"id":425,"depth":35,"text":426},{"id":498,"depth":35,"text":499},{"id":576,"depth":35,"text":577},{"id":106,"depth":35,"text":686},{"id":810,"depth":35,"text":811},{"id":1123,"depth":35,"text":1124},{"id":1199,"depth":35,"text":1200,"children":1580},[1581],{"id":1275,"depth":41,"text":1276},{"id":1352,"depth":35,"text":1353},{"id":1510,"depth":35,"text":1511},[],"A quick dictionary of drangler, the command-line tool that deploys, operates and migrates Drupflare sites.","md","drangler, in One Page","drangler is the command-line tool for a Drupflare site's whole life: stand one up, keep it healthy, develop a module against it, and move sites on or off. Every command takes --json and prints the same object its text is built from.",{},true,"\u002Fdrangler",[],{"title":5,"description":1585},[],"Kv-5FcuUpYzOZ6pOVUlI2LpHvDAjSFsFO7AeNhNfu64",1793683736804]