提交 ca181ba0 编写于 作者: K kohsuke

implemented a mechanism for Hudson to install itself as a Windows service.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@12376 71c3de6d-444a-0410-be80-ed276b4c234a
上级 fe8eeaac
...@@ -72,8 +72,8 @@ ...@@ -72,8 +72,8 @@
end using Ant seems to be the easiest. end using Ant seems to be the easiest.
--> -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.jvnet.maven-antrun-extended-plugin</groupId>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-antrun-extended-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<phase>generate-resources</phase> <phase>generate-resources</phase>
...@@ -89,6 +89,10 @@ ...@@ -89,6 +89,10 @@
<mkdir dir="target/classes/hudson" /> <mkdir dir="target/classes/hudson" />
<echo file="target/classes/hudson/hudson-version.properties">version=${build.version} <echo file="target/classes/hudson/hudson-version.properties">version=${build.version}
</echo> </echo>
<!-- download winsw.exe -->
<mkdir dir="target/classes/windows-service" />
<resolveArtifact artifactId="winsw" tofile="target/classes/windows-service/hudson.exe" />
</tasks> </tasks>
</configuration> </configuration>
<goals> <goals>
...@@ -469,6 +473,14 @@ ...@@ -469,6 +473,14 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<version>1.0</version>
<classifier>bin</classifier>
<type>exe</type>
<scope>provided</scope><!-- this isn't really a dependency that Maven should care about, so putting 'provided' -->
</dependency>
<!-- offline profiler API to put in the classpath if we need it --> <!-- offline profiler API to put in the classpath if we need it -->
<!--dependency> <!--dependency>
<groupId>com.yourkit.api</groupId> <groupId>com.yourkit.api</groupId>
......
package hudson.lifecycle;
import hudson.ExtensionPoint;
import hudson.model.Hudson;
/**
* Provides the capability for starting/stopping/restarting/uninstalling Hudson.
*
* <p>
* The steps to perform these operations depend on how Hudson is launched,
* so the concrete instance of this method (which is VM-wide singleton) is discovered
* by looking up a FQCN from the system property "hudson.lifecycle".
*
* @author Kohsuke Kawaguchi
* @since 1.254
*/
public abstract class Lifecycle implements ExtensionPoint {
private static Lifecycle INSTANCE = null;
/**
* Gets the singleton instance.
*
* @return never null
*/
public synchronized static Lifecycle get() {
if(INSTANCE==null) {
String p = System.getProperty("hudson.lifecycle");
if(p!=null) {
try {
ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader;
INSTANCE = (Lifecycle)cl.loadClass(p).newInstance();
} catch (InstantiationException e) {
InstantiationError x = new InstantiationError(e.getMessage());
x.initCause(e);
throw x;
} catch (IllegalAccessException e) {
IllegalAccessError x = new IllegalAccessError(e.getMessage());
x.initCause(e);
throw x;
} catch (ClassNotFoundException e) {
NoClassDefFoundError x = new NoClassDefFoundError(e.getMessage());
x.initCause(e);
throw x;
}
} else {
// no lifecycle given. use the default one
INSTANCE = new Lifecycle() {
};
}
}
return INSTANCE;
}
}
package hudson.lifecycle;
import hudson.model.ManagementLink;
import hudson.model.Hudson;
import hudson.AbortException;
import hudson.FilePath;
import hudson.util.StreamTaskListener;
import hudson.Launcher.LocalLauncher;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.net.URL;
/**
* {@link ManagementLink} that allows the installation as a Windows service.
*
* @author Kohsuke Kawaguchi
*/
public class WindowsInstallerLink extends ManagementLink {
/**
* Location of the hudson.war.
* In general case, we can't determine this value, yet having this is a requirement for the installer.
*/
private final File hudsonWar;
/**
* If the installation is completed, this value holds the installation directory.
*/
private volatile File installationDir;
private WindowsInstallerLink(File hudsonWar) {
this.hudsonWar = hudsonWar;
}
public String getIconFileName() {
return "installer.gif";
}
public String getUrlName() {
return "install";
}
public String getDisplayName() {
return "Install as Windows Service";
}
public String getDescription() {
return "Installs Hudson as a Windows service to this system, so that Hudson starts automatically when the machine boots.";
}
/**
* Is the installation successful?
*/
public boolean isInstalled() {
return installationDir!=null;
}
// public void doCheckDir(StaplerRequest req, StaplerResponse rsp, @QueryParameter("value") String value) {
// }
/**
* Performs installation.
*/
public void doDoInstall(StaplerRequest req, StaplerResponse rsp, @QueryParameter("dir") String _dir) throws IOException, ServletException {
if(installationDir!=null) {
// installation already complete
sendError("Installation is already complete",req,rsp);
return;
}
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
File dir = new File(_dir).getAbsoluteFile();
dir.mkdirs();
if(!dir.exists()) {
sendError("Failed to create installation directory: "+dir,req,rsp);
return;
}
try {
// copy files over there
copy(req, rsp, dir, getClass().getResource("/windows-service/hudson.exe"), "hudson.exe");
copy(req, rsp, dir, getClass().getResource("/windows-service/hudson.xml"), "hudson.xml");
copy(req, rsp, dir, hudsonWar.toURL(), "hudson.war");
// install as a service
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamTaskListener task = new StreamTaskListener(baos);
task.getLogger().println("Installing a service");
int r = new LocalLauncher(task).launch(new String[]{new File(dir, "hudson.exe").getPath(), "install"}, new String[0], task.getLogger(), new FilePath(dir)).join();
if(r!=0) {
sendError(baos.toString(),req,rsp);
return;
}
// installation was successful
installationDir = dir;
rsp.sendRedirect(".");
} catch (AbortException e) {
// this exception is used as a signal to terminate processing. the error should have been already reported
} catch (InterruptedException e) {
throw new ServletException(e);
}
}
/**
* Copies a single resource into the target folder, by the given name, and handle errors gracefully.
*/
private void copy(StaplerRequest req, StaplerResponse rsp, File dir, URL src, String name) throws ServletException, IOException {
try {
FileUtils.copyURLToFile(src,new File(dir, name));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to copy "+name,e);
sendError("Failed to copy "+name+": "+e.getMessage(),req,rsp);
throw new AbortException();
}
}
public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if(installationDir==null) {
// if the user reloads the page after Hudson has restarted,
// it comes back here. In such a case, don't let this restart Hudson.
// so just send them back to the top page
rsp.sendRedirect(req.getContextPath()+"/");
return;
}
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
rsp.forward(this,"_restart",req);
// initiate an orderly shutdown after we finished serving this request
new Thread("terminator") {
public void run() {
try {
Thread.sleep(1000);
// let the service start after we close our sockets, to avoid conflicts
Runtime.getRuntime().addShutdownHook(new Thread("service starter") {
public void run() {
try {
LOGGER.info("Starting a Windows service");
StreamTaskListener task = new StreamTaskListener(System.out);
int r = new LocalLauncher(task).launch(new String[]{new File(installationDir, "hudson.exe").getPath(), "start"}, new String[0], task.getLogger(), new FilePath(installationDir)).join();
task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
/**
* Displays the error in a page.
*/
protected final void sendError(Exception e, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
sendError(e.getMessage(),req,rsp);
}
protected final void sendError(String message, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
req.setAttribute("message",message);
req.setAttribute("pre",true);
rsp.forward(Hudson.getInstance(),"error",req);
}
/**
* Decide if {@link WindowsInstallerLink} should show up in UI, and if so, register it.
*/
public static void registerIfApplicable() {
if(!Hudson.isWindows())
return; // this is a Windows only feature
if(Lifecycle.get() instanceof WindowsServiceLifecycle)
return; // already installed as Windows service
// this system property is set by the launcher when we run "java -jar hudson.war"
// and this is how we know where is hudson.war.
String war = System.getProperty("executable-war");
if(war!=null && new File(war).exists())
LIST.add(new WindowsInstallerLink(new File(war)));
}
private static final Logger LOGGER = Logger.getLogger(WindowsInstallerLink.class.getName());
}
package hudson.lifecycle;
/**
* {@link Lifecycle} for Hudson installed as Windows service.
*
* @author Kohsuke Kawaguchi
*/
public class WindowsServiceLifecycle extends Lifecycle {
}
...@@ -17,6 +17,7 @@ import hudson.Util; ...@@ -17,6 +17,7 @@ import hudson.Util;
import static hudson.Util.fixEmpty; import static hudson.Util.fixEmpty;
import hudson.WebAppMain; import hudson.WebAppMain;
import hudson.XmlFile; import hudson.XmlFile;
import hudson.lifecycle.WindowsInstallerLink;
import hudson.model.Descriptor.FormException; import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener; import hudson.model.listeners.ItemListener;
import hudson.model.listeners.JobListener; import hudson.model.listeners.JobListener;
...@@ -402,6 +403,8 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node, ...@@ -402,6 +403,8 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
rms.setKey(getSecretKey()); rms.setKey(getSecretKey());
rms.setParameter("remember_me"); // this is the form field name in login.jelly rms.setParameter("remember_me"); // this is the form field name in login.jelly
HudsonFilter.REMEMBER_ME_SERVICES_PROXY.setDelegate(rms); HudsonFilter.REMEMBER_ME_SERVICES_PROXY.setDelegate(rms);
WindowsInstallerLink.registerIfApplicable();
} }
public TcpSlaveAgentListener getTcpSlaveAgentListener() { public TcpSlaveAgentListener getTcpSlaveAgentListener() {
...@@ -1532,6 +1535,9 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node, ...@@ -1532,6 +1535,9 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
for (Action a : getActions()) for (Action a : getActions())
if(a.getUrlName().equals(token)) if(a.getUrlName().equals(token))
return a; return a;
for (Action a : getManagementLinks())
if(a.getUrlName().equals(token))
return a;
return null; return null;
} }
......
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<st:statusCode value="503" /><!-- SERVICE NOT AVAILABLE -->
<l:layout permission="${app.ADMINISTER}">
<st:include it="${app}" page="sidepanel.jelly" />
<l:main-panel>
<h1 style="margin-top:4em">
${%Please wait while Hudson is restarting}<span id="progress">...</span>
</h1>
<p style="color:grey">
${%blurb}
</p>
<script>applySafeRedirector('${rootURL}/')</script>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
blurb=You should be taken automatically to the new Hudson in a few seconds. \
If for some reasons the service fails to start, check Windows event log for errors and consult \
<a href="http://hudson.gotdns.com/wiki/display/HUDSON/Hudson+windows+service+fails+to+start">online wiki page</a>.
\ No newline at end of file
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout permission="${app.ADMINISTER}">
<st:include it="${app}" page="sidepanel.jelly" />
<l:main-panel>
<j:choose>
<j:when test="${!it.installed}">
<h1>
<img src="${imagesURL}/48x48/installer.gif" />
${%Install as Windows Service}
</h1>
<form action="doInstall" method="post">
<table>
<f:entry title="${%Installation Directory}">
<f:textbox name="dir" value="${app.rootDir}" /><!-- checkUrl="'/install/checkDir?value='+escape(this.value)" -->
</f:entry>
<f:block>
<f:submit value="${%Install}" />
</f:block>
</table>
</form>
</j:when>
<!-- already installed -->
<j:otherwise>
<h1>
<img src="${imagesURL}/48x48/blue.gif" />
${%Installation Complete}
</h1>
<p>${%restartBlurb}</p>
<form action="restart" method="post">
<f:submit value="${%Yes}" />
</form>
</j:otherwise>
</j:choose>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
restartBlurb=Installation is successfully completed. Do you want to stop this Hudson and start a newly installed Windows service?
\ No newline at end of file
...@@ -13,34 +13,7 @@ ...@@ -13,34 +13,7 @@
${%Your browser will reload automatically when Hudson is ready.} ${%Your browser will reload automatically when Hudson is ready.}
</p> </p>
<script><![CDATA[ <script>applySafeRedirector('.')</script>
var i=0;
new PeriodicalExecuter(function() {
i = (i+1)%4;
var s = "";
for( var j=0; j<i; j++ )
s+='.';
$('progress').innerHTML = s;
},1);
window.setTimeout(function() {
var statusChecker = arguments.callee;
new Ajax.Request(".", {
method: "get",
onFailure: function(rsp) {
if(rsp.status==503) {
// redirect as long as we are still loading
window.setTimeout(statusChecker,5000);
} else {
window.location.reload();
}
},
onSuccess: function(rsp) {
window.location.reload();
}
});
}, 5000);
]]></script>
</l:main-panel> </l:main-panel>
</l:layout> </l:layout>
</j:jelly> </j:jelly>
\ No newline at end of file
<!--
Windows service definition for Hudson
To uninstall, run "hudson.exe stop" to stop the service, then "hudson.exe uninstall" to uninstall the service.
Both commands don't produce any output if the execution is successful.
-->
<service>
<id>hudson</id>
<name>Hudson</name>
<description>This service runs Hudson continous integration system.</description>
<env name="HUDSON_HOME" value="%BASE%"/>
<!--
if you'd like to run Hudson with a specific version of Java, specify a full path to java.exe.
The following value assumes that you have java in your PATH.
-->
<executable>java</executable>
<arguments>-Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\hudson.war" --httpPort=8080</arguments>
</service>
此差异已折叠。
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
</webResources> </webResources>
<!-- for putting Main-Class into war --> <!-- for putting Main-Class into war -->
<archive> <archive>
<manifest> <manifest>e
<mainClass>Main</mainClass> <mainClass>Main</mainClass>
</manifest> </manifest>
</archive> </archive>
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
--> -->
<groupId>org.jvnet.hudson</groupId> <groupId>org.jvnet.hudson</groupId>
<artifactId>executable-war</artifactId> <artifactId>executable-war</artifactId>
<version>1.1</version> <version>1.2</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
......
此差异由.gitattributes 抑制。
...@@ -1340,3 +1340,38 @@ var updateCenter = { ...@@ -1340,3 +1340,38 @@ var updateCenter = {
}); });
} }
}; };
/*
redirects to a page once the page is ready.
@param url
Specifies the URL to redirect the user.
*/
function applySafeRedirector(url) {
var i=0;
new PeriodicalExecuter(function() {
i = (i+1)%4;
var s = "";
for( var j=0; j<i; j++ )
s+='.';
$('progress').innerHTML = s;
},1);
window.setTimeout(function() {
var statusChecker = arguments.callee;
new Ajax.Request(url, {
method: "get",
onFailure: function(rsp) {
if(rsp.status==503) {
// redirect as long as we are still loading
window.setTimeout(statusChecker,5000);
} else {
window.location.replace(url);
}
},
onSuccess: function(rsp) {
window.location.replace(url);
}
});
}, 5000);
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册