[{"data":1,"prerenderedAt":3205},["ShallowReactive",2],{"docs-nav":3,"docs:\u002Fdocs\u002Fremote-daemon":139},[4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136],{"path":5,"title":6},"\u002Fdocs\u002Faccess-gating","Protect a local route",{"path":8,"title":9},"\u002Fdocs\u002Farchitecture\u002Fsecurity-and-trust","Security and trust",{"path":11,"title":12},"\u002Fdocs\u002Fconcepts\u002Fbuilds-and-deployments","Build and deploy",{"path":14,"title":15},"\u002Fdocs\u002Fconcepts\u002Fconfiguration-ownership","Where configuration belongs",{"path":17,"title":18},"\u002Fdocs\u002Fconcepts\u002Fenvironments-and-secrets","Environment values and secrets",{"path":20,"title":21},"\u002Fdocs\u002Fconcepts\u002Fmental-model","How projects fit together",{"path":23,"title":24},"\u002Fdocs\u002Fconcepts\u002Fplans-and-apply","Plans and apply",{"path":26,"title":27},"\u002Fdocs\u002Fconcepts\u002Ftargets-and-runtimes","Choose a target and runtime",{"path":29,"title":30},"\u002Fdocs\u002Fcontribute\u002Freleases","Publish a release",{"path":32,"title":33},"\u002Fdocs\u002Fdevelopment-mounts","Development mounts",{"path":35,"title":36},"\u002Fdocs\u002Fgetting-started","Getting started",{"path":38,"title":39},"\u002Fdocs\u002Fhelp\u002Ffaq","FAQ",{"path":41,"title":42},"\u002Fdocs\u002Fhelp\u002Ftroubleshooting","Troubleshooting",{"path":44,"title":45},"\u002Fdocs\u002Fhow-to\u002Fdeploy-production","Prepare a deployment environment",{"path":47,"title":48},"\u002Fdocs\u002Fhow-to\u002Fswarm-cluster","Run on a multi-node Swarm",{"path":50,"title":51},"\u002Fdocs","mm documentation",{"path":53,"title":54},"\u002Fdocs\u002Fjobs-and-schedules","Jobs and schedules",{"path":56,"title":57},"\u002Fdocs\u002Flocal-dashboard","Local dashboard",{"path":59,"title":60},"\u002Fdocs\u002Flocal-https","Local HTTPS",{"path":62,"title":63},"\u002Fdocs\u002Flocal-services","Local services",{"path":65,"title":66},"\u002Fdocs\u002Fmcp","Let an AI agent work on your project",{"path":68,"title":69},"\u002Fdocs\u002Fmulti-project-hosts","Run multiple projects on one host",{"path":71,"title":72},"\u002Fdocs\u002Fonboarding","Set up a project",{"path":74,"title":75},"\u002Fdocs\u002Fopenbao","Use OpenBao",{"path":77,"title":78},"\u002Fdocs\u002Fproject\u002Fcontributing","Contributing",{"path":80,"title":81},"\u002Fdocs\u002Fproject\u002Flicense","License",{"path":83,"title":84},"\u002Fdocs\u002Fproject\u002Fsecurity","Security",{"path":86,"title":87},"\u002Fdocs\u002Freference\u002Fcapability-matrix","Supported features and platforms",{"path":89,"title":90},"\u002Fdocs\u002Freference\u002Fcli","CLI command reference",{"path":92,"title":93},"\u002Fdocs\u002Freference\u002Fconfiguration","Configuration",{"path":95,"title":96},"\u002Fdocs\u002Freference\u002Fenvironment-variables","Environment variables",{"path":98,"title":99},"\u002Fdocs\u002Freference\u002Ferrors-and-exit-codes","Errors and exit codes",{"path":101,"title":102},"\u002Fdocs\u002Freference\u002Fknown-issues","Known limitations",{"path":104,"title":105},"\u002Fdocs\u002Fremote-access","Manage remote access",{"path":107,"title":108},"\u002Fdocs\u002Fremote-daemon","Set up a remote host",{"path":110,"title":111},"\u002Fdocs\u002Fruntime-observability","Status, logs, and commands",{"path":113,"title":114},"\u002Fdocs\u002Fsecret-delivery","Pass secrets to your app",{"path":116,"title":117},"\u002Fdocs\u002Fsecret-providers","Choose a secret store",{"path":119,"title":120},"\u002Fdocs\u002Fstart\u002Finstall","Install mm",{"path":122,"title":123},"\u002Fdocs\u002Fterminal-ui","Terminal UI",{"path":125,"title":126},"\u002Fdocs\u002Ftutorials\u002Fdotnet-web-api",".NET web API example",{"path":128,"title":129},"\u002Fdocs\u002Ftutorials\u002Ffirst-local-app","First local app",{"path":131,"title":132},"\u002Fdocs\u002Ftutorials\u002Flaravel-rust","Laravel and Rust example",{"path":134,"title":135},"\u002Fdocs\u002Ftutorials\u002Fnode-fullstack","Node full stack example",{"path":137,"title":138},"\u002Fdocs\u002Fvault","Use HashiCorp Vault",{"id":140,"title":108,"body":141,"description":3198,"extension":3199,"meta":3200,"navigation":3201,"path":107,"seo":3202,"stem":3203,"__hash__":3204},"docs\u002Fdocs\u002Fremote-daemon.md",{"type":142,"value":143,"toc":3182},"minimark",[144,160,170,175,182,187,190,211,214,305,309,315,358,365,453,529,536,591,594,675,678,684,687,702,706,719,908,941,944,979,983,993,1088,1094,1130,1135,1149,1270,1281,1288,1292,1304,1681,1694,1698,1709,1869,1875,1976,1983,1989,2022,2029,2044,2048,2051,2121,2142,2453,2465,2478,2484,2498,2501,2505,2573,2583,2598,2602,2613,2651,2665,2739,2757,2760,2767,2836,2849,2853,2864,2867,2891,2915,2918,2946,3094,3118,3122,3129,3150,3178],[145,146,147,148,152,153,156,157,159],"p",{},"Remote hosts run ",[149,150,151],"code",{},"mmd",", the mm daemon. Developers connect over SSH to a single\nlocked ",[149,154,155],{},"mm"," account whose only command is the relay to ",[149,158,151],{},". The daemon checks\neach request against the project's users file and runs the operation itself.\nNobody needs a shell, sudo, or the host's Docker socket.",[161,162,168],"pre",{"className":163,"code":165,"language":166,"meta":167},[164],"language-text","mm → SSH (account mm, relay only) → mmd → container runtime\n","text","",[149,169,165],{"__ignoreMap":167},[145,171,172,174],{},[149,173,151],{}," fetches each deployed commit from the project's git origin with its own\nread-only key, so a deploy runs exactly what is committed.",[145,176,177,178,181],{},"This is an advanced setup. ",[149,179,180],{},"mm target setup"," installs everything\non the host in one reviewed step; you need root on the host only for that step.",[183,184,186],"h2",{"id":185},"requirements","Requirements",[145,188,189],{},"On the host:",[191,192,193,201],"ul",{},[194,195,196,197,200],"li",{},"Debian or Ubuntu with systemd and OpenSSH, and an admin account that can\nuse ",[149,198,199],{},"sudo",". Setup installs Docker Engine from Docker's own repository.",[194,202,203,206,207,210],{},[149,204,205],{},"\u002Fetc\u002Fssh\u002Fsshd_config"," includes ",[149,208,209],{},"\u002Fetc\u002Fssh\u002Fsshd_config.d\u002F*.conf"," before its\nown settings. Debian and Ubuntu do this by default.",[145,212,213],{},"On your machine:",[191,215,216,234,245,266],{},[194,217,218,221,222,225,226,229,230,233],{},[149,219,220],{},"ansible-playbook"," (in ",[149,223,224],{},"\u002Fusr\u002Fbin"," or ",[149,227,228],{},"\u002Fusr\u002Flocal\u002Fbin",") and ",[149,231,232],{},"ssh-keyscan",".",[194,235,236,237,240,241,244],{},"The admin account's key: either the target's own key file (",[149,238,239],{},"mm target add --identity-file","), or an SSH agent (",[149,242,243],{},"SSH_AUTH_SOCK",") holding the key.",[194,246,247,249,250,252,253,255,256,259,260,262,263,233],{},[149,248,155],{}," and ",[149,251,151],{}," built for the host, from the same release. A Linux ",[149,254,155],{},"\ninstalled with ",[149,257,258],{},"--with-daemon"," has ",[149,261,151],{}," beside it, and setup uses that pair\nby default. Otherwise, extract the host's Linux release archive and pass its\ndirectory with ",[149,264,265],{},"--host-binaries",[194,267,268,269],{},"A break-glass key: an Ed25519 key pair that is admin on every project the\nhost runs. Keep the private half offline:",[161,270,274],{"className":271,"code":272,"language":273,"meta":167,"style":167},"language-sh shiki shiki-themes mm","ssh-keygen -t ed25519 -C ops@offline -f ops-recovery\n","sh",[149,275,276],{"__ignoreMap":167},[277,278,281,285,289,293,296,299,302],"span",{"class":279,"line":280},"line",1,[277,282,284],{"class":283},"svrjQ","ssh-keygen",[277,286,288],{"class":287},"sbRf1"," -t",[277,290,292],{"class":291},"sv8ni"," ed25519",[277,294,295],{"class":287}," -C",[277,297,298],{"class":291}," ops@offline",[277,300,301],{"class":287}," -f",[277,303,304],{"class":291}," ops-recovery\n",[183,306,308],{"id":307},"set-up-the-host","Set up the host",[145,310,311,312,314],{},"Add the host as a private target that logs in as the admin account. Only setup\nuses it; developers connect through the repo's target as ",[149,313,155],{},":",[161,316,318],{"className":271,"code":317,"language":273,"meta":167,"style":167},"mm target add prod-eu-setup --host prod-eu.example.com --user admin \\\n  --host-key-fingerprint SHA256:REVIEWED_HOST_KEY_FINGERPRINT\n",[149,319,320,349],{"__ignoreMap":167},[277,321,322,324,327,330,333,336,339,342,345],{"class":279,"line":280},[277,323,155],{"class":283},[277,325,326],{"class":291}," target",[277,328,329],{"class":291}," add",[277,331,332],{"class":291}," prod-eu-setup",[277,334,335],{"class":287}," --host",[277,337,338],{"class":291}," prod-eu.example.com",[277,340,341],{"class":287}," --user",[277,343,344],{"class":291}," admin",[277,346,348],{"class":347},"s1Wbj"," \\\n",[277,350,352,355],{"class":279,"line":351},2,[277,353,354],{"class":287},"  --host-key-fingerprint",[277,356,357],{"class":291}," SHA256:REVIEWED_HOST_KEY_FINGERPRINT\n",[145,359,360,361,364],{},"Plan the setup for the environment the host runs. ",[149,362,363],{},"--origin"," adds the remote\naccess layer to the plan:",[161,366,368],{"className":271,"code":367,"language":273,"meta":167,"style":167},"mm --environment production --target prod-eu-setup --runtime docker-compose \\\n  target setup plan \\\n  --origin git@github.com:acme\u002Fshop.git \\\n  --origin-host-key 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl' \\\n  --break-glass \"ops-recovery=$(cat ops-recovery.pub)\"\n",[149,369,370,393,406,417,428],{"__ignoreMap":167},[277,371,372,374,377,380,383,385,388,391],{"class":279,"line":280},[277,373,155],{"class":283},[277,375,376],{"class":287}," --environment",[277,378,379],{"class":291}," production",[277,381,382],{"class":287}," --target",[277,384,332],{"class":291},[277,386,387],{"class":287}," --runtime",[277,389,390],{"class":291}," docker-compose",[277,392,348],{"class":347},[277,394,395,398,401,404],{"class":279,"line":351},[277,396,397],{"class":291},"  target",[277,399,400],{"class":291}," setup",[277,402,403],{"class":291}," plan",[277,405,348],{"class":347},[277,407,409,412,415],{"class":279,"line":408},3,[277,410,411],{"class":287},"  --origin",[277,413,414],{"class":291}," git@github.com:acme\u002Fshop.git",[277,416,348],{"class":347},[277,418,420,423,426],{"class":279,"line":419},4,[277,421,422],{"class":287},"  --origin-host-key",[277,424,425],{"class":291}," 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl'",[277,427,348],{"class":347},[277,429,431,434,437,441,444,447,450],{"class":279,"line":430},5,[277,432,433],{"class":287},"  --break-glass",[277,435,436],{"class":291}," \"ops-recovery=",[277,438,440],{"class":439},"sIbZ0","$(",[277,442,443],{"class":283},"cat",[277,445,446],{"class":291}," ops-recovery.pub",[277,448,449],{"class":439},")",[277,451,452],{"class":291},"\"\n",[454,455,456,469],"table",{},[457,458,459],"thead",{},[460,461,462,466],"tr",{},[463,464,465],"th",{},"Flag",[463,467,468],{},"Meaning",[470,471,472,482,499,512],"tbody",{},[460,473,474,479],{},[475,476,477],"td",{},[149,478,363],{},[475,480,481],{},"Git URL the host fetches each deployed commit from, with its own read-only key: HTTPS, SSH, or an absolute path on the host.",[460,483,484,489],{},[475,485,486],{},[149,487,488],{},"--origin-host-key",[475,490,491,492,495,496,498],{},"The git server's SSH host key, ",[149,493,494],{},"\u003Ctype> \u003Cbase64>",", for SSH origins. Copy it from the git host's published keys; ",[149,497,151],{}," pins it.",[460,500,501,506],{},[475,502,503],{},[149,504,505],{},"--break-glass",[475,507,508,511],{},[149,509,510],{},"NAME=PUBLIC_KEY",". Always works, even when a users file locks everyone out. Repeat for more keys.",[460,513,514,518],{},[475,515,516],{},[149,517,265],{},[475,519,520,521,249,523,525,526,528],{},"Directory with ",[149,522,155],{},[149,524,151],{}," for the host. Default: the directory of the ",[149,527,155],{}," you are running.",[145,530,531,532,535],{},"The plan lists the ",[149,533,534],{},"install-access"," step, the origin, the break-glass keys,\nand the path and digest of each binary it will install. Review it, then run\nthe apply command it prints. That command repeats the same flags, because the\nplan hash covers them:",[161,537,539],{"className":271,"code":538,"language":273,"meta":167,"style":167},"mm --environment production --target prod-eu-setup --runtime docker-compose \\\n  target setup apply --plan PLAN_HASH --origin … --break-glass … --host-binaries …\n",[149,540,541,559],{"__ignoreMap":167},[277,542,543,545,547,549,551,553,555,557],{"class":279,"line":280},[277,544,155],{"class":283},[277,546,376],{"class":287},[277,548,379],{"class":291},[277,550,382],{"class":287},[277,552,332],{"class":291},[277,554,387],{"class":287},[277,556,390],{"class":291},[277,558,348],{"class":347},[277,560,561,563,565,568,571,574,577,580,583,585,588],{"class":279,"line":351},[277,562,397],{"class":291},[277,564,400],{"class":291},[277,566,567],{"class":291}," apply",[277,569,570],{"class":287}," --plan",[277,572,573],{"class":291}," PLAN_HASH",[277,575,576],{"class":287}," --origin",[277,578,579],{"class":291}," …",[277,581,582],{"class":287}," --break-glass",[277,584,579],{"class":291},[277,586,587],{"class":287}," --host-binaries",[277,589,590],{"class":291}," …\n",[145,592,593],{},"Setup first runs the project's bootstrap playbook, which installs the container\nruntime. Then it runs mm's own access playbook, which:",[595,596,597,604,618,632,642,653,666],"ol",{},[194,598,599,600,603],{},"Installs ",[149,601,602],{},"git"," and the OpenSSH server.",[194,605,599,606,608,609,249,612,608,614,617],{},[149,607,155],{}," at ",[149,610,611],{},"\u002Fusr\u002Fbin\u002Fmm",[149,613,151],{},[149,615,616],{},"\u002Fusr\u002Flibexec\u002Fmmd",", and stops if\nthey report different releases.",[194,619,620,621,623,624,627,628,631],{},"Creates the ",[149,622,155],{}," account. It has no password, its shell is the relay\n(",[149,625,626],{},"\u002Fusr\u002Flibexec\u002Fmm-relay-shell","), and its only group is ",[149,629,630],{},"mm-relay",". Setup\nstops if the account is in any other group.",[194,633,634,635,638,639,233],{},"Writes ",[149,636,637],{},"\u002Fetc\u002Fmm\u002Fmmd.json"," and installs ",[149,640,641],{},"mmd.service",[194,643,644,645,648,649,652],{},"Enrolls the project in ",[149,646,647],{},"\u002Fetc\u002Fmm\u002Fhost.yaml"," with the origin, its pinned host\nkey, the environment, and the break-glass keys. Creates the deploy key at\n",[149,650,651],{},"\u002Fvar\u002Flib\u002Fmm\u002Fprojects\u002FPROJECT_ID\u002Fdeploy-key",", unless a key is already\nthere.",[194,654,655,656,249,659,662,663,665],{},"Installs the sshd drop-in and checks it with ",[149,657,658],{},"sshd -t",[149,660,661],{},"sshd -T",": the\n",[149,664,155],{}," account must get only the relay, with no TTY or forwarding. If the\ncheck fails, setup removes a new drop-in and does not reload sshd.",[194,667,634,668,671,672,674],{},[149,669,670],{},"\u002Fetc\u002Fssh\u002Fmm_authorized_keys",", starts ",[149,673,151],{},", and checks it is\nrunning.",[145,676,677],{},"Expected result:",[161,679,682],{"className":680,"code":681,"language":166,"meta":167},[164],"Bootstrapped target prod-eu-setup (…)\n  …\n  verified: remote-access\n  mm and mmd: 0.1.0\n  host key:   ssh-ed25519 AAAA…\n  daemon:     0f3c…\n  deploy key: ssh-ed25519 AAAA… mmd-deploy-019c527b-…\n",[149,683,681],{"__ignoreMap":167},[145,685,686],{},"Add the deploy key to the git host as a read-only deploy key. Keep the host key\nand daemon identity for the repo target in the next section.",[145,688,689,690,249,692,694,695,698,699,701],{},"Run setup again to upgrade ",[149,691,155],{},[149,693,151],{},", to add another environment, or to\nenroll another project from that project's checkout. Setup keeps the other\nprojects, ceilings, and exceptions in ",[149,696,697],{},"host.yaml",", and adds break-glass keys\nwithout replacing existing ones. It rewrites ",[149,700,697],{}," from its parsed\ncontents, so comments in that file are lost.",[183,703,705],{"id":704},"adjust-the-host-policy","Adjust the host policy",[145,707,708,710,711,714,715,718],{},[149,709,647],{}," is the host's own limit on every project. It is root-owned\nmode ",[149,712,713],{},"0600",". Projects cannot change it. Setup writes the project's name,\norigin, environments, and break-glass keys; add ceilings, the first\n",[149,716,717],{},"deploy-from"," rule, and workload exceptions by hand:",[161,720,724],{"className":721,"code":722,"language":723,"meta":167,"style":167},"language-yaml shiki shiki-themes mm","schema-version: 2\nbreak-glass: # admin everywhere; keep the private key offline\n  - name: ops-recovery\n    key: ssh-ed25519 AAAA… ops@offline\nprojects:\n  019c527b-6242-7b43-bfd4-bd60e27c1de3: # the project's id from .config\u002Fmm.yaml\n    name: shop\n    origin: git@github.com:acme\u002Fshop.git\n    origin-host-key: ssh-ed25519 AAAA… # the git host's key, pinned\n    environments: [staging, production] # environments this host runs\n    initial-deploy-from:\n      production: [main] # until the repo sets deploy-from\n    ceiling:\n      production: { level: deploy } # the most the repo may grant here\n","yaml",[149,725,726,737,748,760,770,778,789,800,811,825,850,858,876,884],{"__ignoreMap":167},[277,727,728,732,734],{"class":279,"line":280},[277,729,731],{"class":730},"sWZmh","schema-version",[277,733,314],{"class":439},[277,735,736],{"class":347}," 2\n",[277,738,739,742,744],{"class":279,"line":351},[277,740,741],{"class":730},"break-glass",[277,743,314],{"class":439},[277,745,747],{"class":746},"sP7nD"," # admin everywhere; keep the private key offline\n",[277,749,750,753,756,758],{"class":279,"line":408},[277,751,752],{"class":439},"  -",[277,754,755],{"class":730}," name",[277,757,314],{"class":439},[277,759,304],{"class":291},[277,761,762,765,767],{"class":279,"line":419},[277,763,764],{"class":730},"    key",[277,766,314],{"class":439},[277,768,769],{"class":291}," ssh-ed25519 AAAA… ops@offline\n",[277,771,772,775],{"class":279,"line":430},[277,773,774],{"class":730},"projects",[277,776,777],{"class":439},":\n",[277,779,781,784,786],{"class":279,"line":780},6,[277,782,783],{"class":730},"  019c527b-6242-7b43-bfd4-bd60e27c1de3",[277,785,314],{"class":439},[277,787,788],{"class":746}," # the project's id from .config\u002Fmm.yaml\n",[277,790,792,795,797],{"class":279,"line":791},7,[277,793,794],{"class":730},"    name",[277,796,314],{"class":439},[277,798,799],{"class":291}," shop\n",[277,801,803,806,808],{"class":279,"line":802},8,[277,804,805],{"class":730},"    origin",[277,807,314],{"class":439},[277,809,810],{"class":291}," git@github.com:acme\u002Fshop.git\n",[277,812,814,817,819,822],{"class":279,"line":813},9,[277,815,816],{"class":730},"    origin-host-key",[277,818,314],{"class":439},[277,820,821],{"class":291}," ssh-ed25519 AAAA…",[277,823,824],{"class":746}," # the git host's key, pinned\n",[277,826,828,831,833,836,839,842,844,847],{"class":279,"line":827},10,[277,829,830],{"class":730},"    environments",[277,832,314],{"class":439},[277,834,835],{"class":439}," [",[277,837,838],{"class":291},"staging",[277,840,841],{"class":439},",",[277,843,379],{"class":291},[277,845,846],{"class":439},"]",[277,848,849],{"class":746}," # environments this host runs\n",[277,851,853,856],{"class":279,"line":852},11,[277,854,855],{"class":730},"    initial-deploy-from",[277,857,777],{"class":439},[277,859,861,864,866,868,871,873],{"class":279,"line":860},12,[277,862,863],{"class":730},"      production",[277,865,314],{"class":439},[277,867,835],{"class":439},[277,869,870],{"class":291},"main",[277,872,846],{"class":439},[277,874,875],{"class":746}," # until the repo sets deploy-from\n",[277,877,879,882],{"class":279,"line":878},13,[277,880,881],{"class":730},"    ceiling",[277,883,777],{"class":439},[277,885,887,889,891,894,897,899,902,905],{"class":279,"line":886},14,[277,888,863],{"class":730},[277,890,314],{"class":439},[277,892,893],{"class":439}," {",[277,895,896],{"class":730}," level",[277,898,314],{"class":439},[277,900,901],{"class":291}," deploy",[277,903,904],{"class":439}," }",[277,906,907],{"class":746}," # the most the repo may grant here\n",[191,909,910,917,928],{},[194,911,912,916],{},[913,914,915],"strong",{},"Break-glass keys"," always work, even when a users file locks everyone out.",[194,918,919,924,925,233],{},[913,920,921],{},[149,922,923],{},"ceiling"," caps what the repo can grant per environment. Without one, the\nrepo may grant up to ",[149,926,927],{},"admin",[194,929,930,935,936,938,939,233],{},[913,931,932],{},[149,933,934],{},"origin"," is the only place ",[149,937,151],{}," fetches code from, with the key at\n",[149,940,651],{},[145,942,943],{},"After editing, check the file and restart the daemon:",[161,945,947],{"className":271,"code":946,"language":273,"meta":167,"style":167},"mmd access validate\nmmd access render-keys\nsystemctl restart mmd.service\n",[149,948,949,959,968],{"__ignoreMap":167},[277,950,951,953,956],{"class":279,"line":280},[277,952,151],{"class":283},[277,954,955],{"class":291}," access",[277,957,958],{"class":291}," validate\n",[277,960,961,963,965],{"class":279,"line":351},[277,962,151],{"class":283},[277,964,955],{"class":291},[277,966,967],{"class":291}," render-keys\n",[277,969,970,973,976],{"class":279,"line":408},[277,971,972],{"class":283},"systemctl",[277,974,975],{"class":291}," restart",[277,977,978],{"class":291}," mmd.service\n",[183,980,982],{"id":981},"connect-a-client","Connect a client",[145,984,985,986,249,989,992],{},"Commit the host's public connection facts to the repo, so every clone can reach\nthe environment. Use the host key and daemon identity that setup printed. If\nsomeone else ran setup, get them through a trusted channel; on the host they\nare ",[149,987,988],{},"\u002Fetc\u002Fssh\u002Fssh_host_ed25519_key.pub",[149,990,991],{},"mmd --print-identity",". Name the\ntarget differently from the private setup target, because a private target\nwith the same name takes precedence:",[161,994,996],{"className":721,"code":995,"language":723,"meta":167,"style":167},"# .config\u002Fmm.yaml\ntargets:\n  prod-eu:\n    host: prod-eu.example.com\n    host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n    daemon-fingerprint: 0f3c… # 64 characters\nenvironments:\n  production:\n    target: prod-eu\n    deploy-from: [main]\n",[149,997,998,1003,1010,1017,1027,1037,1050,1057,1064,1074],{"__ignoreMap":167},[277,999,1000],{"class":279,"line":280},[277,1001,1002],{"class":746},"# .config\u002Fmm.yaml\n",[277,1004,1005,1008],{"class":279,"line":351},[277,1006,1007],{"class":730},"targets",[277,1009,777],{"class":439},[277,1011,1012,1015],{"class":279,"line":408},[277,1013,1014],{"class":730},"  prod-eu",[277,1016,777],{"class":439},[277,1018,1019,1022,1024],{"class":279,"line":419},[277,1020,1021],{"class":730},"    host",[277,1023,314],{"class":439},[277,1025,1026],{"class":291}," prod-eu.example.com\n",[277,1028,1029,1032,1034],{"class":279,"line":430},[277,1030,1031],{"class":730},"    host-key",[277,1033,314],{"class":439},[277,1035,1036],{"class":291}," ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n",[277,1038,1039,1042,1044,1047],{"class":279,"line":780},[277,1040,1041],{"class":730},"    daemon-fingerprint",[277,1043,314],{"class":439},[277,1045,1046],{"class":291}," 0f3c…",[277,1048,1049],{"class":746}," # 64 characters\n",[277,1051,1052,1055],{"class":279,"line":791},[277,1053,1054],{"class":730},"environments",[277,1056,777],{"class":439},[277,1058,1059,1062],{"class":279,"line":802},[277,1060,1061],{"class":730},"  production",[277,1063,777],{"class":439},[277,1065,1066,1069,1071],{"class":279,"line":813},[277,1067,1068],{"class":730},"    target",[277,1070,314],{"class":439},[277,1072,1073],{"class":291}," prod-eu\n",[277,1075,1076,1079,1081,1083,1085],{"class":279,"line":827},[277,1077,1078],{"class":730},"    deploy-from",[277,1080,314],{"class":439},[277,1082,835],{"class":439},[277,1084,870],{"class":291},[277,1086,1087],{"class":439},"]\n",[145,1089,1090,1091,1093],{},"These are public keys, not secrets. mm checks both on every connection and\nconnects as ",[149,1092,155],{},". The first time a client connects it pins them; if a later\ncommit changes either, the client refuses until you review the change:",[161,1095,1097],{"className":271,"code":1096,"language":273,"meta":167,"style":167},"mm target trust prod-eu          # shows pinned and new keys\nmm target trust prod-eu --yes    # accept after checking with the host admin\n",[149,1098,1099,1114],{"__ignoreMap":167},[277,1100,1101,1103,1105,1108,1111],{"class":279,"line":280},[277,1102,155],{"class":283},[277,1104,326],{"class":291},[277,1106,1107],{"class":291}," trust",[277,1109,1110],{"class":291}," prod-eu",[277,1112,1113],{"class":746},"          # shows pinned and new keys\n",[277,1115,1116,1118,1120,1122,1124,1127],{"class":279,"line":351},[277,1117,155],{"class":283},[277,1119,326],{"class":291},[277,1121,1107],{"class":291},[277,1123,1110],{"class":291},[277,1125,1126],{"class":287}," --yes",[277,1128,1129],{"class":746},"    # accept after checking with the host admin\n",[1131,1132,1134],"h3",{"id":1133},"swarm-managers-as-failover-members","Swarm managers as failover members",[145,1136,1137,1138,1140,1141,1144,1145,1148],{},"For a multi-node Swarm where several managers run ",[149,1139,151],{},", list the other\nmanagers under ",[149,1142,1143],{},"members",". Each one takes the same facts as the target's own\nhost; ",[149,1146,1147],{},"port"," defaults to 22:",[161,1150,1152],{"className":721,"code":1151,"language":723,"meta":167,"style":167},"targets:\n  production:\n    host: mgr-1.example.com\n    host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n    daemon-fingerprint: 0f3c… # 64 characters\n    members:\n      - host: mgr-2.example.com\n        host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n        daemon-fingerprint: 9b1e…\n      - host: mgr-3.example.com\n        port: 2222\n        host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n        daemon-fingerprint: 4d72…\n",[149,1153,1154,1160,1166,1175,1183,1193,1200,1213,1222,1232,1243,1253,1261],{"__ignoreMap":167},[277,1155,1156,1158],{"class":279,"line":280},[277,1157,1007],{"class":730},[277,1159,777],{"class":439},[277,1161,1162,1164],{"class":279,"line":351},[277,1163,1061],{"class":730},[277,1165,777],{"class":439},[277,1167,1168,1170,1172],{"class":279,"line":408},[277,1169,1021],{"class":730},[277,1171,314],{"class":439},[277,1173,1174],{"class":291}," mgr-1.example.com\n",[277,1176,1177,1179,1181],{"class":279,"line":419},[277,1178,1031],{"class":730},[277,1180,314],{"class":439},[277,1182,1036],{"class":291},[277,1184,1185,1187,1189,1191],{"class":279,"line":430},[277,1186,1041],{"class":730},[277,1188,314],{"class":439},[277,1190,1046],{"class":291},[277,1192,1049],{"class":746},[277,1194,1195,1198],{"class":279,"line":780},[277,1196,1197],{"class":730},"    members",[277,1199,777],{"class":439},[277,1201,1202,1205,1208,1210],{"class":279,"line":791},[277,1203,1204],{"class":439},"      -",[277,1206,1207],{"class":730}," host",[277,1209,314],{"class":439},[277,1211,1212],{"class":291}," mgr-2.example.com\n",[277,1214,1215,1218,1220],{"class":279,"line":802},[277,1216,1217],{"class":730},"        host-key",[277,1219,314],{"class":439},[277,1221,1036],{"class":291},[277,1223,1224,1227,1229],{"class":279,"line":813},[277,1225,1226],{"class":730},"        daemon-fingerprint",[277,1228,314],{"class":439},[277,1230,1231],{"class":291}," 9b1e…\n",[277,1233,1234,1236,1238,1240],{"class":279,"line":827},[277,1235,1204],{"class":439},[277,1237,1207],{"class":730},[277,1239,314],{"class":439},[277,1241,1242],{"class":291}," mgr-3.example.com\n",[277,1244,1245,1248,1250],{"class":279,"line":852},[277,1246,1247],{"class":730},"        port",[277,1249,314],{"class":439},[277,1251,1252],{"class":347}," 2222\n",[277,1254,1255,1257,1259],{"class":279,"line":860},[277,1256,1217],{"class":730},[277,1258,314],{"class":439},[277,1260,1036],{"class":291},[277,1262,1263,1265,1267],{"class":279,"line":878},[277,1264,1226],{"class":730},[277,1266,314],{"class":439},[277,1268,1269],{"class":291}," 4d72…\n",[145,1271,1272,1273,1276,1277,1280],{},"mm connects to ",[149,1274,1275],{},"host"," first, then to each member in list order. It moves to the\nnext one only when it cannot connect, and never resends a request that reached\na daemon. Messages name members by position: ",[149,1278,1279],{},"member-1"," is the first entry.",[145,1282,1283,1284,1287],{},"Members' keys are pinned with the target's own keys. A commit that adds a\nmember, or changes any member's host key or daemon identity, is refused until\nyou review it with ",[149,1285,1286],{},"mm target trust",". Removing or reordering members only drops\nkeys you already trusted, so it needs no review. A removed member that comes\nback later needs review again.",[1131,1289,1291],{"id":1290},"declare-a-cluster-by-node","Declare a cluster by node",[145,1293,1294,1295,1298,1299,249,1301,1303],{},"A target can list its hosts under ",[149,1296,1297],{},"nodes:"," instead of ",[149,1300,1275],{},[149,1302,1143],{},".\nEach node names the hostname it gets and its address on the private network\nthe Swarm uses:",[161,1305,1307],{"className":721,"code":1306,"language":723,"meta":167,"style":167},"targets:\n  production:\n    nodes:\n      - name: app-1\n        host: 203.0.113.11\n        private-address: 10.77.0.1\n        host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n        daemon-fingerprint: 0f3c… # 64 characters\n      - {name: app-2, host: 203.0.113.12, private-address: 10.77.0.2, …}\n      - {name: app-3, host: 203.0.113.13, private-address: 10.77.0.3, …}\n    swarm:\n      private-network:\n        wireguard: {subnet: 10.77.0.0\u002F24} # interface wg0, port 51820\n        # or: interface: eth1\n      address-pool: 10.200.0.0\u002F16 # overlay networks; Docker's default is 10.0.0.0\u002F8\n      address-pool-mask: 24\n      storage: app-1\n      groups: {db: [app-1]}\n      ingress: [app-2, app-3] # omit for every manager\n    setup:\n      user: root\n      origin: git@github.com:example\u002Fapp.git\n      origin-host-key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…\n      deploy-key: {secret: origin.deploy-key, public-key: ssh-ed25519 AAAA… mm-deploy}\n      break-glass:\n        - {name: ops-recovery, key: ssh-ed25519 AAAA… ops}\n",[149,1308,1309,1315,1321,1328,1339,1349,1359,1367,1377,1417,1454,1461,1468,1491,1496,1510,1521,1531,1554,1576,1584,1595,1606,1616,1646,1654],{"__ignoreMap":167},[277,1310,1311,1313],{"class":279,"line":280},[277,1312,1007],{"class":730},[277,1314,777],{"class":439},[277,1316,1317,1319],{"class":279,"line":351},[277,1318,1061],{"class":730},[277,1320,777],{"class":439},[277,1322,1323,1326],{"class":279,"line":408},[277,1324,1325],{"class":730},"    nodes",[277,1327,777],{"class":439},[277,1329,1330,1332,1334,1336],{"class":279,"line":419},[277,1331,1204],{"class":439},[277,1333,755],{"class":730},[277,1335,314],{"class":439},[277,1337,1338],{"class":291}," app-1\n",[277,1340,1341,1344,1346],{"class":279,"line":430},[277,1342,1343],{"class":730},"        host",[277,1345,314],{"class":439},[277,1347,1348],{"class":347}," 203.0.113.11\n",[277,1350,1351,1354,1356],{"class":279,"line":780},[277,1352,1353],{"class":730},"        private-address",[277,1355,314],{"class":439},[277,1357,1358],{"class":347}," 10.77.0.1\n",[277,1360,1361,1363,1365],{"class":279,"line":791},[277,1362,1217],{"class":730},[277,1364,314],{"class":439},[277,1366,1036],{"class":291},[277,1368,1369,1371,1373,1375],{"class":279,"line":802},[277,1370,1226],{"class":730},[277,1372,314],{"class":439},[277,1374,1046],{"class":291},[277,1376,1049],{"class":746},[277,1378,1379,1381,1383,1386,1388,1391,1393,1395,1397,1400,1402,1405,1407,1410,1412,1414],{"class":279,"line":813},[277,1380,1204],{"class":439},[277,1382,893],{"class":439},[277,1384,1385],{"class":730},"name",[277,1387,314],{"class":439},[277,1389,1390],{"class":291}," app-2",[277,1392,841],{"class":439},[277,1394,1207],{"class":730},[277,1396,314],{"class":439},[277,1398,1399],{"class":347}," 203.0.113.12",[277,1401,841],{"class":439},[277,1403,1404],{"class":730}," private-address",[277,1406,314],{"class":439},[277,1408,1409],{"class":347}," 10.77.0.2",[277,1411,841],{"class":439},[277,1413,579],{"class":291},[277,1415,1416],{"class":439},"}\n",[277,1418,1419,1421,1423,1425,1427,1430,1432,1434,1436,1439,1441,1443,1445,1448,1450,1452],{"class":279,"line":827},[277,1420,1204],{"class":439},[277,1422,893],{"class":439},[277,1424,1385],{"class":730},[277,1426,314],{"class":439},[277,1428,1429],{"class":291}," app-3",[277,1431,841],{"class":439},[277,1433,1207],{"class":730},[277,1435,314],{"class":439},[277,1437,1438],{"class":347}," 203.0.113.13",[277,1440,841],{"class":439},[277,1442,1404],{"class":730},[277,1444,314],{"class":439},[277,1446,1447],{"class":347}," 10.77.0.3",[277,1449,841],{"class":439},[277,1451,579],{"class":291},[277,1453,1416],{"class":439},[277,1455,1456,1459],{"class":279,"line":852},[277,1457,1458],{"class":730},"    swarm",[277,1460,777],{"class":439},[277,1462,1463,1466],{"class":279,"line":860},[277,1464,1465],{"class":730},"      private-network",[277,1467,777],{"class":439},[277,1469,1470,1473,1475,1477,1480,1482,1485,1488],{"class":279,"line":878},[277,1471,1472],{"class":730},"        wireguard",[277,1474,314],{"class":439},[277,1476,893],{"class":439},[277,1478,1479],{"class":730},"subnet",[277,1481,314],{"class":439},[277,1483,1484],{"class":291}," 10.77.0.0\u002F24",[277,1486,1487],{"class":439},"}",[277,1489,1490],{"class":746}," # interface wg0, port 51820\n",[277,1492,1493],{"class":279,"line":886},[277,1494,1495],{"class":746},"        # or: interface: eth1\n",[277,1497,1499,1502,1504,1507],{"class":279,"line":1498},15,[277,1500,1501],{"class":730},"      address-pool",[277,1503,314],{"class":439},[277,1505,1506],{"class":291}," 10.200.0.0\u002F16",[277,1508,1509],{"class":746}," # overlay networks; Docker's default is 10.0.0.0\u002F8\n",[277,1511,1513,1516,1518],{"class":279,"line":1512},16,[277,1514,1515],{"class":730},"      address-pool-mask",[277,1517,314],{"class":439},[277,1519,1520],{"class":347}," 24\n",[277,1522,1524,1527,1529],{"class":279,"line":1523},17,[277,1525,1526],{"class":730},"      storage",[277,1528,314],{"class":439},[277,1530,1338],{"class":291},[277,1532,1534,1537,1539,1541,1544,1546,1548,1551],{"class":279,"line":1533},18,[277,1535,1536],{"class":730},"      groups",[277,1538,314],{"class":439},[277,1540,893],{"class":439},[277,1542,1543],{"class":730},"db",[277,1545,314],{"class":439},[277,1547,835],{"class":439},[277,1549,1550],{"class":291},"app-1",[277,1552,1553],{"class":439},"]}\n",[277,1555,1557,1560,1562,1564,1567,1569,1571,1573],{"class":279,"line":1556},19,[277,1558,1559],{"class":730},"      ingress",[277,1561,314],{"class":439},[277,1563,835],{"class":439},[277,1565,1566],{"class":291},"app-2",[277,1568,841],{"class":439},[277,1570,1429],{"class":291},[277,1572,846],{"class":439},[277,1574,1575],{"class":746}," # omit for every manager\n",[277,1577,1579,1582],{"class":279,"line":1578},20,[277,1580,1581],{"class":730},"    setup",[277,1583,777],{"class":439},[277,1585,1587,1590,1592],{"class":279,"line":1586},21,[277,1588,1589],{"class":730},"      user",[277,1591,314],{"class":439},[277,1593,1594],{"class":291}," root\n",[277,1596,1598,1601,1603],{"class":279,"line":1597},22,[277,1599,1600],{"class":730},"      origin",[277,1602,314],{"class":439},[277,1604,1605],{"class":291}," git@github.com:example\u002Fapp.git\n",[277,1607,1609,1612,1614],{"class":279,"line":1608},23,[277,1610,1611],{"class":730},"      origin-host-key",[277,1613,314],{"class":439},[277,1615,1036],{"class":291},[277,1617,1619,1622,1624,1626,1629,1631,1634,1636,1639,1641,1644],{"class":279,"line":1618},24,[277,1620,1621],{"class":730},"      deploy-key",[277,1623,314],{"class":439},[277,1625,893],{"class":439},[277,1627,1628],{"class":730},"secret",[277,1630,314],{"class":439},[277,1632,1633],{"class":291}," origin.deploy-key",[277,1635,841],{"class":439},[277,1637,1638],{"class":730}," public-key",[277,1640,314],{"class":439},[277,1642,1643],{"class":291}," ssh-ed25519 AAAA… mm-deploy",[277,1645,1416],{"class":439},[277,1647,1649,1652],{"class":279,"line":1648},25,[277,1650,1651],{"class":730},"      break-glass",[277,1653,777],{"class":439},[277,1655,1657,1660,1662,1664,1666,1669,1671,1674,1676,1679],{"class":279,"line":1656},26,[277,1658,1659],{"class":439},"        -",[277,1661,893],{"class":439},[277,1663,1385],{"class":730},[277,1665,314],{"class":439},[277,1667,1668],{"class":291}," ops-recovery",[277,1670,841],{"class":439},[277,1672,1673],{"class":730}," key",[277,1675,314],{"class":439},[277,1677,1678],{"class":291}," ssh-ed25519 AAAA… ops",[277,1680,1416],{"class":439},[145,1682,1683,1684,1686,1687,249,1690,1693],{},"Node order is failover order, as with ",[149,1685,1143],{},", and messages name each node.\nmm connects to a node only once its ",[149,1688,1689],{},"host-key",[149,1691,1692],{},"daemon-fingerprint"," are\nrecorded; until then, commands for the environment stop and say so.",[1131,1695,1697],{"id":1696},"set-up-a-cluster-from-the-declaration","Set up a cluster from the declaration",[145,1699,1700,1701,1704,1705,1708],{},"Create each node with this device's mm key (",[149,1702,1703],{},"mm key show",") authorized for the\n",[149,1706,1707],{},"setup.user"," account, usually root. Then, from the repo:",[161,1710,1712],{"className":271,"code":1711,"language":273,"meta":167,"style":167},"mm -e production target setup deploy-key   # once: prints the public key\ngh repo deploy-key add - --title 'mm production' \u003C\u003C\u003C'ssh-ed25519 AAAA… mm-deploy-app-production'\n# set setup.deploy-key.public-key to that line and commit\nmm config split targets                     # once: record rewrites .config\u002Fmm\u002Ftargets.yaml\nmm -e production target setup plan          # scans host keys, binds secret revisions\nmm -e production target setup apply --plan HASH --yes\nmm -e production target setup record        # writes host keys and daemon identities\ngit add .config\u002Fmm\u002Ftargets.yaml && git commit -m \"Record production nodes\" && git push\n",[149,1713,1714,1733,1760,1765,1781,1798,1820,1838],{"__ignoreMap":167},[277,1715,1716,1718,1721,1723,1725,1727,1730],{"class":279,"line":280},[277,1717,155],{"class":283},[277,1719,1720],{"class":287}," -e",[277,1722,379],{"class":291},[277,1724,326],{"class":291},[277,1726,400],{"class":291},[277,1728,1729],{"class":291}," deploy-key",[277,1731,1732],{"class":746},"   # once: prints the public key\n",[277,1734,1735,1738,1741,1743,1745,1748,1751,1754,1757],{"class":279,"line":351},[277,1736,1737],{"class":283},"gh",[277,1739,1740],{"class":291}," repo",[277,1742,1729],{"class":291},[277,1744,329],{"class":291},[277,1746,1747],{"class":291}," -",[277,1749,1750],{"class":287}," --title",[277,1752,1753],{"class":291}," 'mm production'",[277,1755,1756],{"class":439}," \u003C\u003C\u003C",[277,1758,1759],{"class":291},"'ssh-ed25519 AAAA… mm-deploy-app-production'\n",[277,1761,1762],{"class":279,"line":408},[277,1763,1764],{"class":746},"# set setup.deploy-key.public-key to that line and commit\n",[277,1766,1767,1769,1772,1775,1778],{"class":279,"line":419},[277,1768,155],{"class":283},[277,1770,1771],{"class":291}," config",[277,1773,1774],{"class":291}," split",[277,1776,1777],{"class":291}," targets",[277,1779,1780],{"class":746},"                     # once: record rewrites .config\u002Fmm\u002Ftargets.yaml\n",[277,1782,1783,1785,1787,1789,1791,1793,1795],{"class":279,"line":430},[277,1784,155],{"class":283},[277,1786,1720],{"class":287},[277,1788,379],{"class":291},[277,1790,326],{"class":291},[277,1792,400],{"class":291},[277,1794,403],{"class":291},[277,1796,1797],{"class":746},"          # scans host keys, binds secret revisions\n",[277,1799,1800,1802,1804,1806,1808,1810,1812,1814,1817],{"class":279,"line":780},[277,1801,155],{"class":283},[277,1803,1720],{"class":287},[277,1805,379],{"class":291},[277,1807,326],{"class":291},[277,1809,400],{"class":291},[277,1811,567],{"class":291},[277,1813,570],{"class":287},[277,1815,1816],{"class":291}," HASH",[277,1818,1819],{"class":287}," --yes\n",[277,1821,1822,1824,1826,1828,1830,1832,1835],{"class":279,"line":791},[277,1823,155],{"class":283},[277,1825,1720],{"class":287},[277,1827,379],{"class":291},[277,1829,326],{"class":291},[277,1831,400],{"class":291},[277,1833,1834],{"class":291}," record",[277,1836,1837],{"class":746},"        # writes host keys and daemon identities\n",[277,1839,1840,1842,1844,1847,1850,1853,1856,1859,1862,1864,1866],{"class":279,"line":802},[277,1841,602],{"class":283},[277,1843,329],{"class":291},[277,1845,1846],{"class":291}," .config\u002Fmm\u002Ftargets.yaml",[277,1848,1849],{"class":439}," &&",[277,1851,1852],{"class":283}," git",[277,1854,1855],{"class":291}," commit",[277,1857,1858],{"class":287}," -m",[277,1860,1861],{"class":291}," \"Record production nodes\"",[277,1863,1849],{"class":439},[277,1865,1852],{"class":283},[277,1867,1868],{"class":291}," push\n",[145,1870,1871,1874],{},[149,1872,1873],{},"apply"," runs these steps in order, and each one is safe to repeat:",[595,1876,1877,1898,1908,1922,1932,1938,1952,1970],{},[194,1878,1879,1882,1883,1886,1887,1890,1891,1890,1894,1897],{},[913,1880,1881],{},"prepare-nodes",": sets each hostname, writes ",[149,1884,1885],{},"\u002Fetc\u002Fmm\u002Fnode.env","\n(",[149,1888,1889],{},"MM_NODE_NAME",", ",[149,1892,1893],{},"MM_PRIVATE_INTERFACE",[149,1895,1896],{},"MM_PRIVATE_ADDRESS","), and builds\nthe WireGuard mesh when declared, then checks every node reaches the others.",[194,1899,1900,1903,1904,1907],{},[913,1901,1902],{},"provision-nodes",": the project's ",[149,1905,1906],{},".config\u002Fansible\u002Fbootstrap.yml"," and the\nremote access layer on each node, as single-host setup runs them.",[194,1909,1910,1913,1914,1917,1918,1921],{},[913,1911,1912],{},"form-swarm",": ",[149,1915,1916],{},"docker swarm init"," on the first node and ",[149,1919,1920],{},"join"," as\nmanagers on the others, on the private addresses and the overlay pool. A\nrebuilt node's stale entry is removed; a node in another Swarm stops setup.",[194,1923,1924,1927,1928,1931],{},[913,1925,1926],{},"serve-storage",": when ",[149,1929,1930],{},"swarm.storage"," names a node, installs that\nnode's kernel NFS server for shared volumes (NFSv4.2, exported only to the\nnodes' private addresses) and stops mm's NFS container if an earlier\ndeploy started it.",[194,1933,1934,1937],{},[913,1935,1936],{},"push-secrets",": every age secret set for the environment, and the deploy\nkey, sealed to each node's own store with the same revisions.",[194,1939,1940,1943,1944,1947,1948,1951],{},[913,1941,1942],{},"label-nodes",": storage, group, and ingress labels (",[149,1945,1946],{},"mmd swarm converge","),\nincluding ",[149,1949,1950],{},"io.mm.storage-server=host"," on the storage node.",[194,1953,1954,1957,1958,1961,1962,1965,1966,1969],{},[913,1955,1956],{},"sync-access",": the first users-file sync as root on the first node\n(",[149,1959,1960],{},"mmd access sync --require-shared",") at ",[149,1963,1964],{},"HEAD",", or ",[149,1967,1968],{},"--commit SHA",". The commit\nmust be pushed to a branch the environment deploys from.",[194,1971,1972,1975],{},[913,1973,1974],{},"verify",": each node's daemon identity, host key, and version.",[145,1977,1978,1979,1982],{},"The plan binds each node's scanned SSH host key. A key that differs from the\nrecorded one stops the plan; after rebuilding a node, plan again with\n",[149,1980,1981],{},"--trust-new-host-keys"," and check the fingerprints it prints. The plan also\nbinds each secret's revision, so changing a secret after review means planning\nagain.",[145,1984,1985,1988],{},[149,1986,1987],{},"mm config validate"," checks that:",[191,1990,1991,1994,1997,2010,2013],{},[194,1992,1993],{},"node names are lowercase hostname labels, and hosts and private addresses are\nunique;",[194,1995,1996],{},"private addresses are private, inside the WireGuard subnet when one is used,\nand outside the overlay address pool;",[194,1998,1999,1890,2002,2005,2006,2009],{},[149,2000,2001],{},"storage",[149,2003,2004],{},"groups",", and ",[149,2007,2008],{},"ingress"," name declared nodes;",[194,2011,2012],{},"there are between one and seven managers (an even number is a warning);",[194,2014,2015,2018,2019,233],{},[149,2016,2017],{},"setup.deploy-key.secret"," names a secret declared under ",[149,2020,2021],{},"secrets:",[145,2023,2024,2025,2028],{},"A private target with the same name (",[149,2026,2027],{},"mm target add",") takes precedence, for\nexample to go through a jump host.",[145,2030,2031,2032,2035,2036,2039,2040,2043],{},"mm authenticates with this device's mm key (",[149,2033,2034],{},"mm key create","), never with\n",[149,2037,2038],{},"~\u002F.ssh"," or an SSH agent. In CI, set ",[149,2041,2042],{},"MM_SSH_KEY"," to a machine key's private\nkey instead.",[183,2045,2047],{"id":2046},"apply-the-users-file-for-the-first-time","Apply the users file for the first time",[145,2049,2050],{},"Nobody but the break-glass key can connect until an environment reconciles\nthe repo's users file. Push the commit, then reconcile it as root on the host,\nwithout the relay:",[161,2052,2054],{"className":271,"code":2053,"language":273,"meta":167,"style":167},"sudo mmd access sync --project PROJECT_ID --environment production \\\n  --commit 3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a --dry-run   # review\nsudo mmd access sync --project PROJECT_ID --environment production \\\n  --commit 3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a\n",[149,2055,2056,2080,2094,2114],{"__ignoreMap":167},[277,2057,2058,2060,2063,2065,2068,2071,2074,2076,2078],{"class":279,"line":280},[277,2059,199],{"class":283},[277,2061,2062],{"class":291}," mmd",[277,2064,955],{"class":291},[277,2066,2067],{"class":291}," sync",[277,2069,2070],{"class":287}," --project",[277,2072,2073],{"class":291}," PROJECT_ID",[277,2075,376],{"class":287},[277,2077,379],{"class":291},[277,2079,348],{"class":347},[277,2081,2082,2085,2088,2091],{"class":279,"line":351},[277,2083,2084],{"class":287},"  --commit",[277,2086,2087],{"class":291}," 3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a",[277,2089,2090],{"class":287}," --dry-run",[277,2092,2093],{"class":746},"   # review\n",[277,2095,2096,2098,2100,2102,2104,2106,2108,2110,2112],{"class":279,"line":408},[277,2097,199],{"class":283},[277,2099,2062],{"class":291},[277,2101,955],{"class":291},[277,2103,2067],{"class":291},[277,2105,2070],{"class":287},[277,2107,2073],{"class":291},[277,2109,376],{"class":287},[277,2111,379],{"class":291},[277,2113,348],{"class":347},[277,2115,2116,2118],{"class":279,"line":419},[277,2117,2084],{"class":287},[277,2119,2120],{"class":291}," 3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a\n",[145,2122,2123,2126,2127,2130,2131,2133,2134,2137,2138,2141],{},[149,2124,2125],{},"mmd access sync"," checks the commit exactly as ",[149,2128,2129],{},"mm team sync"," does: the host\nfetches it from the enrolled origin with the deploy key, it must be on a\nbranch that ",[149,2132,717],{}," allows, and the host ceilings and Compose\nrestrictions apply. It then writes the environment's users, rewrites\n",[149,2135,2136],{},"authorized_keys",", records ",[149,2139,2140],{},"host-root"," in the audit log, and prints a JSON\nsummary:",[161,2143,2147],{"className":2144,"code":2145,"language":2146,"meta":167,"style":167},"language-json shiki shiki-themes mm","{\n  \"project\": \"019c527b-6242-7b43-bfd4-bd60e27c1de3\",\n  \"environment\": \"production\",\n  \"commit\": \"3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a\",\n  \"previous-commit\": null,\n  \"changed\": true,\n  \"dry-run\": false,\n  \"deploy-from\": [\"main\"],\n  \"users\": [\n    { \"user\": \"alice\", \"machine\": false, \"access\": { \"level\": \"deploy\" }, \"keys\": [\"SHA256:…\"] }\n  ],\n  \"changes\": [{ \"user\": \"alice\", \"after\": { \"level\": \"deploy\" }, \"keys-added\": [\"SHA256:…\"] }],\n  \"sharing\": null\n}\n","json",[149,2148,2149,2154,2173,2189,2205,2221,2237,2253,2271,2285,2363,2368,2435,2449],{"__ignoreMap":167},[277,2150,2151],{"class":279,"line":280},[277,2152,2153],{"class":439},"{\n",[277,2155,2156,2159,2162,2165,2167,2170],{"class":279,"line":351},[277,2157,2158],{"class":439},"  \"",[277,2160,2161],{"class":730},"project",[277,2163,2164],{"class":439},"\"",[277,2166,314],{"class":439},[277,2168,2169],{"class":291}," \"019c527b-6242-7b43-bfd4-bd60e27c1de3\"",[277,2171,2172],{"class":439},",\n",[277,2174,2175,2177,2180,2182,2184,2187],{"class":279,"line":408},[277,2176,2158],{"class":439},[277,2178,2179],{"class":730},"environment",[277,2181,2164],{"class":439},[277,2183,314],{"class":439},[277,2185,2186],{"class":291}," \"production\"",[277,2188,2172],{"class":439},[277,2190,2191,2193,2196,2198,2200,2203],{"class":279,"line":419},[277,2192,2158],{"class":439},[277,2194,2195],{"class":730},"commit",[277,2197,2164],{"class":439},[277,2199,314],{"class":439},[277,2201,2202],{"class":291}," \"3f2c9a1e5b7d4c6f8a0b2d4e6f8a1c3e5b7d9f0a\"",[277,2204,2172],{"class":439},[277,2206,2207,2209,2212,2214,2216,2219],{"class":279,"line":430},[277,2208,2158],{"class":439},[277,2210,2211],{"class":730},"previous-commit",[277,2213,2164],{"class":439},[277,2215,314],{"class":439},[277,2217,2218],{"class":347}," null",[277,2220,2172],{"class":439},[277,2222,2223,2225,2228,2230,2232,2235],{"class":279,"line":780},[277,2224,2158],{"class":439},[277,2226,2227],{"class":730},"changed",[277,2229,2164],{"class":439},[277,2231,314],{"class":439},[277,2233,2234],{"class":347}," true",[277,2236,2172],{"class":439},[277,2238,2239,2241,2244,2246,2248,2251],{"class":279,"line":791},[277,2240,2158],{"class":439},[277,2242,2243],{"class":730},"dry-run",[277,2245,2164],{"class":439},[277,2247,314],{"class":439},[277,2249,2250],{"class":347}," false",[277,2252,2172],{"class":439},[277,2254,2255,2257,2259,2261,2263,2265,2268],{"class":279,"line":802},[277,2256,2158],{"class":439},[277,2258,717],{"class":730},[277,2260,2164],{"class":439},[277,2262,314],{"class":439},[277,2264,835],{"class":439},[277,2266,2267],{"class":291},"\"main\"",[277,2269,2270],{"class":439},"],\n",[277,2272,2273,2275,2278,2280,2282],{"class":279,"line":813},[277,2274,2158],{"class":439},[277,2276,2277],{"class":730},"users",[277,2279,2164],{"class":439},[277,2281,314],{"class":439},[277,2283,2284],{"class":439}," [\n",[277,2286,2287,2290,2293,2296,2298,2300,2303,2305,2307,2310,2312,2314,2316,2318,2320,2323,2325,2327,2329,2331,2334,2336,2338,2341,2344,2346,2349,2351,2353,2355,2358,2360],{"class":279,"line":827},[277,2288,2289],{"class":439},"    {",[277,2291,2292],{"class":439}," \"",[277,2294,2295],{"class":730},"user",[277,2297,2164],{"class":439},[277,2299,314],{"class":439},[277,2301,2302],{"class":291}," \"alice\"",[277,2304,841],{"class":439},[277,2306,2292],{"class":439},[277,2308,2309],{"class":730},"machine",[277,2311,2164],{"class":439},[277,2313,314],{"class":439},[277,2315,2250],{"class":347},[277,2317,841],{"class":439},[277,2319,2292],{"class":439},[277,2321,2322],{"class":730},"access",[277,2324,2164],{"class":439},[277,2326,314],{"class":439},[277,2328,893],{"class":439},[277,2330,2292],{"class":439},[277,2332,2333],{"class":730},"level",[277,2335,2164],{"class":439},[277,2337,314],{"class":439},[277,2339,2340],{"class":291}," \"deploy\"",[277,2342,2343],{"class":439}," },",[277,2345,2292],{"class":439},[277,2347,2348],{"class":730},"keys",[277,2350,2164],{"class":439},[277,2352,314],{"class":439},[277,2354,835],{"class":439},[277,2356,2357],{"class":291},"\"SHA256:…\"",[277,2359,846],{"class":439},[277,2361,2362],{"class":439}," }\n",[277,2364,2365],{"class":279,"line":852},[277,2366,2367],{"class":439},"  ],\n",[277,2369,2370,2372,2375,2377,2379,2382,2384,2386,2388,2390,2392,2394,2396,2399,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2422,2424,2426,2428,2430,2432],{"class":279,"line":860},[277,2371,2158],{"class":439},[277,2373,2374],{"class":730},"changes",[277,2376,2164],{"class":439},[277,2378,314],{"class":439},[277,2380,2381],{"class":439}," [{",[277,2383,2292],{"class":439},[277,2385,2295],{"class":730},[277,2387,2164],{"class":439},[277,2389,314],{"class":439},[277,2391,2302],{"class":291},[277,2393,841],{"class":439},[277,2395,2292],{"class":439},[277,2397,2398],{"class":730},"after",[277,2400,2164],{"class":439},[277,2402,314],{"class":439},[277,2404,893],{"class":439},[277,2406,2292],{"class":439},[277,2408,2333],{"class":730},[277,2410,2164],{"class":439},[277,2412,314],{"class":439},[277,2414,2340],{"class":291},[277,2416,2343],{"class":439},[277,2418,2292],{"class":439},[277,2420,2421],{"class":730},"keys-added",[277,2423,2164],{"class":439},[277,2425,314],{"class":439},[277,2427,835],{"class":439},[277,2429,2357],{"class":291},[277,2431,846],{"class":439},[277,2433,2434],{"class":439}," }],\n",[277,2436,2437,2439,2442,2444,2446],{"class":279,"line":878},[277,2438,2158],{"class":439},[277,2440,2441],{"class":730},"sharing",[277,2443,2164],{"class":439},[277,2445,314],{"class":439},[277,2447,2448],{"class":347}," null\n",[277,2450,2451],{"class":279,"line":886},[277,2452,1416],{"class":439},[145,2454,2455,2458,2459,2462,2463,233],{},[149,2456,2457],{},"--dry-run"," prints the same summary and writes no access state. Running the\ncommand again at the same commit prints ",[149,2460,2461],{},"\"changed\": false"," and changes\nnothing. It only moves access forward: it refuses a commit that the commit\nalready applied is not an ancestor of. Go back with a reviewed ",[149,2464,2129],{},[145,2466,2467,2468,2470,2471,2473,2474,2477],{},"On a Swarm manager the record is shared like a team sync: ",[149,2469,2441],{}," reports\nthe shared record and which managers confirmed it, and the other managers'\n",[149,2472,151],{}," adopt it on their own. Add ",[149,2475,2476],{},"--require-shared"," to refuse when the host is\nnot a Swarm manager, so the record cannot stay on one host by mistake.",[145,2479,2480,2481,233],{},"The break-glass key works too:\n",[149,2482,2483],{},"MM_SSH_KEY=\"$(cat ops-recovery)\" mm --environment production team sync",[145,2485,2486,2487,249,2490,2492,2493,233],{},"After that, every ",[149,2488,2489],{},"mm deploy apply",[149,2491,2129],{}," re-applies the users\nfile from the commit it uses. See ",[2494,2495,2497],"a",{"href":2496},".\u002Fremote-access","remote access",[145,2499,2500],{},"Finally, remove personal login accounts and sudo from the host, and store the\nbreak-glass private key offline.",[183,2502,2504],{"id":2503},"use-the-host","Use the host",[161,2506,2508],{"className":271,"code":2507,"language":273,"meta":167,"style":167},"mm --environment production deploy plan             # the host fetches HEAD's commit\nmm --environment production deploy apply --plan PLAN_HASH --yes\nmm --environment production service status\nmm --environment production service logs api\n",[149,2509,2510,2525,2543,2557],{"__ignoreMap":167},[277,2511,2512,2514,2516,2518,2520,2522],{"class":279,"line":280},[277,2513,155],{"class":283},[277,2515,376],{"class":287},[277,2517,379],{"class":291},[277,2519,901],{"class":291},[277,2521,403],{"class":291},[277,2523,2524],{"class":746},"             # the host fetches HEAD's commit\n",[277,2526,2527,2529,2531,2533,2535,2537,2539,2541],{"class":279,"line":351},[277,2528,155],{"class":283},[277,2530,376],{"class":287},[277,2532,379],{"class":291},[277,2534,901],{"class":291},[277,2536,567],{"class":291},[277,2538,570],{"class":287},[277,2540,573],{"class":291},[277,2542,1819],{"class":287},[277,2544,2545,2547,2549,2551,2554],{"class":279,"line":408},[277,2546,155],{"class":283},[277,2548,376],{"class":287},[277,2550,379],{"class":291},[277,2552,2553],{"class":291}," service",[277,2555,2556],{"class":291}," status\n",[277,2558,2559,2561,2563,2565,2567,2570],{"class":279,"line":419},[277,2560,155],{"class":283},[277,2562,376],{"class":287},[277,2564,379],{"class":291},[277,2566,2553],{"class":291},[277,2568,2569],{"class":291}," logs",[277,2571,2572],{"class":291}," api\n",[145,2574,2575,2578,2579,2582],{},[149,2576,2577],{},"deploy plan"," sends a commit ID. The host fetches it from the origin, checks it\nis on an allowed branch, checks it out under ",[149,2580,2581],{},"\u002Fsrv\u002Fmm\u002Fprojects",", and plans from\nthat checkout. Push the commit before planning.",[145,2584,2585,2586,2589,2590,2593,2594,233],{},"Remote hosts never build. Modules that mm builds deploy from a build receipt:\npublish the images, write the receipt with ",[149,2587,2588],{},"mm build receipt",", and pass it to\n",[149,2591,2592],{},"deploy plan --receipt",". The host checks that the receipt is a build of the\nplanned commit, either for this environment or for one whose configuration\nbuilds the same units. See ",[2494,2595,2597],{"href":2596},".\u002Fconcepts\u002Fbuilds-and-deployments#deploy-a-build-to-a-remote-host","deploy a build to a remote\nhost",[1131,2599,2601],{"id":2600},"deploy-from-ci","Deploy from CI",[145,2603,2604,2605,2608,2609,2612],{},"Build once in a CI environment, then deploy that build to each environment\nwith a machine key. Declare a ",[149,2606,2607],{},"ci"," environment in ",[149,2610,2611],{},".config\u002Fmm.yaml"," for the\nbuild job. In the build job:",[161,2614,2616],{"className":271,"code":2615,"language":273,"meta":167,"style":167},"mm --environment ci build --publish --yes\nmm --environment ci build receipt build-receipt.json\n",[149,2617,2618,2635],{"__ignoreMap":167},[277,2619,2620,2622,2624,2627,2630,2633],{"class":279,"line":280},[277,2621,155],{"class":283},[277,2623,376],{"class":287},[277,2625,2626],{"class":291}," ci",[277,2628,2629],{"class":291}," build",[277,2631,2632],{"class":287}," --publish",[277,2634,1819],{"class":287},[277,2636,2637,2639,2641,2643,2645,2648],{"class":279,"line":351},[277,2638,155],{"class":283},[277,2640,376],{"class":287},[277,2642,2626],{"class":291},[277,2644,2629],{"class":291},[277,2646,2647],{"class":291}," receipt",[277,2649,2650],{"class":291}," build-receipt.json\n",[145,2652,2653,2654,2657,2658,2660,2661,2664],{},"Keep ",[149,2655,2656],{},"build-receipt.json"," as a job artifact. In each deploy job, from a\ncheckout of the same commit, with ",[149,2659,2042],{}," set to a key the users file\ngrants ",[149,2662,2663],{},"deploy"," on the environment:",[161,2666,2668],{"className":271,"code":2667,"language":273,"meta":167,"style":167},"PLAN=$(mm --output json --environment production deploy plan --receipt build-receipt.json | jq -r .binding.plan_hash)\nmm --environment production deploy apply --plan \"$PLAN\" --yes\n",[149,2669,2670,2715],{"__ignoreMap":167},[277,2671,2672,2675,2678,2680,2683,2686,2688,2690,2692,2694,2697,2700,2703,2706,2709,2712],{"class":279,"line":280},[277,2673,2674],{"class":730},"PLAN",[277,2676,2677],{"class":439},"=$(",[277,2679,155],{"class":283},[277,2681,2682],{"class":287}," --output",[277,2684,2685],{"class":291}," json",[277,2687,376],{"class":287},[277,2689,379],{"class":291},[277,2691,901],{"class":291},[277,2693,403],{"class":291},[277,2695,2696],{"class":287}," --receipt",[277,2698,2699],{"class":291}," build-receipt.json",[277,2701,2702],{"class":439}," |",[277,2704,2705],{"class":283}," jq",[277,2707,2708],{"class":287}," -r",[277,2710,2711],{"class":291}," .binding.plan_hash",[277,2713,2714],{"class":439},")\n",[277,2716,2717,2719,2721,2723,2725,2727,2729,2731,2735,2737],{"class":279,"line":351},[277,2718,155],{"class":283},[277,2720,376],{"class":287},[277,2722,379],{"class":291},[277,2724,901],{"class":291},[277,2726,567],{"class":291},[277,2728,570],{"class":287},[277,2730,2292],{"class":291},[277,2732,2734],{"class":2733},"sRW9m","$PLAN",[277,2736,2164],{"class":291},[277,2738,1819],{"class":287},[145,2740,2741,2742,2744,2745,2748,2749,2752,2753,2756],{},"The host deploys the receipt's digests when production's configuration at\nthat commit builds exactly the same units as ",[149,2743,2607],{}," did: the same modules,\nplatforms, image repositories, variants, and build inputs. Otherwise it\nrefuses and names the first module that differs. A module with a build secret\ncan only be promoted when every secret is listed under\n",[149,2746,2747],{},"credential-only-secrets",", because other secrets can hold different values\nper environment. The plan's ",[149,2750,2751],{},"plan.build_receipt.environment"," and the host's\naudit log (",[149,2754,2755],{},"build_receipt_environment",") record the environment the receipt\nwas built for.",[145,2758,2759],{},"The receipt holds image names, digests, and build settings, but no secret\nvalues.",[145,2761,2762,2763,2766],{},"To skip rebuilding unchanged modules on a fresh CI runner, import the previous\npublication's receipt before building. ",[149,2764,2765],{},"mm build --publish"," looks for reusable\nimages only in mm's local state, not in the registry:",[161,2768,2770],{"className":271,"code":2769,"language":273,"meta":167,"style":167},"PLAN=$(mm --output json --environment ci build import previous-receipt.json --cache-only | jq -r .plan_hash)\nmm --environment ci build import previous-receipt.json --cache-only --plan \"$PLAN\" --yes\n",[149,2771,2772,2810],{"__ignoreMap":167},[277,2773,2774,2776,2778,2780,2782,2784,2786,2788,2790,2793,2796,2799,2801,2803,2805,2808],{"class":279,"line":280},[277,2775,2674],{"class":730},[277,2777,2677],{"class":439},[277,2779,155],{"class":283},[277,2781,2682],{"class":287},[277,2783,2685],{"class":291},[277,2785,376],{"class":287},[277,2787,2626],{"class":291},[277,2789,2629],{"class":291},[277,2791,2792],{"class":291}," import",[277,2794,2795],{"class":291}," previous-receipt.json",[277,2797,2798],{"class":287}," --cache-only",[277,2800,2702],{"class":439},[277,2802,2705],{"class":283},[277,2804,2708],{"class":287},[277,2806,2807],{"class":291}," .plan_hash",[277,2809,2714],{"class":439},[277,2811,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834],{"class":279,"line":351},[277,2813,155],{"class":283},[277,2815,376],{"class":287},[277,2817,2626],{"class":291},[277,2819,2629],{"class":291},[277,2821,2792],{"class":291},[277,2823,2795],{"class":291},[277,2825,2798],{"class":287},[277,2827,570],{"class":287},[277,2829,2292],{"class":291},[277,2831,2734],{"class":2733},[277,2833,2164],{"class":291},[277,2835,1819],{"class":287},[145,2837,2838,2839,2172,2842,2845,2846,233],{},"Remote exec supports output and cancellation. It currently rejects ",[149,2840,2841],{},"--stdin",[149,2843,2844],{},"--tty",", and interactive ",[149,2847,2848],{},"shell",[183,2850,2852],{"id":2851},"secrets-and-the-deploy-key-on-the-host","Secrets and the deploy key on the host",[145,2854,2855,2856,2859,2860,2863],{},"The host resolves a deployment's age secrets from its own store,\n",[149,2857,2858],{},"\u002Fvar\u002Flib\u002Fmm-home\u002Fsecrets\u002FPROJECT_ID.age",", opened with the age identity in\n",[149,2861,2862],{},"\u002Fvar\u002Flib\u002Fmm-home\u002Fidentities\u002Fage.key",". Two root commands copy secrets from\nthe operator's store into it. They exchange a sealed bundle that mm builds\non the operator's machine; you cannot write one by hand.",[145,2865,2866],{},"Print the host store's age recipient. The command creates the store and the\nidentity if they are missing, and prints the same recipient every time:",[161,2868,2870],{"className":271,"code":2869,"language":273,"meta":167,"style":167},"mmd secrets recipient --project PROJECT_ID --environment integration\n",[149,2871,2872],{"__ignoreMap":167},[277,2873,2874,2876,2879,2882,2884,2886,2888],{"class":279,"line":280},[277,2875,151],{"class":283},[277,2877,2878],{"class":291}," secrets",[277,2880,2881],{"class":291}," recipient",[277,2883,2070],{"class":287},[277,2885,2073],{"class":291},[277,2887,376],{"class":287},[277,2889,2890],{"class":291}," integration\n",[161,2892,2894],{"className":2144,"code":2893,"language":2146,"meta":167,"style":167},"{ \"recipient\": \"age1…\" }\n",[149,2895,2896],{"__ignoreMap":167},[277,2897,2898,2901,2903,2906,2908,2910,2913],{"class":279,"line":280},[277,2899,2900],{"class":439},"{",[277,2902,2292],{"class":439},[277,2904,2905],{"class":730},"recipient",[277,2907,2164],{"class":439},[277,2909,314],{"class":439},[277,2911,2912],{"class":291}," \"age1…\"",[277,2914,2362],{"class":439},[145,2916,2917],{},"Import a bundle sealed to that recipient. It is read from stdin, either as\nASCII-armored or as binary age data, up to 24 MiB:",[161,2919,2921],{"className":271,"code":2920,"language":273,"meta":167,"style":167},"mmd secrets import --project PROJECT_ID --environment integration \u003C bundle.age\n",[149,2922,2923],{"__ignoreMap":167},[277,2924,2925,2927,2929,2931,2933,2935,2937,2940,2943],{"class":279,"line":280},[277,2926,151],{"class":283},[277,2928,2878],{"class":291},[277,2930,2792],{"class":291},[277,2932,2070],{"class":287},[277,2934,2073],{"class":291},[277,2936,376],{"class":287},[277,2938,2939],{"class":291}," integration",[277,2941,2942],{"class":439}," \u003C",[277,2944,2945],{"class":291}," bundle.age\n",[161,2947,2949],{"className":2144,"code":2948,"language":2146,"meta":167,"style":167},"{\n  \"project\": \"019c527b-6242-7b43-bfd4-bd60e27c1de3\",\n  \"environment\": \"integration\",\n  \"secrets\": {\n    \"age:\u002F\u002Fdatabase-password\": { \"revision\": \"4f0c…\", \"changed\": true }\n  },\n  \"deploy-key\": {\n    \"public-key\": \"ssh-ed25519 AAAA… mm-deploy-integration\",\n    \"changed\": false\n  }\n}\n",[149,2950,2951,2955,2969,2984,2998,3038,3043,3056,3072,3085,3090],{"__ignoreMap":167},[277,2952,2953],{"class":279,"line":280},[277,2954,2153],{"class":439},[277,2956,2957,2959,2961,2963,2965,2967],{"class":279,"line":351},[277,2958,2158],{"class":439},[277,2960,2161],{"class":730},[277,2962,2164],{"class":439},[277,2964,314],{"class":439},[277,2966,2169],{"class":291},[277,2968,2172],{"class":439},[277,2970,2971,2973,2975,2977,2979,2982],{"class":279,"line":408},[277,2972,2158],{"class":439},[277,2974,2179],{"class":730},[277,2976,2164],{"class":439},[277,2978,314],{"class":439},[277,2980,2981],{"class":291}," \"integration\"",[277,2983,2172],{"class":439},[277,2985,2986,2988,2991,2993,2995],{"class":279,"line":419},[277,2987,2158],{"class":439},[277,2989,2990],{"class":730},"secrets",[277,2992,2164],{"class":439},[277,2994,314],{"class":439},[277,2996,2997],{"class":439}," {\n",[277,2999,3000,3003,3006,3008,3010,3012,3014,3017,3019,3021,3024,3026,3028,3030,3032,3034,3036],{"class":279,"line":430},[277,3001,3002],{"class":439},"    \"",[277,3004,3005],{"class":730},"age:\u002F\u002Fdatabase-password",[277,3007,2164],{"class":439},[277,3009,314],{"class":439},[277,3011,893],{"class":439},[277,3013,2292],{"class":439},[277,3015,3016],{"class":730},"revision",[277,3018,2164],{"class":439},[277,3020,314],{"class":439},[277,3022,3023],{"class":291}," \"4f0c…\"",[277,3025,841],{"class":439},[277,3027,2292],{"class":439},[277,3029,2227],{"class":730},[277,3031,2164],{"class":439},[277,3033,314],{"class":439},[277,3035,2234],{"class":347},[277,3037,2362],{"class":439},[277,3039,3040],{"class":279,"line":780},[277,3041,3042],{"class":439},"  },\n",[277,3044,3045,3047,3050,3052,3054],{"class":279,"line":791},[277,3046,2158],{"class":439},[277,3048,3049],{"class":730},"deploy-key",[277,3051,2164],{"class":439},[277,3053,314],{"class":439},[277,3055,2997],{"class":439},[277,3057,3058,3060,3063,3065,3067,3070],{"class":279,"line":802},[277,3059,3002],{"class":439},[277,3061,3062],{"class":730},"public-key",[277,3064,2164],{"class":439},[277,3066,314],{"class":439},[277,3068,3069],{"class":291}," \"ssh-ed25519 AAAA… mm-deploy-integration\"",[277,3071,2172],{"class":439},[277,3073,3074,3076,3078,3080,3082],{"class":279,"line":813},[277,3075,3002],{"class":439},[277,3077,2227],{"class":730},[277,3079,2164],{"class":439},[277,3081,314],{"class":439},[277,3083,3084],{"class":347}," false\n",[277,3086,3087],{"class":279,"line":827},[277,3088,3089],{"class":439},"  }\n",[277,3091,3092],{"class":279,"line":852},[277,3093,1416],{"class":439},[191,3095,3096,3103,3106,3115],{},[194,3097,3098,3099,3102],{},"The host must already run the environment for the project (",[149,3100,3101],{},"mmd access enroll","). A bundle for another project or environment is refused.",[194,3104,3105],{},"Each secret keeps the revision it has in the operator's store, so revisions\non the host and on the operator's machine compare equal. A secret whose\nrevision is already active is left unchanged. Secrets that the bundle does\nnot list stay in the host store.",[194,3107,3108,3109,249,3111,3114],{},"A bundle can carry the environment's deploy key. The import checks that its\nprivate half matches its public line, then replaces\n",[149,3110,651],{},[149,3112,3113],{},"deploy-key.pub",". Later\nsetup runs keep that key, so one key on the git host serves every node.",[194,3116,3117],{},"The output names references and revisions only, never values.",[183,3119,3121],{"id":3120},"recover-or-upgrade","Recover or upgrade",[145,3123,3124,3125,3128],{},"After a connection drops during a change, inspect service status and\n",[149,3126,3127],{},"\u002Fvar\u002Flib\u002Fmm\u002Faudit.jsonl"," before retrying. A lost response does not mean the\noperation failed. Plans expire after ten minutes and cannot be replayed.",[145,3130,3131,3132,249,3134,3136,3137,3140,3141,3144,3145,3149],{},"Upgrade ",[149,3133,151],{},[149,3135,155],{}," together by running setup again with the new release;\na protocol mismatch reports ",[149,3138,3139],{},"MM_REMOTE_UPGRADE_REQUIRED",". Setup keeps\n",[149,3142,3143],{},"\u002Fvar\u002Flib\u002Fmm",", so the daemon identity it prints stays the same. On a Swarm,\nupgrade every manager, then sync each environment once (see\n",[2494,3146,3148],{"href":3147},".\u002Fhow-to\u002Fswarm-cluster#after-upgrading","access on every manager",").",[145,3151,3152,3153,3156,3157,3159,3160,3162,3163,3165,3166,3169,3170,3173,3174,3177],{},"Hosts set up with the earlier per-developer accounts and ",[149,3154,3155],{},"access.yaml"," must\nmove to this layout: ",[149,3158,151],{}," refuses a schema-1 ",[149,3161,3155],{},". Write\n",[149,3164,697],{},", add each developer to the users file with ",[149,3167,3168],{},"mm team join",", sync,\ncheck with ",[149,3171,3172],{},"mm team whoami",", and then delete the old ",[149,3175,3176],{},"mm-*"," accounts.",[3179,3180,3181],"style",{},"html pre.shiki code .svrjQ, html code.shiki .svrjQ{--shiki-default:var(--mm-code-function)}html pre.shiki code .sbRf1, html code.shiki .sbRf1{--shiki-default:var(--mm-code-option)}html pre.shiki code .sv8ni, html code.shiki .sv8ni{--shiki-default:var(--mm-code-string)}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 pre.shiki code .s1Wbj, html code.shiki .s1Wbj{--shiki-default:var(--mm-code-constant)}html pre.shiki code .sIbZ0, html code.shiki .sIbZ0{--shiki-default:var(--mm-code-punctuation)}html pre.shiki code .sWZmh, html code.shiki .sWZmh{--shiki-default:var(--mm-code-key)}html pre.shiki code .sP7nD, html code.shiki .sP7nD{--shiki-default:var(--mm-code-comment)}html pre.shiki code .sRW9m, html code.shiki .sRW9m{--shiki-default:var(--mm-code-variable)}",{"title":167,"searchDepth":351,"depth":351,"links":3183},[3184,3185,3186,3187,3192,3193,3196,3197],{"id":185,"depth":351,"text":186},{"id":307,"depth":351,"text":308},{"id":704,"depth":351,"text":705},{"id":981,"depth":351,"text":982,"children":3188},[3189,3190,3191],{"id":1133,"depth":408,"text":1134},{"id":1290,"depth":408,"text":1291},{"id":1696,"depth":408,"text":1697},{"id":2046,"depth":351,"text":2047},{"id":2503,"depth":351,"text":2504,"children":3194},[3195],{"id":2600,"depth":408,"text":2601},{"id":2851,"depth":351,"text":2852},{"id":3120,"depth":351,"text":3121},"Install mmd on a Linux host with mm target setup, so developers reach it through one restricted SSH account.","md",{},true,{"title":108,"description":3198},"docs\u002Fremote-daemon","Jv-7OIsqHJtsvOrEzhdoVBZ0FW6Wg7_QNJ4xfQaScfo",1794512546198]