<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Asciidoctor 2.0.23"> <meta name="author" content="Perforce Professional Services"> <title>SDP Windows to Linux Migration Guide</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"> <style> /*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */ /* Uncomment the following line when using as a custom stylesheet */ /* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */ html{font-family:sans-serif;-webkit-text-size-adjust:100%} a{background:none} a:focus{outline:thin dotted} a:active,a:hover{outline:0} h1{font-size:2em;margin:.67em 0} b,strong{font-weight:bold} abbr{font-size:.9em} abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none} dfn{font-style:italic} hr{height:0} mark{background:#ff0;color:#000} code,kbd,pre,samp{font-family:monospace;font-size:1em} pre{white-space:pre-wrap} q{quotes:"\201C" "\201D" "\2018" "\2019"} small{font-size:80%} sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} sup{top:-.5em} sub{bottom:-.25em} img{border:0} svg:not(:root){overflow:hidden} figure{margin:0} audio,video{display:inline-block} audio:not([controls]){display:none;height:0} fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} legend{border:0;padding:0} button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} button,input{line-height:normal} button,select{text-transform:none} button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} button[disabled],html input[disabled]{cursor:default} input[type=checkbox],input[type=radio]{padding:0} button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} textarea{overflow:auto;vertical-align:top} table{border-collapse:collapse;border-spacing:0} *,::before,::after{box-sizing:border-box} html,body{font-size:100%} body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} a:hover{cursor:pointer} img,object,embed{max-width:100%;height:auto} object,embed{height:100%} img{-ms-interpolation-mode:bicubic} .left{float:left!important} .right{float:right!important} .text-left{text-align:left!important} .text-right{text-align:right!important} .text-center{text-align:center!important} .text-justify{text-align:justify!important} .hide{display:none} img,object,svg{display:inline-block;vertical-align:middle} textarea{height:auto;min-height:50px} select{width:100%} .subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0} a{color:#2156a5;text-decoration:underline;line-height:inherit} a:hover,a:focus{color:#1d4b8f} a img{border:0} p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} p aside{font-size:.875em;line-height:1.35;font-style:italic} h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} h1{font-size:2.125em} h2{font-size:1.6875em} h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} h4,h5{font-size:1.125em} h6{font-size:1em} hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em} em,i{font-style:italic;line-height:inherit} strong,b{font-weight:bold;line-height:inherit} small{font-size:60%;line-height:inherit} code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} ul,ol{margin-left:1.5em} ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0} ul.circle{list-style-type:circle} ul.disc{list-style-type:disc} ul.square{list-style-type:square} ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit} ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} dl dt{margin-bottom:.3125em;font-weight:bold} dl dd{margin-bottom:1.25em} blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} @media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} h1{font-size:2.75em} h2{font-size:2.3125em} h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} h4{font-size:1.4375em}} table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal} table thead,table tfoot{background:#f7f8f7} table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} table tr.even,table tr.alt{background:#f8f8f7} table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6} h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} .center{margin-left:auto;margin-right:auto} .stretch{width:100%} .clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} .clearfix::after,.float-group::after{clear:both} :not(pre).nobreak{word-wrap:normal} :not(pre).nowrap{white-space:nowrap} :not(pre).pre-wrap{white-space:pre-wrap} :not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed} pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit} pre>code{display:block} pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal} em em{font-style:normal} strong strong{font-weight:400} .keyseq{color:rgba(51,51,51,.8)} kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} .keyseq kbd:first-child{margin-left:0} .keyseq kbd:last-child{margin-right:0} .menuseq,.menuref{color:#000} .menuseq b:not(.caret),.menuref{font-weight:inherit} .menuseq{word-spacing:-.02em} .menuseq b.caret{font-size:1.25em;line-height:.8} .menuseq i.caret{font-weight:bold;text-align:center;width:.45em} b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} b.button::before{content:"[";padding:0 3px 0 2px} b.button::after{content:"]";padding:0 2px 0 3px} p a>code:hover{color:rgba(0,0,0,.9)} #header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} #header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} #header::after,#content::after,#footnotes::after,#footer::after{clear:both} #content{margin-top:1.25em} #content::before{content:none} #header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} #header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} #header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px} #header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} #header .details span:first-child{margin-left:-.125em} #header .details span.email a{color:rgba(0,0,0,.85)} #header .details br{display:none} #header .details br+span::before{content:"\00a0\2013\00a0"} #header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} #header .details br+span#revremark::before{content:"\00a0|\00a0"} #header #revnumber{text-transform:capitalize} #header #revnumber::after{content:"\00a0"} #content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} #toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} #toc>ul{margin-left:.125em} #toc ul.sectlevel0>li>a{font-style:italic} #toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} #toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} #toc li{line-height:1.3334;margin-top:.3334em} #toc a{text-decoration:none} #toc a:active{text-decoration:underline} #toctitle{color:#7a2518;font-size:1.2em} @media screen and (min-width:768px){#toctitle{font-size:1.375em} body.toc2{padding-left:15em;padding-right:0} body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} #toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} #toc.toc2>ul{font-size:.9em;margin-bottom:0} #toc.toc2 ul ul{margin-left:0;padding-left:1em} #toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} body.toc2.toc-right{padding-left:0;padding-right:15em} body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} @media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} #toc.toc2{width:20em} #toc.toc2 #toctitle{font-size:1.375em} #toc.toc2>ul{font-size:.95em} #toc.toc2 ul ul{padding-left:1.25em} body.toc2.toc-right{padding-left:0;padding-right:20em}} #content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px} #content #toc>:first-child{margin-top:0} #content #toc>:last-child{margin-bottom:0} #footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em} #footer-text{color:hsla(0,0%,100%,.8);line-height:1.44} #content{margin-bottom:.625em} .sect1{padding-bottom:.625em} @media screen and (min-width:768px){#content{margin-bottom:1.25em} .sect1{padding-bottom:1.25em}} .sect1:last-child{padding-bottom:0} .sect1+.sect1{border-top:1px solid #e7e7e9} #content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} #content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} #content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} #content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} #content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} details{margin-left:1.25rem} details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent} details>summary::-webkit-details-marker{display:none} details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)} details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)} details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem} .admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} .paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} .admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} .admonitionblock>table td.icon{text-align:center;width:80px} .admonitionblock>table td.icon img{max-width:none} .admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} .admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere} .admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} .exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px} .sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px} .sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} .exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0} .exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} .literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em} @media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}} @media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}} .literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8} .literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)} .listingblock>.content{position:relative} .listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5} .listingblock:hover code[data-lang]::before{display:block} .listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5} .listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} .listingblock pre.highlightjs{padding:0} .listingblock pre.highlightjs>code{padding:1em;border-radius:4px} .listingblock pre.prettyprint{border-width:0} .prettyprint{background:#f7f7f8} pre.prettyprint .linenums{line-height:1.45;margin-left:2em} pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0} pre.prettyprint li code[data-lang]::before{opacity:1} pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none} table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none} table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal} table.linenotable td.code{padding-left:.75em} table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} pre.pygments span.linenos{display:inline-block;margin-right:.75em} .quoteblock{margin:0 1em 1.25em 1.5em;display:table} .quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em} .quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} .quoteblock blockquote{margin:0;padding:0;border:0} .quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} .quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} .quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} .verseblock{margin:0 1em 1.25em} .verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} .verseblock pre strong{font-weight:400} .verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} .quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} .quoteblock .attribution br,.verseblock .attribution br{display:none} .quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} .quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} .quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} .quoteblock.abstract{margin:0 1em 1.25em;display:block} .quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} .quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf} .quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0} .quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} .quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0} p.tableblock:last-child{margin-bottom:0} td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere} td.tableblock>.content>:last-child{margin-bottom:-1.25em} table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} table.grid-all>*>tr>*{border-width:1px} table.grid-cols>*>tr>*{border-width:0 1px} table.grid-rows>*>tr>*{border-width:1px 0} table.frame-all{border-width:1px} table.frame-ends{border-width:1px 0} table.frame-sides{border-width:0 1px} table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0} table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0} table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0} table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0} table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7} th.halign-left,td.halign-left{text-align:left} th.halign-right,td.halign-right{text-align:right} th.halign-center,td.halign-center{text-align:center} th.valign-top,td.valign-top{vertical-align:top} th.valign-bottom,td.valign-bottom{vertical-align:bottom} th.valign-middle,td.valign-middle{vertical-align:middle} table thead th,table tfoot th{font-weight:bold} tbody tr th{background:#f7f8f7} tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} p.tableblock>code:only-child{background:none;padding:0} p.tableblock{font-size:1em} ol{margin-left:1.75em} ul li ol{margin-left:1.5em} dl dd{margin-left:1.125em} dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} ul.unstyled,ol.unstyled{margin-left:0} li>p:empty:only-child::before{content:"";display:inline-block} ul.checklist>li>p:first-child{margin-left:-1em} ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em} ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} ul.inline>li{margin-left:1.25em} .unstyled dl dt{font-weight:400;font-style:normal} ol.arabic{list-style-type:decimal} ol.decimal{list-style-type:decimal-leading-zero} ol.loweralpha{list-style-type:lower-alpha} ol.upperalpha{list-style-type:upper-alpha} ol.lowerroman{list-style-type:lower-roman} ol.upperroman{list-style-type:upper-roman} ol.lowergreek{list-style-type:lower-greek} .hdlist>table,.colist>table{border:0;background:none} .hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} td.hdlist1{font-weight:bold;padding-bottom:1.25em} td.hdlist2{word-wrap:anywhere} .literalblock+.colist,.listingblock+.colist{margin-top:-.5em} .colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} .colist td:not([class]):first-child img{max-width:none} .colist td:not([class]):last-child{padding:.25em 0} .thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd} .imageblock.left{margin:.25em .625em 1.25em 0} .imageblock.right{margin:.25em 0 1.25em .625em} .imageblock>.title{margin-bottom:0} .imageblock.thumb,.imageblock.th{border-width:6px} .imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} .image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} .image.left{margin-right:.625em} .image.right{margin-left:.625em} a.image{text-decoration:none;display:inline-block} a.image object{pointer-events:none} sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} sup.footnote a,sup.footnoteref a{text-decoration:none} sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline} #footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} #footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} #footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} #footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} #footnotes .footnote:last-of-type{margin-bottom:0} #content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} div.unbreakable{page-break-inside:avoid} .big{font-size:larger} .small{font-size:smaller} .underline{text-decoration:underline} .overline{text-decoration:overline} .line-through{text-decoration:line-through} .aqua{color:#00bfbf} .aqua-background{background:#00fafa} .black{color:#000} .black-background{background:#000} .blue{color:#0000bf} .blue-background{background:#0000fa} .fuchsia{color:#bf00bf} .fuchsia-background{background:#fa00fa} .gray{color:#606060} .gray-background{background:#7d7d7d} .green{color:#006000} .green-background{background:#007d00} .lime{color:#00bf00} .lime-background{background:#00fa00} .maroon{color:#600000} .maroon-background{background:#7d0000} .navy{color:#000060} .navy-background{background:#00007d} .olive{color:#606000} .olive-background{background:#7d7d00} .purple{color:#600060} .purple-background{background:#7d007d} .red{color:#bf0000} .red-background{background:#fa0000} .silver{color:#909090} .silver-background{background:#bcbcbc} .teal{color:#006060} .teal-background{background:#007d7d} .white{color:#bfbfbf} .white-background{background:#fafafa} .yellow{color:#bfbf00} .yellow-background{background:#fafa00} span.icon>.fa{cursor:default} a span.icon>.fa{cursor:inherit} .admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} .admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} .admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} .admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} .admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} .admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} .conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} .conum[data-value] *{color:#fff!important} .conum[data-value]+b{display:none} .conum[data-value]::after{content:attr(data-value)} pre .conum[data-value]{position:relative;top:-.125em} b.conum *{color:inherit!important} .conum:not([data-value]):empty{display:none} dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em} p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem} p{margin-bottom:1.25rem} .sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} .exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc} .print-only{display:none!important} @page{margin:1.25cm .75cm} @media print{*{box-shadow:none!important;text-shadow:none!important} html{font-size:80%} a{color:inherit!important;text-decoration:underline!important} a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} abbr[title]{border-bottom:1px dotted} abbr[title]::after{content:" (" attr(title) ")"} pre,blockquote,tr,img,object,svg{page-break-inside:avoid} thead{display:table-header-group} svg{max-width:100%} p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} #header,#content,#footnotes,#footer{max-width:none} #toc,.sidebarblock,.exampleblock>.content{background:none!important} #toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} body.book #header{text-align:center} body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} body.book #header .details{border:0!important;display:block;padding:0!important} body.book #header .details span:first-child{margin-left:0!important} body.book #header .details br{display:block} body.book #header .details br+span::before{content:none!important} body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} .listingblock code[data-lang]::before{display:block} #footer{padding:0 .9375em} .hide-on-print{display:none!important} .print-only{display:block!important} .hide-for-print{display:none!important} .show-for-print{display:inherit!important}} @media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem} .sect1{padding:0!important} .sect1+.sect1{border:0} #footer{background:none} #footer-text{color:rgba(0,0,0,.6);font-size:.9em}} @media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> </head> <body class="book"> <div id="header"> <h1>SDP Windows to Linux Migration Guide</h1> <div class="details"> <span id="author" class="author">Perforce Professional Services</span><br> <span id="email" class="email"><a href="mailto:consulting-helix-core@perforce.com">consulting-helix-core@perforce.com</a></span><br> <span id="revnumber">version v2024.1.1,</span> <span id="revdate">2024-11-20</span> </div> <div id="toc" class="toc"> <div id="toctitle">Table of Contents</div> <ul class="sectlevel1"> <li><a href="#_draft_notice">DRAFT NOTICE</a></li> <li><a href="#_preface">Preface</a></li> <li><a href="#_overview">1. Overview</a></li> <li><a href="#_migration_planning">2. Migration Planning</a> <ul class="sectlevel2"> <li><a href="#_plan_for_user_impact">2.1. Plan for User Impact</a></li> <li><a href="#_failover_migration">2.2. Failover Migration</a></li> <li><a href="#_custom_triggers_and_extensions">2.3. Custom Triggers and Extensions</a></li> <li><a href="#_other_custom_automation_on_windows_commit_server_machine">2.4. Other Custom Automation on Windows Commit Server Machine</a></li> <li><a href="#_depot_root_and_depot_spec_map_fields">2.5. Depot Root and Depot Spec Map Fields</a> <ul class="sectlevel3"> <li><a href="#_sample_procedure_to_prepare_for_archive_replication">2.5.1. Sample Procedure to Prepare for Archive Replication</a></li> <li><a href="#_remote_depots">2.5.2. Remote Depots</a></li> <li><a href="#_archive_depots">2.5.3. Archive Depots</a></li> </ul> </li> <li><a href="#_the_journalprefix">2.6. The journalPrefix</a></li> <li><a href="#_find_incompatible_configuration_settings">2.7. Find Incompatible Configuration Settings</a> <ul class="sectlevel3"> <li><a href="#_sample_procedure_to_replace_p4log_configurable">2.7.1. Sample Procedure to replace P4LOG configurable</a></li> <li><a href="#_other_windows_paths_in_configuration">2.7.2. Other Windows Paths in Configuration</a></li> </ul> </li> <li><a href="#_uncompressed_journals">2.8. Uncompressed Journals</a></li> <li><a href="#_set_target_p4d_version">2.9. Set Target P4D Version</a> <ul class="sectlevel3"> <li><a href="#_if_windows_p4d_is_2019_1">2.9.1. If Windows P4D is 2019.1+</a></li> <li><a href="#_if_windows_p4d_is_2013_3_to_2018_2">2.9.2. If Windows P4D is 2013.3 to 2018.2</a></li> <li><a href="#_if_starting_p4d_is_2013_2_or_older">2.9.3. If Starting P4D is 2013.2 or older</a></li> </ul> </li> <li><a href="#_avoid_case_sensitivity_conversion">2.10. Avoid Case Sensitivity Conversion</a></li> </ul> </li> <li><a href="#_helix_core_topology">3. Helix Core Topology</a> <ul class="sectlevel2"> <li><a href="#_helix_proxies">3.1. Helix Proxies</a></li> <li><a href="#_helix_brokers">3.2. Helix Brokers</a></li> <li><a href="#_helix_core_p4d_servers">3.3. Helix Core P4D Servers</a> <ul class="sectlevel3"> <li><a href="#_edge_servers">3.3.1. Edge Servers</a></li> <li><a href="#_filtered_replicas">3.3.2. Filtered Replicas</a></li> <li><a href="#_unfiltered_replicas">3.3.3. Unfiltered Replicas</a></li> <li><a href="#_standby_servers">3.3.4. Standby Servers</a></li> <li><a href="#_distribution_servers">3.3.5. Distribution Servers</a></li> <li><a href="#_helix_swarm">3.3.6. Helix Swarm</a></li> <li><a href="#_helix_authentication_service">3.3.7. Helix Authentication Service</a></li> <li><a href="#_p4dtg">3.3.8. P4DTG</a></li> </ul> </li> </ul> </li> <li><a href="#_preparation">4. Preparation</a> <ul class="sectlevel2"> <li><a href="#_define_linux_sdp_instance_name">4.1. Define Linux SDP Instance Name</a></li> <li><a href="#_define_linux_replica_on_windows_commit_server">4.2. Define Linux Replica on Windows Commit Server</a></li> <li><a href="#_provision_new_linux_server_machines">4.3. Provision New Linux Server Machines</a> <ul class="sectlevel3"> <li><a href="#_select_operating_system">4.3.1. Select Operating System</a></li> <li><a href="#_install_helix_core_software_on_linux">4.3.2. Install Helix Core Software on Linux</a></li> </ul> </li> <li><a href="#_pull_archive_files_to_linux">4.4. Pull Archive Files to Linux</a></li> <li><a href="#_define_other_metadata_changes_for_linux">4.5. Define other Metadata Changes for Linux</a></li> <li><a href="#_dry_run">4.6. DRY RUN</a></li> <li><a href="#_craft_cutover_procedure">4.7. Craft Cutover Procedure</a></li> </ul> </li> <li><a href="#_sample_cutover_procedure">Appendix A: Sample Cutover Procedure</a> <ul class="sectlevel2"> <li><a href="#_sample_migration_scenarion">A.1. Sample Migration Scenarion</a></li> <li><a href="#_one_week_prior_to_cutover_procedure">A.2. One Week Prior to Cutover Procedure</a></li> <li><a href="#_one_day_prior_to_cutover_procedure">A.3. One Day Prior to Cutover Procedure</a></li> <li><a href="#_cutover_procedure">A.4. Cutover Procedure</a></li> </ul> </li> <li><a href="#_why_migrate">Appendix B: Why Migrate?</a></li> <li><a href="#_draft_notice_2">5. DRAFT NOTICE</a></li> </ul> </div> </div> <div id="content"> <div class="sect1"> <h2 id="_draft_notice">DRAFT NOTICE</h2> <div class="sectionbody"> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <i class="fa icon-warning" title="Warning"></i> </td> <td class="content"> This document is in DRAFT status and should not be relied on yet. It is a preview of a document to be completed in a future release. </td> </tr> </table> </div> </div> </div> <div class="sect1"> <h2 id="_preface">Preface</h2> <div class="sectionbody"> <div class="paragraph"> <p>This guide documents the process for migrating a Helix Core (P4D) service from a Windows server machine to Linux. A migration can be minimally disruptive to users if planned and executed properly. This document informs the planning and execution of a Windows to Linux migration.</p> </div> <div class="paragraph"> <p>Because P4D on Linux can run in the same case-insensitive mode that is familiar to users operating on P4D on Windows, the migration can be nearly seamless to users. After preparation, the eventual cutover is done with a <code>p4 failover</code> command in a scheduled maintenance window (or a series of failovers if edge servers or filtered replicas are involved). A failover smoothly transitions the P4D service from one machine (a Windows server machine in this case) to another (the Linux server machine), with no loss of data and minimal disruption.</p> </div> <div class="paragraph"> <p>The preparation typically involves straightforward (though potentially long-running) tasks for the administrator. If the Windows P4D runs with custom <a href="https://www.perforce.com/manuals/p4sag/Content/P4SAG/chapter.scripting.triggers.html">triggers</a> or <a href="https://www.perforce.com/manuals/extensions/Content/Extensions/Home-extensions.html">extensions</a>, there will be a degree of complexity depending on how complex those custom triggers and extensions are, and how they are handled. Typical options for handing them include porting or ditching (temporarily or permanently) the custom triggers or extensions. Aside from triggers and extensions, if any additional custom automation operates on the Windows P4D server machine directly, similar handling may be required.</p> </div> <div class="paragraph"> <p>Regardless of the effort and potential complexity handling customization (if any) for admins, the migration can culminate in a nearly seamless transition for users.</p> </div> <div class="paragraph"> <p><strong>Please Give Us Feedback</strong></p> </div> <div class="paragraph"> <p>Perforce welcomes feedback from our users. Please send any suggestions for improving this document to <a href="mailto:consulting-helix-core@perforce.com">consulting-helix-core@perforce.com</a>.</p> </div> </div> </div> <div class="sect1"> <h2 id="_overview">1. Overview</h2> <div class="sectionbody"> <div class="paragraph"> <p>A Windows to Linux Migration has these elements:</p> </div> <div class="ulist"> <ul> <li> <p>Migration Planning</p> </li> <li> <p>Provision New Linux Server machines.</p> </li> <li> <p>Install Perforce Helix on Linux.</p> </li> <li> <p>Setup Linux Replica server spec on Windows.</p> </li> <li> <p>Define adjustments to configurables.</p> </li> <li> <p>Pull and verify archives. This may take a long while if there is a lot of data to pull, potentially requiring multiple iterations of the pull/verify process.</p> </li> <li> <p>Do a Dry Run of the cutover.</p> </li> <li> <p>Port and/or Test Triggers</p> </li> <li> <p>Test, Test, Test in the Dry Run environment (which will later be the new Production environment).</p> </li> <li> <p>Correct data issues identified in planning and during dry runs.</p> </li> <li> <p>Craft a Cutover Procedure.</p> </li> <li> <p>Execute the Cutover Procedure.</p> </li> </ul> </div> <div class="paragraph"> <p>For purposes of this document, it does not matter if the servers are on-premises ("on-prem") or in a private or public cloud environment such as AWS, Azure, or GCP.</p> </div> <div class="paragraph"> <p>Each of these components is covered in detail in this guide.</p> </div> </div> </div> <div class="sect1"> <h2 id="_migration_planning">2. Migration Planning</h2> <div class="sectionbody"> <div class="paragraph"> <p>It is helpful to review the related document <a href="https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/SDP_MigrationAndUpgradeGuide.html">SDP Migration and Upgrade Guide</a>. This document discusses a Big Blue Green Cutover (BBGC) style of migration. For a Windows to Linux migration, use a special type of BBGC called a Failover Migration: After preparations are complete, a <code>p4 failover</code> completes the migration to Linux (or a series of failovers if edge servers are involved).</p> </div> <div class="paragraph"> <p>The Windows service may or may not be operated using <a href="https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/SDP_Guide.Windows.html">Server Deployment Package (SDP) for Windows</a>. Regardless of whether the Windows service is managed with SDP, the Windows service is largely left alone during the migration. The target environment will always be setup per best practices as implemented with the <a href="https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/SDP_Guide.Unix.html">Linux Server Deployment Package (SDP)</a>.</p> </div> <div class="sect2"> <h3 id="_plan_for_user_impact">2.1. Plan for User Impact</h3> <div class="paragraph"> <p>The migration can be nearly seamless. Typical impacts to users (humans and automation/bots) for a Windows to Linux migration include:</p> </div> <div class="ulist"> <ul> <li> <p>Users may need to login again after the Linux server machine(s) becomes the live production environment, depending on whether certain configurables like <code>auth.id</code> are adjusted during migration.</p> </li> <li> <p>If SSL is enabled, users will need to trust the new Linux server machine(s) when they become the live production environment.</p> </li> <li> <p>Depending on how user traffic is directed to the Windows server, there may be an impact:</p> <div class="ulist"> <ul> <li> <p>If users connect using a P4PORT that includes an an IP address, users will need to change the P4PORT they use.</p> </li> <li> <p>If the failover plan involves changing a DNS name (as opposed to some instantansous method of traffic redirection), there will be delays associated with DNS changes and DNS cache flushing. (The time required for a DNS change varies greatly across organizations, but is typically predicable in any given organization.)</p> </li> </ul> </div> </li> <li> <p>If the migration is planned to include a change in authentication mechanism, e.g. standard LDAP → SAML/SSO with the <a href="https://github.com/perforce/helix-authentication-service">Helix Authentication Service (HAS)</a>, users will need to adapt to this.</p> </li> <li> <p>There will be some amount of downtime for the scheduled cutover. This typically fits in a 2-hour maintenance window, and in best cases can be as little as a few minutes. If the starting p4d version is old (2018.2 or earlier) or ancient (2013.2 or earlier), a longer downtime may be required if the data set is large.</p> </li> </ul> </div> <div class="paragraph"> <p>Other than being aware of the above, users to not need to do any special preparation for the cutover. For example, users <em>do not</em> need to be concerned about the state of files in their workspaces. Whatever state files in workspaces are in at the time of cutover — checked out to default or numbered pending changelists, shelved or not, etc. — is not affected by the cutover.</p> </div> </div> <div class="sect2"> <h3 id="_failover_migration">2.2. Failover Migration</h3> <div class="paragraph"> <p>This document focuses on the failover style strategy. This entails creating a server spec (ServerID) for a standby of the commit server that we’ll call <code>p4d_fs_linux</code>, that will operate for a time as a Linux standby replica of the current production Windows commit server. Depending on various factors such as data scale, project priority and complexity, etc. this Linux replica of the Windows commit server may operate for days, weeks or even months before it is ready for the planned and scheduled failover that will promote the Linux standby server to become the new commit server.</p> </div> <div class="paragraph"> <p>This Failover strategy has several benefits:</p> </div> <div class="ulist"> <ul> <li> <p>Minimum disruption to end users for the cutover.</p> </li> <li> <p>Allows for extensive testing of the new Linux server(s) and infrastructure prior to cutover.</p> </li> <li> <p>The effect on the original Windows server(s) and infrastructure is minimal.</p> </li> <li> <p>Rollback, while hopefully unnecessary, is straightforward.</p> </li> </ul> </div> <div class="paragraph"> <p>While planning and preparation will take time and effort, the disruption to end users can be minimal.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> If your current method of operating Helix Core on Windows does not produce a regular metadata <strong>checkpoint</strong>, a change is required to get at least some basic form of checkpoint process in place. (If you are not sure what a checkpoint is, see: <a href="https://www.perforce.com/manuals/p4sag/Content/P4SAG/backup-recovery-concepts.html">backup and recovery concepts</a>.) </td> </tr> </table> </div> <div class="paragraph"> <p>The Failover strategy requires that the Windows Helix Core P4D service be at version 2019.1 (latest patch) or later. If it is not already at the latest patch available of 2019.1 or a later major version, see <a href="#_combining_upgrade_with_migration">[_combining_upgrade_with_migration]</a>.</p> </div> </div> <div class="sect2"> <h3 id="_custom_triggers_and_extensions">2.3. Custom Triggers and Extensions</h3> <div class="paragraph"> <p>The largest single variable affecting effort for a Windows to Linux migration is effort dealing with custom automation, such as <a href="https://www.perforce.com/manuals/p4sag/Content/P4SAG/chapter.scripting.triggers.html">triggers</a> or <a href="https://www.perforce.com/manuals/extensions/Content/Extensions/Home-extensions.html">extensions</a>. This can be literally zero effort if there are no custom triggers or extensions (or none that need to survive the migration). If porting and/or testing is required, that becomes a software development and testing project on its own that folds into the larger migration project.</p> </div> <div class="paragraph"> <p>Any custom triggers or extensions will need to be reviewed. Any that can’t be discarded will need to evaluated for porting and testing needs.</p> </div> <div class="paragraph"> <p>Triggers written in a native Windows langage such as batch or PowerShell, or operated as compiled .exe files, will need to be ported. Even triggers written in more portably languages such as Python or Perl will need testing and may need adjustment to operate in the Linux environment.</p> </div> <div class="paragraph"> <p>Extensions are written in Lua, the interpreter for which is entirely containdd in the Helix Core p4d binary itself. As such, custom extensions are less likely to require porting. However, they should still be evaluated and/or tested to be sure they have no Windows OS depenencies in their implementation.</p> </div> <div class="paragraph"> <p>Extensions provided by Perforce Software, such as those assoicated with Helix Swarm and the Helix Authentication Service, are inherently cross-platform and do not need to be ported.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> If it is acceptable to go without a particular for a time, porting of that trigger could be deferred. In that case, the trigger would be disables during the cutover of the Windows to Linux migration, and then re-add the trigger some time after on Linux. Deferring can remove porting of some triggers from the critical path of the migration project. </td> </tr> </table> </div> </div> <div class="sect2"> <h3 id="_other_custom_automation_on_windows_commit_server_machine">2.4. Other Custom Automation on Windows Commit Server Machine</h3> <div class="paragraph"> <p>Determine whether you have any custom softawre that runs directly on the Windows commit server machine. Custom automation that executes directly on the Windows server machine itself needs to be evaluated for porting and testing needs.</p> </div> <div class="paragraph"> <p>Any automation that merely connects as a client to the Windows p4d server, such as build server farms, need not be considered (other than possibly needing to login or trust p4d again and/or possibly change the P4PORT, as noted in <a href="#_plan_for_user_impact">Section 2.1, “Plan for User Impact”</a>). The Linux server speaks the same protocol as Windows.</p> </div> </div> <div class="sect2"> <h3 id="_depot_root_and_depot_spec_map_fields">2.5. Depot Root and Depot Spec Map Fields</h3> <div class="paragraph"> <p>This section desribes a change that must be done on the Windows commit server early in the process, before a Linux standby replica can be setup.</p> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <i class="fa icon-important" title="Important"></i> </td> <td class="content"> Read this section and be certain you are comfortable with the theory and commands before making any changes to a live production system. </td> </tr> </table> </div> <div class="paragraph"> <p>Perforce Helix depot specs have a field named <code>Map:</code> that, if used, must be eliminated prior to the deployment of Linux standby servers. This applies to depots that contained archive files to be replicated, including depots of type <code>local</code>, <code>stream</code>, <code>spec</code>, <code>extension</code>, <code>unload</code>, <code>tangent</code>, and <code>trait</code>.</p> </div> <div class="paragraph"> <p>To list all of your depots and their types, run this command:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 -ztag -F "%type% %name%" depots</pre> </div> </div> <div class="paragraph"> <p>For a Linux server to replicate archives from a Windows commit server, the <code>server.depot.root</code> configurable must be set on the Windows commit server, and the <code>Map:</code> fields of all depots must be changed to the default value.</p> </div> <div class="paragraph"> <p>Check to see if the <code>server.depot.root</code> configurable is set with:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure show server.depot.root</pre> </div> </div> <div class="paragraph"> <p>If that displays a value, then it is set. Otherwise it is not.</p> </div> <div class="paragraph"> <p>Next, check the <code>Map:</code> field of all depots with this command:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 -ztag -F "%type% %name% %map%" depots</pre> </div> </div> <div class="paragraph"> <p>Ignore depots of type <code>remote</code> or <code>archive</code>.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> If done carefully, the changes to set <code>server.depot.root</code> and clear the <code>Map:</code> field of each depot spec can be done non-disruptively on the live running Windows Helix Core service. </td> </tr> </table> </div> <div class="paragraph"> <p>The key to making the adjustment to depot spec <code>Map:</code> fields and the <code>server.depot.root</code> configurable non-disruptive is to understand that the p4d server will use the <code>Map:</code> field value <em>if it is set to anything other than the default</em>, and otherwise will fall back to the <code>server.depot.root</code> configurable as a default location, to find archive files for the given depot. If the value of the <code>Map:</code> field of any given depot is <code><em>TheDepotName</em>/…​</code>, that means the <code>Map:</code> field value is not explicitly set, and thus it will fall back to using the <code>server.depot.root</code> configurable for that depot. If the value of the <code>Map:</code> field is anything other then <code><em>TheDepotName</em>/…​</code>, then the <code>Map:</code> field value will be used to find archive files for that depot.</p> </div> <div class="paragraph"> <p>Your goal in making the change is to make it so that, immediately as/after the depot spec is updated to set the <code>Map:</code> field to its default value, the <code>server.depot.root</code> will cause the server to look in the exact same physical location for versioned files. Thus, there is no point in time during the procedure where the p4d server cannot locate its archive files, not even a nanosecond.</p> </div> <div class="paragraph"> <p>Before making changes, the singular <code>server.depot.root</code> value must be made to work for <em>all</em> existing depots. Make the single <code>server.depot.root</code> path work without actually moving any versioned files by using Windows directory symlinks. If individual depots are on different drives, put symlinks to all depots in the directory pointed to by the <code>server.depot.root</code> configurable so that p4d can find all depot files from that path. You may also find the Map fields use Windows UNC paths or if Windows junctions.</p> </div> <div class="sect3"> <h4 id="_sample_procedure_to_prepare_for_archive_replication">2.5.1. Sample Procedure to Prepare for Archive Replication</h4> <div class="paragraph"> <p>You mission is to make it so all depots find there archive files using <em>only</em> the <code>server.depot.root</code> configurable. Do whatever tricks with symlinks are needed to make it appear to the p4d server that all depot storage directories appear under the directory referenced by the <code>server.depot.root</code> configurable.</p> </div> <div class="paragraph"> <p>Victory looks like having the <code>server.depot.root</code> point to a single directory, say <code>S:\P4Depots</code>, and that <code>S:\P4Depots</code> contains either a subdirctory or a symmlink for all other depots.</p> </div> <div class="paragraph"> <p><strong>STEP 1: Review all Depot Specs</strong></p> </div> <div class="paragraph"> <p>Examine the current <code>server.depot.root</code> value and the <code>Map:</code> field values for depots of all types other than <code>remote</code> or <code>archive</code>. Start with:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure show server.depot.root p4 -ztag -F "%type% %name% %map%" depots</pre> </div> </div> <div class="paragraph"> <p>Get a sense for the any patterns. Do all depots have a non-default <code>Map:</code> field set? Do all depots instead have the default <code>Map:</code> field value, and thus fall back to the <code>server.depot.root</code> setting?</p> </div> <div class="paragraph"> <p><strong>STEP 2: Set server.depot.root</strong></p> </div> <div class="paragraph"> <p>Choose an appropriate value for <code>server.depot.root</code>. If the <code>server.depot.root</code> configurable was already set, it need not be changed.</p> </div> <div class="paragraph"> <p><strong>STEP 3: Create Directory Symlinks</strong></p> </div> <div class="paragraph"> <p>In the <code>server.depot.root</code> directory</p> </div> <div class="paragraph"> <p>EDITME: Add an example illustrating multiple depots with a mix of different <code>Map:</code> field values (and some unset), and using <code>MKDIR /D</code> in the <code>server.depot.root</code> dir to create symlinks, EDITME: Add reference to clear_depot_spec_Map_fields.sh.</p> </div> </div> <div class="sect3"> <h4 id="_remote_depots">2.5.2. Remote Depots</h4> <div class="paragraph"> <p>Depots of type <code>remote</code>, which are references to paths in entirely separate Helix Core data sets, are unaffected by Windows to Linux migrations.</p> </div> </div> <div class="sect3"> <h4 id="_archive_depots">2.5.3. Archive Depots</h4> <div class="paragraph"> <p>Special planning is required if there are any depots of type <code>archive</code> containing digital assets archived with <code>p4 archive</code>. Archive depots were intended to work removable storage, and removable storage on Windows server machines will not likely be compatible with Linux server machines due to filesystem differences. The most basic strategy is to <code>p4 restore</code> all archive revisions and then re-archive them after the migration. This may require provisioning a different type of removable storage device. Restoring before migration is the recommended approach unless it is not pragmatic for some reason.</p> </div> </div> </div> <div class="sect2"> <h3 id="_the_journalprefix">2.6. The journalPrefix</h3> <div class="paragraph"> <p>The Windows commit server must have the <code>journalPrefix</code> value set in order to set up the Linux replica. It can be set to any value that works to enable the p4d service to find numbered journals that have already been rotated. However, if no value is set at all, a value must be set before the Linux replica can be setup.</p> </div> <div class="paragraph"> <p>In some cases, the <code>journalPrefix</code> is undefined as a configurable, but a <code>journalPrefix</code> is provided as an argument to custom scripts that create checkpoints or rotate journals. In these cases, the <code>journalPrefix</code> for the Windows commit server should be set to the same used in custom checkpoint scripts.</p> </div> <div class="paragraph"> <p>In other cases, the <code>journalPrefix</code> is undefined, and so numbered/rotated journals (if there are any) appear in the default location in the P4ROOT directory. In these cases, an appropriate journalPrefix value should be set immediately, with a value set so that journal land in an appropriate directory.</p> </div> <div class="paragraph"> <p>A sample command to set the journalPrefix is:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure set journalPrefix=C:\P4Data\checkpoints\p4_1</pre> </div> </div> </div> <div class="sect2"> <h3 id="_find_incompatible_configuration_settings">2.7. Find Incompatible Configuration Settings</h3> <div class="paragraph"> <p>Using the <code>p4 configure</code> command to interact with <code>db.config</code> is a good way, and in many cases the only way, to set various configuration items with a Helix Core server. However, there are certain settings that must not be defined with <code>p4 configure</code>, as they conflict with settings the SDP defines with shell environment variables on Linux.</p> </div> <div class="paragraph"> <p>Review the output of the command <code>p4 configure show allservers</code> and see if any of the following are set for the <code>any</code> config (the global defaults):</p> </div> <div class="ulist"> <ul> <li> <p><code>P4JOURNAL</code></p> </li> <li> <p><code>P4PORT</code></p> </li> <li> <p><code>P4LOG</code></p> </li> <li> <p><code>P4TICKETS</code></p> </li> <li> <p><code>P4TRUST</code></p> </li> </ul> </div> <div class="paragraph"> <p>If any of these are set with <code>p4 configure</code>, the migration plan will need to deal with unsetting them after first ensuring they are set in some other way on the Windows service. Following is an example of how to replace how P4LOG is set displays in the output of <code>p4 configure show all servers</code>. Note that changing this requires a brief service restart to take effect.</p> </div> <div class="sect3"> <h4 id="_sample_procedure_to_replace_p4log_configurable">2.7.1. Sample Procedure to replace P4LOG configurable</h4> <div class="paragraph"> <p>This is an example of to unset a P4LOG setting if it is set in such a way that it shows up with a <code>p4 configure show allservers</code> command. The goal is to unset the configurable, but replace it with a Windows service setting so there is no effective change in the value used on Windows.</p> </div> <div class="paragraph"> <p>This sample assumes the Windows service name is <code>Perforce</code> and the <code>p4 configure show allservers</code> output contained a setting of P4LOG with a Windows path.</p> </div> <div class="paragraph"> <p>First, set the <code>P4LOG</code> setting and associate it with the Windows service name:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 set -S Perforce P4LOG=L:\p4logs\p4d.log</pre> </div> </div> <div class="paragraph"> <p>That will set the P4LOG variable so that it is associated with the Windows service named <code>Perforce</code>. Once that is done, it can be unset as a configurable, such as in this example:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4d.exe -r E:\PerforceRoot "-cunset P4LOG"</pre> </div> </div> <div class="paragraph"> <p>Next, stop and then start the Windows service as you normally would.</p> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <i class="fa icon-important" title="Important"></i> </td> <td class="content"> If the P4LOG setting was set for the <code>any</code> config, and there are multiple servers in your topology, be sure to set the value for the Windows service on all p4d server machines before unsetting the global default configurable. </td> </tr> </table> </div> </div> <div class="sect3"> <h4 id="_other_windows_paths_in_configuration">2.7.2. Other Windows Paths in Configuration</h4> <div class="paragraph"> <p>Also scan the <code>p4 configure show allservers</code> output for other settings that contain Windows paths, such as Structured Logs defined to reference a Windows path. Such things will need to be be overridden in the server spec for the Linux replica. For example, if you see:</p> </div> <div class="literalblock"> <div class="content"> <pre>any: serverlog.file.11=E:\PerforceRoot\triggers.csv</pre> </div> </div> <div class="paragraph"> <p>You’ll want to create an override for the Linux replica by doing:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure set p4d_fs_linux#serverlog.file.11=/p4/1/logs/triggers.csv</pre> </div> </div> </div> </div> <div class="sect2"> <h3 id="_uncompressed_journals">2.8. Uncompressed Journals</h3> <div class="paragraph"> <p>Examine how checkpoints and journals are currently taken in the Windows environment (or if they are taken at all).</p> </div> <div class="paragraph"> <p>If journals on the Windows service are compressed, replication will not work. Replication requires uncompressed journals.</p> </div> <div class="paragraph"> <p>One thing to check for is whether scripts call the <code>p4d -jc</code> or <code>p4d -jd</code> command with the <code>-z</code> option (lowercase 'z'). If so, the '-z' should be changed to <code>-Z</code> (uppercase 'Z'). The lowerase <code>-z</code> compresses both checkpoints and numbered journal files, and thus is not suitable for replication. The uppercase <code>-Z</code> compresses the checkpoint file, but not the numbered journal files, and is intended for replication. Other changes to custom scripts that manage checkpoints in the Windows environment may be warranted.</p> </div> </div> <div class="sect2"> <h3 id="_set_target_p4d_version">2.9. Set Target P4D Version</h3> <div class="paragraph"> <p>Define the desired target P4D version. For illustration in this document, we will assume a target P4D version of 2023.1; 2023.2 or later will also be appropriate.</p> </div> <div class="sect3"> <h4 id="_if_windows_p4d_is_2019_1">2.9.1. If Windows P4D is 2019.1+</h4> <div class="paragraph"> <p>If the Windows P4D version is 2019.1 or later, there are two viable options to upgrading:</p> </div> <div class="ulist"> <ul> <li> <p>The Windows infrastructure can be upgraded in place to the target P4D version immediately, before the Linux replica is setup. This will require a service outage for the Windows service early in the project, before the Linux replica is setup. Because the P4D version is 2019.1 or later, upgrades will be relatively straightforward.</p> </li> <li> <p>Leave the Windows P4D version as it is. In this case, the plan should account for doing the P4D upgrade in the Linux topology immediately after the failover that promotes the Linux server to be the commit server, in the same maintenance window (before testing and turning the system over to users).</p> </li> </ul> </div> </div> <div class="sect3"> <h4 id="_if_windows_p4d_is_2013_3_to_2018_2">2.9.2. If Windows P4D is 2013.3 to 2018.2</h4> <div class="paragraph"> <p>If the starting P4D version is older than 2019.1 but at least 2013.3, the plan must account for first upgrading the Windows service in place to the target version.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> A special upgrade procedure is required for upgrades that go to-or-thru P4D 2019.1. </td> </tr> </table> </div> <div class="paragraph"> <p>As a general note, in all cases for P4D upgrades, a single "hop" is done, from the original P4D version directly to the new target P4D version. That applies even if the starting P4D version is an antique 1995.1 and the target version is a modern 2023.2. There is neither need nor value in breaking the upgrade up into multiple steps. Upgrades that go thru certain major versions where architectural changes were made (e.g. 2013.3, 2019.1, etc), additional and specific upgrade procedures will be needed based on the starting and target P4D version.</p> </div> </div> <div class="sect3"> <h4 id="_if_starting_p4d_is_2013_2_or_older">2.9.3. If Starting P4D is 2013.2 or older</h4> <div class="paragraph"> <p>If the starting P4D version is older than 2013.3, a checkpoint replay is required.</p> </div> <div class="paragraph"> <p>Other strategies can be considered that would not require upgrading in place if avoiding an in-place upgrade is a priority. That would entail longer downtime and other complexity. Such options are not explored in detail in this document.</p> </div> </div> </div> <div class="sect2"> <h3 id="_avoid_case_sensitivity_conversion">2.10. Avoid Case Sensitivity Conversion</h3> <div class="paragraph"> <p>Since this document is about Windows to Linux migrations, the data set will naturally and necessarily be case-insensitive at the start of the project. This document does not discuss case sensitivity change, as it is unnecessary for a Windows to Linux migration. If there is a desire to become case-sensitive (for example, to support Linux clients), we advise deferring that as a separate project to be done after the Windows to Linux migration is complete.</p> </div> <div class="paragraph"> <p>A Windows to Linux migration that preserves the original case-insensitive behavior, as described in this document, is minimally disruptive. A case sensitivity conversion is best to defer until the Windows to Linux migration, for several reasons:</p> </div> <div class="ulist"> <ul> <li> <p>The conversion to case-sensitive can only be done on Linux.</p> </li> <li> <p>Case sensitivity conversion can be disruptive to users and workflows, and may result in data loss (although data that will be lost will be known before the loss).</p> </li> <li> <p>Case sensitivity conversion requires significant downtime.</p> </li> <li> <p>Case sensitivity conversion requires duplication of 100% of versioned file storage (during development and testing of the case conversion process on your data).</p> </li> <li> <p>Case sensitivy conversion may potentially disrupt tooling that interacts with your server.</p> </li> </ul> </div> <div class="paragraph"> <p>Generally speaking a case sensitivity conversion is more complex than a Windows to Linux conversion, sufficiently so that we advise relegating case sensitivity conversion to a separate project from the Windows to Linux migration. The case sensitivity conversion, if done at all, can be started after the Windows to Linux migration is complete. The case sensitivity involves doing neurosurgery on your Helix Core data set using the <a href="https://ftp.perforce.com/perforce/tools/p4-migrate/p4migrate.html">p4migrate</a> utility.</p> </div> <div class="paragraph"> <p>Further discussion on case sensitivity conversions is outside the scope of this document.</p> </div> </div> </div> </div> <div class="sect1"> <h2 id="_helix_core_topology">3. Helix Core Topology</h2> <div class="sectionbody"> <div class="paragraph"> <p>The complexity of a Windows to Linux migration project is naturally affected by the baseline compelxity of the Helix Core ecosystem operating on Windows.</p> </div> <div class="paragraph"> <p>Is your server a single machine, or are there many server machines? In any case, you’ll want to think in terms of a "Big Blue/Green Deploy." Every active Windows server machine in the current production topology (the "Blue" servers), including all replicas, edges, and proxies, will all need equivalent Linux server machines to replace them (the "Green" servers). Replicas are straightforward to handle. Handling edges and/or filtered replicas adds complex complexity to be aware of.</p> </div> <div class="paragraph"> <p>Consider what Perforce Helix server machines and services exist in your Windows topology:</p> </div> <div class="sect2"> <h3 id="_helix_proxies">3.1. Helix Proxies</h3> <div class="paragraph"> <p>In some cases, Linux proxies will have existed with a Windows commit server all along, as running proxies on Linux is advisable even in a topology with a Windows servers (for some of the same reasons that a Windows to Linux migration is popular, such as much faster native filesystems).</p> </div> <div class="paragraph"> <p>Any Windows should be migrated to Linux as well. However, while strongly discouraged, a Windows p4p (proxy) can remain in place with a Linux p4d server topology (so long as it operates in case-insensitive mode, which we assume in this document).</p> </div> </div> <div class="sect2"> <h3 id="_helix_brokers">3.2. Helix Brokers</h3> <div class="paragraph"> <p>Helix Brokers should be migrated to Linux as well. However, while strongly discouraged, a Windows p4broker can remain in place with a Linux p4d server topology.</p> </div> <div class="paragraph"> <p>If brokers are configured with any custom software (broker "filter" scripts), porting this software to Linux should be accounted for in planning.</p> </div> </div> <div class="sect2"> <h3 id="_helix_core_p4d_servers">3.3. Helix Core P4D Servers</h3> <div class="paragraph"> <p>Every Helix Core topology will have exactly one commit server. If there is only a single server in the topology, it is the commit server. It may have additional p4d servers that extend the topology. Following are types of p4d servers (various typess of replicas) and their implications for a Windows to Linux migration:</p> </div> <div class="sect3"> <h4 id="_edge_servers">3.3.1. Edge Servers</h4> <div class="paragraph"> <p>For purposes of a migration, edge servers should be classified into one of two categories:</p> </div> <div class="ulist"> <ul> <li> <p>Edge servers that must survive the migration with workspaces intact.</p> </li> <li> <p>Edge servers that must survive the migration, but can lose all workspaces.</p> </li> <li> <p>Edge servers that can be discarded.</p> </li> </ul> </div> <div class="paragraph"> <p>For Windows edge servers that must survive with workspaces intact, a microcosm of the plan for the commit server can be applied to the edge server. A Linux standby of the Windows edge server can be configured and failed over to before the failover of the Windows commit server.</p> </div> <div class="paragraph"> <p>If there are no custom triggers, the failover of a Windows edge server to its Linux standby can done far ahead of the failover of the Windows commit to its standby — days, weeks, or even months ahead. However, if there are triggers, that generally means all Windows edges servers that must survive with workspaces intact must be failed over to their Linux standbys in the same maintenacne window that the commit server is failed over to, e.g. minutes or hours before.</p> </div> <div class="paragraph"> <p>For Windows edge servers that must survive but for which workspaces are not needed (such as edges that have no human users but service only automated build farms), those will require Linux server machines, but will be created fresh in the Linux environment, with workspaces discarded during the Cutover Procedure. These will be loaded with a standard checkpoint from the commit server, albeit excluding edge-specific tables like <code>db.have</code>.</p> </div> </div> <div class="sect3"> <h4 id="_filtered_replicas">3.3.2. Filtered Replicas</h4> <div class="paragraph"> <p>Filtered replicas that must survive the migration are handled in the same way as edge that needs its workspaces. That is, a Linux standby of the Windows filtered replica is setup and failover over to ahead of the commit server failover.</p> </div> <div class="paragraph"> <p>Unlike edge servers, the filtered forwarding replica can always be failed over to long ahead of the commit server, regardless of whether there are custom triggers (as triggers never fire on replicas).</p> </div> </div> <div class="sect3"> <h4 id="_unfiltered_replicas">3.3.3. Unfiltered Replicas</h4> <div class="paragraph"> <p>Unfiltered replicas are simply reseeded (loaded with a fresh checkpoint) during the Cutover Procedure.</p> </div> </div> <div class="sect3"> <h4 id="_standby_servers">3.3.4. Standby Servers</h4> <div class="paragraph"> <p>Standby servers are simply reseeded (loaded with a fresh checkpoint) during the Cutover Procedure.</p> </div> </div> <div class="sect3"> <h4 id="_distribution_servers">3.3.5. Distribution Servers</h4> <div class="paragraph"> <p>A Windows to Linux migration has no impact to exsiting servers of type <code>distribution-server</code>.</p> </div> <div class="paragraph"> <p>EDITME: Should be true, but test this to confirm.</p> </div> </div> <div class="sect3"> <h4 id="_helix_swarm">3.3.6. Helix Swarm</h4> <div class="paragraph"> <p>Helix Swarm is essentially a client to the Helix Core server, and as such is largely unaffected by a Windows to Linux migration. It may possibly need to change the configured P4PORT it uses to connect to the commit server, as noted in <a href="#_plan_for_user_impact">Section 2.1, “Plan for User Impact”</a>). In the case of Helix Swarm, this would involve update its <code>config.php</code> and reloading Swarm’s configuration.</p> </div> </div> <div class="sect3"> <h4 id="_helix_authentication_service">3.3.7. Helix Authentication Service</h4> <div class="paragraph"> <p>If the Helix Authentication Service (HAS) has been deployed for the Windows commit server, it can be left in place and will be entirely unaffected by and unaware of the Windows to Linux Migration.</p> </div> <div class="paragraph"> <p>Optionally, the HAS service can be moved onto the Linux commit server machine for easier management.</p> </div> </div> <div class="sect3"> <h4 id="_p4dtg">3.3.8. P4DTG</h4> <div class="paragraph"> <p>If the Perforce Defect Tracking Gateway (P4DTG) has deployed for the Windows commit server and operates on a separate server machine, it can be left in place and will be entirely unaffected by and unaware of the Windows to Linux Migration.</p> </div> <div class="paragraph"> <p>If P4DTG operates on Windows, it could be migrated to Linux as well, or left in place. However, while there are many compelling reasons to migrate Helix Core to Linux, there is less of a need to migrate P4DTG if it is stable and operating well. Migrating P4DTG to Linux (e.g. if normalization to all Linux infrastructure is a goal) can be done entirely independetly of, or as part of, the Windows to Linux migration project.</p> </div> </div> </div> </div> </div> <div class="sect1"> <h2 id="_preparation">4. Preparation</h2> <div class="sectionbody"> <div class="sect2"> <h3 id="_define_linux_sdp_instance_name">4.1. Define Linux SDP Instance Name</h3> <div class="paragraph"> <p>See <a href="https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/SDP_Guide.Windows.html#_instance">the definition of Instance in SDP parlance</a>.</p> </div> <div class="paragraph"> <p>Set the intended SDP instance name. For purposes of this document, we’ll use the SDP default instance name of <code>1</code>.</p> </div> </div> <div class="sect2"> <h3 id="_define_linux_replica_on_windows_commit_server">4.2. Define Linux Replica on Windows Commit Server</h3> <div class="paragraph"> <p>Reminder, setting a value for <code>journalPrefix</code> on the Windows commit server and dealing with the <code>server.depot.root</code> must be done before setting up the Linux replica. See:</p> </div> <div class="ulist"> <ul> <li> <p><a href="#_depot_root_and_depot_spec_map_fields">Section 2.5, “Depot Root and Depot Spec Map Fields”</a></p> </li> <li> <p><a href="#_the_journalprefix">Section 2.6, “The journalPrefix”</a></p> </li> </ul> </div> <div class="paragraph"> <p>Address the above items before moving forward.</p> </div> <div class="paragraph"> <p>On the Windows commit server, create a server spec to represent p4d on Linux. Call it <code>p4d_fs_linux</code>. Run this command:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 server p4d_fs_linux</pre> </div> </div> <div class="paragraph"> <p>Set these field values:</p> </div> <div class="ulist"> <ul> <li> <p><code>ServerID: p4d_fs_linux</code></p> </li> <li> <p><code>Type: server</code></p> </li> <li> <p><code>Services: forwarding-standby</code></p> </li> <li> <p><code>Description: Linux replica of Windows commit server.</code></p> </li> </ul> </div> <div class="paragraph"> <p>Next, determine a P4PORT value that can be used from the Linux replica server machine to reference the Windows commit server. Getting this to work may entail opening a firewall rule on the Windows commit server to ensure that the Linux server can reach it on whatever port p4d runs on (often 1666).</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure set p4d_fs_linux#P4TARGET=_P4PORT_OF_WINDOWS_SERVER_FROM_LINUX</pre> </div> </div> <div class="paragraph"> <p>Then define other configurables with commands like these, to be run on your Windows commit server:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 configure set p4d_fs_linux#db.replication=readonly p4 configure set p4d_fs_linux#rpl.forward.all=1 p4 configure set p4d_fs_linux#rpl.compress=4 p4 configure set p4d_fs_linux#server=4 p4 configure set p4d_fs_linux#monitor=2 p4 configure set p4d_fs_linux#serviceUser=svc_p4d_fs_linux p4 configure set p4d_fs_linux#rpl.journalcopy.location=1 p4 configure set p4d_fs_linux#journalPrefix=/p4/1/checkpoints/p4_1 p4 configure set p4d_fs_linux#server.depot.root=/p4/1/depots p4 configure set p4d_fs_linux#startup.1="journalcopy -i 0" p4 configure set p4d_fs_linux#startup.2="pull -i -L 1" p4 configure set p4d_fs_linux#startup.3="pull -u -i 1" p4 configure set p4d_fs_linux#startup.4="pull -u -i 1" p4 configure set p4d_fs_linux#startup.5="pull -u -i 1" p4 configure set p4d_fs_linux#startup.6="pull -u -i 1 --batch=50" p4 configure set p4d_fs_linux#startup.7="pull -u -i 1 --batch=50" p4 configure set p4d_fs_linux#startup.8="pull -u -i 1 --batch=50"</pre> </div> </div> <div class="paragraph"> <p>Next, create the replication servie user, and set a password for it (generate or think of a password first):</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 --field Type=service user -o svc_p4d_fs_linux | p4 user -f -i p4 passwd svc_p4d_fs_linux</pre> </div> </div> <div class="paragraph"> <p>Store the password securely, however you store passwords (e.g. in some kind of password vault).</p> </div> <div class="paragraph"> <p>Next, add the <code>svc_p4d_fs_linux</code> user to a group name <code>ServiceUsers</code>, and ensure that group has a <code>Timeout</code> value set to <code>unlimited</code>. Then add this line near the bottom of the Protections table:</p> </div> <div class="literalblock"> <div class="content"> <pre>super group ServiceUsers * //...</pre> </div> </div> <div class="paragraph"> <p>Once the above metadata change are complete, the Linux replica is defined. The next routine checkpoint to be taken in the Windows environment will have the definition of the Linux replica "baked in", and thus it can be used to seed the Linux replica.</p> </div> </div> <div class="sect2"> <h3 id="_provision_new_linux_server_machines">4.3. Provision New Linux Server Machines</h3> <div class="paragraph"> <p>EDITME - Add content here.</p> </div> <div class="sect3"> <h4 id="_select_operating_system">4.3.1. Select Operating System</h4> <div class="paragraph"> <p>As of this writing, the best options are:</p> </div> <div class="ulist"> <ul> <li> <p>Ubuntu 22.04 (or 20.04)</p> </li> <li> <p>RHEL/Rocky Linux 9 (or 8)</p> </li> </ul> </div> </div> <div class="sect3"> <h4 id="_install_helix_core_software_on_linux">4.3.2. Install Helix Core Software on Linux</h4> <div class="paragraph"> <p>On the Linux server machines that do not yet have any data, use the Helix Installer, do a Configured Install.</p> </div> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <i class="fa icon-warning" title="Warning"></i> </td> <td class="content"> The Helix Installer is only to be used on truly "green" server machines, those with <em>absolutely no</em> Helix Core data on them yet. </td> </tr> </table> </div> <div class="paragraph"> <p>Run these commands as <code>root</code> on the server machine:</p> </div> <div class="literalblock"> <div class="content"> <pre>mkdir -p /hxdepots/reset cd /hxdepots/reset curl -L -s -O https://swarm.workshop.perforce.com/download/guest/perforce_software/helix-installer/main/src/reset_sdp.sh chmod +x reset_sdp.sh ./reset_sdp.sh -C > settings.cfg</pre> </div> </div> <div class="paragraph"> <p>In <code>settings.cfg</code>, change these settings:</p> </div> <div class="ulist"> <ul> <li> <p>DNS_name_of_master_server=</p> </li> <li> <p>P4_PORT=</p> </li> <li> <p>Instance=</p> </li> <li> <p>Password=</p> </li> <li> <p>CaseSensitive=0</p> </li> <li> <p>P4USER=</p> </li> <li> <p>ServerID=</p> </li> <li> <p>ServerType=</p> </li> <li> <p>P4BinRel=</p> </li> <li> <p>P4APIRel=</p> </li> </ul> </div> <div class="paragraph"> <p>Then run the script:</p> </div> <div class="literalblock"> <div class="content"> <pre>./reset_sdp.sh -no_sd -c settings.cfg 2>&1 | tee log.reset_sdp.txt</pre> </div> </div> <div class="literalblock"> <div class="content"> <pre>su - perforce p4 set</pre> </div> </div> <div class="literalblock"> <div class="content"> <pre>cd /p4/common/site [[ -d config ]] || mkdir config cd config</pre> </div> </div> </div> </div> <div class="sect2"> <h3 id="_pull_archive_files_to_linux">4.4. Pull Archive Files to Linux</h3> <div class="paragraph"> <p>Once the Linux replica are setup, a variety of strategies can be used to transfer archive files.</p> </div> <div class="paragraph"> <p>Plan to execute about 3 iterations of <code>p4verify.sh</code> on Linux, to get p4d to pull the archives. The first pass, starting with no archive files, is to start a bulk pull. That could take hours, days or weeks depending on data scale. Also, it may need some nudging, clearing and resetting the replication "pull queue."</p> </div> <div class="paragraph"> <p>The second to fill in gaps, and the 3rd pass should be clean.</p> </div> <div class="paragraph"> <p>Depending on scale of data, you may want to consider using outside-p4d mechanisms for transferring some archives (especially the <code>.gz</code> files, <code>,v</code> files should be transferred with <code>p4 pull</code> ideally).</p> </div> <div class="paragraph"> <p>There are lots of variations on how to get the archives files there. This document focuses primarily on using replication (i.e. replica <code>startup.N</code> threads calling <code>p4 pull</code> commands) for these reasons:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>It has an advantage in that, if the Linux p4d writes an archive, it can always find it, even it it’s a funky path with Unicode bytes in the path. By contrast, files copied outside p4d may not be found by the Linux p4d if the path to the file (including the base filename and any directory in the path) contain any high-byte, non-ASCII characters.</p> </li> <li> <p>It can be throttled up or down (by configuring more or fewer <code>startup.N</code> threads and tuning batching parameters).</p> </li> <li> <p>It is generally safe and non-disruptive to the production Windows environment.</p> </li> <li> <p>It requires no special setup.</p> </li> </ol> </div> <div class="paragraph"> <p>The above noted, for initial, first-time bulk pulls of Terabytes of data, a Windows port of rsync might be considered for pulling <code>.gz</code> archives files (and only those). It may well pull bulk archives faster than replication. However, rsync entails extra setup effort (not covered in this document), and also has greater risk of impacting the production Windows environment.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> A live running rsync daemon is required to run on Linux for the Windows port of rsync to talk to. For Linux to Linux transfers, the rsync utility does not require a live running rsync daemon, but one will be required for this scenario. The daemon service can be crafted with a configuration to land files in a location relative to a depot root directory on Linux that mirrors the path relative to the depot root on Windows. </td> </tr> </table> </div> <div class="paragraph"> <p>There are many options here; somehow or other the goal is to get the archive files in place so <code>p4verify.sh</code> is happy.</p> </div> </div> <div class="sect2"> <h3 id="_define_other_metadata_changes_for_linux">4.5. Define other Metadata Changes for Linux</h3> <div class="paragraph"> <p>During the migration to Linux and the Server Deployment Package, a script should be crafted that defines best practice configurables to be set during the cutover, immediately after the failover and upgrade. The Linux server testing should be done with these best practices in place.</p> </div> <div class="paragraph"> <p>The SDP contains a <code>configure_new_server.sh</code> that defines best practices for a new server, mostly by running many <code>p4 configure set</code> commands to set various configurables. This should not be used exactly as it is because, as the name implies, it is intended for an entirely new server. However, it should be used as a guide to develop a custom script specific to this migration. Typically the procedure starts by creating a script named something like <code>configure_THIS_server.sh</code>, initially copied from the stock SDP script <code>configure_new_server.sh</code>.</p> </div> <div class="paragraph"> <p>Then the <code>configure_THIS_server.sh</code> script is edited in light of the output of <code>p4 configure show</code> on the commit server. Generally the editing involves:</p> </div> <div class="ulist"> <ul> <li> <p>Remove code in the script that creates a <code>spec</code> and <code>unload</code> depots if those already exist or are not desired.</p> </li> <li> <p>Remove setting any configurables that do not need to be set because they already are in the data set.</p> </li> <li> <p>Remove setting any configurables that do not need to be set because a locally tuned value is better for the environment than the script default.</p> </li> <li> <p>Adjust setting any configurables to desired values.</p> </li> </ul> </div> <div class="paragraph"> <p>Ultimately, the goal of developing this script is to capture the results of the process of reviewing and applying best practice configurables to the current data set. The script typically takes a few hours to develop and review, but can be executed in mere seconds during the cutover. This script should be exercised during the dry run and again for the Production Cutover.</p> </div> </div> <div class="sect2"> <h3 id="_dry_run">4.6. DRY RUN</h3> <div class="paragraph"> <p>At least one Dry Run is required to confidently execute a migration. Plan to have at least one.</p> </div> <div class="paragraph"> <p>In the dry run, the <code>p4 failover</code> command is NOT used, nor is user traffic directed to the Linux server. Instead, the Linux service is stopped, and the <code>$P4ROOT/server.id</code> file is simply hand-edited to be the ServerId the the commit server. Then the service is restarted.</p> </div> <div class="paragraph"> <p>At that point, the Linux commit server will believe itself to be the new commit server, even though users will still be using the Windows server for real work. Then the Linux server can be tested in various ways:</p> </div> <div class="ulist"> <ul> <li> <p>Test connectivity from all user access points.</p> </li> <li> <p>Test connectivity from all server access points, including replicas, proxies, and any integrated systems such as Jenkins, Swarm, P4DTG, etc.</p> </li> <li> <p>If there are any <code>ldap</code> specs, ensure the targeted LDAP servers can be reached from the Linux server. (This may require firewall adjustments).</p> </li> <li> <p>If the migration include a Helix Core upgrade, test the new version.</p> </li> <li> <p>If configurables were changed, do any testing warranted by the changes to configurables.</p> </li> </ul> </div> <div class="paragraph"> <p>In addition to the value of exercising a procedure that is largely similar to the Production Cutover procedure, the dry run procedure is useful in gathering timing info related to various steps in the process. Endeavour to capture timing of key steps in the process.</p> </div> <div class="paragraph"> <p>After all Dry Runs are declared complete, the Linux server environment can be reset to be a replica of the Windows environment once again, and the archive pull process repeated (which should be much faster this time around, as most archives are in place).</p> </div> </div> <div class="sect2"> <h3 id="_craft_cutover_procedure">4.7. Craft Cutover Procedure</h3> <div class="paragraph"> <p>Craft the Production Cutover procedure with learnings from the dry run(s).</p> </div> </div> </div> </div> <div class="sect1"> <h2 id="_sample_cutover_procedure">Appendix A: Sample Cutover Procedure</h2> <div class="sectionbody"> <div class="sect2"> <h3 id="_sample_migration_scenarion">A.1. Sample Migration Scenarion</h3> <div class="paragraph"> <p>The following is a sample cutover procedure for a topology with a commit server and an edge server, with custom triggers that have been ported to Linux.</p> </div> <div class="paragraph"> <p>The sample instructions assume the <code>perforce</code> OS user on the Linux servers has been setup with the proper shell environment, specifically that the <code>~/.bashrc</code> has sourced the <code>/p4/common/bin/p4_vars</code> file with the appropriate SDP instance parameter.</p> </div> <div class="paragraph"> <p>The preparation for this sample cutover scenario would have included:</p> </div> <div class="ulist"> <ul> <li> <p>As set of ported and tested Linux custom triggers (replacing former custom triggers on Windows) deployed on all Linux servers as <code>/p4/common/site/bin/triggers</code> folder.</p> </li> <li> <p>A triggers table suited for operation on the Linux server after it becomes th commit server.</p> </li> </ul> </div> </div> <div class="sect2"> <h3 id="_one_week_prior_to_cutover_procedure">A.2. One Week Prior to Cutover Procedure</h3> <div class="paragraph"> <p><strong>STEP 1: Verify Replication</strong></p> </div> <div class="paragraph"> <p>Verify that replication is healthy on the Linux replica, the Windows edge, and the Linux replica of the Windows edge server.</p> </div> </div> <div class="sect2"> <h3 id="_one_day_prior_to_cutover_procedure">A.3. One Day Prior to Cutover Procedure</h3> <div class="paragraph"> <p><strong>STEP 1: Verify Replication</strong></p> </div> <div class="paragraph"> <p>Verify that replication is healthy on the Linux replica, the Windows edge, and the Linux replica of the Windows edge server.</p> </div> <div class="paragraph"> <p><strong>STEP 2: Checkpoint Linux Replicas</strong></p> </div> <div class="paragraph"> <p>On the Linux standby of the Windows commit server, and separately and in parallel on any Linux standbys of Windows edge servers, request a checkpoint:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 admin checkpoint -Z</pre> </div> </div> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <i class="fa icon-warning" title="Warning"></i> </td> <td class="content"> Do a <code>p4 info</code> first and confirm that the target ServerID is that of the Linux edge server, to avoid taking an unintentional "live checkpoint" of the commit server. </td> </tr> </table> </div> <div class="paragraph"> <p>Next, on the Windows commit server, execute a journal rotation:</p> </div> <div class="literalblock"> <div class="content"> <pre>p4 admin journal</pre> </div> </div> <div class="paragraph"> <p>Once this command has been run, it will trigger the Linux server to start taking a checkpoint. On the Linux server, a checkpoint should immediately appear in the checkpoints directory.</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <i class="fa icon-tip" title="Tip"></i> </td> <td class="content"> The checkpoints directory is '/p4/<em>N</em>/checkpoints' for the standby of the commit server, `/p4/N/checkpoints.ShortServerID </td> </tr> </table> </div> <div class="paragraph"> <p>Monitor the checkpoints directory and await the appearance of a *.md5 with the same number as the checkpoint. The existence of the MD5 file indicates the successful completion of the checkpoint process.</p> </div> <div class="paragraph"> <p>Use the <code>watch</code> utility and wait until the *.md5 file appears on the Linux standby of the Windows commit:</p> </div> <div class="literalblock"> <div class="content"> <pre>watch -n 5 "cd /p4/1/checkpoints; ls -lrt *.gz *.md5 | tail -5"</pre> </div> </div> <div class="paragraph"> <p>In parallel, use the <code>watch</code> utility and wait until the *.md5 file appears on the Linux standby of the Windows edge:</p> </div> <div class="literalblock"> <div class="content"> <pre>watch -n 5 "cd /p4/1/checkpoints.edge_syd; ls -lrt *.gz *.md5 | tail -5"</pre> </div> </div> <div class="paragraph"> <p><strong>STEP 3</strong>: Replay Checkpoint to offline_db.</p> </div> <div class="paragraph"> <p>On the Linux commit and edge servers, replay their local checkpoint files created in the prior step into to the offline_db. This can be done regardless of whether the local p4d service is replicating or even online at all. The replay to the offline_db is nether affected by nor disruptive to the p4d service. It can be done on the commit and the standby in parallel, though can only be one on each machine only after the checkpoint completes and the local *.md5 file exists on the given machine.</p> </div> <div class="literalblock"> <div class="content"> <pre>nohup recreate_offline_db.sh < /dev/null > /dev/null 2>&1 &</pre> </div> </div> <div class="paragraph"> <p>Monitor until completion with:</p> </div> <div class="literalblock"> <div class="content"> <pre>tail -f $LOGS/recreate_offline_db.log</pre> </div> </div> <div class="paragraph"> <p>This preparation of the offline_db allows the Linux service to start operation with daily checkpoints with a reasonably current offline_db. It may a day or so behind by the time the cutover occurs. (If for some reason the cutover is postponed by more than a few days, repeat this procedure of creating and replaying checkpoints on Linux to keep the offline_db reasonably current. Repeating the procedure will replace the offline_db with a more recent checkpoint).</p> </div> </div> <div class="sect2"> <h3 id="_cutover_procedure">A.4. Cutover Procedure</h3> <div class="paragraph"> <p><strong>STEP 1: Verify Replication</strong></p> </div> <div class="paragraph"> <p>Verify that replication is healthy on the Linux replica, the Windows edge, and the Linux replica of the Windows edge server.</p> </div> <div class="paragraph"> <p><strong>STEP 2: Disabled Scheduled Tasks</strong></p> </div> <div class="paragraph"> <p>On the old Windows commit and edge server machines, disable any Scheduled Tasks related to backups or checkpoints. Also ensure no long-running checkpoint or backup operations are in progress that won’t be complete by the time of the intended cutover.</p> </div> <div class="paragraph"> <p><strong>STEP 3: Disable Crontabs</strong></p> </div> <div class="paragraph"> <p>On the new Linux commit and edge server machines, save and then disable all crontabs intended for routine production operation (and they may have been left on during dry runs).</p> </div> <div class="paragraph"> <p><strong>STEP 4: Lockout Users with Protections</strong></p> </div> <div class="paragraph"> <p><strong>STEP 5: Stop Services</strong></p> </div> <div class="paragraph"> <p><strong>STEP 6: Start Services</strong></p> </div> <div class="paragraph"> <p><strong>STEP 7: Rotate Journal</strong></p> </div> <div class="paragraph"> <p><strong>STEP 8: Verify Replication</strong></p> </div> <div class="paragraph"> <p><strong>STEP 9: Failover Edge Server</strong></p> </div> <div class="paragraph"> <p><strong>STEP 10: Apply Metadata Changes for Linux</strong></p> </div> <div class="paragraph"> <p>Apply metadata changes required for operation on Linux and commit server now being on SDP.</p> </div> <div class="paragraph"> <p><strong>STEP 11: Failover Commit Server</strong></p> </div> <div class="paragraph"> <p><strong>STEP 12: Do Sanity Tests</strong></p> </div> <div class="paragraph"> <p><strong>STEP 13: Decide: GO/NO GO</strong></p> </div> <div class="paragraph"> <p><strong>STEP 14: Restore Default Protections</strong></p> </div> <div class="paragraph"> <p><strong>STEP 15: Direct User Traffic to Linux</strong></p> </div> <div class="paragraph"> <p><strong>STEP 16: Enable crontabs</strong></p> </div> </div> </div> </div> <div class="sect1"> <h2 id="_why_migrate">Appendix B: Why Migrate?</h2> <div class="sectionbody"> <div class="paragraph"> <p>Migrations from Windows to Linux have been the single most consistent theme in Perforce Consulting in over two decades, for many reasons. The procedures have evolved over time, with the modern "failover style" replication being the latest in seamless cutover.</p> </div> <div class="paragraph"> <p>EDITME Add some of the many reasons.</p> </div> </div> </div> <div class="sect1"> <h2 id="_draft_notice_2">5. DRAFT NOTICE</h2> <div class="sectionbody"> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <i class="fa icon-warning" title="Warning"></i> </td> <td class="content"> This document is in DRAFT status and should not be relied on yet. It is a preview of a document to be completed in a future release. </td> </tr> </table> </div> </div> </div> </div> <div id="footer"> <div id="footer-text"> Version v2024.1.1<br> Last updated 2024-11-20 18:54:54 -0500 </div> </div> </body> </html>
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#6 | 30915 | C. Thomas Tyler |
Released SDP 2024.1.30913 (2024/11/20). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#5 | 30388 | C. Thomas Tyler |
Released SDP 2024.1.30385 (2024/06/11). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#4 | 30297 | C. Thomas Tyler |
Released SDP 2023.2.30295 (2024/05/08). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#3 | 30043 | C. Thomas Tyler |
Released SDP 2023.2.30041 (2023/12/22). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#2 | 29954 | C. Thomas Tyler |
Released SDP 2023.1.29949 (2023/12/01). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#1 | 29891 | C. Thomas Tyler |
Released SDP 2023.1.29699 (2023/07/11). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
//guest/perforce_software/sdp/dev/doc/SDP_Win2Linux_Guide.html | |||||
#3 | 29743 | C. Thomas Tyler | WIP. | ||
#2 | 29741 | C. Thomas Tyler | WIP. | ||
#1 | 29740 | C. Thomas Tyler | Bootstrapped empty doc. |